diff --git a/Builds/VisualStudio2013/RippleD.vcxproj b/Builds/VisualStudio2013/RippleD.vcxproj
index dc150e7f5..db771305b 100644
--- a/Builds/VisualStudio2013/RippleD.vcxproj
+++ b/Builds/VisualStudio2013/RippleD.vcxproj
@@ -34,6 +34,12 @@
true
true
+
+ true
+ true
+ true
+ true
+
true
true
@@ -109,7 +115,7 @@
true
true
-
+
true
true
true
@@ -127,25 +133,19 @@
true
true
-
- true
- true
- true
- true
-
true
true
true
true
-
+
+
true
true
true
true
-
true
true
@@ -2216,6 +2216,8 @@
+
+
@@ -2242,25 +2244,36 @@
-
+
-
+
-
-
-
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Builds/VisualStudio2013/RippleD.vcxproj.filters b/Builds/VisualStudio2013/RippleD.vcxproj.filters
index 3f1e0e00e..403e43f4c 100644
--- a/Builds/VisualStudio2013/RippleD.vcxproj.filters
+++ b/Builds/VisualStudio2013/RippleD.vcxproj.filters
@@ -304,6 +304,9 @@
{67371f65-f9be-45b1-81e8-c83ef3336e5c}
+
+ {c429638b-4572-44e4-a48a-c18fdd094ae1}
+
@@ -1104,33 +1107,9 @@
[1] Ripple\peerfinder
-
- [1] Ripple\peerfinder\impl
-
-
- [1] Ripple\peerfinder\impl
-
-
- [1] Ripple\peerfinder\impl
-
-
- [1] Ripple\peerfinder\impl
-
-
- [1] Ripple\peerfinder\impl
-
-
- [1] Ripple\peerfinder\impl
-
[1] Ripple\json\impl
-
- [1] Ripple\peerfinder\impl
-
-
- [1] Ripple\peerfinder\impl
-
[1] Ripple\types\impl
@@ -1155,9 +1134,6 @@
[1] Ripple\resource\impl
-
- [1] Ripple\peerfinder\impl
-
[1] Ripple\sitefiles
@@ -1464,6 +1440,33 @@
[1] Ripple\common\impl
+
+ [1] Ripple\common\impl
+
+
+ [1] Ripple\peerfinder\impl
+
+
+ [1] Ripple\peerfinder\impl
+
+
+ [1] Ripple\peerfinder\impl
+
+
+ [1] Ripple\peerfinder\impl
+
+
+ [1] Ripple\peerfinder\impl
+
+
+ [1] Ripple\peerfinder\impl
+
+
+ [1] Ripple\peerfinder\impl
+
+
+ [1] Ripple\peerfinder\sim
+
@@ -2472,42 +2475,6 @@
[1] Ripple\peerfinder
-
- [1] Ripple\peerfinder\api
-
-
- [1] Ripple\peerfinder\api
-
-
- [1] Ripple\peerfinder\api
-
-
- [1] Ripple\peerfinder\api
-
-
- [1] Ripple\peerfinder\impl
-
-
- [1] Ripple\peerfinder\impl
-
-
- [1] Ripple\peerfinder\impl
-
-
- [1] Ripple\peerfinder\impl
-
-
- [1] Ripple\peerfinder\impl
-
-
- [1] Ripple\peerfinder\impl
-
-
- [1] Ripple\peerfinder\impl
-
-
- [1] Ripple\peerfinder\api
-
[1] Ripple\validators\impl
@@ -2520,27 +2487,6 @@
[1] Ripple\validators\impl
-
- [1] Ripple\peerfinder\impl
-
-
- [1] Ripple\peerfinder\impl
-
-
- [1] Ripple\peerfinder\impl
-
-
- [1] Ripple\peerfinder\impl
-
-
- [1] Ripple\peerfinder\impl
-
-
- [1] Ripple\peerfinder\impl
-
-
- [1] Ripple\peerfinder\impl
-
[1] Ripple\types\api
@@ -2589,18 +2535,6 @@
[1] Ripple\resource\api
-
- [1] Ripple\peerfinder\impl
-
-
- [1] Ripple\peerfinder\impl
-
-
- [1] Ripple\peerfinder\impl
-
-
- [1] Ripple\peerfinder\impl
-
[1] Ripple\algorithm\api
@@ -2985,6 +2919,114 @@
[2] Old Ripple\ripple_app\main
+
+ [1] Ripple\common
+
+
+ [1] Ripple\common
+
+
+ [1] Ripple\peerfinder\impl
+
+
+ [1] Ripple\peerfinder\impl
+
+
+ [1] Ripple\peerfinder\impl
+
+
+ [1] Ripple\peerfinder\impl
+
+
+ [1] Ripple\peerfinder\impl
+
+
+ [1] Ripple\peerfinder\impl
+
+
+ [1] Ripple\peerfinder\impl
+
+
+ [1] Ripple\peerfinder\impl
+
+
+ [1] Ripple\peerfinder\impl
+
+
+ [1] Ripple\peerfinder\impl
+
+
+ [1] Ripple\peerfinder\impl
+
+
+ [1] Ripple\peerfinder\impl
+
+
+ [1] Ripple\peerfinder\impl
+
+
+ [1] Ripple\peerfinder\impl
+
+
+ [1] Ripple\peerfinder\impl
+
+
+ [1] Ripple\peerfinder\impl
+
+
+ [1] Ripple\peerfinder\impl
+
+
+ [1] Ripple\peerfinder\impl
+
+
+ [1] Ripple\peerfinder\impl
+
+
+ [1] Ripple\peerfinder\impl
+
+
+ [1] Ripple\peerfinder\impl
+
+
+ [1] Ripple\peerfinder\api
+
+
+ [1] Ripple\peerfinder\api
+
+
+ [1] Ripple\peerfinder\api
+
+
+ [1] Ripple\peerfinder\api
+
+
+ [1] Ripple\peerfinder\api
+
+
+ [1] Ripple\peerfinder\sim
+
+
+ [1] Ripple\peerfinder\sim
+
+
+ [1] Ripple\peerfinder\sim
+
+
+ [1] Ripple\peerfinder\sim
+
+
+ [1] Ripple\peerfinder\sim
+
+
+ [1] Ripple\peerfinder\sim
+
+
+ [1] Ripple\peerfinder\sim
+
+
+ [1] Ripple\peerfinder\sim
+
diff --git a/SConstruct b/SConstruct
index 57bb15797..90fde8937 100644
--- a/SConstruct
+++ b/SConstruct
@@ -163,7 +163,6 @@ COMPILED_FILES.extend (['src/ripple/beast/ripple_beastc.c'])
# New-style Ripple unity sources
#
COMPILED_FILES.extend([
- 'src/ripple/common/ripple_common.cpp',
'src/ripple/http/ripple_http.cpp',
'src/ripple/json/ripple_json.cpp',
'src/ripple/peerfinder/ripple_peerfinder.cpp',
@@ -175,7 +174,8 @@ COMPILED_FILES.extend([
'src/ripple/sslutil/ripple_sslutil.cpp',
'src/ripple/testoverlay/ripple_testoverlay.cpp',
'src/ripple/types/ripple_types.cpp',
- 'src/ripple/validators/ripple_validators.cpp'
+ 'src/ripple/validators/ripple_validators.cpp',
+ 'src/ripple/common/ripple_common.cpp',
])
# ------------------------------
diff --git a/src/ripple/algorithm/api/CycledSet.h b/src/ripple/algorithm/api/CycledSet.h
index a89466171..ae375ed10 100644
--- a/src/ripple/algorithm/api/CycledSet.h
+++ b/src/ripple/algorithm/api/CycledSet.h
@@ -32,7 +32,7 @@ namespace ripple {
container.
*/
template ,
class KeyEqual = std::equal_to ,
class Allocator = std::allocator >
class CycledSet
diff --git a/src/ripple_app/peers/NameResolver.h b/src/ripple/common/Resolver.h
similarity index 66%
rename from src/ripple_app/peers/NameResolver.h
rename to src/ripple/common/Resolver.h
index 0860e0a52..0e7c7248f 100644
--- a/src/ripple_app/peers/NameResolver.h
+++ b/src/ripple/common/Resolver.h
@@ -17,42 +17,37 @@
*/
//==============================================================================
-#ifndef RIPPLE_PEER_NAMERESOLVER_H_INCLUDED
-#define RIPPLE_PEER_NAMERESOLVER_H_INCLUDED
+#ifndef RIPPLE_COMMON_RESOLVER_H_INCLUDED
+#define RIPPLE_COMMON_RESOLVER_H_INCLUDED
+#include
#include
+#include "../../beast/beast/boost/ErrorCode.h"
+#include "../../beast/beast/Net.h"
+
+
namespace ripple {
-class NameResolver
+class Resolver
{
public:
- typedef std::function <
- void (std::string, std::vector, boost::system::error_code)>
- HandlerType;
+ typedef boost::function <
+ void (std::string,
+ std::vector ) >
+ HandlerType;
- static NameResolver* New (boost::asio::io_service& io_service,
- Journal journal);
+ virtual ~Resolver () = 0;
- virtual ~NameResolver () = 0;
-
- /** Initiate an asynchronous cancellation. */
+ /** Issue an asynchronous stop request. */
virtual void stop_async () = 0;
- /** Cancel all pending resolutions.
- This call blocks until all pending work items are canceled. It is
- guaranteed that no handlers will be called after this function
- returns.
- You *must* call this function before the object is destroyed.
- */
+ /** Issue a synchronous stop request. */
virtual void stop () = 0;
/** resolve all hostnames on the list
@param names the names to be resolved
@param handler the handler to call
-
- @note the handler may be called multiple times for a single name
- since a name may resolve to multiple IPs.
*/
/** @{ */
template
@@ -67,6 +62,6 @@ public:
/** @} */
};
-};
+}
-#endif
\ No newline at end of file
+#endif
diff --git a/src/ripple/common/ResolverAsio.h b/src/ripple/common/ResolverAsio.h
new file mode 100644
index 000000000..ecd6e7c60
--- /dev/null
+++ b/src/ripple/common/ResolverAsio.h
@@ -0,0 +1,40 @@
+//------------------------------------------------------------------------------
+/*
+ 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.
+*/
+//==============================================================================
+
+#ifndef RIPPLE_COMMON_RESOLVERASIO_H_INCLUDED
+#define RIPPLE_COMMON_RESOLVERASIO_H_INCLUDED
+
+#include
+
+#include "../beast/beast/utility/Journal.h"
+#include "Resolver.h"
+
+namespace ripple {
+
+class ResolverAsio : public Resolver
+{
+public:
+ static ResolverAsio* New (
+ boost::asio::io_service& io_service,
+ beast::Journal journal);
+};
+
+}
+
+#endif
diff --git a/src/ripple_app/peers/NameResolver.cpp b/src/ripple/common/impl/ResolverAsio.cpp
similarity index 65%
rename from src/ripple_app/peers/NameResolver.cpp
rename to src/ripple/common/impl/ResolverAsio.cpp
index 56609ef8f..5f58a95d9 100644
--- a/src/ripple_app/peers/NameResolver.cpp
+++ b/src/ripple/common/impl/ResolverAsio.cpp
@@ -17,16 +17,24 @@
*/
//==============================================================================
+#include "../../beast/modules/beast_asio/async/AsyncObject.h"
+#include "../../beast/beast/asio/IPAddressConversion.h"
+
#include
+#include
+
+#include "boost/asio.hpp"
+
+#include "../ResolverAsio.h"
namespace ripple {
-class NameResolverImpl
- : public NameResolver
- , public AsyncObject
+class ResolverAsioImpl
+ : public ResolverAsio
+ , public AsyncObject
{
public:
- typedef std::pair HostAndPort;
+ typedef std::pair HostAndPort;
Journal m_journal;
@@ -34,12 +42,11 @@ public:
boost::asio::io_service::strand m_strand;
boost::asio::ip::tcp::resolver m_resolver;
- std::atomic m_called_stop;
- bool m_stopped;
- bool m_idle;
+ WaitableEvent m_stop_complete;
+ std::atomic m_stop_called;
+ std::atomic m_stopped;
- // Notification that we need to exit
- WaitableEvent m_event;
+ bool m_idle;
// Represents a unit of work for the resolver to do
struct Work
@@ -54,79 +61,99 @@ public:
names.reserve(names_.size());
std::reverse_copy (names_.begin(), names_.end(),
- std::back_inserter(names));
+ std::back_inserter (names));
}
};
std::deque m_work;
- NameResolverImpl (boost::asio::io_service& io_service,
+ ResolverAsioImpl (boost::asio::io_service& io_service,
Journal journal)
: m_journal (journal)
, m_io_service (io_service)
, m_strand (io_service)
, m_resolver (io_service)
- , m_called_stop (0)
+ , m_stop_complete (true)
+ , m_stop_called (false)
, m_stopped (false)
, m_idle (true)
- , m_event (true)
{
addReference ();
}
- ~NameResolverImpl ()
+ ~ResolverAsioImpl ()
{
check_precondition (m_work.empty());
check_precondition (m_stopped);
}
- //--------------------------------------------------------------------------
- //
+ //-------------------------------------------------------------------------
// AsyncObject
- //
- //--------------------------------------------------------------------------
-
void asyncHandlersComplete()
{
- m_event.signal ();
+ m_stop_complete.signal ();
}
//--------------------------------------------------------------------------
//
- // NameResolver
+ // Resolver
//
//--------------------------------------------------------------------------
- void do_stop (CompletionCounter)
- {
- m_journal.debug << "Stopped";
- m_stopped = true;
- m_work.clear ();
- m_resolver.cancel ();
- removeReference ();
- }
-
void stop_async ()
{
- if (m_called_stop.exchange (1) == 0)
+ if (m_stop_called.exchange (true) == false)
{
- m_io_service.dispatch (m_strand.wrap (boost::bind (
- &NameResolverImpl::do_stop,
- this, CompletionCounter (this))));
+ m_io_service.dispatch ( m_strand.wrap ( boost::bind (
+ &ResolverAsioImpl::do_stop,
+ this, CompletionCounter (this))));
- m_journal.debug << "Stopping";
+ m_journal.debug << "Queued a stop request";
}
}
void stop ()
{
stop_async ();
- m_event.wait();
+ m_journal.debug << "Waiting to stop";
+ m_stop_complete.wait();
+ m_journal.debug << "Stopped";
+ }
+
+ void resolve (
+ std::vector const& names,
+ HandlerType const& handler)
+ {
+ check_precondition (m_stop_called.load () == 0);
+ check_precondition (!names.empty());
+
+ // TODO NIKB use rvalue references to construct and move
+ // reducing cost.
+ m_io_service.dispatch (m_strand.wrap (boost::bind (
+ &ResolverAsioImpl::do_resolve, this,
+ names, handler, CompletionCounter (this))));
+ }
+
+ //-------------------------------------------------------------------------
+ // Resolver
+ void do_stop (CompletionCounter)
+ {
+ if (meets_precondition (m_stop_called == true) &&
+ meets_precondition (m_stopped == false))
+ {
+ m_work.clear ();
+ m_resolver.cancel ();
+ m_stopped.exchange (true);
+
+ m_journal.debug << "Stopped";
+
+ removeReference ();
+ }
}
void do_finish (
std::string name,
- const boost::system::error_code& ec,
+ boost::system::error_code const& ec,
HandlerType handler,
boost::asio::ip::tcp::resolver::iterator iter,
CompletionCounter)
@@ -142,16 +169,16 @@ public:
{
while (iter != boost::asio::ip::tcp::resolver::iterator())
{
- addresses.push_back (IPAddressConversion::from_asio(*iter));
+ addresses.push_back (IPAddressConversion::from_asio (*iter));
++iter;
}
}
- handler (name, addresses, ec);
+ handler (name, addresses);
m_io_service.post (m_strand.wrap (boost::bind (
- &NameResolverImpl::do_work, this,
- CompletionCounter(this))));
+ &ResolverAsioImpl::do_work, this,
+ CompletionCounter (this))));
}
HostAndPort parseName(std::string const& str)
@@ -159,12 +186,15 @@ public:
std::string host (str);
std::string port;
- std::string::size_type colon (host.find(':'));
+ std::string::size_type sep (host.find(':'));
- if (colon != std::string::npos)
+ if(sep == std::string::npos)
+ sep = host.find(' ');
+
+ if(sep != std::string::npos)
{
- port = host.substr (colon + 1);
- host.erase(colon);
+ port = host.substr(sep + 1);
+ host.erase(sep);
}
return std::make_pair(host, port);
@@ -172,35 +202,35 @@ public:
void do_work (CompletionCounter)
{
- if (m_called_stop.load () == 1)
+ if (m_stop_called.load () == 1)
return;
// We don't have any work to do at this time
- if (m_work.empty())
+ if (m_work.empty ())
{
m_idle = true;
m_journal.trace << "Sleeping";
return;
}
- if (m_work.front().names.empty())
+ std::string const name (m_work.front ().names.back());
+ HandlerType handler (m_work.front ().handler);
+
+ m_work.front ().names.pop_back ();
+
+ if (m_work.front ().names.empty ())
m_work.pop_front();
- std::string const name (m_work.front().names.back());
- HandlerType handler (m_work.front().handler);
+ HostAndPort const hp (parseName (name));
- m_work.front().names.pop_back();
-
- HostAndPort const hp (parseName(name));
-
- if (hp.first.empty())
+ if (hp.first.empty ())
{
m_journal.error <<
"Unable to parse '" << name << "'";
m_io_service.post (m_strand.wrap (boost::bind (
- &NameResolverImpl::do_work, this,
- CompletionCounter(this))));
+ &ResolverAsioImpl::do_work, this,
+ CompletionCounter (this))));
return;
}
@@ -209,10 +239,10 @@ public:
hp.first, hp.second);
m_resolver.async_resolve (query, boost::bind (
- &NameResolverImpl::do_finish, this, name,
+ &ResolverAsioImpl::do_finish, this, name,
boost::asio::placeholders::error, handler,
boost::asio::placeholders::iterator,
- CompletionCounter(this)));
+ CompletionCounter (this)));
}
void do_resolve (std::vector const& names,
@@ -220,7 +250,7 @@ public:
{
check_precondition (! names.empty());
- if (m_called_stop.load () == 0)
+ if (m_stop_called.load () == 0)
{
// TODO NIKB use emplace_back once we move to C++11
m_work.push_back(Work(names, handler));
@@ -237,39 +267,25 @@ public:
m_idle = false;
m_io_service.post (m_strand.wrap (boost::bind (
- &NameResolverImpl::do_work, this,
- CompletionCounter(this))));
+ &ResolverAsioImpl::do_work, this,
+ CompletionCounter (this))));
}
}
}
-
- void resolve (
- std::vector const& names,
- HandlerType const& handler)
- {
- check_precondition (m_called_stop.load () == 0);
- check_precondition (!names.empty());
-
- // TODO NIKB use rvalue references to construct and move
- // reducing cost.
- m_io_service.dispatch (m_strand.wrap (boost::bind (
- &NameResolverImpl::do_resolve, this,
- names, handler, CompletionCounter(this))));
- }
};
//-----------------------------------------------------------------------------
-NameResolver::~NameResolver()
-{
-
-}
-
-NameResolver* NameResolver::New (
+ResolverAsio *ResolverAsio::New (
boost::asio::io_service& io_service,
- Journal journal)
+ beast::Journal journal)
+{
+ return new ResolverAsioImpl (io_service, journal);
+}
+
+//-----------------------------------------------------------------------------
+Resolver::~Resolver ()
{
- return new NameResolverImpl (io_service, journal);
}
}
diff --git a/src/ripple/common/ripple_common.cpp b/src/ripple/common/ripple_common.cpp
index 1fea9fcf2..24cd2b982 100644
--- a/src/ripple/common/ripple_common.cpp
+++ b/src/ripple/common/ripple_common.cpp
@@ -24,3 +24,14 @@
#include "impl/counted_bind.cpp"
#include "impl/KeyCache.cpp"
#include "impl/TaggedCache.cpp"
+
+using namespace beast;
+
+#ifndef NDEBUG
+# define consistency_check(cond) bassert(cond)
+#else
+# define consistency_check(cond)
+#endif
+
+#include "impl/ResolverAsio.cpp"
+
diff --git a/src/ripple/http/impl/Types.h b/src/ripple/http/impl/Types.h
index ea58de012..29a2dad70 100644
--- a/src/ripple/http/impl/Types.h
+++ b/src/ripple/http/impl/Types.h
@@ -46,10 +46,10 @@ inline std::string to_string (endpoint_t const& endpoint)
inline endpoint_t to_asio (Port const& port)
{
- if (port.addr.isV4())
+ if (port.addr.is_v4())
{
- IPAddress::V4 v4 (port.addr.v4());
- std::string const& s (v4.to_string());
+ IP::AddressV4 v4 (port.addr.to_v4());
+ std::string const& s (to_string (v4));
return endpoint_t (address().from_string (s), port.port);
}
diff --git a/src/ripple/peerfinder/README.md b/src/ripple/peerfinder/README.md
index 628ae31ce..f02fcd877 100644
--- a/src/ripple/peerfinder/README.md
+++ b/src/ripple/peerfinder/README.md
@@ -86,6 +86,17 @@ mtENDPOINTS message.
"Bootstrap Strategy"
--------------------
+Fresh endpoints are ones we have seen recently via mtENDPOINTS.
+These are best to give out to someone who needs additional
+connections as quickly as possible, since it is very likely
+that the fresh endpoints have open incoming slots.
+
+Reliable endpoints are ones which are highly likely to be
+connectible over long periods of time. They might not necessarily
+have an incoming slot, but they are good for bootstrapping when
+there are no peers yet. Typically these are what we would want
+to store in a database or local config file for a future launch.
+
Nouns:
bootstrap_ip
diff --git a/src/ripple/peerfinder/TODO.md b/src/ripple/peerfinder/TODO.md
index 573947313..1d742be83 100644
--- a/src/ripple/peerfinder/TODO.md
+++ b/src/ripple/peerfinder/TODO.md
@@ -2,3 +2,87 @@
- Add TestOverlay unit test that passes mtENDPOINTs messages
around and enforces connection policy using a custom Callback.
+- Detect self connection
+- Detect duplicate connection
+- Migrate portions of Peer handling code
+- Track more stats in Bootcache and report them in onWrite
+- Test onPeerConnectionChanged() for PROXY handshakes
+- Remove uptime, slots from mtENDPOINTS message.
+- Finish mtENDPOINTS message-sending rate limit code.
+- Make the graceful close state also work for active peers that we want to close.
+- Fix FixedPeers correctly
+- Keep track of which peers have sent us endpoints, and which endpoints we
+ have sent to peers, and try not to send a peer an endpoint that we have
+ either received from them, or sent to them lately.
+- Use EphemeralCache as the name of the live endpoint cache.
+- Rename Endpoint to Address where it makes sense to do so.
+- Remove uptime, slots from mtENDPOINTS
+- Consider removing featureList from mtENDPOINTS
+ * For security reasons we might want to only advertise other people's
+ features if we have verified them locally.
+- Add firewalled test.
+ Consider ourselves firewalled (can't receive incoming connections) if:
+ * We have advertised ourselves for incomingFirewalledThresholdSeconds (Tuning.h)
+ without receiving an incoming connection.
+- We may want to provide hooks in RPC and WSConnection logic that allows incoming
+ connections to those ports from public IPs to count as incoming connections for
+ the purpose of the firewalled tests.
+- Send mtENDPOINTS if all these conditions are met:
+ 1. We are not full
+ 2. No incoming peer within the last incomingPeerCooldownSeconds (Tuning.h)
+ 3. We have not failed the firewalled test
+- When an outgoing attempt fails, penalize the entry if it exists in the
+ ephemeral cache as if it failed the listening test.
+- Avoid propagating ephemeral entries if they failed the listening test.
+- Make fixed peers more robust by remembering the public key and using it to
+ know when the fixed peer is connected regardless of whether the connection is
+ inbound.
+
+- Track more stats in Bootcache and report them in onWrite
+
+- Test onPeerConnectionChanged() for PROXY handshakes
+
+- Remove uptime, slots from mtENDPOINTS message.
+
+- Finish mtENDPOINTS message-sending rate limit code.
+
+- Make the graceful close state also work for active peers that we want to close.
+
+- Fix FixedPeers correctly
+
+- Keep track of which peers have sent us endpoints, and which endpoints we
+ have sent to peers, and try not to send a peer an endpoint that we have
+ either received from them, or sent to them lately.
+
+- Use EphemeralCache as the name of the live endpoint cache.
+
+- Rename Endpoint to Address where it makes sense to do so.
+
+- Remove uptime, slots from mtENDPOINTS
+
+- Consider removing featureList from mtENDPOINTS
+ * For security reasons we might want to only advertise other people's
+ features if we have verified them locally.
+
+- Add firewalled test.
+ Consider ourselves firewalled (can't receive incoming connections) if:
+ * We have advertised ourselves for incomingFirewalledThresholdSeconds (Tuning.h)
+ without receiving an incoming connection.
+
+- We may want to provide hooks in RPC and WSConnection logic that allows incoming
+ connections to those ports from public IPs to count as incoming connections for
+ the purpose of the firewalled tests.
+
+- Send mtENDPOINTS if all these conditions are met:
+ 1. We are not full
+ 2. No incoming peer within the last incomingPeerCooldownSeconds (Tuning.h)
+ 3. We have not failed the firewalled test
+
+- When an outgoing attempt fails, penalize the entry if it exists in the
+ ephemeral cache as if it failed the listening test.
+
+- Avoid propagating ephemeral entries if they failed the listening test.
+
+- Make fixed peers more robust by remembering the public key and using it to
+ know when the fixed peer is connected regardless of whether the connection is
+ inbound.
diff --git a/src/ripple/peerfinder/api/Callback.h b/src/ripple/peerfinder/api/Callback.h
index 90017f17d..24a845309 100644
--- a/src/ripple/peerfinder/api/Callback.h
+++ b/src/ripple/peerfinder/api/Callback.h
@@ -30,15 +30,22 @@ namespace PeerFinder {
*/
struct Callback
{
- /** Sends a set of Endpoint records to the specified peer. */
- virtual void sendPeerEndpoints (PeerID const& id,
- std::vector const& endpoints) = 0;
-
/** Initiate outgoing Peer connections to the specified set of endpoints. */
- virtual void connectPeerEndpoints (std::vector const& list) = 0;
+ virtual void connectPeers (IPAddresses const& addresses) = 0;
- /** Impose a load charge on the specified peer. */
- virtual void chargePeerLoadPenalty (PeerID const& id) = 0;
+ /** Disconnect the handshaked peer with the specified address.
+ @param graceful `true` to wait for send buffers to drain before closing.
+ */
+ virtual void disconnectPeer (
+ IPAddress const& remote_address, bool graceful) = 0;
+
+ /** Activate the handshaked peer with the specified address. */
+ virtual void activatePeer (
+ IPAddress const& remote_address) = 0;
+
+ /** Sends a set of Endpoint records to the specified peer. */
+ virtual void sendEndpoints (IPAddress const& remote_address,
+ Endpoints const& endpoints) = 0;
};
}
diff --git a/src/ripple/peerfinder/api/Config.h b/src/ripple/peerfinder/api/Config.h
index 732d2385e..902e30abd 100644
--- a/src/ripple/peerfinder/api/Config.h
+++ b/src/ripple/peerfinder/api/Config.h
@@ -23,31 +23,52 @@
namespace ripple {
namespace PeerFinder {
-/** Configuration for the Manager. */
+/** PeerFinder configuration settings. */
struct Config
{
- Config ();
+ /** The largest number of public peer slots to allow.
+ This includes both inbound and outbound, but does not include
+ fixed peers.
+ */
+ int maxPeers;
- static int const minOutCount = 10;
- static int const outPercent = 15;
- int maxPeerCount;
+ /** The number of automatic outbound connections to maintain.
+ Outbound connections are only maintained if autoConnect
+ is `true`. The value can be fractional; The decision to round up
+ or down will be made using a per-process pseudorandom number and
+ a probability proportional to the fractional part.
+ Example:
+ If outPeers is 9.3, then 30% of nodes will maintain 9 outbound
+ connections, while 70% of nodes will maintain 10 outbound
+ connections.
+ */
+ double outPeers;
- /** True if we want to accept incoming connections. */
+ /** `true` if we want to accept incoming connections. */
bool wantIncoming;
- /** True if we want to establish connections automatically */
- bool connectAutomatically;
+ /** `true` if we want to establish connections automatically */
+ bool autoConnect;
+ /** The listening port number. */
uint16 listeningPort;
- std::string featureList;
+
+ /** The set of features we advertise. */
+ std::string features;
+
+ //--------------------------------------------------------------------------
+
+ /** Create a configuration with default values. */
+ Config ();
+
+ /** Returns a suitable value for outPeers according to the rules. */
+ double calcOutPeers () const;
+
+ /** Adjusts the values so they follow the business rules. */
+ void applyTuning ();
/** Write the configuration into a property stream */
- void onWrite(PropertyStream::Map& map);
-
- /** Called to set sensible default values for anything
- that hasn't been initialized.
- */
- void fillInDefaultValues();
+ void onWrite (PropertyStream::Map& map);
};
}
diff --git a/src/ripple/peerfinder/api/Endpoint.h b/src/ripple/peerfinder/api/Endpoint.h
index 28a62d93e..695093564 100644
--- a/src/ripple/peerfinder/api/Endpoint.h
+++ b/src/ripple/peerfinder/api/Endpoint.h
@@ -27,25 +27,12 @@ namespace PeerFinder {
struct Endpoint
{
Endpoint ();
-
- IPAddress address;
+
int hops;
- uint32 incomingSlotsAvailable;
- uint32 incomingSlotsMax;
- uint32 uptimeSeconds;
- std::string featureList;
+ IPAddress address;
+ std::string features;
};
-inline bool operator< (Endpoint const& lhs, Endpoint const& rhs)
-{
- return lhs.address < rhs.address;
-}
-
-inline bool operator== (Endpoint const& lhs, Endpoint const& rhs)
-{
- return lhs.address == rhs.address;
-}
-
}
}
diff --git a/src/ripple/peerfinder/api/Manager.h b/src/ripple/peerfinder/api/Manager.h
index 9d455ceb0..a3fc874d6 100644
--- a/src/ripple/peerfinder/api/Manager.h
+++ b/src/ripple/peerfinder/api/Manager.h
@@ -53,13 +53,13 @@ public:
*/
virtual void setConfig (Config const& config) = 0;
- /** Add a set of strings for peers that should always be connected.
+ /** Add a peer that should always be connected.
This is useful for maintaining a private cluster of peers.
- If a string is not parseable as a numeric IP address it will
- be passed to a DNS resolver to perform a lookup.
+ The string is the name as specified in the configuration
+ file, along with the set of corresponding IP addresses.
*/
- virtual void addFixedPeers (
- std::vector const& strings) = 0;
+ virtual void addFixedPeer (std::string const& name,
+ std::vector const& addresses) = 0;
/** Add a set of strings as fallback IPAddress sources.
@param name A label used for diagnostics.
@@ -73,46 +73,42 @@ public:
virtual void addFallbackURL (std::string const& name,
std::string const& url) = 0;
- /** Called when an (outgoing) connection attempt to a particular address
- is about to begin.
+ //--------------------------------------------------------------------------
+
+ /** Called when a peer connection is accepted. */
+ virtual void onPeerAccept (IPAddress const& local_address,
+ IPAddress const& remote_address) = 0;
+
+ /** Called when an outgoing peer connection is attempted. */
+ virtual void onPeerConnect (IPAddress const& address) = 0;
+
+ /** Called when an outgoing peer connection attempt succeeds. */
+ virtual void onPeerConnected (IPAddress const& local_address,
+ IPAddress const& remote_address) = 0;
+
+ /** Called when the real public address is discovered.
+ Currently this happens when we receive a PROXY handshake. The
+ protocol HELLO message will happen after the PROXY handshake.
*/
- virtual void onPeerConnectAttemptBegins (IPAddress const& address) = 0;
+ virtual void onPeerAddressChanged (
+ IPAddress const& currentAddress, IPAddress const& newAddress) = 0;
- /** Called when an (outgoing) connection attempt to a particular address
- completes, whether it succeeds or fails.
+ /** Called when a peer connection finishes the protocol handshake.
+ @param id The node public key of the peer.
+ @param inCluster The peer is a member of our cluster.
*/
- virtual void onPeerConnectAttemptCompletes (IPAddress const& address,
- bool success) = 0;
+ virtual void onPeerHandshake (
+ IPAddress const& address, PeerID const& id, bool inCluster) = 0;
- /** Called when a new peer connection is established but before get
- we exchange hello messages.
- */
- virtual void onPeerConnected (IPAddress const& address,
- bool inbound) = 0;
-
- /** Called when a new peer connection is established after we exchange
- hello messages.
- Internally, we add the peer to our tracking table, validate that
- we can connect to it, and begin advertising it to others after
- we are sure that its connection is stable.
- */
- virtual void onPeerHandshake (PeerID const& id,
- IPAddress const& address,
- bool inbound) = 0;
-
- /** Called when an existing peer connection drops for whatever reason.
- Internally, we mark the peer as no longer connected, calculate
- stability metrics, and consider whether we should try to reconnect
- to it or drop it from our list.
- */
- virtual void onPeerDisconnected (PeerID const& id) = 0;
+ /** Always called when the socket closes. */
+ virtual void onPeerClosed (IPAddress const& address) = 0;
/** Called when mtENDPOINTS is received. */
- virtual void onPeerEndpoints (PeerID const& id,
- std::vector const& endpoints) = 0;
+ virtual void onPeerEndpoints (IPAddress const& address,
+ Endpoints const& endpoints) = 0;
- /** Called when a legacy IP/port address is received (from mtPEER). */
- virtual void onPeerLegacyEndpoint (IPAddress const& ep) = 0;
+ /** Called when legacy IP/port addresses are received. */
+ virtual void onLegacyEndpoints (IPAddresses const& addresses) = 0;
};
}
diff --git a/src/ripple/peerfinder/api/Types.h b/src/ripple/peerfinder/api/Types.h
index f9cf3d6e9..1c7620b72 100644
--- a/src/ripple/peerfinder/api/Types.h
+++ b/src/ripple/peerfinder/api/Types.h
@@ -26,6 +26,12 @@ namespace PeerFinder {
/** Used to identify peers. */
typedef RipplePublicKey PeerID;
+/** Represents a set of addresses. */
+typedef std::vector IPAddresses;
+
+/** A set of Endpoint used for connecting. */
+typedef std::vector Endpoints;
+
}
}
diff --git a/src/ripple/peerfinder/impl/Bootcache.h b/src/ripple/peerfinder/impl/Bootcache.h
new file mode 100644
index 000000000..d30df53c0
--- /dev/null
+++ b/src/ripple/peerfinder/impl/Bootcache.h
@@ -0,0 +1,535 @@
+//------------------------------------------------------------------------------
+/*
+ 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.
+*/
+//==============================================================================
+
+#ifndef RIPPLE_PEERFINDER_BOOTCACHE_H_INCLUDED
+#define RIPPLE_PEERFINDER_BOOTCACHE_H_INCLUDED
+
+namespace ripple {
+namespace PeerFinder {
+
+/** Stores IP addresses useful for gaining initial connections.
+ Ideal bootstrap addresses have the following attributes:
+ - High uptime
+ - Many successful connect attempts
+*/
+class Bootcache
+{
+public:
+ /** An item used for connecting. */
+ class Endpoint
+ {
+ public:
+ Endpoint ()
+ : m_uptime (0)
+ , m_valence (0)
+ {
+ }
+
+ Endpoint (IPAddress const& address, int uptime, int valence)
+ : m_address (address)
+ , m_uptime (uptime)
+ , m_valence (valence)
+ {
+ }
+
+ IPAddress const& address () const
+ {
+ return m_address;
+ }
+
+ int uptime () const
+ {
+ return m_uptime;
+ }
+
+ int valence () const
+ {
+ return m_valence;
+ }
+
+ private:
+ IPAddress m_address;
+ int m_uptime;
+ int m_valence;
+ };
+
+ typedef std::vector Endpoints;
+
+ //--------------------------------------------------------------------------
+
+ /** An entry in the bootstrap cache. */
+ struct Entry
+ {
+ Entry ()
+ : cumulativeUptimeSeconds (0)
+ , sessionUptimeSeconds (0)
+ , connectionValence (0)
+ , active (false)
+ {
+ }
+
+ /** Update the uptime measurement based on the time. */
+ void update (DiscreteTime const now)
+ {
+ // Must be active!
+ consistency_check (active);
+ // Clock must be monotonically increasing
+ consistency_check (now >= whenActive);
+ // Remove the uptime we added earlier in the
+ // session and add back in the new uptime measurement.
+ DiscreteTime const uptimeSeconds (now - whenActive);
+ cumulativeUptimeSeconds -= sessionUptimeSeconds;
+ cumulativeUptimeSeconds += uptimeSeconds;
+ sessionUptimeSeconds = uptimeSeconds;
+ }
+
+ /** Our cumulative uptime with this address with no failures. */
+ int cumulativeUptimeSeconds;
+
+ /** Amount of uptime from the current session (if any). */
+ int sessionUptimeSeconds;
+
+ /** Number of consecutive connection successes or failures.
+ If the number is positive, indicates the number of
+ consecutive successful connection attempts, else the
+ absolute value indicates the number of consecutive
+ connection failures.
+ */
+ int connectionValence;
+
+ /** `true` if the peer has handshaked and is currently connected. */
+ bool active;
+
+ /** Time when the peer became active. */
+ DiscreteTime whenActive;
+ };
+
+ //--------------------------------------------------------------------------
+
+ /* Comparison function for entries.
+
+ 1. Sort descending by cumulative uptime
+ 2. For all uptimes == 0,
+ Sort descending by connection successes
+ 3. For all successes == 0
+ Sort ascending by number of failures
+ */
+ struct Less
+ {
+ template
+ bool operator() (
+ Iter const& lhs_iter, Iter const& rhs_iter)
+ {
+ Entry const& lhs (lhs_iter->second);
+ Entry const& rhs (rhs_iter->second);
+ // Higher cumulative uptime always wins
+ if (lhs.cumulativeUptimeSeconds > rhs.cumulativeUptimeSeconds)
+ return true;
+ else if (lhs.cumulativeUptimeSeconds <= rhs.cumulativeUptimeSeconds
+ && rhs.cumulativeUptimeSeconds != 0)
+ return false;
+ // At this point both uptimes will be zero
+ consistency_check (lhs.cumulativeUptimeSeconds == 0 &&
+ rhs.cumulativeUptimeSeconds == 0);
+ if (lhs.connectionValence > rhs.connectionValence)
+ return true;
+ return false;
+ }
+ };
+
+ //--------------------------------------------------------------------------
+
+ typedef boost::unordered_map Entries;
+
+ typedef std::vector SortedEntries;
+
+ Store& m_store;
+ DiscreteClock m_clock;
+ Journal m_journal;
+ Entries m_entries;
+
+ // Time after which we can update the database again
+ DiscreteTime m_whenUpdate;
+
+ // Set to true when a database update is needed
+ bool m_needsUpdate;
+
+ Bootcache (
+ Store& store,
+ DiscreteClock clock,
+ Journal journal)
+ : m_store (store)
+ , m_clock (clock)
+ , m_journal (journal)
+ , m_whenUpdate (clock())
+ {
+ }
+
+ ~Bootcache ()
+ {
+ update ();
+ }
+
+ //--------------------------------------------------------------------------
+
+ /** Load the persisted data from the Store into the container. */
+ void load ()
+ {
+ typedef std::vector StoredData;
+ StoredData const list (m_store.loadBootstrapCache ());
+
+ std::size_t count (0);
+
+ for (StoredData::const_iterator iter (list.begin());
+ iter != list.end(); ++iter)
+ {
+ std::pair result (
+ m_entries.emplace (boost::unordered::piecewise_construct,
+ boost::make_tuple (iter->address), boost::make_tuple ()));
+ if (result.second)
+ {
+ ++count;
+ Entry& entry (result.first->second);
+ entry.cumulativeUptimeSeconds = iter->cumulativeUptimeSeconds;
+ entry.connectionValence = iter->connectionValence;
+ }
+ else
+ {
+ if (m_journal.error) m_journal.error << leftw (18) <<
+ "Bootcache discard " << iter->address;
+ }
+ }
+
+ if (count > 0)
+ {
+ if (m_journal.info) m_journal.info << leftw (18) <<
+ "Bootcache loaded " << count <<
+ ((count > 1) ? " addresses" : " address");
+ }
+
+ prune ();
+ }
+
+ /** Returns the number of entries in the cache. */
+ std::size_t size () const
+ {
+ return m_entries.size();
+ }
+
+ /** Returns up to the specified number of the best addresses. */
+ IPAddresses getAddresses (int n)
+ {
+ SortedEntries const list (sort());
+ IPAddresses result;
+ int count (0);
+ result.reserve (n);
+ for (SortedEntries::const_iterator iter (
+ list.begin()); ++count <= n && iter != list.end(); ++iter)
+ result.push_back ((*iter)->first);
+ consistency_check (result.size() <= n);
+ return result;
+ }
+
+ /** Returns all entries in the cache. */
+ Endpoints fetch () const
+ {
+ Endpoints result;
+ result.reserve (m_entries.size ());
+ for (Entries::const_iterator iter (m_entries.begin ());
+ iter != m_entries.end (); ++iter)
+ result.emplace_back (iter->first,
+ iter->second.cumulativeUptimeSeconds,
+ iter->second.connectionValence);
+ return result;
+ }
+
+ /** Called periodically to perform time related tasks. */
+ void periodicActivity ()
+ {
+ checkUpdate();
+ }
+
+ /** Called when an address is learned from a message. */
+ bool insert (IPAddress const& address)
+ {
+ std::pair result (
+ m_entries.emplace (boost::unordered::piecewise_construct,
+ boost::make_tuple (address), boost::make_tuple ()));
+ if (result.second)
+ {
+ if (m_journal.trace) m_journal.trace << leftw (18) <<
+ "Bootcache insert " << address;
+ prune ();
+ flagForUpdate();
+ }
+ return result.second;
+ }
+
+ /** Called when an outbound connection attempt fails to handshake. */
+ void onConnectionFailure (IPAddress const& address)
+ {
+ Entries::iterator iter (m_entries.find (address));
+ // If the entry doesn't already exist don't bother remembering
+ // it since the connection failed.
+ //
+ if (iter == m_entries.end())
+ return;
+ Entry& entry (iter->second);
+ // Reset cumulative uptime to zero. We are aggressive
+ // with resetting uptime to prevent the entire network
+ // from settling on just a handful of addresses.
+ //
+ entry.cumulativeUptimeSeconds = 0;
+ entry.sessionUptimeSeconds = 0 ;
+ // Increment the number of consecutive failures.
+ if (entry.connectionValence > 0)
+ entry.connectionValence = 0;
+ --entry.connectionValence;
+ int const count (std::abs (entry.connectionValence));
+ if (m_journal.debug) m_journal.debug << leftw (18) <<
+ "Bootcache failed " << address <<
+ " with " << count <<
+ ((count > 1) ? " attempts" : " attempt");
+ flagForUpdate();
+ }
+
+ /** Called when an outbound connection handshake completes. */
+ void onConnectionHandshake (IPAddress const& address,
+ HandshakeAction action)
+ {
+ std::pair result (
+ m_entries.emplace (boost::unordered::piecewise_construct,
+ boost::make_tuple (address), boost::make_tuple ()));
+ Entry& entry (result.first->second);
+ // Can't already be active!
+ consistency_check (! entry.active);
+ // Reset session uptime
+ entry.sessionUptimeSeconds = 0;
+ // Count this as a connection success
+ if (entry.connectionValence < 0)
+ entry.connectionValence = 0;
+ ++entry.connectionValence;
+ // Update active status
+ if (action == doActivate)
+ {
+ entry.active = true;
+ entry.whenActive = m_clock();
+ }
+ else
+ {
+ entry.active = false;
+ }
+ // Prune if we made the container larger
+ if (result.second)
+ prune ();
+ flagForUpdate();
+ if (m_journal.info) m_journal.info << leftw (18) <<
+ "Bootcache connect " << address <<
+ " with " << entry.connectionValence <<
+ ((entry.connectionValence > 1) ? " successes" : " success");
+ }
+
+ /** Called periodically while the peer is active. */
+ //
+ // VFALCO TODO Can't we just put the active ones into an intrusive list
+ // and update their uptime in periodicActivity() now that
+ // we have the m_clock member?
+ //
+ void onConnectionActive (IPAddress const& address)
+ {
+ std::pair result (
+ m_entries.emplace (boost::unordered::piecewise_construct,
+ boost::make_tuple (address), boost::make_tuple ()));
+ // Must exist!
+ consistency_check (! result.second);
+ Entry& entry (result.first->second);
+ entry.update (m_clock());
+ flagForUpdate();
+ }
+
+ template
+ static std::string uptime_phrase (Seconds seconds)
+ {
+ if (seconds > 0)
+ return std::string (" with ") +
+ RelativeTime (seconds).to_string() +
+ " uptime";
+ return std::string ();
+ }
+ /** Called when an active outbound connection closes. */
+ void onConnectionClosed (IPAddress const& address)
+ {
+ Entries::iterator iter (m_entries.find (address));
+ // Must exist!
+ consistency_check (iter != m_entries.end());
+ Entry& entry (iter->second);
+ // Must be active!
+ consistency_check (entry.active);
+ if (m_journal.trace) m_journal.trace << leftw (18) <<
+ "Bootcache close " << address <<
+ uptime_phrase (entry.cumulativeUptimeSeconds);
+ entry.update (m_clock());
+ entry.sessionUptimeSeconds = 0;
+ entry.active = false;
+ flagForUpdate();
+ }
+
+ //--------------------------------------------------------------------------
+ //
+ // Diagnostics
+ //
+ //--------------------------------------------------------------------------
+
+ void onWrite (PropertyStream::Map& map)
+ {
+ map ["entries"] = uint32(m_entries.size());
+ }
+
+ static std::string valenceString (int valence)
+ {
+ std::stringstream ss;
+ if (valence >= 0)
+ ss << '+';
+ ss << valence;
+ return ss.str();
+ }
+
+ void dump (Journal::ScopedStream const& ss) const
+ {
+ std::vector const list (csort ());
+ ss << std::endl << std::endl <<
+ "Bootcache (size " << list.size() << ")";
+ for (std::vector ::const_iterator iter (
+ list.begin()); iter != list.end(); ++iter)
+ {
+ ss << std::endl <<
+ (*iter)->first << ", " <<
+ RelativeTime ((*iter)->second.cumulativeUptimeSeconds) << ", "
+ << valenceString ((*iter)->second.connectionValence);
+ if ((*iter)->second.active)
+ ss <<
+ ", active";
+ }
+ }
+
+ //--------------------------------------------------------------------------
+ //
+ //--------------------------------------------------------------------------
+
+private:
+ // Returns a vector of entry iterators sorted by descending score
+ std::vector csort () const
+ {
+ std::vector result;
+ result.reserve (m_entries.size());
+ for (Entries::const_iterator iter (m_entries.begin());
+ iter != m_entries.end(); ++iter)
+ result.push_back (iter);
+ std::random_shuffle (result.begin(), result.end());
+ // should be std::unstable_sort (c++11)
+ std::sort (result.begin(), result.end(), Less());
+ return result;
+ }
+
+ // Returns a vector of entry iterators sorted by descending score
+ std::vector sort ()
+ {
+ std::vector result;
+ result.reserve (m_entries.size());
+ for (Entries::iterator iter (m_entries.begin());
+ iter != m_entries.end(); ++iter)
+ result.push_back (iter);
+ std::random_shuffle (result.begin(), result.end());
+ // should be std::unstable_sort (c++11)
+ std::sort (result.begin(), result.end(), Less());
+ return result;
+ }
+
+ // Checks the cache size and prunes if its over the limit.
+ void prune ()
+ {
+ if (m_entries.size() <= Tuning::bootcacheSize)
+ return;
+ // Calculate the amount to remove
+ int count ((m_entries.size() *
+ Tuning::bootcachePrunePercent) / 100);
+ int pruned (0);
+ SortedEntries list (sort ());
+ for (SortedEntries::const_reverse_iterator iter (
+ list.rbegin()); count > 0 && iter != list.rend(); ++iter)
+ {
+ Entry& entry ((*iter)->second);
+ // skip active entries
+ if (entry.active)
+ continue;
+ if (m_journal.trace) m_journal.trace << leftw (18) <<
+ "Bootcache pruned" << (*iter)->first <<
+ uptime_phrase (entry.cumulativeUptimeSeconds) <<
+ " and valence " << entry.connectionValence;
+ m_entries.erase (*iter);
+ --count;
+ ++pruned;
+ }
+
+ if (m_journal.debug) m_journal.debug << leftw (18) <<
+ "Bootcache pruned " << pruned << " entries total";
+ }
+
+ // Updates the Store with the current set of entries if needed.
+ void update ()
+ {
+ if (! m_needsUpdate)
+ return;
+ typedef std::vector StoredData;
+ StoredData list;
+ list.reserve (m_entries.size());
+ for (Entries::const_iterator iter (m_entries.begin());
+ iter != m_entries.end(); ++iter)
+ {
+ Store::SavedBootstrapAddress entry;
+ entry.address = iter->first;
+ entry.cumulativeUptimeSeconds = iter->second.cumulativeUptimeSeconds;
+ entry.connectionValence = iter->second.connectionValence;
+ list.push_back (entry);
+ }
+ m_store.updateBootstrapCache (list);
+ // Reset the flag and cooldown timer
+ m_needsUpdate = false;
+ m_whenUpdate = m_clock() + Tuning::bootcacheCooldownSeconds;
+ }
+
+ // Checks the clock and calls update if we are off the cooldown.
+ void checkUpdate ()
+ {
+ if (m_needsUpdate && m_whenUpdate < m_clock())
+ update ();
+ }
+
+ // Called when changes to an entry will affect the Store.
+ void flagForUpdate ()
+ {
+ m_needsUpdate = true;
+ checkUpdate ();
+ }
+};
+
+}
+}
+
+#endif
diff --git a/src/ripple/peerfinder/impl/Cache.h b/src/ripple/peerfinder/impl/Cache.h
deleted file mode 100644
index e8a815f5a..000000000
--- a/src/ripple/peerfinder/impl/Cache.h
+++ /dev/null
@@ -1,147 +0,0 @@
-//------------------------------------------------------------------------------
-/*
- 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.
-*/
-//==============================================================================
-
-#ifndef RIPPLE_PEERFINDER_CACHE_H_INCLUDED
-#define RIPPLE_PEERFINDER_CACHE_H_INCLUDED
-
-namespace ripple {
-namespace PeerFinder {
-
-/** The Endpoint cache holds the short-lived relayed Endpoint messages.
-*/
-class Cache
-{
-private:
- typedef boost::unordered_map <
- IPAddress, CachedEndpoint, IPAddress::hasher> Table;
-
- typedef std::set <
- IPAddress*, PtrCompareFunctor > AddressSet;
-
- Journal m_journal;
-
- Table m_endpoints;
-
- // Tracks all the cached endpoints stored in the endpoint table
- // in oldest-to-newest order. The oldest item is at the head.
- List m_list;
-
- // A set of IP addresses which we know about
- AddressSet m_addresses;
-
- unsigned int m_generation;
-
-public:
- explicit Cache (Journal journal)
- : m_journal (journal)
- , m_generation(0)
- {
- }
-
- ~Cache ()
- {
- }
-
- std::size_t size() const
- {
- return m_endpoints.size();
- }
-
- void sweep (DiscreteTime now)
- {
- List ::iterator iter (m_list.begin());
-
- while (iter != m_list.end())
- {
- if (iter->whenExpires > now)
- break;
-
- CachedEndpoint &ep (*iter);
-
- // We need to remove the entry from the list before
- // we remove it from the table.
- iter = m_list.erase(iter);
-
- m_journal.debug << ep.message.address << " expired.";
-
- m_endpoints.erase (ep.message.address);
- }
- }
-
- // Insert or update an existing entry with the new message
- void insert (Endpoint const& message, DiscreteTime now)
- {
- std::pair result (
- m_endpoints.emplace (message.address, CachedEndpoint(message, now)));
-
- CachedEndpoint& entry (result.first->second);
-
- // We ignore messages that we receive at a higher hop count. We should
- // consider having a counter that monotonically increases per reboot
- // so that we can detect a server restart.
- if (!result.second && (entry.message.hops > message.hops))
- return;
-
- if (!result.second)
- {
- entry.message.hops = std::min (entry.message.hops, message.hops);
-
- // Copy the other fields based on uptime
- if (entry.message.uptimeSeconds < message.uptimeSeconds)
- {
- entry.message.incomingSlotsAvailable = message.incomingSlotsAvailable;
- entry.message.incomingSlotsMax = message.incomingSlotsMax;
- entry.message.uptimeSeconds = message.uptimeSeconds;
- entry.message.featureList = message.featureList;
- }
-
- entry.whenExpires = now + cacheSecondsToLive;
-
- // It must already be in the list. Remove it in preparation.
- m_list.erase (m_list.iterator_to(entry));
- }
-
- m_journal.debug << message.address <<
- "valid " << entry.whenExpires <<
- " (" << entry.message.incomingSlotsAvailable <<
- "/" << entry.message.incomingSlotsMax << ")";
-
- m_list.push_back (entry);
- }
-
- // Get all known endpoints, sorted by distance (i.e. by hop).
- //
- Giveaways getGiveawayList()
- {
- Giveaways giveaway;
-
- for (List ::iterator iter (m_list.begin());
- iter != m_list.end(); iter++)
- {
- giveaway.add (*iter);
- }
-
- return giveaway;
- }
-};
-
-}
-}
-
-#endif
diff --git a/src/ripple/peerfinder/impl/Config.cpp b/src/ripple/peerfinder/impl/Config.cpp
index aa693d8de..03dfd1a6c 100644
--- a/src/ripple/peerfinder/impl/Config.cpp
+++ b/src/ripple/peerfinder/impl/Config.cpp
@@ -21,28 +21,36 @@ namespace ripple {
namespace PeerFinder {
Config::Config ()
- : maxPeerCount (0)
- , wantIncoming (false)
- , connectAutomatically (false)
+ : maxPeers (Tuning::defaultMaxPeers)
+ , outPeers (calcOutPeers ())
+ , wantIncoming (true)
+ , autoConnect (true)
, listeningPort (0)
{
}
-void Config::onWrite(PropertyStream::Map &map)
+double Config::calcOutPeers () const
{
- map ["min_out_count"] = minOutCount;
- map ["out_percent"] = outPercent;
- map ["max_peer_count"] = maxPeerCount;
- map ["want_incoming"] = wantIncoming;
- map ["connect_automatically"] = connectAutomatically;
- map ["listening_port"] = listeningPort;
- map ["feature_list"] = featureList;
+ return std::max (
+ maxPeers * Tuning::outPercent * 0.01,
+ double (Tuning::minOutCount));
}
-void Config::fillInDefaultValues()
+void Config::applyTuning ()
{
- if (maxPeerCount == 0)
- maxPeerCount = defaultMaxPeerCount;
+ if (maxPeers < Tuning::minOutCount)
+ maxPeers = Tuning::minOutCount;
+ outPeers = calcOutPeers ();
+}
+
+void Config::onWrite (PropertyStream::Map &map)
+{
+ map ["max_peers"] = maxPeers;
+ map ["out_peers"] = outPeers;
+ map ["want_incoming"] = wantIncoming;
+ map ["auto_connect"] = autoConnect;
+ map ["port"] = listeningPort;
+ map ["features"] = features;
}
}
diff --git a/src/ripple/peerfinder/impl/Endpoint.cpp b/src/ripple/peerfinder/impl/Endpoint.cpp
index d9956abf9..176fe697a 100644
--- a/src/ripple/peerfinder/impl/Endpoint.cpp
+++ b/src/ripple/peerfinder/impl/Endpoint.cpp
@@ -24,9 +24,6 @@ namespace PeerFinder {
Endpoint::Endpoint ()
: hops (0)
- , incomingSlotsAvailable (0)
- , incomingSlotsMax (0)
- , uptimeSeconds (0)
{
}
diff --git a/src/ripple/peerfinder/impl/FixedPeer.h b/src/ripple/peerfinder/impl/FixedPeer.h
new file mode 100644
index 000000000..eacd04d01
--- /dev/null
+++ b/src/ripple/peerfinder/impl/FixedPeer.h
@@ -0,0 +1,79 @@
+//------------------------------------------------------------------------------
+/*
+ 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.
+*/
+//==============================================================================
+
+#ifndef RIPPLE_PEERFINDER_FIXEDPEER_H_INCLUDED
+#define RIPPLE_PEERFINDER_FIXEDPEER_H_INCLUDED
+
+namespace ripple {
+namespace PeerFinder {
+
+/** Stores information about a fixed peer.
+ A fixed peer is defined in the config file and can be specified using
+ either an IP address or a hostname (which may resolve to zero or more
+ addresses).
+ A fixed peer which has multiple IP addresses is considered connected
+ if there is a connection to any one of its addresses.
+*/
+class FixedPeer
+{
+public:
+ /* The config name */
+ std::string const m_name;
+
+ /* The corresponding IP address(es) */
+ IPAddresses m_addresses;
+
+ FixedPeer (std::string const& name,
+ IPAddresses const& addresses)
+ : m_name (name)
+ , m_addresses (addresses)
+ {
+ bassert (!m_addresses.empty ());
+
+ // NIKB TODO add support for multiple IPs
+ m_addresses.resize (1);
+ }
+
+ // NIKB TODO support peers which resolve to more than a single address
+ IPAddress getAddress () const
+ {
+ if (m_addresses.size ())
+ return m_addresses.at(0);
+
+ return IPAddress ();
+ }
+
+ template
+ bool hasAddress (IPAddress const& address, Comparator compare) const
+ {
+ for (IPAddresses::const_iterator iter = m_addresses.cbegin();
+ iter != m_addresses.cend(); ++iter)
+ {
+ if (compare (*iter, address))
+ return true;
+ }
+
+ return false;
+ }
+};
+
+}
+}
+
+#endif
diff --git a/src/ripple/peerfinder/impl/Giveaways.h b/src/ripple/peerfinder/impl/Giveaways.h
index e699c6054..ee0d63d46 100644
--- a/src/ripple/peerfinder/impl/Giveaways.h
+++ b/src/ripple/peerfinder/impl/Giveaways.h
@@ -23,70 +23,124 @@
namespace ripple {
namespace PeerFinder {
-/** The Giveaways holds a vector of HopVectors, one of each hop.
-*/
+/** Holds a rotating set of endpoint messages to give away. */
class Giveaways
{
- std::vector m_hopVector;
- bool m_shuffled;
+public:
+ typedef std::vector Bucket;
+ typedef boost::array Buckets;
+
+ Endpoints m_endpoints;
+ std::size_t m_remain;
+ Buckets m_buckets;
+
+ void prepare ()
+ {
+ for (Buckets::iterator iter (m_buckets.begin());
+ iter != m_buckets.end(); ++iter)
+ iter->reserve (m_endpoints.size ());
+ }
public:
- typedef std::vector ::iterator iterator;
- typedef std::vector ::reverse_iterator reverse_iterator;
-
- Giveaways()
- : m_hopVector(maxPeerHopCount)
- , m_shuffled(false)
+ bool is_consistent ()
{
-
+ // Make sure the counts add up
+ std::size_t count (0);
+ for (Buckets::const_iterator iter (m_buckets.begin());
+ iter != m_buckets.end(); ++iter)
+ count += iter->size();
+ return count == m_remain;
}
- // Add the endpoint to the appropriate hop vector.
- void add (CachedEndpoint &endpoint)
+ void refill ()
{
- if (endpoint.message.hops < maxPeerHopCount)
- m_hopVector[endpoint.message.hops].add(endpoint);
- }
-
- // Resets the Giveaways, preparing to allow a new peer to iterate over it.
- void reset ()
- {
- for (size_t i = 0; i != m_hopVector.size(); i++)
+ // Empty out the buckets
+ for (Buckets::iterator iter (m_buckets.begin());
+ iter != m_buckets.end(); ++iter)
+ iter->clear();
+ // Put endpoints back into buckets
+ for (Endpoints::const_iterator iter (m_endpoints.begin());
+ iter != m_endpoints.end(); ++iter)
{
- if (!m_shuffled)
- m_hopVector[i].shuffle ();
-
- m_hopVector[i].reset ();
+ Endpoint const& ep (*iter);
+ consistency_check (ep.hops <= Tuning::maxHops);
+ m_buckets [ep.hops].push_back (&ep);
}
-
- // Once this has been called, the hop vectors have all been shuffled
- // and we do not need to shuffle them again for the lifetime of this
- // instance.
- m_shuffled = true;
+ // Shuffle the buckets
+ for (Buckets::iterator iter (m_buckets.begin());
+ iter != m_buckets.end(); ++iter)
+ std::random_shuffle (iter->begin(), iter->end());
+ m_remain = m_endpoints.size();
+ consistency_check (is_consistent ());
}
- // Provides an iterator that starts from hop 0 and goes all the way to
- // the max hop.
- iterator begin ()
+public:
+ explicit Giveaways (Endpoints const& endpoints)
+ : m_endpoints (endpoints)
+ , m_remain (0)
{
- return m_hopVector.begin();
+ prepare();
}
- iterator end ()
+#if BEAST_COMPILER_SUPPORTS_MOVE_SEMANTICS
+ Giveaways (Endpoints&& endpoints)
+ : m_endpoints (endpoints)
+ , m_remain (0)
{
- return m_hopVector.end();
+ prepare();
+ }
+#endif
+
+ /** Append up to `n` Endpoint to the specified container.
+ The entries added to the container will have hops incremented.
+ */
+ template
+ void append (Endpoints::size_type n, EndpointContainer& c)
+ {
+ n = std::min (n, m_endpoints.size());
+ c.reserve (c.size () + n);
+ if (m_remain < n)
+ refill ();
+ for (cyclic_iterator iter (
+ m_buckets.begin (), m_buckets.begin (), m_buckets.end()); n;)
+ {
+ Bucket& bucket (*iter++);
+ if (! bucket.empty ())
+ {
+ c.emplace_back (*bucket.back ());
+ bucket.pop_back ();
+ ++c.back ().hops;
+ --n;
+ --m_remain;
+ }
+ }
+ consistency_check (is_consistent ());
}
- // Provides an iterator that starts from the max hop and goes all the way
- // down to hop 0.
- reverse_iterator rbegin ()
- {
- return m_hopVector.rbegin();
- }
-
- reverse_iterator rend ()
- {
- return m_hopVector.rend();
+ /** Retrieve a fresh set of endpoints, preferring high hops.
+ The entries added to the container will have hops incremented.
+ */
+ template
+ void reverse_append (Endpoints::size_type n, EndpointContainer& c)
+ {
+ n = std::min (n, m_endpoints.size());
+ c.reserve (c.size () + n);
+ if (m_remain < n)
+ refill ();
+ for (cyclic_iterator iter (
+ m_buckets.rbegin (), m_buckets.rbegin (), m_buckets.rend()); n;)
+ {
+ Bucket& bucket (*iter++);
+ if (! bucket.empty ())
+ {
+ c.emplace_back (*bucket.back ());
+ bucket.pop_back ();
+ ++c.back ().hops;
+ --n;
+ --m_remain;
+ }
+ }
+ consistency_check (is_consistent ());
}
};
diff --git a/src/ripple/peerfinder/impl/GiveawaysAtHop.h b/src/ripple/peerfinder/impl/GiveawaysAtHop.h
deleted file mode 100644
index 269136448..000000000
--- a/src/ripple/peerfinder/impl/GiveawaysAtHop.h
+++ /dev/null
@@ -1,126 +0,0 @@
-//------------------------------------------------------------------------------
-/*
- 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.
-*/
-//==============================================================================
-
-#ifndef RIPPLE_PEERFINDER_GIVEAWAYSATHOP_H_INCLUDED
-#define RIPPLE_PEERFINDER_GIVEAWAYSATHOP_H_INCLUDED
-
-namespace ripple {
-namespace PeerFinder {
-
-/** A GiveawaysAtHop contains a list of all the endpoints that are a particular
- number of hops away from us.
-*/
-class GiveawaysAtHop
-{
-public:
- typedef std::vector::iterator iterator;
-
-private:
- // List of endpoints that haven't been seen during this iteration
- std::vector m_list;
-
- // List of endpoints that have been used during this iteration
- std::vector m_used;
-
- // This iterator tracks where we are in the list between calls. It is
- // set to the beginning of the list by calling reset().
- iterator m_position;
-
-public:
- // This function adds a new endpoint to the list of endpoints
- // that we will be returning.
- void add (CachedEndpoint &endpoint)
- {
- if (endpoint.color)
- m_list.push_back(&endpoint);
- else
- m_used.push_back(&endpoint);
- }
-
- // Shuffles the list of peers we are about to hand out.
- void shuffle ()
- {
- std::random_shuffle (m_list.begin (), m_list.end ());
- }
-
- // Prepare to begin iterating over the entire set of peers again.
- bool reset ()
- {
- // We need to add any entries from the stale vector in the tail
- // end of the fresh vector. We do not need to shuffle them.
- if (!m_used.empty())
- {
- m_list.insert(m_list.end (), m_used.begin (), m_used.end ());
- m_used.clear();
- }
-
- // And start iterating the list from the beginning.
- m_position = m_list.begin();
-
- // Return whether there is anything in this vector to iterate.
- return !empty();
- }
-
- // Determines if we have any giveaways at the current hop could; if we
- // do not you should not dereference the iterator returned from "begin" or
- // "rbegin"
- bool empty() const
- {
- return m_list.empty();
- }
-
- // This is somewhat counterintuitive, but it doesn't really "begin"
- // iteration, but allows us to resume it.
- iterator begin ()
- {
- return m_position;
- }
-
- // The iterator to the last fresh endpoint we have available. Once we get
- // to this point, we have provided this peer with all endpoints in our list.
- iterator end ()
- {
- return m_list.end();
- }
-
- // Removes the specified item from the "fresh" list of endpoints and returns
- // an iterator to the next one to use. This means that the peer decided
- // to use this iterator.
- iterator erase (iterator iter)
- {
- // NIKB FIXME change node color, if it's going from fresh to stale
-
- m_used.push_back(*iter);
-
- return m_list.erase(iter);
- }
-
- // Reserves entries to allow inserts to be efficient.
- void reserve (size_t n)
- {
- m_used.reserve (n);
- m_list.reserve (n);
- }
-};
-
-
-}
-}
-
-#endif
diff --git a/src/ripple/peerfinder/impl/LegacyEndpointCache.h b/src/ripple/peerfinder/impl/LegacyEndpointCache.h
deleted file mode 100644
index 526bea889..000000000
--- a/src/ripple/peerfinder/impl/LegacyEndpointCache.h
+++ /dev/null
@@ -1,263 +0,0 @@
-//------------------------------------------------------------------------------
-/*
- 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.
-*/
-//==============================================================================
-
-#ifndef RIPPLE_PEERFINDER_LEGACYENDPOINTCACHE_H_INCLUDED
-#define RIPPLE_PEERFINDER_LEGACYENDPOINTCACHE_H_INCLUDED
-
-#include
-
-namespace ripple {
-namespace PeerFinder {
-
-/** A container for managing the cache of legacy endpoints. */
-class LegacyEndpointCache
-{
-public:
- typedef std::vector FlattenedList;
-
-private:
-#if 0
- typedef boost::multi_index_container <
- LegacyEndpoint, boost::multi_index::indexed_by <
- boost::multi_index::hashed_unique <
- BOOST_MULTI_INDEX_MEMBER(PeerFinder::LegacyEndpoint,IPAddress,address),
- IPAddress::hasher>
- >
- > MapType;
-
- MapType m_map;
-#endif
-
- Store& m_store;
- Journal m_journal;
- int m_mutationCount;
-
- //--------------------------------------------------------------------------
-
- /** Updates the database with the cache contents. */
- void update ()
- {
- FlattenedList list (flatten());
- m_store.updateLegacyEndpoints (list);
- m_journal.debug << "Updated " << list.size() << " legacy endpoints";
- }
-
- /** Increments the mutation count and updates the database if needed. */
- void mutate ()
- {
- // This flag keeps us from updating while we are loading
- if (m_mutationCount == -1)
- return;
-
- if (++m_mutationCount >= legacyEndpointMutationsPerUpdate)
- {
- update();
- m_mutationCount = 0;
- }
- }
-
- /** Returns a flattened array of pointers to the legacy endpoints. */
- FlattenedList flatten () const
- {
- FlattenedList list;
-#if 0
- list.reserve (m_map.size());
- for (MapType::iterator iter (m_map.begin());
- iter != m_map.end(); ++iter)
- list.push_back (&*iter);
-#endif
- return list;
- }
-
- /** Prune comparison function, strict weak ordering on desirability. */
- struct PruneLess
- {
- static int checkedScore (LegacyEndpoint const& ep)
- {
- return ep.checked ? (ep.canAccept ? 2 : 1) : 0;
- }
-
- bool operator() (LegacyEndpoint const* lhs,
- LegacyEndpoint const* rhs) const
- {
- // prefer checked and canAccept
- int const checkedCompare (
- checkedScore (*lhs) - checkedScore (*rhs));
- if (checkedCompare > 0)
- return true;
- else if (checkedScore < 0)
- return false;
-
- // prefer newer entries
- if (lhs->whenInserted > rhs->whenInserted)
- return true;
- else if (lhs->whenInserted < rhs->whenInserted)
- return false;
-
- return false;
- }
- };
-
- /** Sort endpoints by desirability and discard the bottom half. */
- void prune()
- {
- FlattenedList list (flatten());
- if (list.size () < 3)
- return;
- std::random_shuffle (list.begin(), list.end());
- std::sort (list.begin(), list.end(), PruneLess());
- FlattenedList::const_iterator pos (list.begin() + list.size()/2 + 1);
-#if 0
- std::size_t const n (m_map.size() - (pos - list.begin()));
- MapType map;
- for (FlattenedList::const_iterator iter (list.begin());
- iter != pos; ++iter)
- map.insert (**iter);
- std::swap (map, m_map);
- m_journal.info << "Pruned " << n << " legacy endpoints";
-#endif
- mutate();
- }
-
- /** Get comparison function. */
- struct GetLess
- {
- bool operator() (LegacyEndpoint const* lhs,
- LegacyEndpoint const* rhs) const
- {
- // Always prefer entries we tried longer ago. This should
- // cycle through the entire cache before re-using an address
- // for making a connection attempt.
- //
- if (lhs->lastGet < rhs->lastGet)
- return true;
- else if (lhs->lastGet > rhs->lastGet)
- return false;
-
- // Fall back to the prune desirability comparison
- return PruneLess() (lhs, rhs);
- }
- };
-
-public:
- LegacyEndpointCache (Store& store, Journal journal)
- : m_store (store)
- , m_journal (journal)
- , m_mutationCount (-1)
- {
- }
-
- ~LegacyEndpointCache ()
- {
- }
-
- std::size_t size() const
- {
-#if 0
- return m_map.size();
-#else
- return 0;
-#endif
- }
-
- /** Load the legacy endpoints cache from the database. */
- void load (DiscreteTime now)
- {
- typedef std::vector List;
- List list;
- m_store.loadLegacyEndpoints (list);
- std::size_t n (0);
- for (List::const_iterator iter (list.begin());
- iter != list.end(); ++iter)
- {
-#if 0
- std::pair result (insert (*iter, now));
- if (result.second)
- ++n;
-#endif
- }
- m_journal.debug << "Loaded " << n << " legacy endpoints";
- m_mutationCount = 0;
- }
-
-#if 0
- /** Attempt to insert the endpoint.
- The return value provides a reference to the new or existing endpoint.
- The bool indicates whether or not the insertion took place.
- */
- std::pair insert (IPAddress const& address, DiscreteTime now)
- {
- std::pair result (
- m_map.insert (LegacyEndpoint (address, now)));
- if (m_map.size() > legacyEndpointCacheSize)
- prune();
- if (result.second)
- mutate();
- return std::make_pair (*result.first, result.second);
- }
-#endif
-
- /** Returns a pointer to the legacy endpoint if it exists, else nullptr. */
- LegacyEndpoint const* find (IPAddress const& address)
- {
-#if 0
- MapType::iterator iter (m_map.find (address));
- if (iter != m_map.end())
- return &*iter;
-#endif
- return nullptr;
- }
-
- /** Updates the metadata following a connection attempt.
- @param canAccept A flag indicating if the connection succeeded.
- */
- void checked (IPAddress const& address, bool canAccept)
- {
- LegacyEndpoint const* endpoint (find (address));
- if (endpoint != nullptr)
- {
- endpoint->checked = true;
- endpoint->canAccept = canAccept;
- mutate();
- }
- }
-
- /** Appends up to n addresses for establishing outbound peers.
- Also updates the lastGet field of the LegacyEndpoint so we will avoid
- re-using the address until we have tried all the others.
- */
- void get (std::size_t n, std::vector & result, DiscreteTime now) const
- {
- FlattenedList list (flatten());
- std::random_shuffle (list.begin(), list.end());
- std::sort (list.begin(), list.end(), GetLess());
- n = std::min (n, list.size());
- for (FlattenedList::iterator iter (list.begin());
- n-- && iter!=list.end(); ++iter)
- {
- result.push_back ((*iter)->address);
- (*iter)->lastGet = now;
- }
- }
-};
-
-}
-}
-
-#endif
diff --git a/src/ripple/peerfinder/impl/Livecache.cpp b/src/ripple/peerfinder/impl/Livecache.cpp
new file mode 100644
index 000000000..30ec94d8e
--- /dev/null
+++ b/src/ripple/peerfinder/impl/Livecache.cpp
@@ -0,0 +1,98 @@
+//------------------------------------------------------------------------------
+/*
+ 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.
+*/
+//==============================================================================
+
+namespace ripple {
+namespace PeerFinder {
+
+class LivecacheTests : public UnitTest
+{
+public:
+ ManualClock m_clock_source;
+
+ // Add the address as an endpoint
+ void add (uint32 index, uint16 port, Livecache& c)
+ {
+ Endpoint ep;
+ ep.hops = 0;
+ ep.address = IPAddress (
+ IP::AddressV4 (index), port);
+ c.insert (ep);
+ }
+
+ void testFetch ()
+ {
+ beginTestCase ("fetch");
+
+ Livecache c (m_clock_source, Journal());
+
+ add (1, 1, c);
+ add (2, 1, c);
+ add (3, 1, c);
+ add (4, 1, c);
+ add (4, 2, c);
+ add (4, 3, c);
+ add (5, 1, c);
+ add (6, 1, c);
+ add (6, 2, c);
+ add (7, 1, c);
+
+ Endpoints const eps (c.fetch_unique ());
+
+ struct IsAddr
+ {
+ explicit IsAddr (uint32 index_)
+ : index (index_)
+ { }
+ bool operator() (Endpoint const& ep) const
+ { return ep.address.to_v4().value == index; }
+ uint32 index;
+ };
+
+ expect (std::count_if (
+ eps.begin(), eps.end(), IsAddr (1)) == 1);
+ expect (std::count_if (
+ eps.begin(), eps.end(), IsAddr (2)) == 1);
+ expect (std::count_if (
+ eps.begin(), eps.end(), IsAddr (3)) == 1);
+ expect (std::count_if (
+ eps.begin(), eps.end(), IsAddr (4)) == 1);
+ expect (std::count_if (
+ eps.begin(), eps.end(), IsAddr (5)) == 1);
+ expect (std::count_if (
+ eps.begin(), eps.end(), IsAddr (6)) == 1);
+ expect (std::count_if (
+ eps.begin(), eps.end(), IsAddr (7)) == 1);
+
+ pass();
+ }
+
+ void runTest ()
+ {
+ testFetch ();
+ }
+
+ LivecacheTests () : UnitTest ("PeerFinder:Livecache", "ripple")
+ {
+ }
+};
+
+static LivecacheTests livecacheTests;
+
+}
+}
diff --git a/src/ripple/peerfinder/impl/Livecache.h b/src/ripple/peerfinder/impl/Livecache.h
new file mode 100644
index 000000000..d77770b41
--- /dev/null
+++ b/src/ripple/peerfinder/impl/Livecache.h
@@ -0,0 +1,252 @@
+//------------------------------------------------------------------------------
+/*
+ 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.
+*/
+//==============================================================================
+
+#ifndef RIPPLE_PEERFINDER_LIVECACHE_H_INCLUDED
+#define RIPPLE_PEERFINDER_LIVECACHE_H_INCLUDED
+
+namespace ripple {
+namespace PeerFinder {
+
+/** The Livecache holds the short-lived relayed Endpoint messages.
+
+ Since peers only advertise themselves when they have open slots,
+ we want these messags to expire rather quickly after the peer becomes
+ full.
+
+ Addresses added to the cache are not connection-tested to see if
+ they are connectible (with one small exception regarding neighbors).
+ Therefore, these addresses are not suitable for persisting across
+ launches or for bootstrapping, because they do not have verifiable
+ and locally observed uptime and connectibility information.
+*/
+class Livecache
+{
+public:
+ struct Entry;
+
+ typedef List EntryList;
+
+ struct Entry : public EntryList::Node
+ {
+ Entry (Endpoint const& endpoint_, DiscreteTime whenExpires_)
+ : endpoint (endpoint_)
+ , whenExpires (whenExpires_)
+ {
+ }
+
+ Endpoint endpoint;
+ DiscreteTime whenExpires;
+ };
+
+ typedef std::set SortedTable;
+ typedef boost::unordered_map AddressTable;
+
+ DiscreteClock m_clock;
+ Journal m_journal;
+ AddressTable m_byAddress;
+ SortedTable m_bySorted;
+
+ // Tracks all the cached endpoints stored in the endpoint table
+ // in oldest-to-newest order. The oldest item is at the head.
+ EntryList m_list;
+
+public:
+ /** Create the cache. */
+ explicit Livecache (
+ DiscreteClock clock,
+ Journal journal)
+ : m_clock (clock)
+ , m_journal (journal)
+ {
+ }
+
+ /** Returns `true` if the cache is empty. */
+ bool empty () const
+ {
+ return m_byAddress.empty ();
+ }
+
+ /** Returns the number of entries in the cache. */
+ AddressTable::size_type size() const
+ {
+ return m_byAddress.size();
+ }
+
+ /** Erase entries whose time has expired. */
+ void sweep ()
+ {
+ DiscreteTime const now (m_clock());
+ AddressTable::size_type count (0);
+ for (EntryList::iterator iter (m_list.begin());
+ iter != m_list.end();)
+ {
+ // Short circuit the loop since the list is sorted
+ if (iter->whenExpires > now)
+ break;
+ Entry& entry (*iter);
+ if (m_journal.trace) m_journal.trace << leftw (18) <<
+ "Livecache expired " << entry.endpoint.address;
+ // Must erase from list before map
+ iter = m_list.erase (iter);
+ meets_postcondition (m_bySorted.erase (
+ entry.endpoint) == 1);
+ meets_postcondition (m_byAddress.erase (
+ entry.endpoint.address) == 1);
+ ++count;
+ }
+
+ if (count > 0)
+ {
+ if (m_journal.debug) m_journal.debug << leftw (18) <<
+ "Livecache expired " << count <<
+ ((count > 1) ? "entries" : "entry");
+ }
+ }
+
+ /** Creates or updates an existing entry based on a new message. */
+ void insert (Endpoint endpoint)
+ {
+ // Caller is responsible for validation
+ check_precondition (endpoint.hops <= Tuning::maxHops);
+ DiscreteTime const now (m_clock());
+ DiscreteTime const whenExpires (
+ now + Tuning::liveCacheSecondsToLive);
+ std::pair result (
+ m_byAddress.emplace (boost::unordered::piecewise_construct,
+ boost::make_tuple (endpoint.address),
+ boost::make_tuple (endpoint, whenExpires)));
+ Entry& entry (result.first->second);
+ // Drop duplicates at higher hops
+ if (! result.second && (endpoint.hops > entry.endpoint.hops))
+ {
+ std::size_t const excess (
+ endpoint.hops - entry.endpoint.hops);
+ if (m_journal.trace) m_journal.trace << leftw(18) <<
+ "Livecache drop " << endpoint.address <<
+ " at hops +" << excess;
+ return;
+ }
+ // Update metadata if the address already exists
+ if (! result.second)
+ {
+ meets_postcondition (m_bySorted.erase (
+ result.first->second.endpoint) == 1);
+ if (endpoint.hops < entry.endpoint.hops)
+ {
+ if (m_journal.debug) m_journal.debug << leftw (18) <<
+ "Livecache update " << endpoint.address <<
+ " at hops " << endpoint.hops;
+ entry.endpoint.hops = endpoint.hops;
+ }
+ else
+ {
+ if (m_journal.trace) m_journal.trace << leftw (18) <<
+ "Livecache refresh " << endpoint.address <<
+ " at hops " << endpoint.hops;
+ }
+
+ entry.endpoint.features = endpoint.features;
+ entry.whenExpires = whenExpires;
+
+ m_list.erase (m_list.iterator_to(entry));
+ }
+ else
+ {
+ if (m_journal.debug) m_journal.debug << leftw (18) <<
+ "Livecache insert " << endpoint.address <<
+ " at hops " << endpoint.hops;
+ }
+ meets_postcondition (m_bySorted.insert (entry.endpoint).second);
+ m_list.push_back (entry);
+ }
+
+ /** Returns the full set of endpoints in a Giveaways class. */
+ Giveaways giveaways()
+ {
+ Endpoints endpoints;
+ endpoints.reserve (m_list.size());
+ for (EntryList::const_iterator iter (m_list.cbegin());
+ iter != m_list.cend(); ++iter)
+ {
+ endpoints.push_back (iter->endpoint);
+ endpoints.back ().hops;
+ }
+ if (! endpoints.empty())
+ return Giveaways (endpoints);
+ return Giveaways (endpoints);
+ }
+
+ /** Returns an ordered list all entries with unique addresses. */
+ Endpoints fetch_unique () const
+ {
+ Endpoints result;
+ if (m_bySorted.empty ())
+ return result;
+ result.reserve (m_bySorted.size ());
+ Endpoint const& front (*m_bySorted.begin());
+ IP::Address prev (front.address.address());
+ result.emplace_back (front);
+ for (SortedTable::const_iterator iter (++m_bySorted.begin());
+ iter != m_bySorted.end(); ++iter)
+ {
+ IP::Address const addr (iter->address.address());
+ if (addr != prev)
+ {
+ result.emplace_back (*iter);
+ ++result.back().hops;
+ prev = addr;
+ }
+ }
+ return result;
+ }
+
+ /** Produce diagnostic output. */
+ void dump (Journal::ScopedStream& ss) const
+ {
+ ss << std::endl << std::endl <<
+ "Livecache (size " << m_byAddress.size() << ")";
+ for (AddressTable::const_iterator iter (m_byAddress.begin());
+ iter != m_byAddress.end(); ++iter)
+ {
+ Entry const& entry (iter->second);
+ ss << std::endl <<
+ entry.endpoint.address << ", " <<
+ entry.endpoint.hops << " hops";
+ }
+ }
+
+ /** Returns a histogram of message counts by hops. */
+ typedef boost::array Histogram;
+ Histogram histogram () const
+ {
+ Histogram h;
+ for (Histogram::iterator iter (h.begin());
+ iter != h.end(); ++iter)
+ *iter = 0;
+ for (EntryList::const_iterator iter (m_list.begin());
+ iter != m_list.end(); ++iter)
+ ++h[iter->endpoint.hops];
+ return h;
+ }
+};
+
+}
+}
+
+#endif
diff --git a/src/ripple/peerfinder/impl/Logic.h b/src/ripple/peerfinder/impl/Logic.h
index 9d7207d84..10a091b7e 100644
--- a/src/ripple/peerfinder/impl/Logic.h
+++ b/src/ripple/peerfinder/impl/Logic.h
@@ -20,108 +20,88 @@
#ifndef RIPPLE_PEERFINDER_LOGIC_H_INCLUDED
#define RIPPLE_PEERFINDER_LOGIC_H_INCLUDED
+#define FIX_ME 0
+
namespace ripple {
namespace PeerFinder {
-// Fresh endpoints are ones we have seen recently via mtENDPOINTS.
-// These are best to give out to someone who needs additional
-// connections as quickly as possible, since it is very likely
-// that the fresh endpoints have open incoming slots.
-//
-// Reliable endpoints are ones which are highly likely to be
-// connectible over long periods of time. They might not necessarily
-// have an incoming slot, but they are good for bootstrapping when
-// there are no peers yet. Typically these are what we would want
-// to store in a database or local config file for a future launch.
-
-//------------------------------------------------------------------------------
-
-#if 0
-typedef boost::multi_index_container <
- PeerInfo, boost::multi_index::indexed_by <
- boost::multi_index::hashed_unique <
- BOOST_MULTI_INDEX_MEMBER(PeerFinder::PeerInfo,PeerID,id),
- PeerID::hasher>,
- boost::multi_index::hashed_non_unique <
- BOOST_MULTI_INDEX_MEMBER(PeerFinder::PeerInfo,IPAddress,address),
- IPAddress::hasher>
- >
-> Peers;
-#endif
-
-//------------------------------------------------------------------------------
-
/** The Logic for maintaining the list of Peer addresses.
We keep this in a separate class so it can be instantiated
for unit tests.
*/
class Logic
{
-private:
- typedef std::set < IPAddress > IPAddressSet;
-
public:
- template < class T, class C = std::less >
- struct PtrComparator
- {
- bool operator()(const T *x, const T *y) const
- {
- C comp;
+ // Maps live sockets to the metadata
+ typedef boost::unordered_map Peers;
- return comp(*x, *y);
- }
- };
+ // A set of unique Ripple public keys
+ typedef std::set Keys;
+
+ // A set of non-unique IPAddresses without ports, used
+ // to filter duplicates when making outgoing connections.
+ typedef std::multiset Addresses;
struct State
{
- State ()
+ State (
+ Store* store,
+ DiscreteClock clock,
+ Journal journal)
: stopping (false)
- { }
+ , slots (clock)
+ , livecache (clock, Journal (
+ journal, Reporting::livecache))
+ , bootcache (*store, clock, Journal (
+ journal, Reporting::bootcache))
+ {
+ }
- /** True if we are stopping. */
+ // True if we are stopping.
bool stopping;
- /** The source we are currently fetching.
- This is used to cancel I/O during program exit.
- */
+ // The source we are currently fetching.
+ // This is used to cancel I/O during program exit.
SharedPtr fetchSource;
+
+ // Configuration settings
+ Config config;
+
+ // Slot counts and other aggregate statistics.
+ Slots slots;
+
+ // Live livecache from mtENDPOINTS messages
+ Livecache livecache;
+
+ // LiveCache of addresses suitable for gaining initial connections
+ Bootcache bootcache;
+
+ // Metadata for live sockets
+ Peers peers;
+
+ // Set of public keys belonging to active peers
+ Keys keys;
+
+ // The addresses (but not port) we are connected to
+ // Note that this set can contain duplicates
+ Addresses addresses;
};
typedef SharedData SharedState;
+ Journal m_journal;
SharedState m_state;
-
- //--------------------------------------------------------------------------
-
DiscreteClock m_clock;
Callback& m_callback;
Store& m_store;
Checker& m_checker;
- Journal m_journal;
-
- Config m_config;
// A list of peers that should always be connected
- typedef std::set FixedPeers;
+ typedef boost::unordered_map FixedPeers;
FixedPeers m_fixedPeers;
// A list of dynamic sources to consult as a fallback
- std::vector > m_sources;
-
- // The current tally of peer slot statistics
- Slots m_slots;
-
-#if 0
- // Our view of the current set of connected peers.
- Peers m_peers;
-#endif
-
- Cache m_cache;
-
- LegacyEndpointCache m_legacyCache;
-
- // Our set of connection attempts currently in-progress
- IPAddressSet m_attemptsInProgress;
+ std::vector > m_sources;
//--------------------------------------------------------------------------
@@ -131,22 +111,23 @@ public:
Store& store,
Checker& checker,
Journal journal)
- : m_clock (clock)
+ : m_journal (journal, Reporting::logic)
+ , m_state (&store, clock, journal)
+ , m_clock (clock)
, m_callback (callback)
, m_store (store)
, m_checker (checker)
- , m_journal (journal)
- , m_slots (clock)
- , m_cache (journal)
- , m_legacyCache (store, journal)
{
- /* assign sensible default values */
- m_config.fillInDefaultValues();
+ setConfig (Config ());
}
- DiscreteTime get_now()
+ // Load persistent state information from the Store
+ //
+ void load ()
{
- return m_clock();
+ SharedState::Access state (m_state);
+
+ state->bootcache.load ();
}
/** Stop the logic.
@@ -163,135 +144,318 @@ public:
state->fetchSource->cancel ();
}
- // Output statistics
- void onWrite (PropertyStream::Map& map)
+ //--------------------------------------------------------------------------
+
+ /** Returns a new set of connection addresses from the live cache. */
+ IPAddresses fetch_livecache (std::size_t needed, SharedState::Access& state)
{
- // VFALCO NOTE These ugly casts are needed because
- // of how std::size_t is declared on some linuxes
- //
- map ["cache"] = uint32(m_cache.size());
- map ["legacy"] = uint32(m_legacyCache.size());
- map ["fixed_desired"] = uint32 (m_fixedPeers.size());
-
+ Endpoints endpoints (state->livecache.fetch_unique());
+ Endpoints temp;
+ temp.reserve (endpoints.size ());
+
{
- PropertyStream::Map child ("slots", map);
- m_slots.onWrite (child);
+ // Remove the addresses we are currently connected to
+ struct LessWithoutPortSet
+ {
+ bool operator() (
+ Endpoint const& lhs, IPAddress const& rhs) const
+ {
+ return lhs.address.address() < rhs.address();
+ }
+ bool operator() (
+ Endpoint const& lhs, Endpoint const& rhs) const
+ {
+ return lhs.address.address() < rhs.address.address();
+ }
+ bool operator() (
+ IPAddress const& lhs, Endpoint const& rhs) const
+ {
+ return lhs.address() < rhs.address.address();
+ }
+ bool operator() (
+ IPAddress const& lhs, IPAddress const& rhs) const
+ {
+ return lhs.address() < rhs.address();
+ }
+ };
+ std::set_difference (endpoints.begin (), endpoints.end (),
+ state->addresses.begin (), state->addresses.end (),
+ std::back_inserter (temp), LessWithoutPortSet ());
+ std::swap (endpoints, temp);
+ temp.clear ();
}
{
- PropertyStream::Map child ("config", map);
- m_config.onWrite (child);
+ // Sort by hops descending
+ struct LessHops
+ {
+ bool operator() (Endpoint const& lhs, Endpoint const& rhs) const
+ {
+ return lhs.hops > rhs.hops;
+ }
+ };
+ std::sort (endpoints.begin (), endpoints.end (), LessHops ());
}
+
+ if (endpoints.size () > needed)
+ endpoints.resize (needed);
+
+ IPAddresses result;
+ result.reserve (endpoints.size ());
+ for (Endpoints::const_iterator iter (endpoints.begin ());
+ iter != endpoints.end (); ++iter)
+ result.push_back (iter->address);
+ return result;
}
//--------------------------------------------------------------------------
- // Load persistent state information from the Store
- //
- void load ()
+ /** Returns a new set of connection addresses from the live cache. */
+ IPAddresses fetch_bootcache (std::size_t needed, SharedState::Access& state)
{
- m_legacyCache.load (get_now());
- }
-
- // Returns a suitable Endpoint representing us.
- //
- Endpoint thisEndpoint ()
- {
- // Why would someone call this if we don't want incoming?
- bassert (m_config.wantIncoming);
-
- Endpoint ep;
- ep.address = IPAddress (
- IPAddress::V4 ()).withPort (m_config.listeningPort);
- ep.hops = 0;
- ep.incomingSlotsAvailable = m_slots.inboundSlots;
- ep.incomingSlotsMax = m_slots.inboundSlotsMaximum;
- ep.uptimeSeconds = m_slots.uptimeSeconds();
-
- return ep;
- }
-
- // Returns true if the IPAddress contains no invalid data.
- //
- bool validIPAddress (IPAddress const& address)
- {
- if (! address.isPublic())
- return false;
- if (address.port() == 0)
- return false;
- return true;
- }
-
- // Return endpoints to which we want to try to make outgoing connections.
- // We preferentially return endpoints which are far away from as to try to
- // improve the algebraic connectivity of the network graph. For more see
- // http://en.wikipedia.org/wiki/Algebraic_connectivity
- //
- void getNewOutboundEndpoints (int needed, std::vector & list)
- {
- Giveaways giveaway (m_cache.getGiveawayList());
- int count = 0;
-
- for (Giveaways::reverse_iterator iter (giveaway.rbegin());
- iter != giveaway.rend(); ++iter)
+ // Get everything
+ Bootcache::Endpoints endpoints (state->bootcache.fetch ());
+ struct LessRank
{
- // Check whether we have anything at the current hop level.
- iter->reset ();
-
- for(GiveawaysAtHop::iterator iter2 (iter->begin());
- iter2 != iter->end() && (count != needed); ++iter2)
+ bool operator() (Bootcache::Endpoint const& lhs,
+ Bootcache::Endpoint const& rhs) const
{
- CachedEndpoint *ep (*iter2);
-
- // NIKB TODO we need to check whether this peer is already
- // connected prior to just returning it and wasting time
- // trying to establish a redundant connection.
-
- if(ep->message.incomingSlotsAvailable != 0)
- {
- list.push_back(ep->message.address);
- ++count;
- }
+ if (lhs.uptime() > rhs.uptime())
+ return true;
+ else if (lhs.uptime() <= rhs.uptime() && rhs.uptime() != 0)
+ return false;
+ if (lhs.valence() > rhs.valence())
+ return true;
+ return false;
}
+ };
+
+ {
+ // Sort ignoring port
+ struct LessWithoutPort
+ {
+ bool operator() (Bootcache::Endpoint const& lhs,
+ Bootcache::Endpoint const& rhs) const
+ {
+ if (lhs.address().at_port (0) < rhs.address().at_port (0))
+ return true;
+ // Break ties by preferring higher ranks
+ //return m_rank (lhs, rhs);
+ return false;
+ }
+
+ LessRank m_rank;
+ };
+ std::sort (endpoints.begin (), endpoints.end (), LessWithoutPort());
}
+
+ Bootcache::Endpoints temp;
+ temp.reserve (endpoints.size ());
+
+ {
+ // Remove all but the first unique addresses ignoring port
+ struct EqualWithoutPort
+ {
+ bool operator() (Bootcache::Endpoint const& lhs,
+ Bootcache::Endpoint const& rhs) const
+ {
+ return lhs.address().at_port (0) ==
+ rhs.address().at_port (0);
+ }
+ };
+
+ std::unique_copy (endpoints.begin (), endpoints.end (),
+ std::back_inserter (temp), EqualWithoutPort ());
+ std::swap (endpoints, temp);
+ temp.clear ();
+ }
+
+ {
+ // Remove the addresses we are currently connected to
+ struct LessWithoutPortSet
+ {
+ bool operator() (Bootcache::Endpoint const& lhs,
+ IPAddress const& rhs) const
+ {
+ return lhs.address().at_port (0) < rhs.at_port (0);
+ }
+ bool operator() (Bootcache::Endpoint const& lhs,
+ Bootcache::Endpoint const& rhs) const
+ {
+ return lhs.address().at_port (0) <
+ rhs.address().at_port (0);
+ }
+ bool operator() (IPAddress const& lhs,
+ Bootcache::Endpoint const& rhs) const
+ {
+ return lhs.at_port (0) < rhs.address().at_port (0);
+ }
+ bool operator() (IPAddress const& lhs,
+ IPAddress const& rhs) const
+ {
+ return lhs.at_port (0) < rhs.at_port (0);
+ }
+ };
+ std::set_difference (endpoints.begin (), endpoints.end (),
+ state->addresses.begin (), state->addresses.end (),
+ std::back_inserter (temp), LessWithoutPortSet ());
+ std::swap (endpoints, temp);
+ temp.clear ();
+ }
+
+ {
+ // Sort by rank descending
+ std::sort (endpoints.begin (), endpoints.end (), LessRank ());
+ }
+
+ if (endpoints.size () > needed)
+ endpoints.resize (needed);
+
+ IPAddresses result;
+ result.reserve (endpoints.size ());
+ for (Bootcache::Endpoints::const_iterator iter (endpoints.begin ());
+ iter != endpoints.end (); ++iter)
+ result.emplace_back (iter->address());
+ return result;
}
- // If configured to make outgoing connections, do us in order
- // to bring us up to desired out count.
- //
+ /** Returns a set of addresses for fixed peers we aren't connected to. */
+ IPAddresses fetch_unconnected_fixedpeers (SharedState::Access& state)
+ {
+ IPAddresses addrs;
+
+ addrs.reserve (m_fixedPeers.size());
+
+ struct EqualWithoutPort
+ {
+ bool operator() (IPAddress const& lhs,
+ IPAddress const& rhs) const
+ {
+ return lhs.at_port (0) == rhs.at_port (0);
+ }
+ };
+
+ struct select_unconnected
+ {
+ SharedState::Access& state;
+ IPAddresses& addresses;
+
+ select_unconnected (SharedState::Access& state_,
+ IPAddresses& addresses_)
+ : state (state_)
+ , addresses (addresses_)
+ { }
+
+ void operator() (FixedPeers::value_type const& peer)
+ {
+ for(Addresses::const_iterator iter = state->addresses.cbegin();
+ iter != state->addresses.cend(); ++iter)
+ {
+ if (peer.second.hasAddress (*iter, EqualWithoutPort ()))
+ return;
+ }
+
+ addresses.push_back (peer.second.getAddress ());
+ }
+ };
+
+ std::for_each (m_fixedPeers.begin(), m_fixedPeers.end(),
+ select_unconnected (state, addrs));
+
+ if (m_journal.debug)
+ {
+ m_journal.debug << "Unconnected peer list:";
+
+ for(IPAddresses::const_iterator iter = addrs.cbegin();
+ iter != addrs.cend(); ++iter)
+ m_journal.debug << "[" << *iter << "]" << std::endl;
+ }
+
+ return addrs;
+ }
+
+ //--------------------------------------------------------------------------
+
+ /** Create new outbound connection attempts as needed.
+ This implements PeerFinder's "Outbound Connection Strategy"
+ */
void makeOutgoingConnections ()
{
- std::vector list;
+ SharedState::Access state (m_state);
- if (m_config.connectAutomatically)
+ // Always make outgoing connections to all configured fixed peers
+ // that are not currently connected.
+ if (m_fixedPeers.size() != 0)
{
- if (m_slots.outDesired > m_slots.outboundCount)
- {
- int const needed (std::min (
- m_slots.outDesired - m_slots.outboundCount,
- int (maxAddressesPerAttempt)));
+ IPAddresses addrs (fetch_unconnected_fixedpeers (state));
- getNewOutboundEndpoints (needed, list);
+ if (!addrs.empty())
+ m_callback.connectPeers (addrs);
+ }
+
+ // Count how many more attempts we need
+ if (! state->config.autoConnect)
+ return;
+ std::size_t const needed (
+ state->slots.additionalAttemptsNeeded ());
+ if (needed <= 0)
+ return;
+
+ if (m_journal.debug) m_journal.debug << leftw (18) <<
+ "Logic need " << needed << " outbound attempts";
+
+ /* Stage #1 Livecache
+ Stay in stage #1 until there are no entries left in the Livecache,
+ and there are no more outbound connection attempts in progress.
+ While in stage #1, all addresses for new connection attempts come
+ from the Livecache.
+ */
+ if (state->slots.connectCount () > 0 || ! state->livecache.empty ())
+ {
+ IPAddresses const addrs (fetch_livecache (needed, state));
+ if (! addrs.empty ())
+ {
+ if (m_journal.debug) m_journal.debug << leftw (18) <<
+ "Logic connect " << addrs.size () << " live " <<
+ ((addrs.size () > 1) ? "endpoints" : "endpoint");
+ m_callback.connectPeers (addrs);
+ }
+ return;
+ }
+
+ /* Stage #2 Bootcache Fetch
+ If the Bootcache is empty, try to get addresses from the current
+ set of Sources and add them into the Bootstrap cache.
+
+ Pseudocode:
+ If ( domainNames.count() > 0 AND (
+ unusedBootstrapIPs.count() == 0
+ OR activeNameResolutions.count() > 0) )
+ ForOneOrMore (DomainName that hasn't been resolved recently)
+ Contact DomainName and add entries to the unusedBootstrapIPs
+ return;
+ */
+ {
+ // VFALCO TODO Stage #2
+ }
+
+ /* Stage #3 Bootcache
+ If the Bootcache contains entries that we haven't tried lately,
+ then attempt them.
+ */
+ {
+ IPAddresses const addrs (fetch_bootcache (needed, state));
+ if (! addrs.empty ())
+ {
+ if (m_journal.debug) m_journal.debug << leftw (18) <<
+ "Logic connect " << addrs.size () << " boot " <<
+ ((addrs.size () > 1) ? "addresses" : "address");
+ m_callback.connectPeers (addrs);
+ return;
}
}
- if (m_slots.fixedCount < m_fixedPeers.size())
- {
- list.reserve (list.size() + m_fixedPeers.size() - m_slots.fixedCount);
-
- for (FixedPeers::const_iterator iter (m_fixedPeers.begin());
- iter != m_fixedPeers.end(); ++iter)
- {
-#if 0
- // Make sure the fixed peer is not already connected
- if (m_peers.get<1>().find (*iter) == m_peers.get<1>().end())
- list.push_back (*iter);
-#endif
- }
- }
-
- if (! list.empty())
- m_callback.connectPeerEndpoints (list);
+ // If we get here we are stuck
}
//--------------------------------------------------------------------------
@@ -302,38 +466,46 @@ public:
void setConfig (Config const& config)
{
- m_config = config;
-
- /* give sensible defaults to any uninitialized fields */
- m_config.fillInDefaultValues();
-
- m_slots.update (m_config);
+ SharedState::Access state (m_state);
+ state->config = config;
+ state->slots.onConfig (state->config);
}
- void addFixedPeers (std::vector const& strings)
+ void addFixedPeer (std::string const& name,
+ std::vector const& addresses)
{
- for (std::vector ::const_iterator iter (strings.begin());
- iter != strings.end(); ++iter)
- {
- IPAddress ep (IPAddress::from_string (*iter));
-
- if (ep.empty ())
- ep = IPAddress::from_string_altform(*iter);
-
- if (! ep.empty ())
- {
- m_fixedPeers.insert (ep);
-
- m_journal.info << "Added fixed peer " << *iter;
- }
- else
- {
- // VFALCO TODO Attempt name resolution
- m_journal.error << "Failed to resolve: '" << *iter << "'";
- }
+ if (addresses.empty ())
+ {
+ if (m_journal.info) m_journal.info <<
+ "Could not resolve fixed peer '" << name << "'";
+ return;
}
- m_journal.info << m_fixedPeers.size() << " fixed peers added.";
+ // NIKB TODO There is a problem with the following code: if you have
+ // two entries which resolve to the same IP address, you
+ // will end up with duplicate entries that resolve to the
+ // same IP. This will cause issues when you try to fetch
+ // a peer set because you will end up adding the same IP
+ // to the connection set twice.
+ //
+ // The entries can be distinct (e.g. two hostnames that
+ // resolve to the same IP) or they can be the same entry
+ // but with whitespace/format changes.
+ std::pair result (
+ m_fixedPeers.insert (std::make_pair (
+ name, FixedPeer (name, addresses))));
+
+ // If we have a duplicate, ignore it.
+ if (!result.second)
+ {
+ if (m_journal.error) m_journal.error <<
+ "'" << name << "' is already listed as fixed";
+ }
+ else
+ {
+ if (m_journal.debug) m_journal.debug <<
+ "'" << name <<"' added as fixed";
+ }
}
void addStaticSource (SharedPtr const& source)
@@ -346,391 +518,73 @@ public:
m_sources.push_back (source);
}
- // Called periodically to sweep the cache and remove aged out items.
- //
+ //--------------------------------------------------------------------------
+
+ // Called periodically to sweep the livecache and remove aged out items.
void sweepCache ()
{
- m_cache.sweep (get_now());
-
-#if 0
- for (Peers::iterator iter (m_peers.begin());
- iter != m_peers.end(); ++iter)
- iter->received.cycle();
-#endif
- }
-
- // Called when an outbound connection attempt is started
- //
- void onPeerConnectAttemptBegins (IPAddress const& address)
- {
- std::pair ret =
- m_attemptsInProgress.insert (address);
-
- // We are always notified of connection attempts so if we think that
- // something was in progress and a connection attempt begins then
- // something is very wrong.
-
- bassert (ret.second);
-
- if (ret.second)
- m_journal.debug << "Attempt for " << address << " is in progress";
- else
- m_journal.error << "Attempt for " << address << " was already in progress";
- }
-
- // Called when an outbound connection attempt completes
- //
- void onPeerConnectAttemptCompletes (IPAddress const& address, bool success)
- {
- IPAddressSet::size_type ret = m_attemptsInProgress.erase (address);
-
- bassert (ret == 1);
-
- if (ret == 1)
- m_journal.debug << "Attempt for " << address <<
- " completed: " << (success ? "success" : "failure");
- else
- m_journal.error << "Attempt for untracked " << address <<
- " completed: " << (success ? "success" : "failure");
- }
-
- // Called when a peer connection is established but before a handshake
- // occurs.
- void onPeerConnected (IPAddress const& address, bool incoming)
- {
- m_journal.error << "Connected: " << address <<
- (incoming ? " (incoming)" : " (outgoing)");
- }
-
- // Called when a peer connection is established.
- // We are guaranteed that the PeerID is not already in our map.
- // but we are *NOT* guaranteed that the IP isn't. So we need
- // to be careful.
- void onPeerHandshake (PeerID const& id,
- IPAddress const& address, bool inbound)
- {
- m_journal.debug << "Handshake: " << address;
-
- // If this is outgoing, record the success
- if (! inbound)
- m_legacyCache.checked (address, true);
-
-#if 0
- std::pair result (
- m_peers.insert (
- PeerInfo (id, address, inbound, get_now())));
- bassert (result.second);
-#endif
-
- m_slots.addPeer (m_config, inbound);
-
- // VFALCO NOTE Update fixed peers count (HACKED)
- for (FixedPeers::const_iterator iter (m_fixedPeers.begin());
- iter != m_fixedPeers.end(); ++iter)
+ SharedState::Access state (m_state);
+ state->livecache.sweep ();
+ for (Peers::iterator iter (state->peers.begin());
+ iter != state->peers.end(); ++iter)
{
- if (iter->withPort (0) == address.withPort (0))
- ++m_slots.fixedCount;
+ //Peer& peer (iter->second);
+ //peer.received.cycle();
}
}
- // Called when a peer is disconnected.
- // We are guaranteed to get this exactly once for each
- // corresponding call to onPeerHandshake.
- //
- void onPeerDisconnected (PeerID const& id)
+ // Called periodically to update uptime for connected outbound peers.
+ void processUptime (SharedState::Access& state)
{
-#if 0
- Peers::iterator iter (m_peers.find (id));
- bassert (iter != m_peers.end());
- PeerInfo const& peer (*iter);
- m_journal.debug << "Disconnected: " << peer.address;
- m_slots.dropPeer (m_config, peer.inbound);
-
- // VFALCO NOTE Update fixed peers count (HACKED)
- for (FixedPeers::const_iterator iter (m_fixedPeers.begin());
- iter != m_fixedPeers.end(); ++iter)
+ for (Peers::iterator iter (state->peers.begin());
+ iter != state->peers.end(); ++iter)
{
- if (iter->withPort (0) == peer.address.withPort (0))
- --m_slots.fixedCount;
- }
+ Peer const& peer (iter->second);
- // Must come last
- m_peers.erase (iter);
-#endif
+ if (! peer.inbound() && peer.state() == Peer::stateActive)
+ state->bootcache.onConnectionActive (
+ peer.remote_address());
+ }
+ }
+
+ // Called every so often to perform periodic tasks.
+ void periodicActivity ()
+ {
+ SharedState::Access state (m_state);
+ processUptime (state);
+ state->bootcache.periodicActivity ();
}
//--------------------------------------------------------------------------
//
- // CachedEndpoints
+ // Bootcache livecache sources
//
//--------------------------------------------------------------------------
- // Returns true if the Endpoint contains no invalid data.
+ // Add one address.
+ // Returns `true` if the address is new.
//
- bool validEndpoint (Endpoint const& endpoint)
+ bool addBootcacheAddress (IPAddress const& address,
+ SharedState::Access& state)
{
- // This function is here in case we add more stuff
- // we want to validate to the Endpoint struct.
- //
- return validIPAddress (endpoint.address);
+ return state->bootcache.insert (address);
}
- // Prunes invalid endpoints from a list.
+ // Add a set of addresses.
+ // Returns the number of addresses added.
//
- void pruneEndpoints (
- std::string const& source, std::vector & list)
+ int addBootcacheAddresses (IPAddresses const& list)
{
- for (std::vector ::iterator iter (list.begin());
- iter != list.end();)
- {
- if (! validEndpoint (*iter))
- {
- iter = list.erase (iter);
- m_journal.error <<
- "Invalid endpoint " << iter->address <<
- " from " << source;
- }
- else
- {
- ++iter;
- }
- }
+ int count (0);
+ SharedState::Access state (m_state);
+ for (IPAddresses::const_iterator iter (
+ list.begin()); iter != list.end(); ++iter)
+ if (addBootcacheAddress (*iter, state))
+ ++count;
+ return count;
}
- // Send mtENDPOINTS for the specified peer
- //
- void sendEndpoints (PeerInfo const& peer, Giveaways &giveaway)
- {
-#if 0
- typedef std::vector List;
-#endif
- std::vector endpoints;
-
- // Add us to the list if we want incoming
- // VFALCO TODO Reconsider this logic
- //if (m_slots.inboundSlots > 0)
- if (m_config.wantIncoming)
- endpoints.push_back (thisEndpoint ());
-
- // We iterate over the hop list we have, adding one
- // peer per hop (if possible) until we add the maximum
- // number of peers we are allowed to send or we can't
- // send anything else.
- for (int i = 0; i != numberOfEndpoints; ++i)
- {
- for (Giveaways::iterator iter = giveaway.begin();
- iter != giveaway.end(); ++iter)
- {
- GiveawaysAtHop::iterator iter2 = iter->begin();
-
- while(iter2 != iter->end())
- {
- // FIXME NIKB check if the peer wants to receive this
- // endpoint and add it to the list of endpoints we will
- // send if he does.
-
- if(false)
- iter2 = iter->erase(iter2);
- else
- ++iter2;
- }
- }
- }
-
- if (! endpoints.empty())
- m_callback.sendPeerEndpoints (peer.id, endpoints);
- }
-
- // Send mtENDPOINTS for each peer as needed
- //
- void sendEndpoints ()
- {
-#if 0
- if (! m_peers.empty())
- {
- m_journal.trace << "Sending endpoints...";
-
- DiscreteTime const now (get_now());
-
- // fill in endpoints.
- Giveaways giveaway(m_cache.getGiveawayList());
-
- for (Peers::iterator iter (m_peers.begin());
- iter != m_peers.end(); ++iter)
- {
- PeerInfo const& peer (*iter);
-
- // Reset the giveaway to begin a fresh iteration.
- giveaway.reset ();
-
- if (peer.whenSendEndpoints <= now)
- {
- sendEndpoints (peer, giveaway);
- peer.whenSendEndpoints = now +
- secondsPerMessage;
- }
- }
- }
-#endif
- }
-
- // Called when the Checker completes a connectivity test
- //
- void onCheckEndpoint (PeerID const& id,
- IPAddress address, Checker::Result const& result)
- {
- if (result.error == boost::asio::error::operation_aborted)
- return;
-
-#if 0
- Peers::iterator iter (m_peers.find (id));
- if (iter != m_peers.end())
- {
- PeerInfo const& peer (*iter);
-
- // Mark that a check for this peer is finished.
- peer.connectivityCheckInProgress = false;
-
- if (! result.error)
- {
- peer.checked = true;
- peer.canAccept = result.canAccept;
-
- if (peer.canAccept)
- m_journal.info << peer.address <<
- " passed listening test";
- else
- m_journal.warning << peer.address <<
- " cannot accept incoming connections";
- }
- else
- {
- // VFALCO TODO Should we retry depending on the error?
- peer.checked = true;
- peer.canAccept = false;
-
- m_journal.error << "Listening test for " <<
- peer.address << " failed: " <<
- result.error.message();
- }
- }
- else
- {
- // The peer disconnected before we finished the check
- m_journal.debug << "Finished listening test for " <<
- id << " but the peer disconnected. ";
- }
-#endif
- }
-
- // Called when a peer sends us the mtENDPOINTS message.
- //
- void onPeerEndpoints (PeerID const& id, std::vector list)
- {
-#if 0
- Peers::iterator iter (m_peers.find (id));
- bassert (iter != m_peers.end());
-
- DiscreteTime const now (get_now());
- PeerInfo const& peer (*iter);
-
- pruneEndpoints (peer.address.to_string(), list);
-
- // Log at higher severity if this is the first time
- m_journal.stream (peer.whenAcceptEndpoints == 0 ?
- Journal::kInfo : Journal::kTrace) <<
- "Received " << list.size() <<
- " endpoints from " << peer.address;
-
- // We charge a load penalty if the peer sends us more than
- // numberOfEndpoints peers in a single message
- if (list.size() > numberOfEndpoints)
- {
- m_journal.warning << "Charging " << peer.address <<
- " for sending too many endpoints";
-
- m_callback.chargePeerLoadPenalty(id);
- }
-
- m_journal.debug << peer.address <<
- " sent us " << list.size() << " endpoints.";
-
- // Process each entry
- //
- int neighborCount (0);
- for (std::vector ::const_iterator iter (list.begin());
- iter != list.end(); ++iter)
- {
- Endpoint const& message (*iter);
-
- // Remember that this peer gave us this address
- peer.received.insert (message.address);
-
- m_journal.debug << message.address <<
- " at " << message.hops << " hops.";
-
- if (message.hops == 0)
- {
- ++neighborCount;
- if (neighborCount == 1)
- {
- if (peer.connectivityCheckInProgress)
- {
- m_journal.warning << "Connectivity check for " <<
- message.address << "already in progress.";
- }
- else if (! peer.checked)
- {
- // Mark that a check for this peer is now in progress.
- peer.connectivityCheckInProgress = true;
-
- // Test the peer's listening port before
- // adding it to the cache for the first time.
- //
- m_checker.async_test (message.address, bind (
- &Logic::onCheckEndpoint, this, id,
- message.address, _1));
-
- // Note that we simply discard the first Endpoint
- // that the neighbor sends when we perform the
- // listening test. They will just send us another
- // one in a few seconds.
- }
- else if (peer.canAccept)
- {
- // We only add to the cache if the neighbor passed the
- // listening test, else we silently drop their message
- // since their listening port is misconfigured.
- //
- m_cache.insert (message, get_now());
- }
- }
- }
- else
- {
- m_cache.insert (message, get_now());
- }
- }
-
- if (neighborCount > 1)
- {
- m_journal.warning << peer.address <<
- " sent " << neighborCount << " entries with hops=0";
- // VFALCO TODO Should we apply load charges?
- }
-
- peer.whenAcceptEndpoints = now + secondsPerMessage;
-#endif
- }
-
- //--------------------------------------------------------------------------
- //
- // LegacyEndpoint
- //
- //--------------------------------------------------------------------------
-
- // Fetch addresses into the LegacyEndpointCache for bootstrapping
- //
+ // Fetch bootcache addresses from the specified source.
void fetch (SharedPtr const& source)
{
Source::Results results;
@@ -743,6 +597,9 @@ public:
state->fetchSource = source;
}
+ // VFALCO NOTE The fetch is synchronous,
+ // not sure if that's a good thing.
+ //
source->fetch (results, m_journal);
{
@@ -755,85 +612,790 @@ public:
if (! results.error)
{
- std::size_t newEntries (0);
-#if 0
- DiscreteTime now (get_now());
-#endif
-
- for (std::vector ::const_iterator iter (results.list.begin());
- iter != results.list.end(); ++iter)
- {
-#if 0
- std::pair result (
- m_legacyCache.insert (*iter, now));
- if (result.second)
- ++newEntries;
-#endif
- }
-
- m_journal.debug <<
- "Fetched " << results.list.size() <<
- " legacy endpoints (" << newEntries << " new) "
- "from " << source->name();
+ int const count (addBootcacheAddresses (results.addresses));
+ if (m_journal.info) m_journal.info << leftw (18) <<
+ "Logic added " << count <<
+ " new " << ((count == 1) ? "address" : "addresses") <<
+ " from " << source->name();
}
else
{
- m_journal.error <<
- "Fetch " << source->name() << "failed: " <<
+ if (m_journal.error) m_journal.error << leftw (18) <<
+ "Logic failed " << "'" << source->name() << "' fetch, " <<
results.error.message();
}
+
+ }
+
+ //--------------------------------------------------------------------------
+ //
+ // Endpoint message handling
+ //
+ //--------------------------------------------------------------------------
+
+ // Returns a suitable Endpoint representing us.
+ Endpoint thisEndpoint (SharedState::Access& state)
+ {
+ // Why would someone call this if we don't want incoming?
+ consistency_check (state->config.wantIncoming);
+ Endpoint ep;
+ ep.hops = 0;
+ ep.address = IPAddress (
+ IP::AddressV4 ()).at_port (state->config.listeningPort);
+ ep.features = state->config.features;
+ return ep;
+ }
+
+ // Returns true if the IPAddress contains no invalid data.
+ bool is_valid_address (IPAddress const& address)
+ {
+ if (is_unspecified (address))
+ return false;
+ if (! is_public (address))
+ return false;
+ if (address.port() == 0)
+ return false;
+ return true;
+ }
+
+ // Creates a set of endpoints suitable for a temporary peer.
+ // Sent to a peer when we are full, before disconnecting them.
+ //
+ Endpoints getSomeEndpoints ()
+ {
+ SharedState::Access state (m_state);
+ Endpoints result (state->livecache.fetch_unique ());
+ std::random_shuffle (result.begin(), result.end());
+ if (result.size () > Tuning::redirectEndpointCount)
+ result.resize (Tuning::redirectEndpointCount);
+ return result;
+ }
+
+ // Send mtENDPOINTS for the specified peer
+ void sendEndpointsTo (Peer const& peer, Giveaways& g)
+ {
+ Endpoints endpoints;
+
+ if (endpoints.size() < Tuning::numberOfEndpoints)
+ {
+ SharedState::Access state (m_state);
+
+ // Add an entry for ourselves if:
+ // 1. We want incoming
+ // 2. We have slots
+ // 3. We haven't failed the firewalled test
+ //
+ if (state->config.wantIncoming && state->slots.inboundSlots() > 0)
+ endpoints.push_back (thisEndpoint (state));
+ }
+
+ if (endpoints.size() < Tuning::numberOfEndpoints)
+ {
+ g.append (Tuning::numberOfEndpoints - endpoints.size(), endpoints);
+ }
+
+ if (! endpoints.empty())
+ {
+ if (m_journal.trace) m_journal.trace << leftw (18) <<
+ "Logic sending " << peer.remote_address() <<
+ " with " << endpoints.size() <<
+ ((endpoints.size() > 1) ? " endpoints" : " endpoint");
+ m_callback.sendEndpoints (peer.remote_address(), endpoints);
+ }
}
- // Completion handler for a LegacyEndpoint listening test.
- //
- void onCheckLegacyEndpoint (IPAddress const& endpoint,
- Checker::Result const& result)
+ // Send mtENDPOINTS for each peer as needed
+ void sendEndpoints ()
+ {
+ SharedState::Access state (m_state);
+ if (! state->peers.empty())
+ {
+ DiscreteTime const now (m_clock());
+ DiscreteTime const whenSendEndpoints (
+ now + Tuning::secondsPerMessage);
+ Giveaways g (state->livecache.giveaways ());
+ for (Peers::iterator iter (state->peers.begin());
+ iter != state->peers.end(); ++iter)
+ {
+ Peer& peer (iter->second);
+ if (peer.state() == Peer::stateActive)
+ {
+ if (peer.whenSendEndpoints <= now)
+ {
+ sendEndpointsTo (peer, g);
+ peer.whenSendEndpoints = whenSendEndpoints;
+ }
+ }
+ }
+ }
+ }
+
+ // Called when the Checker completes a connectivity test
+ void checkComplete (IPAddress const& address,
+ IPAddress const & checkedAddress, Checker::Result const& result)
{
if (result.error == boost::asio::error::operation_aborted)
return;
+ SharedState::Access state (m_state);
+ Peers::iterator const iter (state->peers.find (address));
+ Peer& peer (iter->second);
+
+ if (iter == state->peers.end())
+ {
+ // The peer disconnected before we finished the check
+ if (m_journal.debug) m_journal.debug << leftw (18) <<
+ "Logic tested " << address <<
+ " but the connection was closed";
+ return;
+ }
+
+ // Mark that a check for this peer is finished.
+ peer.connectivityCheckInProgress = false;
+
if (! result.error)
{
- if (result.canAccept)
- m_journal.info << "Legacy address " << endpoint <<
- " passed listening test";
+ peer.checked = true;
+ peer.canAccept = result.canAccept;
+
+ if (peer.canAccept)
+ {
+ if (m_journal.debug) m_journal.debug << leftw (18) <<
+ "Logic testing " << address << " succeeded";
+ }
else
- m_journal.warning << "Legacy address " << endpoint <<
- " cannot accept incoming connections";
+ {
+ if (m_journal.info) m_journal.info << leftw (18) <<
+ "Logic testing " << address << " failed";
+ }
}
else
{
- m_journal.error << "Listening test for legacy address " <<
- endpoint << " failed: " << result.error.message();
- }
- }
+ // VFALCO TODO Should we retry depending on the error?
+ peer.checked = true;
+ peer.canAccept = false;
- void onPeerLegacyEndpoint (IPAddress const& address)
- {
- if (! validIPAddress (address))
- return;
-#if 0
- std::pair result (
- m_legacyCache.insert (address, get_now()));
- if (result.second)
+ if (m_journal.error) m_journal.error << leftw (18) <<
+ "Logic testing " << iter->first << " with error, " <<
+ result.error.message();
+ }
+
+ if (peer.canAccept)
{
- // its new
- m_journal.trace << "New legacy endpoint: " << address;
-
-#if 0
- // VFALCO NOTE Temporarily we are doing a check on each
- // legacy endpoint to test the async code
- //
- m_checker.async_test (address, bind (
- &Logic::onCheckLegacyEndpoint,
- this, address, _1));
-#endif
+ // VFALCO TODO Why did I think this line was needed?
+ //state->bootcache.onConnectionHandshake (address);
}
+ else
+ {
+ state->bootcache.onConnectionFailure (address);
+ }
+ }
+
+ //--------------------------------------------------------------------------
+ //
+ // Socket Hooks
+ //
+ //--------------------------------------------------------------------------
+
+ // Returns `true` if the address matches the remote address of one
+ // of our outbound sockets.
+ //
+ // VFALCO TODO Do the lookup using an additional index by local address
+ bool haveLocalOutboundAddress (IPAddress const& local_address,
+ SharedState::Access& state)
+ {
+ for (Peers::const_iterator iter (state->peers.begin());
+ iter != state->peers.end(); ++iter)
+ {
+ Peer const& peer (iter->second);
+ if (peer.outbound () &&
+ peer.local_address() == local_address)
+ return true;
+ }
+ return false;
+ }
+
+ //--------------------------------------------------------------------------
+ bool isFixed (IPAddress const& address) const
+ {
+ struct EqualWithoutPort
+ {
+ bool operator() (IPAddress const& lhs,
+ IPAddress const& rhs) const
+ {
+ return lhs.at_port (0) == rhs.at_port (0);
+ }
+ };
+
+ for (FixedPeers::const_iterator iter = m_fixedPeers.cbegin();
+ iter != m_fixedPeers.cend(); ++iter)
+ {
+ if (iter->second.hasAddress (address, EqualWithoutPort ()))
+ return true;
+ }
+
+ return false;
+ }
+
+ //--------------------------------------------------------------------------
+
+ void onPeerAccept (IPAddress const& local_address,
+ IPAddress const& remote_address)
+ {
+ if (m_journal.debug) m_journal.debug << leftw (18) <<
+ "Logic accept" << remote_address <<
+ " on local " << local_address;
+ SharedState::Access state (m_state);
+ state->slots.onPeerAccept ();
+ state->addresses.insert (remote_address.at_port (0));
+
+ // FIXME m_fixedPeers contains both an IP and a port and incoming peers
+ // wouldn't match the port, so wouldn't get identified as fixed. One
+ // solution is to have fixed peers tracked by IP and not port. The
+ // port will be used only for outbound connections. Another option is
+ // to always consider incoming connections as non-fixed, then after
+ // an outbound connection to the peer is established and we realize
+ // we have a duplicate connection to a fixed peer, to find that peer
+ // and mark it as fixed.
+ //
+ //bool fixed (m_fixedPeers.count (
+ // remote_address.address ()) != 0);
+
+ bool fixed (false);
+
+ std::pair result (
+ state->peers.emplace (boost::unordered::piecewise_construct,
+ boost::make_tuple (remote_address),
+ boost::make_tuple (remote_address, true, fixed)));
+ // Address must not already exist!
+ consistency_check (result.second);
+ // Prevent self connect
+ if (haveLocalOutboundAddress (remote_address, state))
+ {
+ if (m_journal.warning) m_journal.warning << leftw (18) <<
+ "Logic dropping " << remote_address << " as self connect";
+ m_callback.disconnectPeer (remote_address, false);
+ return;
+ }
+ }
+
+ void onPeerConnect (IPAddress const& remote_address)
+ {
+ if (m_journal.debug) m_journal.debug << leftw (18) <<
+ "Logic connect " << remote_address;
+ SharedState::Access state (m_state);
+ state->slots.onPeerConnect ();
+ state->addresses.insert (remote_address.at_port (0));
+
+ bool fixed (isFixed (remote_address));
+
+ // VFALCO TODO Change to use forward_as_tuple
+ std::pair result (
+ state->peers.emplace (boost::unordered::piecewise_construct,
+ boost::make_tuple (remote_address),
+ boost::make_tuple (remote_address, false, fixed)));
+ // Address must not already exist!
+ consistency_check (result.second);
+ }
+
+ void onPeerConnected (IPAddress const& local_address,
+ IPAddress const& remote_address)
+ {
+ if (m_journal.trace) m_journal.trace << leftw (18) <<
+ "Logic connected" << remote_address <<
+ " on local " << local_address;
+ SharedState::Access state (m_state);
+ Peers::iterator const iter (state->peers.find (remote_address));
+ // Address must exist!
+ consistency_check (iter != state->peers.end());
+ Peer& peer (iter->second);
+ peer.local_address (local_address);
+ peer.state (Peer::stateConnected);
+ }
+
+ void onPeerHandshake (IPAddress const& remote_address,
+ PeerID const& key, bool inCluster)
+ {
+ if (m_journal.debug) m_journal.debug << leftw (18) <<
+ "Logic handshake " << remote_address <<
+ " with key " << key;
+ SharedState::Access state (m_state);
+ Peers::iterator const iter (state->peers.find (remote_address));
+ // Address must exist!
+ consistency_check (iter != state->peers.end());
+ Peer& peer (iter->second);
+ // Must be accepted or connected
+ consistency_check (
+ peer.state() == Peer::stateAccept ||
+ peer.state() == Peer::stateConnected);
+ // Mark cluster peers as necessary
+ peer.cluster (inCluster);
+ // Check for self connect by public key
+ bool const self (state->keys.find (key) != state->keys.end());
+ // Determine what action to take
+ HandshakeAction const action (
+ state->slots.onPeerHandshake (peer.inbound(), self, peer.fixed(), peer.cluster()));
+ // VFALCO NOTE this is a bit messy the way slots takes the
+ // 'self' bool and returns the action.
+ if (self)
+ {
+ if (m_journal.debug) m_journal.debug << leftw (18) <<
+ "Logic dropping " << remote_address <<
+ " as self key";
+ }
+ // Pass address metadata to bootcache
+ if (peer.outbound())
+ state->bootcache.onConnectionHandshake (
+ remote_address, action);
+ //
+ // VFALCO TODO Check for duplicate connections!!!!
+ //
+ if (action == doActivate)
+ {
+ // Track the public key
+ std::pair const result (
+ state->keys.insert (key));
+ // Must not already exist!
+ consistency_check (result.second);
+ peer.activate (key, m_clock());
+ m_callback.activatePeer (remote_address);
+ }
+ else
+ {
+ peer.state (Peer::stateClosing);
+ if (action == doRedirect)
+ {
+ // Must be inbound!
+ consistency_check (peer.inbound());
+ Endpoints const endpoints (getSomeEndpoints ());
+ if (! endpoints.empty ())
+ {
+ if (m_journal.trace) m_journal.trace << leftw (18) <<
+ "Logic redirect " << remote_address <<
+ " with " << endpoints.size() <<
+ ((endpoints.size() > 1) ? " addresses" : " address");
+ m_callback.sendEndpoints (peer.remote_address(), endpoints);
+ }
+ else
+ {
+ if (m_journal.warning) m_journal.warning << leftw (18) <<
+ "Logic deferred " << remote_address;
+ }
+ }
+ m_callback.disconnectPeer (remote_address, true);
+ }
+ }
+
+ void onPeerClosed (IPAddress const& remote_address)
+ {
+ SharedState::Access state (m_state);
+ {
+ Addresses::iterator iter (state->addresses.find (
+ remote_address.at_port (0)));
+ // Address must exist!
+ consistency_check (iter != state->addresses.end());
+ state->addresses.erase (iter);
+ }
+ Peers::iterator const iter (state->peers.find (remote_address));
+ // Address must exist!
+ consistency_check (iter != state->peers.end());
+ Peer& peer (iter->second);
+ switch (peer.state())
+ {
+ // Accepted but no handshake
+ case Peer::stateAccept:
+ // Connection attempt failed
+ case Peer::stateConnect:
+ // Connected but no handshake
+ case Peer::stateConnected:
+ {
+ // Update slots
+ state->slots.onPeerClosed (peer.inbound (), false,
+ peer.cluster (), peer.fixed ());
+ if (peer.outbound())
+ {
+ state->bootcache.onConnectionFailure (remote_address);
+ }
+ else
+ {
+ if (m_journal.trace) m_journal.trace << leftw (18) <<
+ "Logic accept " << remote_address << " failed";
+ }
+
+ // VFALCO TODO If the address exists in the ephemeral/live
+ // endpoint livecache then we should mark the failure
+ // as if it didn't pass the listening test. We should also
+ // avoid propagating the address.
+ }
+ break;
+
+ // The peer was assigned an open slot.
+ case Peer::stateActive:
+ {
+ // Remove the key
+ Keys::iterator const iter (state->keys.find (peer.id()));
+ // Key must exist!
+ consistency_check (iter != state->keys.end());
+ state->keys.erase (iter);
+ if (peer.outbound())
+ state->bootcache.onConnectionClosed (remote_address);
+ state->slots.onPeerClosed (peer.inbound (), true,
+ peer.fixed (), peer.cluster ());
+ if (m_journal.trace) m_journal.trace << leftw (18) <<
+ "Logic closed active " << peer.remote_address();
+ }
+ break;
+
+ // The peer handshaked but we were full on slots
+ // or it was a self connection.
+ case Peer::stateClosing:
+ {
+ if (m_journal.trace) m_journal.trace << leftw (18) <<
+ "Logic closed " << remote_address;
+ state->slots.onPeerGracefulClose ();
+ }
+ break;
+
+ default:
+ consistency_check (false);
+ break;
+ };
+
+ state->peers.erase (iter);
+ }
+
+ void onPeerAddressChanged (
+ IPAddress const& currentAddress, IPAddress const& newAddress)
+ {
+#if FIX_ME
+ // VFALCO TODO Demote this to trace after PROXY is tested.
+ m_journal.debug <<
+ "onPeerAddressChanged (" << currentAddress <<
+ ", " << newAddress << ")";
+
+ SharedState::Access state (m_state);
+
+ Connections::iterator iter (
+ state->connections.find (currentAddress));
+
+ // Current address must exist!
+ consistency_check (iter != state->connections.end());
+
+ Connection& connection (iter->second);
+
+ // Connection must be inbound!
+ consistency_check (connection.inbound());
+
+ // Connection must be connected!
+ consistency_check (connection.state() == Connection::stateConnected);
+
+ // Create a new Connection entry for the new address
+ std::pair result (
+ state->connections.emplace (newAddress,
+ Connection (iter->second)));
+
+ // New address must not already exist!
+ consistency_check (result.second);
+
+ // Remove old Connection entry
+ state->connections.erase (iter);
+
+ // Update the address on the peer
+ Peer& peer (result.first->second.peersIterator()->second);
+ peer.address = newAddress;
#endif
}
+
+ void onPeerEndpoints (IPAddress const& address, Endpoints list)
+ {
+ if (m_journal.trace) m_journal.trace << leftw (18) <<
+ "Endpoints from " << address <<
+ " contained " << list.size () <<
+ ((list.size() > 1) ? " entries" : " entry");
+ SharedState::Access state (m_state);
+ Peers::iterator const iter (state->peers.find (address));
+ // Address must exist!
+ consistency_check (iter != state->peers.end());
+ Peer& peer (iter->second);
+ // Must be handshaked!
+ consistency_check (peer.state() == Peer::stateActive);
+ // Preprocess the endpoints
+ {
+ bool neighbor (false);
+ for (Endpoints::iterator iter (list.begin());
+ iter != list.end();)
+ {
+ Endpoint& ep (*iter);
+ if (ep.hops > Tuning::maxHops)
+ {
+ if (m_journal.warning) m_journal.warning << leftw (18) <<
+ "Endpoints drop " << ep.address <<
+ " for excess hops " << ep.hops;
+ iter = list.erase (iter);
+ continue;
+ }
+ if (ep.hops == 0)
+ {
+ if (! neighbor)
+ {
+ // Fill in our neighbors remote address
+ neighbor = true;
+ ep.address = peer.remote_address().at_port (
+ ep.address.port ());
+ }
+ else
+ {
+ if (m_journal.warning) m_journal.warning << leftw (18) <<
+ "Endpoints drop " << ep.address <<
+ " for extra self";
+ iter = list.erase (iter);
+ continue;
+ }
+ }
+ if (! is_valid_address (ep.address))
+ {
+ if (m_journal.warning) m_journal.warning << leftw (18) <<
+ "Endpoints drop " << ep.address <<
+ " as invalid";
+ iter = list.erase (iter);
+ continue;
+ }
+ ++iter;
+ }
+ }
+
+ DiscreteTime const now (m_clock());
+
+ for (Endpoints::const_iterator iter (list.begin());
+ iter != list.end(); ++iter)
+ {
+ Endpoint const& ep (*iter);
+
+ //peer.received.insert (ep.address);
+
+ if (ep.hops == 0)
+ {
+ if (peer.connectivityCheckInProgress)
+ {
+ if (m_journal.warning) m_journal.warning << leftw (18) <<
+ "Logic testing " << ep.address << " already in progress";
+ }
+ else if (! peer.checked)
+ {
+ // Mark that a check for this peer is now in progress.
+ peer.connectivityCheckInProgress = true;
+
+ // Test the peer's listening port before
+ // adding it to the livecache for the first time.
+ //
+ m_checker.async_test (ep.address, bind (
+ &Logic::checkComplete, this, address,
+ ep.address, _1));
+
+ // Note that we simply discard the first Endpoint
+ // that the neighbor sends when we perform the
+ // listening test. They will just send us another
+ // one in a few seconds.
+ }
+ else if (peer.canAccept)
+ {
+ // We only add to the livecache if the neighbor passed the
+ // listening test, else we silently drop their messsage
+ // since their listening port is misconfigured.
+ //
+ state->livecache.insert (ep);
+ state->bootcache.insert (ep.address);
+ }
+ }
+ else
+ {
+ state->livecache.insert (ep);
+ state->bootcache.insert (ep.address);
+ }
+ }
+
+ peer.whenAcceptEndpoints = now + Tuning::secondsPerMessage;
+ }
+
+ void onLegacyEndpoints (
+ IPAddresses const& list)
+ {
+ // Ignoring them also seems a valid choice.
+ SharedState::Access state (m_state);
+ for (IPAddresses::const_iterator iter (list.begin());
+ iter != list.end(); ++iter)
+ state->bootcache.insert (*iter);
+ }
+
+ //--------------------------------------------------------------------------
+ //
+ // PropertyStream
+ //
+ //--------------------------------------------------------------------------
+
+ void writePeers (PropertyStream::Set& set, Peers const& peers)
+ {
+ for (Peers::const_iterator iter (peers.begin());
+ iter != peers.end(); ++iter)
+ {
+ PropertyStream::Map item (set);
+ Peer const& peer (iter->second);
+ item ["local_address"] = to_string (peer.local_address ());
+ item ["remote_address"] = to_string (peer.remote_address ());
+ if (peer.inbound())
+ item ["inbound"] = "yes";
+ switch (peer.state())
+ {
+ case Peer::stateAccept:
+ item ["state"] = "accept"; break;
+
+ case Peer::stateConnect:
+ item ["state"] = "connect"; break;
+
+ case Peer::stateConnected:
+ item ["state"] = "connected"; break;
+
+ case Peer::stateActive:
+ item ["state"] = "active"; break;
+
+ case Peer::stateClosing:
+ item ["state"] = "closing"; break;
+
+ default:
+ consistency_check (false);
+ break;
+ };
+ }
+ }
+
+ void onWrite (PropertyStream::Map& map)
+ {
+ SharedState::Access state (m_state);
+
+ // VFALCO NOTE These ugly casts are needed because
+ // of how std::size_t is declared on some linuxes
+ //
+ map ["livecache"] = uint32 (state->livecache.size());
+ map ["bootcache"] = uint32 (state->bootcache.size());
+ map ["fixed"] = uint32 (m_fixedPeers.size());
+
+ {
+ PropertyStream::Set child ("peers", map);
+ writePeers (child, state->peers);
+ }
+
+ {
+ PropertyStream::Map child ("slots", map);
+ state->slots.onWrite (child);
+ }
+
+ {
+ PropertyStream::Map child ("config", map);
+ state->config.onWrite (child);
+ }
+
+ {
+ PropertyStream::Map child ("bootcache", map);
+ state->bootcache.onWrite (child);
+ }
+ }
+
+ //--------------------------------------------------------------------------
+ //
+ // Diagnostics
+ //
+ //--------------------------------------------------------------------------
+
+ State const& state () const
+ {
+ return *SharedState::ConstAccess (m_state);
+ }
+
+ Slots const& slots () const
+ {
+ return SharedState::ConstAccess (m_state)->slots;
+ }
+
+ static std::string stateString (Peer::State state)
+ {
+ switch (state)
+ {
+ case Peer::stateAccept: return "accept";
+ case Peer::stateConnect: return "connect";
+ case Peer::stateConnected: return "connected";
+ case Peer::stateActive: return "active";
+ case Peer::stateClosing: return "closing";
+ default:
+ break;
+ };
+ return "?";
+ }
+
+ void dump_peers (Journal::ScopedStream& ss,
+ SharedState::ConstAccess const& state) const
+ {
+ ss << std::endl << std::endl <<
+ "Peers";
+ for (Peers::const_iterator iter (state->peers.begin());
+ iter != state->peers.end(); ++iter)
+ {
+ Peer const& peer (iter->second);
+ ss << std::endl <<
+ peer.remote_address () <<
+ (peer.inbound () ? " (in) " : " ") <<
+ stateString (peer.state ()) << " " <<
+ peer.id();
+ }
+ }
+
+ void dump (Journal::ScopedStream& ss) const
+ {
+ SharedState::ConstAccess state (m_state);
+
+ state->bootcache.dump (ss);
+ state->livecache.dump (ss);
+ dump_peers (ss, state);
+ ss << std::endl <<
+ state->slots.state_string ();
+ ss << std::endl;
+ }
+
};
}
}
#endif
+
+/*
+
+Terms
+
+'Book' an order book
+'Offer' an entry in a book
+'Inverse Book' the book for the opposite direction
+
+'Directory' Holds offers with the same quality level
+
+An order book is a list of offers. The book has the following
+canonical order. The primary key is the quality (ratio of input to
+output). The secondary key is an ordinal to break ties for two offers
+with the same quality (first come first serve).
+
+Three places where books are iterated in canonical order:
+
+1. When responding to a client request for a book
+
+2. When placing an offer in the inverse book
+
+3. When processing a payment that goes through the book
+
+A directory is a type of structure in the ledger
+
+
+
+Invariants:
+
+- All that is needed to process a transaction is the current Ledger object.
+
+*/
diff --git a/src/ripple/peerfinder/impl/Manager.cpp b/src/ripple/peerfinder/impl/Manager.cpp
index de88e784a..566e9fce8 100644
--- a/src/ripple/peerfinder/impl/Manager.cpp
+++ b/src/ripple/peerfinder/impl/Manager.cpp
@@ -38,7 +38,7 @@ public:
DeadlineTimer m_connectTimer;
DeadlineTimer m_messageTimer;
DeadlineTimer m_cacheTimer;
-
+
//--------------------------------------------------------------------------
ManagerImp (
@@ -78,28 +78,22 @@ public:
config)));
}
- void addFixedPeers (
- std::vector const& strings)
+ void addFixedPeer (std::string const& name,
+ std::vector const& addresses)
{
-#if 1
- m_logic.addFixedPeers (strings);
-#else
- m_queue.dispatch (m_context.wrap (
- bind (&Logic::addFixedPeers, &m_logic,
- std::vector (strings))));
-#endif
+ m_queue.dispatch (
+ m_context.wrap (
+ boost::bind (&Logic::addFixedPeer, &m_logic,
+ name, addresses)));
}
void addFallbackStrings (std::string const& name,
std::vector const& strings)
{
-#if RIPPLE_USE_PEERFINDER
m_queue.dispatch (
m_context.wrap (
- bind (
- &Logic::addStaticSource, &m_logic,
- SourceStrings::New (name, strings))));
-#endif
+ bind (&Logic::addStaticSource, &m_logic,
+ SourceStrings::New (name, strings))));
}
void addFallbackURL (std::string const& name, std::string const& url)
@@ -107,75 +101,74 @@ public:
// VFALCO TODO This needs to be implemented
}
- void onPeerConnectAttemptBegins (IPAddress const& address)
+ //--------------------------------------------------------------------------
+
+ void onPeerAccept (IPAddress const& local_address,
+ IPAddress const& remote_address)
{
-#if RIPPLE_USE_PEERFINDER
m_queue.dispatch (
m_context.wrap (
- bind (&Logic::onPeerConnectAttemptBegins, &m_logic,
+ bind (&Logic::onPeerAccept, &m_logic,
+ local_address, remote_address)));
+ }
+
+ void onPeerConnect (IPAddress const& address)
+ {
+ m_queue.dispatch (
+ m_context.wrap (
+ bind (&Logic::onPeerConnect, &m_logic,
address)));
-#endif
}
- void onPeerConnectAttemptCompletes (IPAddress const& address, bool success)
+ void onPeerConnected (IPAddress const& local_address,
+ IPAddress const& remote_address)
{
-#if RIPPLE_USE_PEERFINDER
- m_queue.dispatch (
- m_context.wrap (
- bind (&Logic::onPeerConnectAttemptCompletes, &m_logic,
- address, success)));
-#endif
- }
-
- void onPeerConnected (const IPAddress &address, bool incoming)
- {
-#if RIPPLE_USE_PEERFINDER
m_queue.dispatch (
m_context.wrap (
bind (&Logic::onPeerConnected, &m_logic,
- address, incoming)));
-#endif
+ local_address, remote_address)));
}
- void onPeerHandshake (PeerID const& id,
- IPAddress const& address, bool incoming)
+ void onPeerAddressChanged (
+ IPAddress const& currentAddress, IPAddress const& newAddress)
+ {
+ m_queue.dispatch (
+ m_context.wrap (
+ bind (&Logic::onPeerAddressChanged, &m_logic,
+ currentAddress, newAddress)));
+ }
+
+ void onPeerHandshake (IPAddress const& address, PeerID const& id,
+ bool cluster)
{
-#if RIPPLE_USE_PEERFINDER
m_queue.dispatch (
m_context.wrap (
bind (&Logic::onPeerHandshake, &m_logic,
- id, address, incoming)));
-#endif
+ address, id, cluster)));
}
- void onPeerDisconnected (const PeerID& id)
+ void onPeerClosed (IPAddress const& address)
{
-#if RIPPLE_USE_PEERFINDER
m_queue.dispatch (
m_context.wrap (
- bind (&Logic::onPeerDisconnected, &m_logic,
- id)));
-#endif
+ bind (&Logic::onPeerClosed, &m_logic,
+ address)));
}
- void onPeerLegacyEndpoint (IPAddress const& ep)
+ void onPeerEndpoints (IPAddress const& address,
+ Endpoints const& endpoints)
{
-#if RIPPLE_USE_PEERFINDER
- m_queue.dispatch (
- m_context.wrap (
- bind (&Logic::onPeerLegacyEndpoint, &m_logic,
- ep)));
-#endif
- }
-
- void onPeerEndpoints (PeerID const& id,
- std::vector const& endpoints)
- {
-#if RIPPLE_USE_PEERFINDER
m_queue.dispatch (
beast::bind (&Logic::onPeerEndpoints, &m_logic,
- id, endpoints));
-#endif
+ address, endpoints));
+ }
+
+ void onLegacyEndpoints (IPAddresses const& addresses)
+ {
+ m_queue.dispatch (
+ m_context.wrap (
+ beast::bind (&Logic::onLegacyEndpoints, &m_logic,
+ addresses)));
}
//--------------------------------------------------------------------------
@@ -192,9 +185,9 @@ public:
{
std::string const& s (*iter);
IPAddress addr (IPAddress::from_string (s));
- if (addr.empty ())
+ if (is_unspecified (addr))
addr = IPAddress::from_string_altform(s);
- if (! addr.empty())
+ if (! is_unspecified (addr))
{
// add IPAddress to bootstrap cache
++n;
@@ -213,9 +206,9 @@ public:
{
std::string const& s (*iter);
IPAddress addr (IPAddress::from_string (s));
- if (addr.empty ())
+ if (is_unspecified (addr))
addr = IPAddress::from_string_altform(s);
- if (! addr.empty())
+ if (! is_unspecified (addr))
{
// add IPAddress to fixed peers
}
@@ -282,7 +275,7 @@ public:
m_context.wrap (
bind (&Logic::makeOutgoingConnections, &m_logic)));
- m_connectTimer.setExpiration (secondsPerConnect);
+ m_connectTimer.setExpiration (Tuning::secondsPerConnect);
}
else if (timer == m_messageTimer)
{
@@ -290,7 +283,7 @@ public:
m_context.wrap (
bind (&Logic::sendEndpoints, &m_logic)));
- m_messageTimer.setExpiration (secondsPerMessage);
+ m_messageTimer.setExpiration (Tuning::secondsPerMessage);
}
else if (timer == m_cacheTimer)
{
@@ -298,8 +291,13 @@ public:
m_context.wrap (
bind (&Logic::sweepCache, &m_logic)));
- m_cacheTimer.setExpiration (cacheSecondsToLive);
+ m_cacheTimer.setExpiration (Tuning::liveCacheSecondsToLive);
}
+
+ // VFALCO NOTE Bit of a hack here...
+ m_queue.dispatch (
+ m_context.wrap (
+ bind (&Logic::periodicActivity, &m_logic)));
}
void init ()
@@ -322,13 +320,12 @@ public:
m_logic.load ();
}
- m_connectTimer.setExpiration (secondsPerConnect);
- m_messageTimer.setExpiration (secondsPerMessage);
- m_cacheTimer.setExpiration (cacheSecondsToLive);
-
- m_queue.post (
- m_context.wrap (
- bind (&Logic::makeOutgoingConnections, &m_logic)));
+ m_connectTimer.setExpiration (Tuning::secondsPerConnect);
+ m_messageTimer.setExpiration (Tuning::secondsPerMessage);
+ m_cacheTimer.setExpiration (Tuning::liveCacheSecondsToLive);
+
+ m_queue.post (m_context.wrap (
+ bind (&Logic::makeOutgoingConnections, &m_logic)));
}
void run ()
diff --git a/src/ripple/peerfinder/impl/Peer.h b/src/ripple/peerfinder/impl/Peer.h
new file mode 100644
index 000000000..1cf1105bb
--- /dev/null
+++ b/src/ripple/peerfinder/impl/Peer.h
@@ -0,0 +1,195 @@
+//------------------------------------------------------------------------------
+/*
+ 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.
+*/
+//==============================================================================
+
+#ifndef RIPPLE_PEERFINDER_PEER_H_INCLUDED
+#define RIPPLE_PEERFINDER_PEER_H_INCLUDED
+
+namespace ripple {
+namespace PeerFinder {
+
+/** Metadata for an open peer socket. */
+class Peer
+{
+public:
+ enum State
+ {
+ /** Accepted inbound connection, no handshake. */
+ stateAccept,
+
+ /** Outbound connection attempt. */
+ stateConnect,
+
+ /** Outbound connection, no handshake. */
+ stateConnected,
+
+ /** Active peer (handshake completed). */
+ stateActive,
+
+ /** Graceful close in progress. */
+ stateClosing
+ };
+
+ Peer (IPAddress const& remote_address, bool inbound, bool fixed)
+ : m_inbound (inbound)
+ , m_remote_address (remote_address)
+ , m_state (inbound ? stateAccept : stateConnect)
+ , m_fixed (fixed)
+ , m_cluster (false)
+ , checked (inbound ? false : true)
+ , canAccept (inbound ? false : true)
+ , connectivityCheckInProgress (false)
+ {
+ }
+
+ /** Returns the local address on the socket if known. */
+ IPAddress const& local_address () const
+ {
+ return m_local_address;
+ }
+
+ /** Sets the local address on the socket. */
+ void local_address (IPAddress const& address)
+ {
+ consistency_check (is_unspecified (m_local_address));
+ m_local_address = address;
+ }
+
+ /** Returns the remote address on the socket. */
+ IPAddress const& remote_address () const
+ {
+ return m_remote_address;
+ }
+
+ /** Returns `true` if this is an inbound connection. */
+ bool inbound () const
+ {
+ return m_inbound;
+ }
+
+ /** Returns `true` if this is an outbound connection. */
+ bool outbound () const
+ {
+ return ! m_inbound;
+ }
+
+ /** Marks a connection as belonging to a fixed peer. */
+ void fixed (bool fix)
+ {
+ m_fixed = fix;
+ }
+
+ /** Marks `true` if this is a connection belonging to a fixed peer. */
+ bool fixed () const
+ {
+ return m_fixed;
+ }
+
+ void cluster (bool cluster)
+ {
+ m_cluster = cluster;
+ }
+
+ bool cluster () const
+ {
+ return m_cluster;
+ }
+
+ State state() const
+ {
+ return m_state;
+ }
+
+ void state (State s)
+ {
+ m_state = s;
+ }
+
+ PeerID const& id () const
+ {
+ return m_id;
+ }
+
+ void activate (PeerID const& id, DiscreteTime now)
+ {
+ m_state = stateActive;
+ m_id = id;
+
+ whenSendEndpoints = now;
+ whenAcceptEndpoints = now;
+ }
+
+private:
+ // `true` if the connection is incoming
+ bool const m_inbound;
+
+ // The local address on the socket, when it is known.
+ IPAddress m_local_address;
+
+ // The remote address on the socket.
+ IPAddress m_remote_address;
+
+ // Current state of this connection
+ State m_state;
+
+ // The public key. Valid after a handshake.
+ PeerID m_id;
+
+ // Set to indicate that this is a fixed peer.
+ bool m_fixed;
+
+ // Set to indicate that this is a peer that belongs in our cluster
+ // and does not consume a slot. Valid after a handshake.
+ bool m_cluster;
+
+ //--------------------------------------------------------------------------
+
+public:
+ // DEPRECATED public data members
+
+ // Tells us if we checked the connection. Outbound connections
+ // are always considered checked since we successfuly connected.
+ bool checked;
+
+ // Set to indicate if the connection can receive incoming at the
+ // address advertised in mtENDPOINTS. Only valid if checked is true.
+ bool canAccept;
+
+ // Set to indicate that a connection check for this peer is in
+ // progress. Valid always.
+ bool connectivityCheckInProgress;
+
+ // The time after which we will send the peer mtENDPOINTS
+ DiscreteTime whenSendEndpoints;
+
+ // The time after which we will accept mtENDPOINTS from the peer
+ // This is to prevent flooding or spamming. Receipt of mtENDPOINTS
+ // sooner than the allotted time should impose a load charge.
+ //
+ DiscreteTime whenAcceptEndpoints;
+
+ // The set of all recent IPAddress that we have seen from this peer.
+ // We try to avoid sending a peer the same addresses they gave us.
+ //
+ //std::set received;
+};
+
+}
+}
+
+#endif
diff --git a/src/ripple/peerfinder/impl/PeerInfo.h b/src/ripple/peerfinder/impl/PeerInfo.h
deleted file mode 100644
index 5ceadbdb9..000000000
--- a/src/ripple/peerfinder/impl/PeerInfo.h
+++ /dev/null
@@ -1,110 +0,0 @@
-//------------------------------------------------------------------------------
-/*
- 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.
-*/
-//==============================================================================
-
-#ifndef RIPPLE_PEERFINDER_PEERINFO_H_INCLUDED
-#define RIPPLE_PEERFINDER_PEERINFO_H_INCLUDED
-
-namespace ripple {
-namespace PeerFinder {
-
-//typedef AgedHistory > Endpoints;
-
-//--------------------------------------------------------------------------
-
-// we keep one of these for each connected peer
-struct PeerInfo
-{
- enum State
- {
- // Some peculiar, unknown state
- stateUnknown,
-
- // A connection attempt is in progress
- stateConnecting,
-
- // A connection has been established but no handshake yet
- stateConnected,
-
- // A connection has been established and the handshake has completed
- stateEstablished,
-
- // A connection (of some kind) that is being torn down
- stateDisconnecting
- };
-
- PeerInfo (PeerID const& id_,
- IPAddress const& address_,
- bool inbound_,
- DiscreteTime now)
- : id (id_)
- , address (address_)
- , inbound (inbound_)
- , fixed (false)
- , checked (inbound_ ? false : true)
- , canAccept (inbound_ ? false : true)
- , connectivityCheckInProgress (false)
- , peerState (stateUnknown)
- , whenSendEndpoints (now)
- , whenAcceptEndpoints (now)
- {
- }
-
- PeerID id;
- IPAddress address;
- bool inbound;
-
- // Set to indicate that this is a fixed peer.
- bool fixed;
-
- // Tells us if we checked the connection. Outbound connections
- // are always considered checked since we successfuly connected.
- bool mutable checked;
-
- // Set to indicate if the connection can receive incoming at the
- // address advertised in mtENDPOINTS. Only valid if checked is true.
- bool mutable canAccept;
-
- // Set to indicate that a connection check for this peer is in
- // progress. Valid always.
- bool mutable connectivityCheckInProgress;
-
- // Indicates the state for this peer
- State peerState;
-
- // The time after which we will send the peer mtENDPOINTS
- DiscreteTime mutable whenSendEndpoints;
-
- // The time after which we will accept mtENDPOINTS from the peer
- // This is to prevent flooding or spamming. Receipt of mtENDPOINTS
- // sooner than the allotted time should impose a load charge.
- //
- DiscreteTime mutable whenAcceptEndpoints;
-
- // The set of all recent IPAddress that we have seen from this peer.
- // We try to avoid sending a peer the same addresses they gave us.
- //
- CycledSet mutable received;
-};
-
-}
-}
-
-#endif
diff --git a/src/ripple/peerfinder/impl/PrivateTypes.h b/src/ripple/peerfinder/impl/PrivateTypes.h
index 820a7d031..6b29a536a 100644
--- a/src/ripple/peerfinder/impl/PrivateTypes.h
+++ b/src/ripple/peerfinder/impl/PrivateTypes.h
@@ -26,6 +26,14 @@ namespace PeerFinder {
/** Time in seconds since some baseline event in the past. */
typedef int DiscreteTime;
+/** Indicates the action the logic will take after a handshake. */
+enum HandshakeAction
+{
+ doActivate,
+ doRedirect,
+ doClose
+};
+
}
}
diff --git a/src/ripple/peerfinder/impl/Reporting.h b/src/ripple/peerfinder/impl/Reporting.h
new file mode 100644
index 000000000..f0f488e0e
--- /dev/null
+++ b/src/ripple/peerfinder/impl/Reporting.h
@@ -0,0 +1,70 @@
+//------------------------------------------------------------------------------
+/*
+ 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.
+*/
+//==============================================================================
+
+#ifndef RIPPLE_PEERFINDER_REPORTING_H_INCLUDED
+#define RIPPLE_PEERFINDER_REPORTING_H_INCLUDED
+
+namespace ripple {
+namespace PeerFinder {
+
+/** Severity levels for test reporting.
+ This allows more fine grained control over reporting for diagnostics.
+*/
+struct Reporting
+{
+ // Report simulation parameters
+ static bool const params = true;
+
+ // Report simulation crawl time-evolution
+ static bool const crawl = true;
+
+ // Report nodes aggregate statistics
+ static bool const nodes = true;
+
+ // Report nodes detailed information
+ static bool const dump_nodes = false;
+
+ //
+ //
+ //
+
+ // Reports from Network (and children)
+ static Journal::Severity const network = Journal::kWarning;
+
+ // Reports from simulation Node (and children)
+ static Journal::Severity const node = Journal::kAll;
+
+ //
+ //
+ //
+
+ // Reports from Logic
+ static Journal::Severity const logic = Journal::kAll;
+
+ // Reports from Livecache
+ static Journal::Severity const livecache = Journal::kAll;
+
+ // Reports from Bootcache
+ static Journal::Severity const bootcache = Journal::kAll;
+};
+
+}
+}
+
+#endif
diff --git a/src/ripple/peerfinder/impl/Resolver.cpp b/src/ripple/peerfinder/impl/Resolver.cpp
index 5c9bf3725..dd42cf0cb 100644
--- a/src/ripple/peerfinder/impl/Resolver.cpp
+++ b/src/ripple/peerfinder/impl/Resolver.cpp
@@ -45,11 +45,11 @@ private:
static boost::asio::ip::tcp::endpoint fromIPAddress (
IPAddress const& ipEndpoint)
{
- if (ipEndpoint.isV4 ())
+ if (ipEndpoint.is_v4 ())
{
return boost::asio::ip::tcp::endpoint (
boost::asio::ip::address_v4 (
- ipEndpoint.v4().value),
+ ipEndpoint.to_v4().value),
ipEndpoint.port ());
}
bassertfalse;
diff --git a/src/ripple/peerfinder/impl/Cache.cpp b/src/ripple/peerfinder/impl/Seen.h
similarity index 84%
rename from src/ripple/peerfinder/impl/Cache.cpp
rename to src/ripple/peerfinder/impl/Seen.h
index 398cc1921..17e92b299 100644
--- a/src/ripple/peerfinder/impl/Cache.cpp
+++ b/src/ripple/peerfinder/impl/Seen.h
@@ -17,3 +17,15 @@
*/
//==============================================================================
+#ifndef RIPPLE_PEERFINDER_SEEN_H_INCLUDED
+#define RIPPLE_PEERFINDER_SEEN_H_INCLUDED
+
+namespace ripple {
+namespace PeerFinder {
+
+/** Tracks endpoints we've seen from a peer. */
+
+}
+}
+
+#endif
diff --git a/src/ripple/peerfinder/impl/Slots.cpp b/src/ripple/peerfinder/impl/Slots.cpp
deleted file mode 100644
index e8b7a542a..000000000
--- a/src/ripple/peerfinder/impl/Slots.cpp
+++ /dev/null
@@ -1,138 +0,0 @@
-//------------------------------------------------------------------------------
-/*
- 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.
-*/
-//==============================================================================
-
-namespace ripple {
-namespace PeerFinder {
-
-Slots::Slots (DiscreteClock clock, bool roundUpwards)
- : peerCount (0)
- , inboundCount (0)
- , outboundCount (0)
- , fixedCount (0)
- , outDesired (0)
- , inboundSlots (0)
- , inboundSlotsMaximum (0)
- , m_clock (clock)
- , m_startTime (0)
- , m_roundUpwards (roundUpwards)
-{
-}
-
-void Slots::update (Config const& config)
-{
- double outDesiredFraction = 1;
-
- if (config.wantIncoming)
- outDesiredFraction = config.maxPeerCount * (Config::outPercent * .01);
-
- if (m_roundUpwards)
- outDesired = int (std::ceil (outDesiredFraction));
- else
- outDesired = int (std::floor (outDesiredFraction));
-
- if (outDesired < Config::minOutCount)
- outDesired = Config::minOutCount;
-
- if (config.maxPeerCount >= outDesired)
- inboundSlotsMaximum = config.maxPeerCount - outDesired;
- else
- inboundSlotsMaximum = 0;
-
- inboundSlots = std::max (inboundSlotsMaximum - inboundCount, 0);
-}
-
-void Slots::addPeer (Config const& config, bool inbound)
-{
- if (peerCount == 0)
- m_startTime = m_clock();
-
- ++peerCount;
- if (inbound)
- ++inboundCount;
- else
- ++outboundCount;
-
- update (config);
-}
-
-void Slots::dropPeer (Config const& config, bool inbound)
-{
- bool const wasConnected (connected ());
-
- --peerCount;
- if (inbound)
- --inboundCount;
- else
- --outboundCount;
-
- if (wasConnected && ! connected())
- m_startTime = 0;
-
- update (config);
-}
-
-bool Slots::roundUpwards () const
-{
- return m_roundUpwards;
-}
-
-bool Slots::connected () const
-{
- return (peerCount-fixedCount) >= Config::minOutCount;
-}
-
-uint32 Slots::uptimeSeconds () const
-{
- if (m_startTime != 0)
- return m_clock() - m_startTime;
- return 0;
-}
-
-void Slots::updateConnected ()
-{
- bool const wasConnected (m_startTime != 0);
- bool const isConnected (connected());
-
- if (wasConnected && !isConnected)
- {
- m_startTime = 0;
- }
- else if (! wasConnected && isConnected)
- {
- m_startTime = m_clock();
- }
-}
-
-void Slots::onWrite (PropertyStream::Map& map)
-{
- map ["peers"] = peerCount;
- map ["in"] = inboundCount;
- map ["out"] = outboundCount;
- map ["fixed"] = fixedCount;
- map ["out_desired"] = outDesired;
- map ["in_avail"] = inboundSlots;
- map ["in_max"] = inboundSlotsMaximum;
- map ["round"] = roundUpwards();
- map ["connected"] = connected();
- map ["uptime"] =
- RelativeTime (uptimeSeconds()).getDescription ().toStdString();
-}
-
-}
-}
diff --git a/src/ripple/peerfinder/impl/Slots.h b/src/ripple/peerfinder/impl/Slots.h
index 4cfdaa46f..5698dbbbc 100644
--- a/src/ripple/peerfinder/impl/Slots.h
+++ b/src/ripple/peerfinder/impl/Slots.h
@@ -26,59 +26,435 @@ namespace PeerFinder {
class Slots
{
public:
- explicit Slots (
- DiscreteClock clock,
- bool roundUpwards = Random::getSystemRandom().nextBool());
+ explicit Slots (DiscreteClock clock)
+ : m_clock (clock)
+ , m_inboundSlots (0)
+ , m_inboundActive (0)
+ , m_outboundSlots (0)
+ , m_outboundActive (0)
+ , m_fixedPeerConnections (0)
+ , m_clusterPeerConnections (0)
+ , m_acceptCount (0)
+ , m_connectCount (0)
+ , m_closingCount (0)
+ {
+#if 0
+ std::random_device rd;
+ std::mt19937 gen (rd());
+ m_roundingThreshold =
+ std::generate_canonical (gen);
+#else
+ m_roundingThreshold = Random::getSystemRandom().nextDouble();
+#endif
+ }
- void update (Config const& config);
- void addPeer (Config const& config, bool inbound);
- void dropPeer (Config const& config, bool inbound);
+ /** Called when the config is set or changed. */
+ void onConfig (Config const& config)
+ {
+ // Calculate the number of outbound peers we want. If we dont want or can't
+ // accept incoming, this will simply be equal to maxPeers. Otherwise
+ // we calculate a fractional amount based on percentages and pseudo-randomly
+ // round up or down.
+ //
+ if (config.wantIncoming)
+ {
+ // Round outPeers upwards using a Bernoulli distribution
+ m_outboundSlots = std::floor (config.outPeers);
+ if (m_roundingThreshold < (config.outPeers - m_outboundSlots))
+ ++m_outboundSlots;
+ }
+ else
+ {
+ m_outboundSlots = config.maxPeers;
+ }
- // Current total of connected peers that have HELLOed
- int peerCount;
+ // Calculate the largest number of inbound connections we could take.
+ if (config.maxPeers >= m_outboundSlots)
+ m_inboundSlots = config.maxPeers - m_outboundSlots;
+ else
+ m_inboundSlots = 0;
+ }
- // The portion of peers which are incoming connections
- int inboundCount;
+ /** Returns the number of accepted connections that haven't handshaked. */
+ int acceptCount() const
+ {
+ return m_acceptCount;
+ }
- // The portion of peers which are outgoing connections
- int outboundCount;
+ /** Returns the number of connection attempts currently active. */
+ int connectCount() const
+ {
+ return m_connectCount;
+ }
- // The portion of peers which are the fixed peers.
- // Fixed peers don't count towards connection limits.
- int fixedCount;
+ /** Returns the number of connections that are gracefully closing. */
+ int closingCount () const
+ {
+ return m_closingCount;
+ }
- // The number of outgoing peer connections we want (calculated)
- int outDesired;
+ /** Returns the total number of inbound slots. */
+ int inboundSlots () const
+ {
+ return m_inboundSlots;
+ }
- // The number of available incoming slots (calculated)
- int inboundSlots;
+ /** Returns the total number of outbound slots. */
+ int outboundSlots () const
+ {
+ return m_outboundSlots;
+ }
- // The maximum number of incoming slots (calculated)
- int inboundSlotsMaximum;
+ /** Returns the number of inbound peers assigned an open slot. */
+ int inboundActive () const
+ {
+ return m_inboundActive;
+ }
- // Returns `true` if we round fractional slot availability upwards
- bool roundUpwards () const;
+ /** Returns the number of outbound peers assigned an open slot.
+ Fixed peers do not count towards outbound slots used.
+ */
+ int outboundActive () const
+ {
+ return m_outboundActive;
+ }
- // Returns `true` if we meet the criteria of
- // "connected to the network based on the current values of slots.
- //
- bool connected () const;
+ /** Returns the total number of active peers excluding fixed peers. */
+ int totalActive () const
+ {
+ return m_inboundActive + m_outboundActive;
+ }
- // Returns the uptime in seconds
- // Uptime is measured from the last we transitioned from not
- // being connected to the network, to being connected.
- //
- uint32 uptimeSeconds () const;
+ /** Returns the number of unused inbound slots.
+ Fixed peers do not deduct from inbound slots or count towards totals.
+ */
+ int inboundSlotsFree () const
+ {
+ if (m_inboundActive < m_inboundSlots)
+ return m_inboundSlots - m_inboundActive;
+ return 0;
+ }
- // Output statistics
- void onWrite (PropertyStream::Map& map);
+ /** Returns the number of unused outbound slots.
+ Fixed peers do not deduct from outbound slots or count towards totals.
+ */
+ int outboundSlotsFree () const
+ {
+ if (m_outboundActive < m_outboundSlots)
+ return m_outboundSlots - m_outboundActive;
+ return 0;
+ }
+
+ /** Returns the number of fixed peers we have connections to
+ Fixed peers do not deduct from outbound or inbound slots or count
+ towards totals.
+ */
+ int fixedPeers () const
+ {
+ return m_fixedPeerConnections;
+ }
+
+ /** Returns the number of cluster peers we have connections to
+ Cluster nodes do not deduct from outbound or inbound slots or
+ count towards totals, but they are tracked if they are also
+ configured as fixed peers.
+ */
+ int clusterPeers () const
+ {
+ return m_clusterPeerConnections;
+ }
+
+ //--------------------------------------------------------------------------
+
+ /** Called when an inbound connection is accepted. */
+ void onPeerAccept ()
+ {
+ ++m_acceptCount;
+ }
+
+ /** Called when a new outbound connection is attempted. */
+ void onPeerConnect ()
+ {
+ ++m_connectCount;
+ }
+
+ /** Determines if an outbound slot is available and assigns it */
+ HandshakeAction grabOutboundSlot(bool self, bool fixed,
+ bool available, bool cluster)
+ {
+ // If this is a connection to ourselves, we bail.
+ if (self)
+ {
+ ++m_closingCount;
+ return doClose;
+ }
+
+ // Fixed and cluster peers are tracked but are not subject
+ // to limits and don't consume slots. They are always allowed
+ // to connect.
+ if (fixed || cluster)
+ {
+ if (fixed)
+ ++m_fixedPeerConnections;
+
+ if (cluster)
+ ++m_clusterPeerConnections;
+
+ return doActivate;
+ }
+
+ // If we don't have any slots for this peer then reject the
+ // connection.
+ if (!available)
+ {
+ ++m_closingCount;
+ return doClose;
+ }
+
+ ++m_outboundActive;
+ return doActivate;
+ }
+
+ /** Determines if an inbound slot is available and assigns it */
+ HandshakeAction grabInboundSlot(bool self, bool fixed,
+ bool available, bool cluster)
+ {
+ // If this is a connection to ourselves, we bail.
+ if (self)
+ {
+ ++m_closingCount;
+ return doClose;
+ }
+
+ // Fixed and cluster peers are tracked but are not subject
+ // to limits and don't consume slots. They are always allowed
+ // to connect.
+ if (fixed || cluster)
+ {
+ if (fixed)
+ ++m_fixedPeerConnections;
+
+ if (cluster)
+ ++m_clusterPeerConnections;
+
+ return doActivate;
+ }
+
+ // If we don't have any slots for this peer then reject the
+ // connection and redirect them.
+ if (!available)
+ {
+ ++m_closingCount;
+ return doRedirect;
+ }
+
+ ++m_inboundActive;
+ return doActivate;
+ }
+
+ /** Called when a peer handshakes.
+ Returns the disposition for this peer, including whether we should
+ activate the connection, issue a redirect or simply close it.
+ */
+ HandshakeAction onPeerHandshake (bool inbound, bool self, bool fixed, bool cluster)
+ {
+ if (cluster)
+ return doActivate;
+
+ if (inbound)
+ {
+ // Must not be zero!
+ consistency_check (m_acceptCount > 0);
+ --m_acceptCount;
+
+ return grabInboundSlot (self, fixed,
+ inboundSlotsFree () > 0, cluster);
+ }
+
+ // Must not be zero!
+ consistency_check (m_connectCount > 0);
+ --m_connectCount;
+
+ return grabOutboundSlot (self, fixed,
+ outboundSlotsFree () > 0, cluster);
+ }
+
+ /** Called when a peer socket is closed gracefully. */
+ void onPeerGracefulClose ()
+ {
+ // Must not be zero!
+ consistency_check (m_closingCount > 0);
+ --m_closingCount;
+ }
+
+ /** Called when a peer socket is closed.
+ A value of `true` for active means the peer was assigned an open slot.
+ */
+ void onPeerClosed (bool inbound, bool active, bool fixed, bool cluster)
+ {
+ if (active)
+ {
+ if (inbound)
+ {
+ // Fixed peer connections are tracked but don't count towards slots
+ if (fixed || cluster)
+ {
+ if (fixed)
+ {
+ consistency_check (m_fixedPeerConnections > 0);
+ --m_fixedPeerConnections;
+ }
+
+ if (cluster)
+ {
+ consistency_check (m_clusterPeerConnections > 0);
+ --m_clusterPeerConnections;
+ }
+ }
+ else
+ {
+ // Must not be zero!
+ consistency_check (m_inboundActive > 0);
+ --m_inboundActive;
+ }
+ }
+ else
+ {
+ // Fixed peer connections are tracked but don't count towards slots
+ if (fixed || cluster)
+ {
+ if (fixed)
+ {
+ consistency_check (m_fixedPeerConnections > 0);
+ --m_fixedPeerConnections;
+ }
+
+ if (cluster)
+ {
+ consistency_check (m_clusterPeerConnections > 0);
+ --m_clusterPeerConnections;
+ }
+ }
+ else
+ {
+ // Must not be zero!
+ consistency_check (m_outboundActive > 0);
+ --m_outboundActive;
+ }
+ }
+ }
+ else if (inbound)
+ {
+ // Must not be zero!
+ consistency_check (m_acceptCount > 0);
+ --m_acceptCount;
+ }
+ else
+ {
+ // Must not be zero!
+ consistency_check (m_connectCount > 0);
+ --m_connectCount;
+ }
+ }
+
+ //--------------------------------------------------------------------------
+
+ /** Returns the number of new connection attempts we should make. */
+ int additionalAttemptsNeeded () const
+ {
+ // Don't go over the maximum concurrent attempt limit
+ if (m_connectCount >= Tuning::maxConnectAttempts)
+ return 0;
+ int needed (outboundSlotsFree ());
+ // This is the most we could attempt right now
+ int const available (
+ Tuning::maxConnectAttempts - m_connectCount);
+ return std::min (needed, available);
+ }
+
+ /** Returns true if the slot logic considers us "connected" to the network. */
+ bool isConnectedToNetwork () const
+ {
+ // We will consider ourselves connected if we have reached
+ // the number of outgoing connections desired, or if connect
+ // automatically is false.
+ //
+ // Fixed peers do not count towards the active outgoing total.
+
+ if (m_outboundSlots > 0)
+ return false;
+
+ return true;
+ }
+
+ /** Output statistics. */
+ void onWrite (PropertyStream::Map& map)
+ {
+ map ["accept"] = acceptCount();
+ map ["connect"] = connectCount();
+ map ["close"] = closingCount();
+ map ["in"] << inboundActive() << "/" << inboundSlots();
+ map ["out"] << outboundActive() << "/" << outboundSlots();
+ map ["fixed"] = fixedPeers();
+ }
+
+ /** Records the state for diagnostics. */
+ std::string state_string () const
+ {
+ std::stringstream ss;
+ ss <<
+ outboundActive() << "/" << outboundSlots() << " out, " <<
+ inboundActive() << "/" << inboundSlots() << " in, " <<
+ connectCount() << " connecting, " <<
+ closingCount() << " closing"
+ ;
+ return ss.str();
+ }
+
+ //--------------------------------------------------------------------------
private:
- void updateConnected();
-
DiscreteClock m_clock;
- DiscreteTime m_startTime;
- bool m_roundUpwards;
+
+ /** Total number of inbound slots. */
+ int m_inboundSlots;
+
+ /** Number of inbound slots assigned to active peers. */
+ int m_inboundActive;
+
+ /** Total number of outbound slots. */
+ int m_outboundSlots;
+
+ /** Number of outbound slots assigned to active peers. */
+ int m_outboundActive;
+
+ /** Number of fixed peer connections that we have. */
+ int m_fixedPeerConnections;
+
+ /** Number of cluster peer connections that we have. */
+ int m_clusterPeerConnections;
+
+ // Number of inbound connections that are
+ // not active or gracefully closing.
+ int m_acceptCount;
+
+ // Number of outgoing connections that are
+ // not active or gracefully closing.
+ //
+ int m_connectCount;
+
+ // Number of connections that are gracefully closing.
+ int m_closingCount;
+
+ // Number of connections that are currently assigned an open slot
+ //int m_activeCount;
+
+ /** Fractional threshold below which we round down.
+ This is used to round the value of Config::outPeers up or down in
+ such a way that the network-wide average number of outgoing
+ connections approximates the recommended, fractional value.
+ */
+ double m_roundingThreshold;
};
}
diff --git a/src/ripple/peerfinder/impl/Sorts.h b/src/ripple/peerfinder/impl/Sorts.h
new file mode 100644
index 000000000..a86859628
--- /dev/null
+++ b/src/ripple/peerfinder/impl/Sorts.h
@@ -0,0 +1,53 @@
+//------------------------------------------------------------------------------
+/*
+ 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.
+*/
+//==============================================================================
+
+#ifndef RIPPLE_PEERFINDER_SORTS_H_INCLUDED
+#define RIPPLE_PEERFINDER_SORTS_H_INCLUDED
+
+namespace ripple {
+namespace PeerFinder {
+
+/** Total ordering for Endpoint.
+
+ The ordering must have these properties:
+
+ - Endpoints with addresses differing only by port should be
+ sorted adjacent, by descending hop count.
+
+ - The port number must participate in the ordering
+*/
+struct LessEndpoints
+{
+ bool operator() (Endpoint const& lhs, Endpoint const& rhs) const
+ {
+ if (lhs.address.address() < rhs.address.address())
+ return true;
+ if (lhs.address.address() > rhs.address.address())
+ return false;
+ // Break ties by preferring higher hops
+ if (lhs.hops > rhs.hops)
+ return true;
+ return lhs.address.port () < rhs.address.port ();
+ }
+};
+
+}
+}
+
+#endif
diff --git a/src/ripple/peerfinder/impl/Source.h b/src/ripple/peerfinder/impl/Source.h
index 07074f21a..1a28ffae9 100644
--- a/src/ripple/peerfinder/impl/Source.h
+++ b/src/ripple/peerfinder/impl/Source.h
@@ -41,7 +41,7 @@ public:
ErrorCode error;
// list of fetched endpoints
- std::vector list;
+ IPAddresses addresses;
};
virtual ~Source () { }
diff --git a/src/ripple/peerfinder/impl/SourceStrings.cpp b/src/ripple/peerfinder/impl/SourceStrings.cpp
index 7d3e1d81b..1993d4b7f 100644
--- a/src/ripple/peerfinder/impl/SourceStrings.cpp
+++ b/src/ripple/peerfinder/impl/SourceStrings.cpp
@@ -40,15 +40,15 @@ public:
void fetch (Results& results, Journal journal)
{
- results.list.resize (0);
- results.list.reserve (m_strings.size());
+ results.addresses.resize (0);
+ results.addresses.reserve (m_strings.size());
for (int i = 0; i < m_strings.size (); ++i)
{
IPAddress ep (
IPAddress::from_string_altform (
m_strings [i]));
- if (! ep.empty())
- results.list.push_back (ep);
+ if (! is_unspecified (ep))
+ results.addresses.push_back (ep);
}
}
diff --git a/src/ripple/peerfinder/impl/Store.h b/src/ripple/peerfinder/impl/Store.h
index cc676305f..8d0013fa8 100644
--- a/src/ripple/peerfinder/impl/Store.h
+++ b/src/ripple/peerfinder/impl/Store.h
@@ -29,11 +29,18 @@ class Store
public:
virtual ~Store () { }
- virtual void loadLegacyEndpoints (
- std::vector & list) = 0;
+ struct SavedBootstrapAddress
+ {
+ IPAddress address;
+ int cumulativeUptimeSeconds;
+ int connectionValence;
+ };
- virtual void updateLegacyEndpoints (
- std::vector const& list) = 0;
+ virtual std::vector
+ loadBootstrapCache () = 0;
+
+ virtual void updateBootstrapCache (
+ std::vector const& list) = 0;
};
}
diff --git a/src/ripple/peerfinder/impl/StoreSqdb.h b/src/ripple/peerfinder/impl/StoreSqdb.h
index 2ad17dc89..62305db68 100644
--- a/src/ripple/peerfinder/impl/StoreSqdb.h
+++ b/src/ripple/peerfinder/impl/StoreSqdb.h
@@ -36,7 +36,7 @@ public:
enum
{
// This determines the on-database format of the data
- currentSchemaVersion = 2
+ currentSchemaVersion = 3
};
explicit StoreSqdb (Journal journal = Journal())
@@ -63,10 +63,11 @@ public:
return error;
}
- void loadLegacyEndpoints (
- std::vector & list)
+ // Loads the entire stored bootstrap cache and returns it as an array.
+ //
+ std::vector loadBootstrapCache ()
{
- list.clear ();
+ std::vector list;
Error error;
@@ -75,7 +76,7 @@ public:
if (! error)
{
m_session.once (error) <<
- "SELECT COUNT(*) FROM PeerFinder_LegacyEndpoints "
+ "SELECT COUNT(*) FROM PeerFinder_BootstrapCache "
,sqdb::into (count)
;
}
@@ -83,25 +84,47 @@ public:
if (error)
{
report (error, __FILE__, __LINE__);
- return;
+ return list;
}
list.reserve (count);
{
std::string s;
+ int uptimeSeconds;
+ int connectionValence;
+
sqdb::statement st = (m_session.prepare <<
- "SELECT ipv4 FROM PeerFinder_LegacyEndpoints "
- ,sqdb::into (s)
+ "SELECT "
+ " address, "
+ " uptime, "
+ " valence "
+ "FROM PeerFinder_BootstrapCache "
+ , sqdb::into (s)
+ , sqdb::into (uptimeSeconds)
+ , sqdb::into (connectionValence)
);
if (st.execute_and_fetch (error))
{
do
{
- IPAddress ep (IPAddress::from_string (s));
- if (! ep.empty())
- list.push_back (ep);
+ SavedBootstrapAddress entry;
+
+ entry.address = IPAddress::from_string (s);
+
+ if (! is_unspecified (entry.address))
+ {
+ entry.cumulativeUptimeSeconds = uptimeSeconds;
+ entry.connectionValence = connectionValence;
+
+ list.push_back (entry);
+ }
+ else
+ {
+ m_journal.error <<
+ "Bad address string '" << s << "' in Bootcache table";
+ }
}
while (st.fetch (error));
}
@@ -111,37 +134,48 @@ public:
{
report (error, __FILE__, __LINE__);
}
+
+ return list;
}
- void updateLegacyEndpoints (
- std::vector const& list)
+ // Overwrites the stored bootstrap cache with the specified array.
+ //
+ void updateBootstrapCache (
+ std::vector const& list)
{
- typedef std::vector List;
-
Error error;
sqdb::transaction tr (m_session);
m_session.once (error) <<
- "DELETE FROM PeerFinder_LegacyEndpoints";
+ "DELETE FROM PeerFinder_BootstrapCache";
if (! error)
{
std::string s;
+ int uptimeSeconds;
+ int connectionValence;
+
sqdb::statement st = (m_session.prepare <<
- "INSERT INTO PeerFinder_LegacyEndpoints ( "
- " ipv4 "
+ "INSERT INTO PeerFinder_BootstrapCache ( "
+ " address, "
+ " uptime, "
+ " valence "
") VALUES ( "
- " ? "
+ " ?, ?, ? "
");"
- ,sqdb::use (s)
+ , sqdb::use (s)
+ , sqdb::use (uptimeSeconds)
+ , sqdb::use (connectionValence)
);
- for (List::const_iterator iter (list.begin());
- !error && iter != list.end(); ++iter)
+ for (std::vector ::const_iterator iter (
+ list.begin()); !error && iter != list.end(); ++iter)
{
- IPAddress const& ep ((*iter)->address);
- s = ep.to_string();
+ s = to_string (iter->address);
+ uptimeSeconds = iter->cumulativeUptimeSeconds;
+ connectionValence = iter->connectionValence;
+
st.execute_and_fetch (error);
}
}
@@ -159,7 +193,7 @@ public:
}
// Convert any existing entries from an older schema to the
- // current one, if approrpriate.
+ // current one, if appropriate.
//
Error update ()
{
@@ -184,25 +218,37 @@ public:
if (!m_session.got_data())
version = 0;
- m_journal.info << "Opened version " << version << " database";
+ m_journal.info <<
+ "Opened version " << version << " database";
}
}
if (!error && version != currentSchemaVersion)
{
m_journal.info <<
- "Updateding database to version " << currentSchemaVersion;
+ "Updating database to version " << currentSchemaVersion;
}
- if (!error && (version < 2))
+ if (!error)
{
- if (!error)
- m_session.once (error) <<
- "DROP TABLE IF EXISTS LegacyEndpoints";
+ if (version < 3)
+ {
+ if (!error)
+ m_session.once (error) <<
+ "DROP TABLE IF EXISTS LegacyEndpoints";
- if (!error)
- m_session.once (error) <<
- "DROP TABLE IF EXISTS PeerFinderLegacyEndpoints";
+ if (!error)
+ m_session.once (error) <<
+ "DROP TABLE IF EXISTS PeerFinderLegacyEndpoints";
+
+ if (!error)
+ m_session.once (error) <<
+ "DROP TABLE IF EXISTS PeerFinder_LegacyEndpoints";
+
+ if (!error)
+ m_session.once (error) <<
+ "DROP TABLE IF EXISTS PeerFinder_LegacyEndpoints_Index";
+ }
}
if (!error)
@@ -230,7 +276,6 @@ public:
return error;
}
-
private:
Error init ()
{
@@ -256,9 +301,11 @@ private:
if (! error)
{
m_session.once (error) <<
- "CREATE TABLE IF NOT EXISTS PeerFinder_LegacyEndpoints ( "
- " id INTEGER PRIMARY KEY AUTOINCREMENT, "
- " ipv4 TEXT UNIQUE NOT NULL "
+ "CREATE TABLE IF NOT EXISTS PeerFinder_BootstrapCache ( "
+ " id INTEGER PRIMARY KEY AUTOINCREMENT, "
+ " address TEXT UNIQUE NOT NULL, "
+ " uptime INTEGER,"
+ " valence INTEGER"
");"
;
}
@@ -267,9 +314,9 @@ private:
{
m_session.once (error) <<
"CREATE INDEX IF NOT EXISTS "
- " PeerFinder_LegacyEndpoints_Index ON PeerFinder_LegacyEndpoints "
+ " PeerFinder_BootstrapCache_Index ON PeerFinder_BootstrapCache "
" ( "
- " ipv4 "
+ " address "
" ); "
;
}
diff --git a/src/ripple/peerfinder/impl/Tests.cpp b/src/ripple/peerfinder/impl/Tests.cpp
deleted file mode 100644
index a74121d46..000000000
--- a/src/ripple/peerfinder/impl/Tests.cpp
+++ /dev/null
@@ -1,113 +0,0 @@
-//------------------------------------------------------------------------------
-/*
- 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.
-*/
-//==============================================================================
-
-namespace ripple {
-namespace PeerFinder {
-
-class PeerFinderTests : public UnitTest
-{
-public:
-
-
- //--------------------------------------------------------------------------
-
- // Complete Logic used for tests
- //
- class TestLogic
- : public LogicType
- , public Callback
- , public Store
- , public Checker
- {
- public:
- Journal m_journal;
-
- explicit TestLogic (Journal journal)
- : LogicType (*this, *this, *this, journal)
- , m_journal (journal)
- {
- }
-
- //
- // Callback
- //
-
- void sendPeerEndpoints (PeerID const& id,
- std::vector const& endpoints)
- {
- }
-
- void connectPeerEndpoints (std::vector const& list)
- {
- }
-
- void chargePeerLoadPenalty (PeerID const& id)
- {
- }
-
- //
- // Store
- //
-
- void loadLegacyEndpoints (std::vector & list)
- {
- }
-
- void updateLegacyEndpoints (std::vector const& list)
- {
- }
-
- //
- // Checker
- //
-
- void cancel ()
- {
- }
-
- void async_test (IPAddress const& address,
- AbstractHandler handler)
- {
- Checker::Result result;
- result.address = address;
- result.canAccept = false;
- handler (result);
- }
- };
-
- //--------------------------------------------------------------------------
-
- void runTest ()
- {
- beginTestCase ("logic");
-
- TestLogic logic (journal());
-
- pass ();
- }
-
- PeerFinderTests () : UnitTest ("PeerFinder", "ripple", runManual)
- {
- }
-};
-
-static PeerFinderTests peerFinderTests;
-
-}
-}
diff --git a/src/ripple/peerfinder/impl/Tuning.h b/src/ripple/peerfinder/impl/Tuning.h
index 373a829c2..d7f9edd5f 100644
--- a/src/ripple/peerfinder/impl/Tuning.h
+++ b/src/ripple/peerfinder/impl/Tuning.h
@@ -23,37 +23,79 @@
namespace ripple {
namespace PeerFinder {
-// Tunable constants
+/** Heuristically tuned constants. */
+/** @{ */
+namespace Tuning {
+
enum
{
//---------------------------------------------------------
//
- // Connection policy settings
+ // Automatic Connection Policy
//
+ //---------------------------------------------------------
- // How often we will try to make outgoing connections
- secondsPerConnect = 10
+ /** Time to wait between making batches of connection attempts */
+ secondsPerConnect = 10
- // The largest connections we will attempt simultaneously
- ,maxAddressesPerAttempt = 30
+ /** Maximum number of simultaneous connection attempts. */
+ ,maxConnectAttempts = 5
+
+ /** The percentage of total peer slots that are outbound.
+ The number of outbound peers will be the larger of the
+ minOutCount and outPercent * Config::maxPeers specially
+ rounded.
+ */
+ ,outPercent = 15
+
+ /** A hard minimum on the number of outgoing connections.
+ This is enforced outside the Logic, so that the unit test
+ can use any settings it wants.
+ */
+ ,minOutCount = 10
+
+ /** The default value of Config::maxPeers. */
+ ,defaultMaxPeers = 21
//---------------------------------------------------------
//
- // Endpoint settings
+ // Bootcache
//
+ //---------------------------------------------------------
+
+ // Threshold of cache entries above which we trim.
+ ,bootcacheSize = 1000
+
+ // The percentage of addresses we prune when we trim the cache.
+ ,bootcachePrunePercent = 10
+
+ // The cool down wait between database updates
+ // Ideally this should be larger than the time it takes a full
+ // peer to send us a set of addresses and then disconnect.
+ //
+ ,bootcacheCooldownSeconds = 60
+
+ //---------------------------------------------------------
+ //
+ // Livecache
+ //
+ //---------------------------------------------------------
+
+ // Drop incoming messages with hops greater than this number
+ ,maxHops = 10
// How often we send or accept mtENDPOINTS messages per peer
- ,secondsPerMessage = 5
+ ,secondsPerMessage = 5
// How many Endpoint to send in each mtENDPOINTS
- ,numberOfEndpoints = 10
+ ,numberOfEndpoints = 10
// The most Endpoint we will accept in mtENDPOINTS
- ,numberOfEndpointsMax = 20
+ ,numberOfEndpointsMax = 20
// How long an Endpoint will stay in the cache
// This should be a small multiple of the broadcast frequency
- ,cacheSecondsToLive = 60
+ ,liveCacheSecondsToLive = 60
// The maximum number of hops that we allow. Peers farther
// away than this are dropped.
@@ -61,12 +103,16 @@ enum
// The number of peers that we want by default, unless an
// explicit value is set in the config file.
- ,defaultMaxPeerCount = 20
-
+ ,defaultMaxPeerCount = 20
+
+ /** Number of addresses we provide when redirecting. */
+ ,redirectEndpointCount = 10
+
//---------------------------------------------------------
//
- // LegacyEndpoint Settings
+ // LegacyEndpoints
//
+ //---------------------------------------------------------
// How many legacy endpoints to keep in our cache
,legacyEndpointCacheSize = 1000
@@ -74,6 +120,9 @@ enum
// How many cache mutations between each database update
,legacyEndpointMutationsPerUpdate = 50
};
+/** @} */
+
+}
}
}
diff --git a/src/ripple/peerfinder/impl/iosformat.h b/src/ripple/peerfinder/impl/iosformat.h
new file mode 100644
index 000000000..d4f0c4b8a
--- /dev/null
+++ b/src/ripple/peerfinder/impl/iosformat.h
@@ -0,0 +1,214 @@
+//------------------------------------------------------------------------------
+/*
+ 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.
+*/
+//==============================================================================
+
+#ifndef BEAST_IOSFORMAT_H_INCLUDED
+#define BEAST_IOSFORMAT_H_INCLUDED
+
+#include
+#include
+#include
+
+namespace beast {
+
+// A collection of handy stream manipulators and
+// functions to produce nice looking log output.
+
+/** Left justifies a field at the specified width. */
+struct leftw
+{
+ explicit leftw (int width_)
+ : width (width_)
+ { }
+ int const width;
+ template
+ friend std::basic_ios & operator<< (
+ std::basic_ios & ios, leftw const& p)
+ {
+ ios.setf (std::ios_base::left, std::ios_base::adjustfield);
+ ios.width (p.width);
+ return ios;
+ }
+};
+
+/** Produce a section heading and fill the rest of the line with dashes. */
+template
+std::basic_string heading (
+ std::basic_string title,
+ int width = 80, CharT fill = CharT ('-'))
+{
+ title.reserve (width);
+ title.push_back (CharT (' '));
+ title.resize (width, fill);
+ return title;
+}
+
+/** Produce a dashed line separator, with a specified or default size. */
+struct divider
+{
+ typedef char CharT;
+ explicit divider (int width_ = 80, CharT fill_ = CharT ('-'))
+ : width (width_)
+ , fill (fill_)
+ { }
+ int const width;
+ CharT const fill;
+ template
+ friend std::basic_ostream & operator<< (
+ std::basic_ostream & os, divider const& d)
+ {
+ os << std::basic_string (d.width, d.fill);
+ return os;
+ }
+};
+
+/** Creates a padded field with an optiona fill character. */
+struct fpad
+{
+ explicit fpad (int width_, int pad_ = 0, char fill_ = ' ')
+ : width (width_ + pad_)
+ , fill (fill_)
+ { }
+ int const width;
+ char const fill;
+ template
+ friend std::basic_ostream & operator<< (
+ std::basic_ostream & os, fpad const& f)
+ {
+ os << std::basic_string (f.width, f.fill);
+ return os;
+ }
+};
+
+//------------------------------------------------------------------------------
+
+namespace detail {
+
+template
+std::string to_string (T const& t)
+{
+ std::stringstream ss;
+ ss << t;
+ return ss.str();
+}
+
+}
+
+/** Justifies a field at the specified width. */
+/** @{ */
+template ,
+ class Allocator = std::allocator >
+class field_t
+{
+public:
+ typedef std::basic_string string_t;
+ field_t (string_t const& text_, int width_, int pad_, bool right_)
+ : text (text_)
+ , width (width_)
+ , pad (pad_)
+ , right (right_)
+ { }
+ string_t const text;
+ int const width;
+ int const pad;
+ bool const right;
+ template
+ friend std::basic_ostream & operator<< (
+ std::basic_ostream & os,
+ field_t const& f)
+ {
+ std::size_t const length (f.text.length());
+ if (f.right)
+ {
+ if (length < f.width)
+ os << std::basic_string (
+ f.width - length, CharT2 (' '));
+ os << f.text;
+ }
+ else
+ {
+ os << f.text;
+ if (length < f.width)
+ os << std::basic_string (
+ f.width - length, CharT2 (' '));
+ }
+ if (f.pad != 0)
+ os << string_t (f.pad, CharT (' '));
+ return os;
+ }
+};
+
+template
+field_t field (
+ std::basic_string const& text,
+ int width = 8, int pad = 0, bool right = false)
+{
+ return field_t (
+ text, width, pad, right);
+}
+
+template
+field_t field (
+ CharT const* text, int width = 8, int pad = 0, bool right = false)
+{
+ return field_t ,
+ std::allocator > (std::basic_string , std::allocator > (text),
+ width, pad, right);
+}
+
+template
+field_t field (
+ T const& t, int width = 8, int pad = 0, bool right = false)
+{
+ std::string const text (detail::to_string (t));
+ return field (text, width, pad, right);
+}
+
+template
+field_t rfield (
+ std::basic_string const& text,
+ int width = 8, int pad = 0)
+{
+ return field_t (
+ text, width, pad, true);
+}
+
+template
+field_t rfield (
+ CharT const* text, int width = 8, int pad = 0)
+{
+ return field_t ,
+ std::allocator > (std::basic_string , std::allocator > (text),
+ width, pad, true);
+}
+
+template
+field_t rfield (
+ T const& t, int width = 8, int pad = 0)
+{
+ std::string const text (detail::to_string (t));
+ return field (text, width, pad, true);
+}
+/** @} */
+
+}
+
+#endif
diff --git a/src/ripple/peerfinder/ripple_peerfinder.cpp b/src/ripple/peerfinder/ripple_peerfinder.cpp
index e255fd1e9..ccb975d56 100644
--- a/src/ripple/peerfinder/ripple_peerfinder.cpp
+++ b/src/ripple/peerfinder/ripple_peerfinder.cpp
@@ -23,53 +23,75 @@
#include "../../ripple/algorithm/api/CycledSet.h"
#include "../../ripple/algorithm/api/DiscreteClock.h"
+#include "../../ripple/common/Resolver.h"
+#include
+#include
+#include
+#include
#include
+#include
#include "beast/modules/beast_core/system/BeforeBoost.h"
+#include
#include
#include
#include
-#include
-#include
-#include
#include "beast/modules/beast_sqdb/beast_sqdb.h"
#include "beast/modules/beast_asio/beast_asio.h"
+#include "beast/beast/cyclic_iterator.h"
#include "beast/beast/boost/ErrorCode.h"
+#include "impl/iosformat.h" // VFALCO NOTE move to beast
+
namespace ripple {
using namespace beast;
}
+#ifndef NDEBUG
+# define consistency_check(cond) bassert(cond)
+#else
+# define consistency_check(cond)
+#endif
+
#include "impl/PrivateTypes.h"
+
# include "impl/Tuning.h"
# include "impl/Checker.h"
# include "impl/Resolver.h"
#include "impl/CheckerAdapter.h"
-# include "impl/CachedEndpoint.h"
-# include "impl/GiveawaysAtHop.h"
-# include "impl/Giveaways.h"
-#include "impl/PtrCompareFunctor.h"
-#include "impl/Cache.h"
-#include "impl/Slots.h"
-#include "impl/Source.h"
+# include "impl/Sorts.h"
+# include "impl/Giveaways.h"
+# include "impl/Livecache.h"
+# include "impl/Slots.h"
+# include "impl/Source.h"
#include "impl/SourceStrings.h"
-# include "impl/LegacyEndpoint.h"
# include "impl/Store.h"
-# include "impl/LegacyEndpointCache.h"
-# include "impl/PeerInfo.h"
+# include "impl/Bootcache.h"
+# include "impl/Peer.h"
#include "impl/StoreSqdb.h"
+# include "impl/Reporting.h"
+#include "impl/FixedPeer.h"
# include "impl/Logic.h"
#include "impl/LogicType.h"
#include "impl/Checker.cpp"
#include "impl/Config.cpp"
#include "impl/Endpoint.cpp"
-#include "impl/Cache.cpp"
+#include "impl/Livecache.cpp"
#include "impl/Manager.cpp"
#include "impl/Resolver.cpp"
-#include "impl/Slots.cpp"
#include "impl/SourceStrings.cpp"
-#include "impl/Tests.cpp"
+
+//#include "sim/sync_timer.h"
+
+#include "sim/GraphAlgorithms.h"
+#include "sim/WrappedSink.h"
+# include "sim/Predicates.h"
+# include "sim/FunctionQueue.h"
+# include "sim/Message.h"
+# include "sim/NodeSnapshot.h"
+# include "sim/Params.h"
+#include "sim/Tests.cpp"
diff --git a/src/ripple/peerfinder/sim/FunctionQueue.h b/src/ripple/peerfinder/sim/FunctionQueue.h
new file mode 100644
index 000000000..189bfd7de
--- /dev/null
+++ b/src/ripple/peerfinder/sim/FunctionQueue.h
@@ -0,0 +1,84 @@
+//------------------------------------------------------------------------------
+/*
+ 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.
+*/
+//==============================================================================
+
+#ifndef RIPPLE_PEERFINDER_SIM_FUNCTIONQUEUE_H_INCLUDED
+#define RIPPLE_PEERFINDER_SIM_FUNCTIONQUEUE_H_INCLUDED
+
+namespace ripple {
+namespace PeerFinder {
+namespace Sim {
+
+/** Maintains a queue of functors that can be called later. */
+class FunctionQueue
+{
+private:
+ class BasicWork
+ {
+ public:
+ virtual ~BasicWork ()
+ { }
+ virtual void operator() () = 0;
+ };
+
+ template
+ class Work : public BasicWork
+ {
+ public:
+ explicit Work (Function f)
+ : m_f (f)
+ { }
+ void operator() ()
+ { (m_f)(); }
+ private:
+ Function m_f;
+ };
+
+ std::list > m_work;
+
+public:
+ /** Returns `true` if there is no remaining work */
+ bool empty ()
+ { return m_work.empty(); }
+
+ /** Queue a function.
+ Function must be callable with this signature:
+ void (void)
+ */
+ template
+ void post (Function f)
+ { m_work.emplace_back (new Work (f)); }
+
+ /** Run all pending functions.
+ The functions will be invoked in the order they were queued.
+ */
+ void run ()
+ {
+ while (! m_work.empty ())
+ {
+ (*m_work.front())();
+ m_work.pop_front();
+ }
+ }
+};
+
+}
+}
+}
+
+#endif
diff --git a/src/ripple/peerfinder/sim/GraphAlgorithms.h b/src/ripple/peerfinder/sim/GraphAlgorithms.h
new file mode 100644
index 000000000..7dc6ede2e
--- /dev/null
+++ b/src/ripple/peerfinder/sim/GraphAlgorithms.h
@@ -0,0 +1,82 @@
+//------------------------------------------------------------------------------
+/*
+ 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.
+*/
+//==============================================================================
+
+#ifndef RIPPLE_PEERFINDER_SIM_GRAPHALGORITHMS_H_INCLUDED
+#define RIPPLE_PEERFINDER_SIM_GRAPHALGORITHMS_H_INCLUDED
+
+namespace ripple {
+namespace PeerFinder {
+namespace Sim {
+
+template
+struct VertexTraits;
+
+/** Call a function for each vertex in a connected graph.
+ Function will be called with this signature:
+ void (Vertex&, std::size_t diameter);
+*/
+template
+void breadth_first_traverse (Vertex& start, Function f)
+{
+ typedef VertexTraits Traits;
+ typedef typename Traits::Edges Edges;
+ typedef typename Traits::Edge Edge;
+
+ struct Probe
+ {
+ Probe (Vertex* v, int d)
+ : vertex (v), distance (d)
+ { }
+ Vertex* const vertex;
+ int const distance;
+ };
+
+ typedef std::deque Work;
+ typedef std::set Visited;
+ Work work;
+ Visited visited;
+ work.emplace_back (&start, 0);
+ int diameter (0);
+ while (! work.empty ())
+ {
+ Probe const p (work.front());
+ work.pop_front ();
+ if (visited.find (p.vertex) != visited.end ())
+ continue;
+ diameter = std::max (p.distance, diameter);
+ visited.insert (p.vertex);
+ for (typename Edges::iterator iter (
+ Traits::edges (*p.vertex).begin());
+ iter != Traits::edges (*p.vertex).end(); ++iter)
+ {
+ Vertex* v (Traits::vertex (*iter));
+ if (visited.find (v) != visited.end())
+ continue;
+ if (! iter->closed())
+ work.emplace_back (v, p.distance + 1);
+ }
+ f (*p.vertex, diameter);
+ }
+}
+
+}
+}
+}
+
+#endif
diff --git a/src/ripple/peerfinder/impl/LegacyEndpoint.h b/src/ripple/peerfinder/sim/Message.h
similarity index 59%
rename from src/ripple/peerfinder/impl/LegacyEndpoint.h
rename to src/ripple/peerfinder/sim/Message.h
index b749005ec..cc7c2e714 100644
--- a/src/ripple/peerfinder/impl/LegacyEndpoint.h
+++ b/src/ripple/peerfinder/sim/Message.h
@@ -17,42 +17,26 @@
*/
//==============================================================================
-#ifndef RIPPLE_PEERFINDER_LEGACYENDPOINT_H_INCLUDED
-#define RIPPLE_PEERFINDER_LEGACYENDPOINT_H_INCLUDED
+#ifndef RIPPLE_PEERFINDER_SIM_MESSAGE_H_INCLUDED
+#define RIPPLE_PEERFINDER_SIM_MESSAGE_H_INCLUDED
namespace ripple {
namespace PeerFinder {
+namespace Sim {
-struct LegacyEndpoint
+class Message
{
- LegacyEndpoint ()
- : whenInserted (0)
- , lastGet(0)
- ,checked (false)
- , canAccept (false)
+public:
+ explicit Message (Endpoints const& endpoints)
+ : m_payload (endpoints)
{ }
-
- LegacyEndpoint (IPAddress const& address_, DiscreteTime now)
- : address (address_)
- , whenInserted (now)
- , lastGet(0)
- { }
-
- IPAddress address;
-
- // When we inserted the endpoint into the cache
- DiscreteTime mutable whenInserted;
-
- // When we last used the endpoint for outging connection attempts
- DiscreteTime mutable lastGet;
-
- // True if we ever tried to connect
- bool mutable checked;
-
- // The result of the last connect attempt
- bool mutable canAccept;
+ Endpoints const& payload () const
+ { return m_payload; }
+private:
+ Endpoints m_payload;
};
+}
}
}
diff --git a/src/ripple/peerfinder/impl/CachedEndpoint.h b/src/ripple/peerfinder/sim/NodeSnapshot.h
similarity index 65%
rename from src/ripple/peerfinder/impl/CachedEndpoint.h
rename to src/ripple/peerfinder/sim/NodeSnapshot.h
index 6d29ecdd7..6dfc610e8 100644
--- a/src/ripple/peerfinder/impl/CachedEndpoint.h
+++ b/src/ripple/peerfinder/sim/NodeSnapshot.h
@@ -17,30 +17,20 @@
*/
//==============================================================================
-#ifndef RIPPLE_PEERFINDER_CACHEDENDPOINT_H_INCLUDED
-#define RIPPLE_PEERFINDER_CACHEDENDPOINT_H_INCLUDED
+#ifndef RIPPLE_PEERFINDER_SIM_NODESNAPSHOT_H_INCLUDED
+#define RIPPLE_PEERFINDER_SIM_NODESNAPSHOT_H_INCLUDED
namespace ripple {
namespace PeerFinder {
+namespace Sim {
-struct CachedEndpoint : public List::Node
+/** A snapshot of a Node in the network simulator. */
+struct NodeSnapshot
{
- CachedEndpoint (Endpoint const& message_, DiscreteTime now)
- : message (message_)
- , whenExpires (now + cacheSecondsToLive)
- , color (true)
- {
- }
- Endpoint message;
- DiscreteTime whenExpires;
-
- // The color indicates whether this peer was recently sent out or not. It
- // is recently sent out if the color of the peer matches the color assigned
- // in PeerFinder tables.
- bool color;
};
+}
}
}
diff --git a/src/ripple/peerfinder/impl/PtrCompareFunctor.h b/src/ripple/peerfinder/sim/Params.h
similarity index 67%
rename from src/ripple/peerfinder/impl/PtrCompareFunctor.h
rename to src/ripple/peerfinder/sim/Params.h
index 2b9be743f..7224f97f6 100644
--- a/src/ripple/peerfinder/impl/PtrCompareFunctor.h
+++ b/src/ripple/peerfinder/sim/Params.h
@@ -17,29 +17,33 @@
*/
//==============================================================================
-#ifndef RIPPLE_PEERFINDER_PTRCOMPAREFUNC_H_INCLUDED
-#define RIPPLE_PEERFINDER_PTRCOMPAREFUNC_H_INCLUDED
+#ifndef RIPPLE_PEERFINDER_SIM_PARAMS_H_INCLUDED
+#define RIPPLE_PEERFINDER_SIM_PARAMS_H_INCLUDED
namespace ripple {
namespace PeerFinder {
+namespace Sim {
-//------------------------------------------------------------------------------
-
-/** Compare two instances of a class of type T using the comparator specified
- by class C via pointers. This does not compare the pointers themselves but
- what the pointers point to.
-*/
-template < class T, class C = std::less >
-struct PtrCompareFunctor
+/** Defines the parameters for a network simulation. */
+struct Params
{
- bool operator()(T const *lhs, T const *rhs) const
+ Params ()
+ : steps (50)
+ , nodes (10)
+ , maxPeers (20)
+ , outPeers (9.5)
+ , firewalled (0)
{
- C comp;
-
- return comp(*lhs, *rhs);
}
+
+ int steps;
+ int nodes;
+ int maxPeers;
+ double outPeers;
+ double firewalled; // [0, 1)
};
+}
}
}
diff --git a/src/ripple/peerfinder/sim/Predicates.h b/src/ripple/peerfinder/sim/Predicates.h
new file mode 100644
index 000000000..16d618c29
--- /dev/null
+++ b/src/ripple/peerfinder/sim/Predicates.h
@@ -0,0 +1,78 @@
+//------------------------------------------------------------------------------
+/*
+ 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.
+*/
+//==============================================================================
+
+#ifndef RIPPLE_PEERFINDER_SIM_PREDICATES_H_INCLUDED
+#define RIPPLE_PEERFINDER_SIM_PREDICATES_H_INCLUDED
+
+namespace ripple {
+namespace PeerFinder {
+namespace Sim {
+
+/** UnaryPredicate, returns `true` if the 'to' node on a Link matches. */
+/** @{ */
+template
+class is_remote_node_pred
+{
+public:
+ is_remote_node_pred (Node const& node)
+ : node (node)
+ { }
+ template
+ bool operator() (Link const& l) const
+ { return &node == &l.remote_node(); }
+private:
+ Node const& node;
+};
+
+template
+is_remote_node_pred is_remote_node (Node const& node)
+{
+ return is_remote_node_pred (node);
+}
+
+template
+is_remote_node_pred is_remote_node (Node const* node)
+{
+ return is_remote_node_pred (*node);
+}
+/** @} */
+
+//------------------------------------------------------------------------------
+
+/** UnaryPredicate, `true` if the remote address matches. */
+class is_remote_address
+{
+public:
+ explicit is_remote_address (IPAddress const& address)
+ : m_address (address)
+ { }
+ template
+ bool operator() (Link const& link) const
+ {
+ return link.remote_address() == m_address;
+ }
+private:
+ IPAddress const m_address;
+};
+
+}
+}
+}
+
+#endif
diff --git a/src/ripple/peerfinder/sim/Tests.cpp b/src/ripple/peerfinder/sim/Tests.cpp
new file mode 100644
index 000000000..d92b50d61
--- /dev/null
+++ b/src/ripple/peerfinder/sim/Tests.cpp
@@ -0,0 +1,1084 @@
+//------------------------------------------------------------------------------
+/*
+ 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.
+*/
+//==============================================================================
+
+namespace ripple {
+namespace PeerFinder {
+namespace Sim {
+
+class Link;
+class Message;
+class Network;
+class Node;
+
+// Maybe this should be std::set
+typedef std::list Links;
+
+//------------------------------------------------------------------------------
+
+class Network
+{
+public:
+ typedef std::list Peers;
+
+ typedef boost::unordered_map <
+ IPAddress, boost::reference_wrapper > Table;
+
+ explicit Network (Params const& params,
+ Journal journal = Journal());
+
+ ~Network ();
+
+ Params const& params() const { return m_params; }
+ void prepare ();
+ Journal journal () const;
+ int next_node_id ();
+ DiscreteTime now ();
+ Peers& nodes();
+ Peers const& nodes() const;
+ Node* find (IPAddress const& address);
+ void step ();
+
+ template
+ void post (Function f)
+ { m_queue.post (f); }
+
+private:
+ Params m_params;
+ Journal m_journal;
+ int m_next_node_id;
+ ManualClock m_clock_source;
+ Peers m_nodes;
+ Table m_table;
+ FunctionQueue m_queue;
+};
+
+//------------------------------------------------------------------------------
+
+class Node;
+
+// Represents a link between two peers.
+// The link holds the messages the local node will receive.
+//
+class Link
+{
+public:
+ typedef std::vector Messages;
+
+ Link (
+ Node& local_node,
+ IPAddress const& local_address,
+ Node& remote_node,
+ IPAddress const& remote_address,
+ bool inbound)
+ : m_local_node (&local_node)
+ , m_local_address (local_address)
+ , m_remote_node (&remote_node)
+ , m_remote_address (remote_address)
+ , m_inbound (inbound)
+ , m_closed (false)
+ {
+ }
+
+ // Indicates that the remote closed their end
+ bool closed () const { return m_closed; }
+
+ bool inbound () const { return m_inbound; }
+ bool outbound () const { return ! m_inbound; }
+
+ IPAddress const& remote_address() const { return m_remote_address; }
+ IPAddress const& local_address() const { return m_local_address; }
+
+ Node& remote_node () { return *m_remote_node; }
+ Node const& remote_node () const { return *m_remote_node; }
+ Node& local_node () { return *m_local_node; }
+ Node const& local_node () const { return *m_local_node; }
+
+ void post (Message const& m)
+ {
+ m_pending.push_back (m);
+ }
+
+ bool pending () const
+ {
+ return m_pending.size() > 0;
+ }
+
+ void close ()
+ {
+ m_closed = true;
+ }
+
+ void pre_step ()
+ {
+ std::swap (m_current, m_pending);
+ }
+
+ void step ();
+
+private:
+ Node* m_local_node;
+ IPAddress m_local_address;
+ Node* m_remote_node;
+ IPAddress m_remote_address;
+ bool m_inbound;
+ bool m_closed;
+ Messages m_current;
+ Messages m_pending;
+};
+
+//--------------------------------------------------------------------------
+
+class Node
+ : public Callback
+ , public Store
+ , public Checker
+{
+private:
+ typedef std::vector SavedBootstrapAddresses;
+
+public:
+ struct Config
+ {
+ Config ()
+ : canAccept (true)
+ {
+ }
+
+ bool canAccept;
+ IPAddress listening_address;
+ IPAddress well_known_address;
+ PeerFinder::Config config;
+ };
+
+ Links m_links;
+ std::vector m_livecache_history;
+
+ Node (
+ Network& network,
+ Config const& config,
+ DiscreteClock clock,
+ Journal journal)
+ : m_network (network)
+ , m_id (network.next_node_id())
+ , m_config (config)
+ , m_node_id (PeerID::createFromInteger (m_id))
+ , m_sink (prefix(), journal.sink())
+ , m_journal (Journal (m_sink, journal.severity()), Reporting::node)
+ , m_next_port (m_config.listening_address.port() + 1)
+ , m_logic (boost::in_place (
+ clock, boost::ref (*this), boost::ref (*this), boost::ref (*this), m_journal))
+ , m_whenSweep (m_network.now() + Tuning::liveCacheSecondsToLive)
+ {
+ logic().setConfig (m_config.config);
+ logic().load ();
+ }
+
+ ~Node ()
+ {
+ // Have to destroy the logic early because it calls back into us
+ m_logic = boost::none;
+ }
+
+ void dump (Journal::ScopedStream& ss) const
+ {
+ ss << listening_address();
+ logic().dump (ss);
+ }
+
+ Links& links()
+ {
+ return m_links;
+ }
+
+ Links const& links() const
+ {
+ return m_links;
+ }
+
+ int id () const
+ {
+ return m_id;
+ }
+
+ PeerID const& node_id () const
+ {
+ return m_node_id;
+ }
+
+ Logic& logic ()
+ {
+ return m_logic.get();
+ }
+
+ Logic const& logic () const
+ {
+ return m_logic.get();
+ }
+
+ IPAddress const& listening_address () const
+ {
+ return m_config.listening_address;
+ }
+
+ bool canAccept () const
+ {
+ return m_config.canAccept;
+ }
+
+ void receive (Link const& c, Message const& m)
+ {
+ logic().onPeerEndpoints (c.remote_address(), m.payload());
+ }
+
+ void pre_step ()
+ {
+ for (Links::iterator iter (links().begin());
+ iter != links().end();)
+ {
+ Links::iterator cur (iter++);
+ cur->pre_step ();
+ }
+ }
+
+ void step ()
+ {
+ for (Links::iterator iter (links().begin());
+ iter != links().end();)
+ {
+ Links::iterator cur (iter++);
+ //Link& link (*cur);
+ cur->step ();
+#if 0
+ if (iter->closed ())
+ {
+ // Post notification?
+ iter->local_node().logic().onPeerClosed (
+ iter->remote_address());
+ iter = links().erase (iter);
+ }
+ else
+#endif
+ }
+
+ logic().makeOutgoingConnections ();
+ logic().sendEndpoints ();
+
+ if (m_network.now() >= m_whenSweep)
+ {
+ logic().sweepCache();
+ m_whenSweep = m_network.now() + Tuning::liveCacheSecondsToLive;
+ }
+
+ m_livecache_history.emplace_back (
+ logic().state().livecache.histogram());
+
+ logic().periodicActivity();
+ }
+
+ //----------------------------------------------------------------------
+ //
+ // Callback
+ //
+ //----------------------------------------------------------------------
+
+ void sendEndpoints (IPAddress const& remote_address,
+ Endpoints const& endpoints)
+ {
+ m_network.post (std::bind (&Node::doSendEndpoints, this,
+ remote_address, endpoints));
+ }
+
+ void connectPeers (IPAddresses const& addresses)
+ {
+ m_network.post (std::bind (&Node::doConnectPeers, this,
+ addresses));
+ }
+
+ void disconnectPeer (IPAddress const& remote_address, bool graceful)
+ {
+ m_network.post (std::bind (&Node::doDisconnectPeer, this,
+ remote_address, graceful));
+ }
+
+ void activatePeer (IPAddress const& remote_address)
+ {
+ /* no underlying peer to activate */
+ }
+
+ void doSendEndpoints (IPAddress const& remote_address,
+ Endpoints const& endpoints)
+ {
+ Links::iterator const iter1 (std::find_if (
+ links().begin (), links().end(),
+ is_remote_address (remote_address)));
+ if (iter1 != links().end())
+ {
+ // Drop the message if they closed their end
+ if (iter1->closed ())
+ return;
+ Node& remote_node (iter1->remote_node());
+ // Find their link to us
+ Links::iterator const iter2 (std::find_if (
+ remote_node.links().begin(), remote_node.links().end(),
+ is_remote_address (iter1->local_address ())));
+ consistency_check (iter2 != remote_node.links().end());
+
+ //
+ // VFALCO NOTE This looks wrong! Shouldn't it call receive()
+ // on the link and not the Peer?
+ //
+ Message const m (endpoints);
+ iter2->local_node().receive (*iter2, m);
+ //iter2->post (m);
+ }
+ }
+
+ void doCheckAccept (Node& remote_node, IPAddress const& remote_address)
+ {
+ // Find our link to the remote node
+ Links::iterator iter (std::find_if (m_links.begin (),
+ m_links.end(), is_remote_address (remote_address)));
+ // See if the logic closed the connection
+ if (iter == m_links.end())
+ return;
+ // Post notifications
+ m_network.post (std::bind (&Logic::onPeerHandshake,
+ &remote_node.logic(), iter->local_address(), node_id(), false));
+ m_network.post (std::bind (&Logic::onPeerHandshake,
+ &logic(), remote_address, remote_node.node_id(), false));
+ }
+
+ void doConnectPeers (IPAddresses const& addresses)
+ {
+ for (IPAddresses::const_iterator iter (addresses.begin());
+ iter != addresses.end(); ++iter)
+ {
+ IPAddress const& remote_address (*iter);
+ Node* const remote_node (m_network.find (remote_address));
+ // Post notification
+ m_network.post (std::bind (&Logic::onPeerConnect,
+ &logic(), remote_address));
+ // See if the address is connectible
+ if (remote_node == nullptr || ! remote_node->canAccept())
+ {
+ // Firewalled or no one listening
+ // Post notification
+ m_network.post (std::bind (&Logic::onPeerClosed,
+ &logic(), remote_address));
+ continue;
+ }
+ // Connection established, create links
+ IPAddress const local_address (
+ listening_address().at_port (m_next_port++));
+ m_links.emplace_back (*this, local_address,
+ *remote_node, remote_address, false);
+ remote_node->m_links.emplace_back (*remote_node,
+ remote_address, *this, local_address, true);
+ // Post notifications
+ m_network.post (std::bind (&Logic::onPeerConnected,
+ &logic(), local_address, remote_address));
+ m_network.post (std::bind (&Logic::onPeerAccept,
+ &remote_node->logic(), remote_address, local_address));
+ m_network.post (std::bind (&Node::doCheckAccept,
+ remote_node, boost::ref (*this), local_address));
+ }
+ }
+
+ void doClosed (IPAddress const& remote_address, bool graceful)
+ {
+ // Find our link to them
+ Links::iterator const iter (std::find_if (
+ m_links.begin(), m_links.end(),
+ is_remote_address (remote_address)));
+ // Must be connected!
+ check_invariant (iter != m_links.end());
+ // Must be closed!
+ check_invariant (iter->closed());
+ // Remove our link to them
+ m_links.erase (iter);
+ // Notify
+ m_network.post (std::bind (&Logic::onPeerClosed,
+ &logic(), remote_address));
+ }
+
+ void doDisconnectPeer (IPAddress const& remote_address, bool graceful)
+ {
+ // Find our link to them
+ Links::iterator const iter1 (std::find_if (
+ m_links.begin(), m_links.end(),
+ is_remote_address (remote_address)));
+ if (iter1 == m_links.end())
+ return;
+ Node& remote_node (iter1->remote_node());
+ IPAddress const local_address (iter1->local_address());
+ // Find their link to us
+ Links::iterator const iter2 (std::find_if (
+ remote_node.links().begin(), remote_node.links().end(),
+ is_remote_address (local_address)));
+ if (iter2 != remote_node.links().end())
+ {
+ // Notify the remote that we closed
+ check_invariant (! iter2->closed());
+ iter2->close();
+ m_network.post (std::bind (&Node::doClosed,
+ &remote_node, local_address, graceful));
+ }
+ if (! iter1->closed ())
+ {
+ // Remove our link to them
+ m_links.erase (iter1);
+ // Notify
+ m_network.post (std::bind (&Logic::onPeerClosed,
+ &logic(), remote_address));
+ }
+
+ /*
+ if (! graceful || ! iter2->pending ())
+ {
+ remote_node.links().erase (iter2);
+ remote_node.logic().onPeerClosed (local_address);
+ }
+ */
+ }
+
+ //----------------------------------------------------------------------
+ //
+ // Store
+ //
+ //----------------------------------------------------------------------
+
+ std::vector loadBootstrapCache ()
+ {
+ std::vector result;
+ SavedBootstrapAddress item;
+ item.address = m_config.well_known_address;
+ item.cumulativeUptimeSeconds = 0;
+ item.connectionValence = 0;
+ result.push_back (item);
+ return result;
+ }
+
+ void updateBootstrapCache (
+ std::vector const& list)
+ {
+ m_bootstrap_cache = list;
+ }
+
+ //
+ // Checker
+ //
+
+ void cancel ()
+ {
+ }
+
+ void async_test (IPAddress const& address,
+ AbstractHandler handler)
+ {
+ Node* const node (m_network.find (address));
+ Checker::Result result;
+ result.address = address;
+ if (node != nullptr)
+ result.canAccept = node->canAccept();
+ else
+ result.canAccept = false;
+ handler (result);
+ }
+
+private:
+ std::string prefix()
+ {
+ int const width (5);
+ std::stringstream ss;
+ ss << "#" << m_id << " ";
+ std::string s (ss.str());
+ s.insert (0, std::max (
+ 0, width - int(s.size())), ' ');
+ return s;
+ }
+
+ Network& m_network;
+ int const m_id;
+ Config const m_config;
+ PeerID m_node_id;
+ WrappedSink m_sink;
+ Journal m_journal;
+ IP::Port m_next_port;
+ boost::optional m_logic;
+ DiscreteTime m_whenSweep;
+ SavedBootstrapAddresses m_bootstrap_cache;
+};
+
+//------------------------------------------------------------------------------
+
+void Link::step ()
+{
+ for (Messages::const_iterator iter (m_current.begin());
+ iter != m_current.end(); ++iter)
+ m_local_node->receive (*this, *iter);
+ m_current.clear();
+}
+
+//------------------------------------------------------------------------------
+
+static IPAddress next_address (IPAddress address)
+{
+ if (address.is_v4())
+ {
+ do
+ {
+ address = IPAddress (IP::AddressV4 (
+ address.to_v4().value + 1)).at_port (address.port());
+ }
+ while (! is_public (address));
+
+ return address;
+ }
+
+ bassert (address.is_v6());
+ // unimplemented
+ bassertfalse;
+ return IPAddress();
+}
+
+Network::Network (
+
+ Params const& params,
+ Journal journal)
+ : m_params (params)
+ , m_journal (journal)
+ , m_next_node_id (1)
+{
+}
+
+void Network::prepare ()
+{
+ IPAddress const well_known_address (
+ IPAddress::from_string ("1.0.0.1").at_port (1));
+ IPAddress address (well_known_address);
+
+ for (int i = 0; i < params().nodes; ++i )
+ {
+ if (i == 0)
+ {
+ Node::Config config;
+ config.canAccept = true;
+ config.listening_address = address;
+ config.well_known_address = well_known_address;
+ config.config.maxPeers = params().maxPeers;
+ config.config.outPeers = params().outPeers;
+ config.config.wantIncoming = true;
+ config.config.autoConnect = true;
+ config.config.listeningPort = address.port();
+ m_nodes.emplace_back (
+ *this,
+ config,
+ m_clock_source,
+ m_journal);
+ m_table.emplace (address, boost::ref (m_nodes.back()));
+ address = next_address (address);
+ }
+
+ if (i != 0)
+ {
+ Node::Config config;
+ config.canAccept = Random::getSystemRandom().nextInt (100) >=
+ (m_params.firewalled * 100);
+ config.listening_address = address;
+ config.well_known_address = well_known_address;
+ config.config.maxPeers = params().maxPeers;
+ config.config.outPeers = params().outPeers;
+ config.config.wantIncoming = true;
+ config.config.autoConnect = true;
+ config.config.listeningPort = address.port();
+ m_nodes.emplace_back (
+ *this,
+ config,
+ m_clock_source,
+ m_journal);
+ m_table.emplace (address, boost::ref (m_nodes.back()));
+ address = next_address (address);
+ }
+ }
+}
+
+Network::~Network ()
+{
+}
+
+Journal Network::journal () const
+{
+ return m_journal;
+}
+
+int Network::next_node_id ()
+{
+ return m_next_node_id++;
+}
+
+DiscreteTime Network::now ()
+{
+ return m_clock_source();
+}
+
+Network::Peers& Network::nodes()
+{
+ return m_nodes;
+}
+
+#if 0
+Network::Peers const& Network::nodes() const
+{
+ return m_nodes;
+}
+#endif
+
+Node* Network::find (IPAddress const& address)
+{
+ Table::iterator iter (m_table.find (address));
+ if (iter != m_table.end())
+ return iter->second.get_pointer();
+ return nullptr;
+}
+
+void Network::step ()
+{
+ for (Peers::iterator iter (m_nodes.begin());
+ iter != m_nodes.end();)
+ (iter++)->pre_step();
+
+ for (Peers::iterator iter (m_nodes.begin());
+ iter != m_nodes.end();)
+ (iter++)->step();
+
+ m_queue.run ();
+
+ // Advance the manual clock so that
+ // messages are broadcast at every step.
+ //
+ //m_clock_source.now() += Tuning::secondsPerConnect;
+ m_clock_source.now() += 1;
+}
+
+//------------------------------------------------------------------------------
+
+template <>
+struct VertexTraits
+{
+ typedef Links Edges;
+ typedef Link Edge;
+ static Edges& edges (Node& node)
+ { return node.links(); }
+ static Node* vertex (Link& l)
+ { return &l.remote_node(); }
+};
+
+//------------------------------------------------------------------------------
+
+struct PeerStats
+{
+ PeerStats ()
+ : inboundActive (0)
+ , outboundActive (0)
+ , inboundSlotsFree (0)
+ , outboundSlotsFree (0)
+ {
+ }
+
+ template
+ explicit PeerStats (Peer const& peer)
+ {
+ inboundActive = peer.logic().slots().inboundActive();
+ outboundActive = peer.logic().slots().outboundActive();
+ inboundSlotsFree = peer.logic().slots().inboundSlotsFree();
+ outboundSlotsFree = peer.logic().slots().outboundSlotsFree();
+ }
+
+ PeerStats& operator+= (PeerStats const& rhs)
+ {
+ inboundActive += rhs.inboundActive;
+ outboundActive += rhs.outboundActive;
+ inboundSlotsFree += rhs.inboundSlotsFree;
+ outboundSlotsFree += rhs.outboundSlotsFree;
+ return *this;
+ }
+
+ int totalActive () const
+ { return inboundActive + outboundActive; }
+
+ int inboundActive;
+ int outboundActive;
+ int inboundSlotsFree;
+ int outboundSlotsFree;
+};
+
+//------------------------------------------------------------------------------
+
+inline PeerStats operator+ (PeerStats const& lhs, PeerStats& rhs)
+{
+ PeerStats result (lhs);
+ result += rhs;
+ return result;
+}
+
+//------------------------------------------------------------------------------
+
+/** Aggregates statistics on the connected network. */
+class CrawlState
+{
+public:
+ explicit CrawlState (std::size_t step)
+ : m_step (step)
+ , m_size (0)
+ , m_diameter (0)
+ {
+ }
+
+ std::size_t step () const
+ { return m_step; }
+
+ std::size_t size () const
+ { return m_size; }
+
+ int diameter () const
+ { return m_diameter; }
+
+ PeerStats const& stats () const
+ { return m_stats; }
+
+ // network wide average
+ double outPeers () const
+ {
+ if (m_size > 0)
+ return double (m_stats.outboundActive) / m_size;
+ return 0;
+ }
+
+ // Histogram, shows the number of peers that have a specific number of
+ // active connections. The index into the array is the number of connections,
+ // and the value is the number of peers.
+ //
+ std::vector totalActiveHistogram;
+
+ template
+ void operator() (Peer const& peer, int diameter)
+ {
+ ++m_size;
+ PeerStats const stats (peer);
+ int const bucket (stats.totalActive ());
+ if (totalActiveHistogram.size() < bucket + 1)
+ totalActiveHistogram.resize (bucket + 1);
+ ++totalActiveHistogram [bucket];
+ m_stats += stats;
+ m_diameter = diameter;
+ }
+
+private:
+ std::size_t m_step;
+ std::size_t m_size;
+ PeerStats m_stats;
+ int m_diameter;
+};
+
+//------------------------------------------------------------------------------
+
+/** Report the results of a network crawl. */
+template
+void report_crawl (Stream const& stream, Crawl const& c)
+{
+ if (! stream)
+ return;
+ stream
+ << std::setw (6) << c.step()
+ << std::setw (6) << c.size()
+ << std::setw (6) << std::fixed << std::setprecision(2) << c.outPeers()
+ << std::setw (6) << c.diameter()
+ //<< to_string (c.totalActiveHistogram)
+ ;
+}
+
+template
+void report_crawls (Stream const& stream, CrawlSequence const& c)
+{
+ if (! stream)
+ return;
+ stream
+ << "Crawl Report"
+ << std::endl
+ << std::setw (6) << "Step"
+ << std::setw (6) << "Size"
+ << std::setw (6) << "Out"
+ << std::setw (6) << "Hops"
+ //<< std::setw (6) << "Count"
+ ;
+ for (typename CrawlSequence::const_iterator iter (c.begin());
+ iter != c.end(); ++iter)
+ report_crawl (stream, *iter);
+ stream << std::endl;
+}
+
+/** Report a table with aggregate information on each node. */
+template