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 +void report_nodes (NodeSequence const& nodes, Journal::Stream const& stream) +{ + stream << + divider() << std::endl << + "Nodes Report" << std::endl << + rfield ("ID") << + rfield ("Total") << + rfield ("In") << + rfield ("Out") << + rfield ("Tries") << + rfield ("Live") << + rfield ("Boot") + ; + + for (typename NodeSequence::const_iterator iter (nodes.begin()); + iter != nodes.end(); ++iter) + { + typename NodeSequence::value_type const& node (*iter); + Logic const& logic (node.logic()); + Logic::State const& state (logic.state()); + stream << + rfield (node.id ()) << + rfield (state.slots.totalActive ()) << + rfield (state.slots.inboundActive ()) << + rfield (state.slots.outboundActive ()) << + rfield (state.slots.connectCount ()) << + rfield (state.livecache.size ()) << + rfield (state.bootcache.size ()) + ; + } +} + +//------------------------------------------------------------------------------ + +/** Convert a sequence into a formatted delimited string. + The range is [first, last) +*/ +/** @{ */ +template +std::basic_string + sequence_to_string (InputIterator first, InputIterator last, + std::basic_string const& sep = ",", int width = -1) +{ + std::basic_stringstream ss; + while (first != last) + { + InputIterator const iter (first++); + if (width > 0) + ss << std::setw (width) << *iter; + else + ss << *iter; + if (first != last) + ss << sep; + } + return ss.str(); +} + +template +std::string sequence_to_string (InputIterator first, InputIterator last, + char const* sep, int width = -1) +{ + return sequence_to_string (first, last, std::string (sep), width); +} +/** @} */ + +/** Report the time-evolution of a specified node. */ +template +void report_node_timeline (Node const& node, Stream const& stream) +{ + typename Livecache::Histogram::size_type const histw ( + 3 * Livecache::Histogram::size() - 1); + // Title + stream << + divider () << std::endl << + "Node #" << node.id() << " History" << std::endl << + divider (); + // Legend + stream << + fpad (4) << fpad (2) << + fpad (2) << field ("Livecache entries by hops", histw) << fpad (2) + ; + { + Journal::ScopedStream ss (stream); + ss << + rfield ("Step",4) << fpad (2); + ss << "[ "; + for (typename Livecache::Histogram::size_type i (0); + i < Livecache::Histogram::size(); ++i) + { + ss << rfield (i,2); + if (i != Livecache::Histogram::size() - 1) + ss << fpad (1); + } + ss << " ]"; + } + + // Entries + typedef std::vector History; + History const& h (node.m_livecache_history); + std::size_t step (0); + for (typename History::const_iterator iter (h.begin()); + iter != h.end(); ++iter) + { + ++step; + Livecache::Histogram const& t (*iter); + stream << + rfield (step,4) << fpad (2) << + fpad (2) << + field (sequence_to_string (t.begin (), t.end(), " ", 2), histw) << + fpad (2) + ; + } +} + +//------------------------------------------------------------------------------ + +class PeerFinderTests : public UnitTest +{ +public: + void runTest () + { + //Debug::setAlwaysCheckHeap (true); + + beginTestCase ("network"); + + Params p; + p.steps = 200; + p.nodes = 1000; + p.outPeers = 9.5; + p.maxPeers = 200; + p.firewalled = 0.80; + + Network n (p, Journal (journal(), Reporting::network)); + + // Report network parameters + if (Reporting::params) + { + Journal::Stream const stream (journal().info); + + if (stream) + { + stream + << "Network parameters" + << std::endl + << std::setw (6) << "Steps" + << std::setw (6) << "Nodes" + << std::setw (6) << "Out" + << std::setw (6) << "Max" + << std::setw (6) << "Fire" + ; + + stream + << std::setw (6) << p.steps + << std::setw (6) << p.nodes + << std::setw (6) << std::fixed << std::setprecision (1) << p.outPeers + << std::setw (6) << p.maxPeers + << std::setw (6) << int (p.firewalled * 100) + ; + + stream << std::endl; + } + } + + // + // Run the simulation + // + n.prepare (); + { + // Note that this stream is only for the crawl, + // The network has its own journal. + Journal::Stream const stream ( + journal().info, Reporting::crawl); + + std::vector crawls; + if (Reporting::crawl) + crawls.reserve (p.steps); + + // Iterate the network + for (std::size_t step (0); step < p.steps; ++step) + { + if (Reporting::crawl) + { + crawls.emplace_back (step); + CrawlState& c (crawls.back ()); + breadth_first_traverse ( + n.nodes().front(), c); + } + n.journal().info << + divider () << std::endl << + "Time " << n.now () << std::endl << + divider () + ; + + n.step(); + n.journal().info << std::endl; + } + n.journal().info << std::endl; + + // Report the crawls + report_crawls (stream, crawls); + } + + // Run detailed nodes dump report + if (Reporting::dump_nodes) + { + Journal::Stream const stream (journal().info); + for (Network::Peers::const_iterator iter (n.nodes().begin()); + iter != n.nodes().end(); ++iter) + { + Journal::ScopedStream ss (stream); + Node const& node (*iter); + ss << std::endl << + "--------------" << std::endl << + "#" << node.id() << + " at " << node.listening_address (); + node.logic().dump (ss); + } + } + + // Run aggregate nodes report + if (Reporting::nodes) + { + Journal::Stream const stream (journal().info); + report_nodes (n.nodes (), stream); + stream << std::endl; + } + + // Run Node report + { + Journal::Stream const stream (journal().info); + report_node_timeline (n.nodes().front(), stream); + stream << std::endl; + } + + pass(); + } + + PeerFinderTests () : UnitTest ("PeerFinder", "ripple", runManual) + { + } +}; + +static PeerFinderTests peerFinderTests; + +} +} +} diff --git a/src/ripple/peerfinder/sim/WrappedSink.h b/src/ripple/peerfinder/sim/WrappedSink.h new file mode 100644 index 000000000..d32fa993f --- /dev/null +++ b/src/ripple/peerfinder/sim/WrappedSink.h @@ -0,0 +1,77 @@ +//------------------------------------------------------------------------------ +/* + 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_WRAPPEDSINK_H_INCLUDED +#define RIPPLE_PEERFINDER_SIM_WRAPPEDSINK_H_INCLUDED + +namespace ripple { +namespace PeerFinder { + +/** Wraps a Journal::Sink to prefix it's output. */ +class WrappedSink : public Journal::Sink +{ +public: + WrappedSink (std::string const& prefix, Journal::Sink& sink) + : m_prefix (prefix) + , m_sink (sink) + { + } + + bool active (Journal::Severity level) const + { return m_sink.active (level); } + + bool console () const + { return m_sink.console (); } + + void console (bool output) + { m_sink.console (output); } + + Journal::Severity severity() const + { return m_sink.severity(); } + + void severity (Journal::Severity level) + { m_sink.severity (level); } + + void write (Journal::Severity level, std::string const& text) + { + std::string s (m_prefix); + switch (level) + { + case Journal::kTrace: s += "Trace: "; break; + case Journal::kDebug: s += "Debug: "; break; + case Journal::kInfo: s += "Info : "; break; + case Journal::kWarning: s += "Warn : "; break; + case Journal::kError: s += "Error: "; break; + default: + case Journal::kFatal: s += "Fatal: "; break; + }; + + s+= text; + m_sink.write (level, s); + } + +private: + std::string const m_prefix; + Journal::Sink& m_sink; +}; + +} +} + +#endif diff --git a/src/ripple/peerfinder/sim/sync_timer.h b/src/ripple/peerfinder/sim/sync_timer.h new file mode 100644 index 000000000..2d9bc6772 --- /dev/null +++ b/src/ripple/peerfinder/sim/sync_timer.h @@ -0,0 +1,39 @@ +//------------------------------------------------------------------------------ +/* + 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 BEASTSYNC_SYNC_DEADLINE_TIMER_H_INCLUDED +#define BEASTSYNC_SYNC_DEADLINE_TIMER_H_INCLUDED + +//#include + +namespace beast { + +/** A synchronous timer. */ +template +class sync_deadline_timer +public: + typedef std::size_t duration_type; + typedef beast::implementation_type who; + typedef beast::service_type serviceType < + + implemetation (); + +} + +#endif diff --git a/src/ripple/resource/impl/Key.h b/src/ripple/resource/impl/Key.h index 889909b78..f95c4d4c6 100644 --- a/src/ripple/resource/impl/Key.h +++ b/src/ripple/resource/impl/Key.h @@ -51,7 +51,7 @@ struct Key } private: - IPAddress::hasher m_addr_hash; + std::hash m_addr_hash; boost::hash m_name_hash; }; diff --git a/src/ripple/resource/impl/Logic.h b/src/ripple/resource/impl/Logic.h index 6783eebb3..90ae779c4 100644 --- a/src/ripple/resource/impl/Logic.h +++ b/src/ripple/resource/impl/Logic.h @@ -95,11 +95,11 @@ public: Consumer newInboundEndpoint (IPAddress const& address) { if (isWhitelisted (address)) - return newAdminEndpoint (address.to_string()); + return newAdminEndpoint (to_string (address)); Key key; key.kind = kindInbound; - key.address = address.withPort (0); + key.address = address.at_port (0); Entry* entry (nullptr); @@ -128,7 +128,7 @@ public: Consumer newOutboundEndpoint (IPAddress const& address) { if (isWhitelisted (address)) - return newAdminEndpoint (address.to_string()); + return newAdminEndpoint (to_string (address)); Key key; key.kind = kindOutbound; @@ -362,7 +362,7 @@ public: bool isWhitelisted (IPAddress const& address) { - if (! address.isPublic()) + if (! is_public (address)) return true; return false; diff --git a/src/ripple/resource/impl/Tests.cpp b/src/ripple/resource/impl/Tests.cpp index 0f7c18ae1..5c5d5ce41 100644 --- a/src/ripple/resource/impl/Tests.cpp +++ b/src/ripple/resource/impl/Tests.cpp @@ -60,8 +60,8 @@ public: { Gossip::Item item; item.balance = 100 + random().nextInt (500); - item.address = IPAddress (IPAddress::V4 ( - 207, 127, 82, v + i)); + item.address = IPAddress ( + IP::AddressV4 (207, 127, 82, v + i)); gossip.items.push_back (item); } } @@ -170,7 +170,8 @@ public: Gossip g; Gossip::Item item; item.balance = 100; - item.address = IPAddress (IPAddress::V4 (207, 127, 82, 1)); + item.address = IPAddress ( + IP::AddressV4 (207, 127, 82, 1)); g.items.push_back (item); logic.importConsumers ("g", g); diff --git a/src/ripple/testoverlay/api/NetworkType.h b/src/ripple/testoverlay/api/NetworkType.h index 8d712fa58..f2571ff5e 100644 --- a/src/ripple/testoverlay/api/NetworkType.h +++ b/src/ripple/testoverlay/api/NetworkType.h @@ -137,6 +137,8 @@ private: Peers m_peers; }; +//------------------------------------------------------------------------------ + } #endif diff --git a/src/ripple/testoverlay/impl/TestOverlay.cpp b/src/ripple/testoverlay/impl/TestOverlay.cpp index fa533e7e3..fe53b35de 100644 --- a/src/ripple/testoverlay/impl/TestOverlay.cpp +++ b/src/ripple/testoverlay/impl/TestOverlay.cpp @@ -17,8 +17,7 @@ */ //============================================================================== -namespace TestOverlay -{ +namespace TestOverlay { class Tests : public UnitTest { @@ -143,4 +142,412 @@ public: static Tests tests; +//------------------------------------------------------------------------------ +// +// +// +//------------------------------------------------------------------------------ +/* + +Concepts: + + Link + Logic + Message + Network + Peer + +*/ + +/** UnaryPredicate, returns `true` if the 'to' peer on a Link matches. */ +template +class is_to_pred +{ +public: + typedef PeerType Peer; + + is_to_pred (Peer const& to) + : m_to (to) + { + } + + template + bool operator() (Link const& l) const + { + return &m_to == &l.to(); + } + +private: + Peer const& m_to; +}; + +/** Returns a new is_to_pred for the specified peer. */ +template +is_to_pred is_to (PeerType const& to) +{ + return is_to_pred (to); +} + +/** Returns `true` if the peers are connected. */ +template +bool is_connected (PeerType const& from, PeerType const& to) +{ + return std::find_if (from.links().begin(), from.links().end(), + is_to (to)) != from.links().end(); +} + +//------------------------------------------------------------------------------ + +class BasicMessage +{ +public: + typedef std::size_t UniqueID; + + BasicMessage () + : m_id (0) + { + } + + explicit BasicMessage (UniqueID id) + : m_id (id) + { + } + +private: + UniqueID m_id; +}; + +//------------------------------------------------------------------------------ + +template +class BasicLink +{ +public: + typedef PeerType Peer; + typedef MessageType Message; + typedef std::vector Messages; + + BasicLink (Peer& to, Peer& from, bool inbound) + : m_to (&to) + , m_from (&from) + , m_inbound (inbound) + { + } + + Peer const& to () const + { + return *m_to; + } + + Peer& to () + { + return *m_to; + } + + Peer& from () + { + return *m_from; + } + + Peer const& from () const + { + return *m_from; + } + + bool inbound() const + { + return m_inbound; + } + + bool outbound() const + { + return ! m_inbound; + } + + void send (MessageType const& m) + { + m_later.push_back (m); + } + + void pre_step () + { + std::swap (m_now, m_later); + } + + void step () + { + for (typename Messages::const_iterator iter (m_now.begin()); + iter != m_now.end(); ++iter) + m_to->receive (*iter); + m_now.clear(); + } + +private: + Peer* m_to; + Peer* m_from; + bool m_inbound; + Messages m_now; + Messages m_later; +}; + +//------------------------------------------------------------------------------ + +/** Models a peer. */ +template +class BasicPeer +{ +public: + typedef PeerType Peer; + typedef MessageType Message; + typedef BasicLink Link; + typedef std::vector Links; + + ~BasicPeer () + { + for (typename Links::iterator iter (links().begin()); + iter != links().end(); ++iter) + iter->to().links().erase (std::find_if ( + iter->to().links().begin(), iter->to().links().end(), + is_to (peer()))); + } + + bool operator== (Peer const& rhs) const + { + return this == &rhs; + } + + Peer& peer() + { + return *static_cast(this); + } + + Peer const& peer() const + { + return *static_cast(this); + } + + Links& links() + { + return m_links; + } + + Links const& links() const + { + return m_links; + } + + void connect (Peer& to) + { + m_links.emplace_back (to, peer(), false); + to.m_links.emplace_back (peer(), to, true); + } + + void disconnect (Peer& to) + { + typename Links::iterator const iter (std::find_if ( + links().begin(), links().end(), is_to (to))); + iter->to().links().erase (std::find_if ( + iter->to().links().begin(), iter->to().links.end(), + is_to (peer()))); + } + + void send (Message const& m) + { + for (typename Links::iterator iter (links().begin()); + iter != links().end(); ++iter) + iter->send (m); + } + + void pre_step () + { + for (typename Links::iterator iter (links().begin()); + iter != links().end(); ++iter) + iter->pre_step (); + } + + void step () + { + for (typename Links::iterator iter (links().begin()); + iter != links().end(); ++iter) + iter->step (); + } + +private: + Links m_links; +}; + +//------------------------------------------------------------------------------ + +template +void iterate (PeerContainer& s) +{ + for (typename PeerContainer::iterator iter (s.begin()); iter != s.end();) + { + typename PeerContainer::iterator const cur (iter++); + cur->pre_step(); + } + + for (typename PeerContainer::iterator iter (s.begin()); iter != s.end();) + { + typename PeerContainer::iterator const cur (iter++); + cur->step(); + } +} + +//------------------------------------------------------------------------------ + +template < + typename LogicType +> +class BasicNetwork +{ +public: + typedef LogicType Logic; + + class Peer + { + public: + explicit Peer (int) + { + } + }; + + typedef boost::unordered_map PeerMap; + + BasicNetwork() + { + } + + typename PeerMap::iterator emplace () + { + m_map.emplace (1); + } + +private: + PeerMap m_map; +}; + +//------------------------------------------------------------------------------ + +class Tests2 : public UnitTest +{ +public: + class Message : public BasicMessage + { + public: + }; + + class Peer : public BasicPeer + { + public: + bool m_received; + int* m_count; + + Peer (int* count) + : m_received (false) + , m_count (count) + { + } + + ~Peer () + { + } + + void receive (Message const& m) + { + if (m_received) + return; + ++*m_count; + m_received = true; + send (m); + } + }; + + struct PeerLogic + { + }; + + //-------------------------------------------------------------------------- + + template + void make_peers (PeerContainer& s, + typename PeerContainer::size_type n, + int* count) + { + while (n--) + s.emplace_back(count); + } + + template + void make_connections ( + PeerContainer& s, + typename PeerContainer::size_type outDegree, + RandomType r = RandomType()) + { + // Turn the PeerContainer into a PeerSequence + typedef typename PeerContainer::pointer pointer; + typedef std::vector PeerSequence; + PeerSequence v; + v.reserve (s.size()); + for (typename PeerContainer::iterator iter (s.begin()); + iter != s.end(); ++iter) + v.push_back (&(*iter)); + + // Make random connections + typedef typename PeerSequence::size_type size_type; + size_type const n (v.size()); + for (typename PeerSequence::iterator iter (v.begin()); + iter != v.end(); ++iter) + { + for (size_type i (0); i < outDegree; ++i) + { + for(;;) + { + size_type const j (r.nextInt (n)); + + // prohibit self connection + if (*iter == v[j]) + continue; + + // prohibit duplicate connection + if (is_connected (**iter, *v[j])) + continue; + + (*iter)->connect (*v [j]); + break; + } + } + } + } + + void test1 () + { + beginTestCase ("network"); + + int count (0); + std::vector peers; + make_peers (peers, 10000, &count); + make_connections (peers, 3, random()); + peers[0].send (Message ()); + for (int i = 0; i < 10; ++i) + { + iterate (peers); + journal().info << "count = " << count; + } + pass(); + } + + void runTest () + { + test1 (); + } + + Tests2 () + : UnitTest ("TestOverlay2", "ripple", runManual) + { + } +}; + +static Tests2 tests2; + } diff --git a/src/ripple/testoverlay/ripple_testoverlay.cpp b/src/ripple/testoverlay/ripple_testoverlay.cpp index d69aa1c79..3a86f5d36 100644 --- a/src/ripple/testoverlay/ripple_testoverlay.cpp +++ b/src/ripple/testoverlay/ripple_testoverlay.cpp @@ -21,6 +21,11 @@ #include "ripple_testoverlay.h" +#include +#include +#include +#include + namespace ripple { #include "impl/TestOverlay.cpp" diff --git a/src/ripple/types/api/Base58.h b/src/ripple/types/api/Base58.h index 5fafe245b..fc4ca6454 100644 --- a/src/ripple/types/api/Base58.h +++ b/src/ripple/types/api/Base58.h @@ -75,7 +75,6 @@ public: static Alphabet const& getBitcoinAlphabet (); static Alphabet const& getRippleAlphabet (); - static Alphabet const& getTestnetAlphabet (); static std::string raw_encode ( unsigned char const* begin, unsigned char const* end, diff --git a/src/ripple/types/impl/Base58.cpp b/src/ripple/types/impl/Base58.cpp index f684ed2ba..5bcb1f0e2 100644 --- a/src/ripple/types/impl/Base58.cpp +++ b/src/ripple/types/impl/Base58.cpp @@ -50,14 +50,6 @@ Base58::Alphabet const& Base58::getRippleAlphabet () return alphabet; } -Base58::Alphabet const& Base58::getTestnetAlphabet () -{ - static Alphabet alphabet ( - "RPShNAF39wBUDnEGHJKLM4pQrsT7VWXYZ2bcdeCg65jkm8ofqi1tuvaxyz" - ); - return alphabet; -} - std::string Base58::raw_encode ( unsigned char const* begin, unsigned char const* end, Alphabet const& alphabet, bool withCheck) diff --git a/src/ripple_app/TODO.md b/src/ripple_app/TODO.md index c933e44f9..4c5d46516 100644 --- a/src/ripple_app/TODO.md +++ b/src/ripple_app/TODO.md @@ -1,16 +1,28 @@ # ripple_app -## Peer.cpp +## LedgerMaster.cpp -- Move magic number constants into Tuning.h / Peer constants enum +- Change getLedgerByHash() to not use "all bits zero" to mean + "return the current ledger" -- Wrap lines to 80 columns +- replace uint32 with LedgerIndex and choose appropriate names -- Use Journal +## IHashRouter.h -- Pass Journal in the ctor argument list +- Rename to HashRouter.h -## Peers.cpp +## HashRouter.cpp + +- Rename HashRouter to HashRouterImp + +- Inline functions + +- Comment appropriately + +- Determine the semantics of the uint256, replace it with an appropriate + typedef like RipplePublicKey or whatever is appropriate. + +- Provide good symbolic names for the config tunables. ## Beast diff --git a/src/ripple_app/consensus/LedgerConsensus.cpp b/src/ripple_app/consensus/LedgerConsensus.cpp index 0cebe4ed4..5bf093e24 100644 --- a/src/ripple_app/consensus/LedgerConsensus.cpp +++ b/src/ripple_app/consensus/LedgerConsensus.cpp @@ -289,8 +289,7 @@ public: return SHAMap::pointer (); } - /** We have a complete transaction set, - typically one acuired from the network + /** We have a complete transaction set, typically acquired from the network */ void mapComplete (uint256 const& hash, SHAMap::ref map, bool acquired) { @@ -966,11 +965,11 @@ private: Blob validation = v->getSigned (); protocol::TMValidation val; val.set_validation (&validation[0], validation.size ()); - int j = getApp().getPeers ().relayMessage (NULL, - boost::make_shared - (val, protocol::mtVALIDATION)); + getApp ().getPeers ().foreach (send_always ( + boost::make_shared ( + val, protocol::mtVALIDATION))); WriteLog (lsINFO, LedgerConsensus) - << "CNF Val " << newLCLHash << " to " << j << " peers"; + << "CNF Val " << newLCLHash; } else WriteLog (lsINFO, LedgerConsensus) @@ -1087,27 +1086,31 @@ private: } } - std::vector peerList - = getApp().getPeers ().getPeerVector (); - BOOST_FOREACH (Peer::ref peer, peerList) + struct build_acquire_list { - if (peer->hasTxSet (acquire->getHash ())) - acquire->peerHas (peer); - } + typedef void return_type; + + TransactionAcquire::pointer const& acquire; + + build_acquire_list (TransactionAcquire::pointer const& acq) + : acquire(acq) + { } + + return_type operator() (Peer::ref peer) const + { + if (peer->hasTxSet (acquire->getHash ())) + acquire->peerHas (peer); + } + }; + + getApp().getPeers ().foreach (build_acquire_list (acquire)); acquire->setTimer (); } - - - // Where is this function? SHAMap::pointer find (uint256 const & hash); - - - - /** Compare two proposed transaction sets and create disputed transctions structures for any mismatches */ @@ -1194,9 +1197,9 @@ private: msg.set_rawtransaction (& (tx.front ()), tx.size ()); msg.set_status (protocol::tsNEW); msg.set_receivetimestamp (getApp().getOPs ().getNetworkTimeNC ()); - PackedMessage::pointer packet = boost::make_shared - (msg, protocol::mtTRANSACTION); - getApp().getPeers ().relayMessage (NULL, packet); + getApp ().getPeers ().foreach (send_always ( + boost::make_shared ( + msg, protocol::mtTRANSACTION))); } } @@ -1233,9 +1236,9 @@ private: Blob sig = mOurPosition->sign (); prop.set_nodepubkey (&pubKey[0], pubKey.size ()); prop.set_signature (&sig[0], sig.size ()); - getApp().getPeers ().relayMessage (NULL, - boost::make_shared - (prop, protocol::mtPROPOSE_LEDGER)); + getApp ().getPeers ().foreach (send_always ( + boost::make_shared ( + prop, protocol::mtPROPOSE_LEDGER))); } /** Let peers know that we a particular transactions set so they @@ -1246,9 +1249,9 @@ private: protocol::TMHaveTransactionSet msg; msg.set_hash (hash.begin (), 256 / 8); msg.set_status (direct ? protocol::tsHAVE : protocol::tsCAN_GET); - PackedMessage::pointer packet - = boost::make_shared (msg, protocol::mtHAVE_SET); - getApp().getPeers ().relayMessage (NULL, packet); + getApp ().getPeers ().foreach (send_always ( + boost::make_shared ( + msg, protocol::mtHAVE_SET))); } /** Apply a set of transactions to a ledger @@ -1444,10 +1447,9 @@ private: } s.set_firstseq (uMin); s.set_lastseq (uMax); - - PackedMessage::pointer packet - = boost::make_shared (s, protocol::mtSTATUS_CHANGE); - getApp().getPeers ().relayMessage (NULL, packet); + getApp ().getPeers ().foreach (send_always ( + boost::make_shared ( + s, protocol::mtSTATUS_CHANGE))); WriteLog (lsTRACE, LedgerConsensus) << "send status change to peer"; } @@ -1728,7 +1730,7 @@ private: #if 0 // FIXME: We can't do delayed relay because we don't have the signature - std::set peers + std::set peers if (relay && getApp().getHashRouter ().swapSet (proposal.getSuppress (), set, SF_RELAYED)) { @@ -1740,8 +1742,10 @@ private: closetime nodepubkey signature - PackedMessage::pointer message = boost::make_shared (set, protocol::mtPROPOSE_LEDGER); - getApp().getPeers ().relayMessageBut (peers, message); + getApp ().getPeers ().foreach (send_if_not ( + boost::make_shared ( + set, protocol::mtPROPOSE_LEDGER), + peer_in_set(peers))); } #endif @@ -1804,8 +1808,9 @@ private: protocol::TMValidation val; val.set_validation (&validation[0], validation.size ()); #if 0 - getApp().getPeers ().relayMessage (NULL, - boost::make_shared (val, protocol::mtVALIDATION)); + getApp ().getPeers ().visit (RelayMessage ( + boost::make_shared ( + val, protocol::mtVALIDATION))); #endif getApp().getOPs ().setLastValidation (v); WriteLog (lsWARNING, LedgerConsensus) << "Sending partial validation"; diff --git a/src/ripple_app/data/DBInit.cpp b/src/ripple_app/data/DBInit.cpp index 92049fc6e..608d0361b 100644 --- a/src/ripple_app/data/DBInit.cpp +++ b/src/ripple_app/data/DBInit.cpp @@ -176,7 +176,8 @@ const char* WalletDBInit[] = "CREATE INDEX SeedDomainNext ON SeedDomains (Next);", // Table of PublicKeys user has asked to trust. - // Fetches are made to the CAS. This gets the ripple.txt so even validators without a web server can publish a ripple.txt. + // Fetches are made to the CAS. This gets the ripple.txt so even validators + // without a web server can publish a ripple.txt. // Source: // 'M' = Manually added. : 1500 // 'V' = validators.txt : 1000 @@ -205,7 +206,8 @@ const char* WalletDBInit[] = // Allow us to easily find the next SeedNode to fetch. "CREATE INDEX SeedNodeNext ON SeedNodes (Next);", - // Nodes we trust to not grossly collude against us. Derived from SeedDomains, SeedNodes, and ValidatorReferrals. + // Nodes we trust to not grossly collude against us. Derived from + // SeedDomains, SeedNodes, and ValidatorReferrals. // // Score: // Computed trust score. Higher is better. @@ -219,7 +221,7 @@ const char* WalletDBInit[] = );", // List of referrals. - // - There may be multiple sources for a Validator. The last source is used. + // - There may be multiple sources for a Validator. The last source is used. // Validator: // Public key of referrer. // Entry: @@ -254,42 +256,18 @@ const char* WalletDBInit[] = PRIMARY KEY (Validator,Entry) \ );", - // Table of IPs to contact the network. - // IP: - // IP address to contact. - // Port: - // Port to contact. - // -1 = Default - // Score: - // Computed trust score. Higher is better. - // Source: - // 'C' = Configuration file - // 'V' = Validation file - // 'M' = Manually added. - // 'I' = Inbound connection. - // 'T' = Told by other peer - // 'O' = Other. - // ScanNext: - // When to next scan. Null=not scanning. - // ScanInterval: - // Delay between scans. - "CREATE TABLE PeerIps ( \ - IpPort TEXT NOT NULL PRIMARY KEY, \ - Score INTEGER NOT NULL DEFAULT 0, \ - Source CHARACTER(1) NOT NULL, \ - ScanNext DATETIME DEFAULT 0, \ - ScanInterval INTEGER NOT NULL DEFAULT 0 \ - );", - - "CREATE INDEX PeerScanIndex ON \ - PeerIps(ScanNext);", - "CREATE TABLE Features ( \ Hash CHARACTER(64) PRIMARY KEY, \ FirstMajority BIGINT UNSIGNED, \ LastMajority BIGINT UNSIGNED \ );", + // This removes an old table and its index which are now redundant. This + // code will eventually go away. It's only here to clean up the wallet.db + "DROP TABLE IF EXISTS PeerIps;", + "DROP INDEX IF EXISTS;", + + "END TRANSACTION;" }; diff --git a/src/ripple_app/ledger/InboundLedger.cpp b/src/ripple_app/ledger/InboundLedger.cpp index 7020f4cf4..a1a5214b5 100644 --- a/src/ripple_app/ledger/InboundLedger.cpp +++ b/src/ripple_app/ledger/InboundLedger.cpp @@ -245,9 +245,11 @@ void InboundLedger::onTimer (bool wasProgress, ScopedLockType&) mAggressive = true; mByHash = true; - int pc = getPeerCount (); - if (m_journal.debug) m_journal.debug << - "No progress(" << pc << ") for ledger " << mHash; + + std::size_t pc = getPeerCount (); + WriteLog (lsDEBUG, InboundLedger) << + "No progress(" << pc << + ") for ledger " << mHash; trigger (Peer::pointer ()); if (pc < 4) @@ -258,12 +260,19 @@ void InboundLedger::onTimer (bool wasProgress, ScopedLockType&) /** Add more peers to the set, if possible */ void InboundLedger::addPeers () { - std::vector peerList = getApp().getPeers ().getPeerVector (); + Peers::PeerSequence peerList = getApp().getPeers ().getActivePeers (); int vSize = peerList.size (); if (vSize == 0) + { + WriteLog (lsERROR, InboundLedger) << + "No peers to add for ledger acquisition"; return; + } + + // FIXME-NIKB why are we doing this convoluted thing here instead of simply + // shuffling this vector and then pulling however many entries we need? // We traverse the peer list in random order so as not to favor // any particular peer @@ -403,8 +412,7 @@ void InboundLedger::trigger (Peer::ref peer) { if (peer) m_journal.trace << - "Trigger acquiring ledger " << mHash << " from " << - peer->getIP (); + "Trigger acquiring ledger " << mHash << " from " << peer; else m_journal.trace << "Trigger acquiring ledger " << mHash; @@ -471,11 +479,11 @@ void InboundLedger::trigger (Peer::ref peer) { ScopedLockType sl (mLock, __FILE__, __LINE__); - for (boost::unordered_map::iterator it = mPeers.begin (), end = mPeers.end (); + for (PeerSetMap::iterator it = mPeers.begin (), end = mPeers.end (); it != end; ++it) { Peer::pointer iPeer ( - getApp().getPeers ().getPeerById (it->first)); + getApp().getPeers ().findPeerByShortID (it->first)); if (iPeer) { diff --git a/src/ripple_app/ledger/LedgerMaster.cpp b/src/ripple_app/ledger/LedgerMaster.cpp index f2744eddb..00b6b949c 100644 --- a/src/ripple_app/ledger/LedgerMaster.cpp +++ b/src/ripple_app/ledger/LedgerMaster.cpp @@ -488,7 +488,7 @@ public: Peer::pointer target; int count = 0; - std::vector peerList = getApp().getPeers ().getPeerVector (); + Peers::PeerSequence peerList = getApp().getPeers ().getActivePeers (); BOOST_FOREACH (const Peer::pointer & peer, peerList) { if (peer->hasRange (nextLedger->getLedgerSeq() - 1, nextLedger->getLedgerSeq())) diff --git a/src/ripple_app/main/Application.cpp b/src/ripple_app/main/Application.cpp index 009bdfa66..0b8213602 100644 --- a/src/ripple_app/main/Application.cpp +++ b/src/ripple_app/main/Application.cpp @@ -152,7 +152,6 @@ public: std::unique_ptr m_peerSSLContext; std::unique_ptr m_wsSSLContext; std::unique_ptr m_peers; - OwnedArray m_peerDoors; std::unique_ptr m_rpcDoor; std::unique_ptr m_wsPublicDoor; std::unique_ptr m_wsPrivateDoor; @@ -160,6 +159,8 @@ public: WaitableEvent m_stop; + std::unique_ptr m_resolver; + io_latency_probe m_probe; //-------------------------------------------------------------------------- @@ -303,6 +304,8 @@ public: , mShutdown (false) + , m_resolver (ResolverAsio::New (m_mainIoPool.getService (), Journal ())) + , m_probe (std::chrono::milliseconds (100), m_mainIoPool.getService()) { add (m_resourceManager.get ()); @@ -673,46 +676,13 @@ public: } // VFALCO NOTE Unfortunately, in stand-alone mode some code still - // foolishly calls getPeers(). When this is fixed we can move - // the creation of the peer SSL context and Peers object into - // the conditional. + // foolishly calls getPeers(). When this is fixed we can + // move the instantiation inside a conditional: // - m_peers.reset (add (Peers::New (m_mainIoPool, *m_resourceManager, *m_siteFiles, - m_mainIoPool, m_peerSSLContext->get ()))); - - // If we're not in standalone mode, - // prepare ourselves for networking - // - if (!getConfig ().RUN_STANDALONE) - { - // Create the listening sockets for peers - // - m_peerDoors.add (PeerDoor::New ( - m_mainIoPool, - *m_resourceManager, - PeerDoor::sslRequired, - getConfig ().PEER_IP, - getConfig ().peerListeningPort, - m_mainIoPool, - m_peerSSLContext->get ())); - - if (getConfig ().peerPROXYListeningPort != 0) - { - // Also listen on a PROXY-only port. - m_peerDoors.add (PeerDoor::New ( - m_mainIoPool, - *m_resourceManager, - PeerDoor::sslAndPROXYRequired, - getConfig ().PEER_IP, - getConfig ().peerPROXYListeningPort, - m_mainIoPool, - m_peerSSLContext->get ())); - } - } - else - { - m_journal.info << "Peer interface: disabled"; - } + // if (!getConfig ().RUN_STANDALONE) + m_peers.reset (add (Peers::New (m_mainIoPool, *m_resourceManager, + *m_siteFiles, *m_resolver, m_mainIoPool, + m_peerSSLContext->get ()))); // SSL context used for WebSocket connections. if (getConfig ().WEBSOCKET_SECURE) @@ -817,7 +787,8 @@ public: // if (!getConfig ().RUN_STANDALONE) { - m_peers->start (); + // Should this message be here, conceptually? In theory this sort + // of message, if displayed, should be displayed from PeerFinder. if (getConfig ().PEER_PRIVATE && getConfig ().IPS.empty ()) m_journal.warning << "No outbound peer connections will be made"; @@ -896,6 +867,8 @@ public: // forcing a call to io_service::stop() m_probe.cancel (); + m_resolver->stop(); + m_sweepTimer.cancel(); // VFALCO TODO get rid of this flag diff --git a/src/ripple_app/main/IoServicePool.cpp b/src/ripple_app/main/IoServicePool.cpp index 70ac563c6..0462ac009 100644 --- a/src/ripple_app/main/IoServicePool.cpp +++ b/src/ripple_app/main/IoServicePool.cpp @@ -93,20 +93,22 @@ void IoServicePool::onStart () } void IoServicePool::onStop () +{ + // VFALCO NOTE This is how it SHOULD work + // + //m_work = boost::none; +} + +void IoServicePool::onChildrenStopped () { // VFALCO NOTE This is a hack! We should gracefully // cancel all pending I/O, and delete the work // object using boost::optional, and let run() // just return naturally. // - //m_work = boost::none; m_service.stop (); } -void IoServicePool::onChildrenStopped () -{ -} - // Called every time io_service::run() returns and a thread will exit. // void IoServicePool::onThreadExit() diff --git a/src/ripple_app/main/RPCHTTPServer.cpp b/src/ripple_app/main/RPCHTTPServer.cpp index d2541d8d0..109129a6c 100644 --- a/src/ripple_app/main/RPCHTTPServer.cpp +++ b/src/ripple_app/main/RPCHTTPServer.cpp @@ -68,10 +68,10 @@ public: getConfig ().getRpcPort() != 0) { IPAddress ep (IPAddress::from_string (getConfig().getRpcIP())); - if (! ep.empty()) + if (! is_unspecified (ep)) { HTTP::Port port; - port.addr = ep.withPort(0); + port.addr = ep.at_port(0); if (getConfig ().getRpcPort() != 0) port.port = getConfig ().getRpcPort(); else @@ -112,7 +112,7 @@ public: { // Reject non-loopback connections if RPC_ALLOW_REMOTE is not set if (! getConfig().RPC_ALLOW_REMOTE && - ! session.remoteAddress().isLoopback()) + ! IP::is_loopback (session.remoteAddress())) { session.close(); } @@ -153,7 +153,7 @@ public: void processSession (Job& job, HTTP::Session& session) { session.write (m_deprecatedHandler.processRequest ( - session.content(), session.remoteAddress().withPort(0))); + session.content(), session.remoteAddress().at_port(0))); session.close(); } diff --git a/src/ripple_app/main/RippleMain.cpp b/src/ripple_app/main/RippleMain.cpp index 065b6f367..580c8fb8b 100644 --- a/src/ripple_app/main/RippleMain.cpp +++ b/src/ripple_app/main/RippleMain.cpp @@ -272,7 +272,6 @@ int RippleMain::run (int argc, char const* const* argv) ("rpc_ip", po::value (), "Specify the IP address for RPC command. Format: [':']") ("rpc_port", po::value (), "Specify the port number for RPC command.") ("standalone,a", "Run with no peers.") - ("testnet,t", "Run in test net mode.") ("unittest,u", po::value ()->implicit_value (""), "Perform unit tests.") ("unittest-format", po::value ()->implicit_value ("text"), "Format unit test output. Choices are 'text', 'junit'") ("parameters", po::value< vector > (), "Specify comma separated parameters.") @@ -381,7 +380,6 @@ int RippleMain::run (int argc, char const* const* argv) { getConfig ().setup ( vm.count ("conf") ? vm["conf"].as () : "", // Config file. - !!vm.count ("testnet"), // Testnet flag. !!vm.count ("quiet")); // Quiet flag. if (vm.count ("standalone")) diff --git a/src/ripple_app/misc/HashRouter.cpp b/src/ripple_app/misc/HashRouter.cpp index fc83c6eb8..ea501f6ea 100644 --- a/src/ripple_app/misc/HashRouter.cpp +++ b/src/ripple_app/misc/HashRouter.cpp @@ -33,18 +33,18 @@ private: { } - std::set const& peekPeers () const + std::set const& peekPeers () const { return mPeers; } - void addPeer (uint64 peer) + void addPeer (PeerShortID peer) { if (peer != 0) mPeers.insert (peer); } - bool hasPeer (uint64 peer) const + bool hasPeer (PeerShortID peer) const { return mPeers.count (peer) > 0; } @@ -69,14 +69,14 @@ private: mFlags &= ~flagsToClear; } - void swapSet (std::set & other) + void swapSet (std::set & other) { mPeers.swap (other); } private: int mFlags; - std::set mPeers; + std::set mPeers; }; public: @@ -88,13 +88,13 @@ public: bool addSuppression (uint256 const& index); - bool addSuppressionPeer (uint256 const& index, uint64 peer); - bool addSuppressionPeer (uint256 const& index, uint64 peer, int& flags); + bool addSuppressionPeer (uint256 const& index, PeerShortID peer); + bool addSuppressionPeer (uint256 const& index, PeerShortID peer, int& flags); bool addSuppressionFlags (uint256 const& index, int flag); bool setFlag (uint256 const& index, int flag); int getFlags (uint256 const& index); - bool swapSet (uint256 const& index, std::set& peers, int flag); + bool swapSet (uint256 const& index, std::set& peers, int flag); private: Entry getEntry (uint256 const& ); @@ -162,7 +162,7 @@ HashRouter::Entry HashRouter::getEntry (uint256 const& index) return findCreateEntry (index, created); } -bool HashRouter::addSuppressionPeer (uint256 const& index, uint64 peer) +bool HashRouter::addSuppressionPeer (uint256 const& index, PeerShortID peer) { ScopedLockType sl (mLock, __FILE__, __LINE__); @@ -171,7 +171,7 @@ bool HashRouter::addSuppressionPeer (uint256 const& index, uint64 peer) return created; } -bool HashRouter::addSuppressionPeer (uint256 const& index, uint64 peer, int& flags) +bool HashRouter::addSuppressionPeer (uint256 const& index, PeerShortID peer, int& flags) { ScopedLockType sl (mLock, __FILE__, __LINE__); @@ -220,7 +220,7 @@ bool HashRouter::setFlag (uint256 const& index, int flag) return true; } -bool HashRouter::swapSet (uint256 const& index, std::set& peers, int flag) +bool HashRouter::swapSet (uint256 const& index, std::set& peers, int flag) { ScopedLockType sl (mLock, __FILE__, __LINE__); diff --git a/src/ripple_app/misc/IHashRouter.h b/src/ripple_app/misc/IHashRouter.h index 0e7e7ebe8..ac4301912 100644 --- a/src/ripple_app/misc/IHashRouter.h +++ b/src/ripple_app/misc/IHashRouter.h @@ -39,6 +39,9 @@ class IHashRouter { public: + // The type here *MUST* match the type of Peer::ShortId + typedef uint32 PeerShortID; + // VFALCO NOTE this preferred alternative to default parameters makes // behavior clear. // @@ -55,9 +58,9 @@ public: // VFALCO TODO Replace "Supression" terminology with something more semantically meaningful. virtual bool addSuppression (uint256 const& index) = 0; - virtual bool addSuppressionPeer (uint256 const& index, uint64 peer) = 0; + virtual bool addSuppressionPeer (uint256 const& index, PeerShortID peer) = 0; - virtual bool addSuppressionPeer (uint256 const& index, uint64 peer, int& flags) = 0; + virtual bool addSuppressionPeer (uint256 const& index, PeerShortID peer, int& flags) = 0; virtual bool addSuppressionFlags (uint256 const& index, int flag) = 0; @@ -70,7 +73,7 @@ public: virtual int getFlags (uint256 const& index) = 0; - virtual bool swapSet (uint256 const& index, std::set& peers, int flag) = 0; + virtual bool swapSet (uint256 const& index, std::set& peers, int flag) = 0; // VFALCO TODO This appears to be unused! // diff --git a/src/ripple_app/misc/NetworkOPs.cpp b/src/ripple_app/misc/NetworkOPs.cpp index 7bbff9d56..e6765b1da 100644 --- a/src/ripple_app/misc/NetworkOPs.cpp +++ b/src/ripple_app/misc/NetworkOPs.cpp @@ -240,7 +240,7 @@ public: // VFALCO TODO Try to make all these private since they seem to be...private // void switchLastClosedLedger (Ledger::pointer newLedger, bool duringConsensus); // Used for the "jump" case - bool checkLastClosedLedger (const std::vector&, uint256& networkClosed); + bool checkLastClosedLedger (const Peers::PeerSequence&, uint256& networkClosed); int beginConsensus (uint256 const& networkClosed, Ledger::pointer closingLedger); void tryStartConsensus (); void endConsensus (bool correctLCL); @@ -507,7 +507,7 @@ void NetworkOPsImp::processHeartbeatTimer () LoadManager& mgr (app.getLoadManager ()); mgr.resetDeadlockDetector (); - std::size_t const numPeers = getApp().getPeers ().getPeerVector ().size (); + std::size_t const numPeers = getApp().getPeers ().size (); // do we have sufficient peers? If not, we are disconnected. if (numPeers < getConfig ().NETWORK_QUORUM) @@ -579,13 +579,12 @@ void NetworkOPsImp::processClusterTimer () BOOST_FOREACH (Resource::Gossip::Item const& item, gossip.items) { protocol::TMLoadSource& node = *cluster.add_loadsources(); - node.set_name (item.address); + node.set_name (to_string (item.address)); node.set_cost (item.balance); } - - PackedMessage::pointer message = boost::make_shared(cluster, protocol::mtCLUSTER); - getApp().getPeers().relayMessageCluster (NULL, message); - + getApp ().getPeers ().foreach (send_if ( + boost::make_shared(cluster, protocol::mtCLUSTER), + peer_in_cluster ())); setClusterTimer (); } @@ -847,7 +846,7 @@ void NetworkOPsImp::runTransactionQueue () if (didApply /*|| (mMode != omFULL)*/ ) { - std::set peers; + std::set peers; if (getApp().getHashRouter ().swapSet (txn->getID (), peers, SF_RELAYED)) { @@ -858,9 +857,9 @@ void NetworkOPsImp::runTransactionQueue () tx.set_rawtransaction (&s.getData ().front (), s.getLength ()); tx.set_status (protocol::tsCURRENT); tx.set_receivetimestamp (getNetworkTimeNC ()); // FIXME: This should be when we received it - - PackedMessage::pointer packet = boost::make_shared (tx, protocol::mtTRANSACTION); - getApp().getPeers ().relayMessageBut (peers, packet); + getApp ().getPeers ().foreach (send_if_not ( + boost::make_shared (tx, protocol::mtTRANSACTION), + peer_in_set(peers))); } else m_journal.debug << "recently relayed"; @@ -971,7 +970,7 @@ Transaction::pointer NetworkOPsImp::processTransactionCb ( if (didApply || ((mMode != omFULL) && !bFailHard)) { - std::set peers; + std::set peers; if (getApp().getHashRouter ().swapSet (trans->getID (), peers, SF_RELAYED)) { @@ -981,9 +980,9 @@ Transaction::pointer NetworkOPsImp::processTransactionCb ( tx.set_rawtransaction (&s.getData ().front (), s.getLength ()); tx.set_status (protocol::tsCURRENT); tx.set_receivetimestamp (getNetworkTimeNC ()); // FIXME: This should be when we received it - - PackedMessage::pointer packet = boost::make_shared (tx, protocol::mtTRANSACTION); - getApp().getPeers ().relayMessageBut (peers, packet); + getApp ().getPeers ().foreach (send_if_not ( + boost::make_shared (tx, protocol::mtTRANSACTION), + peer_in_set(peers))); } } } @@ -1181,7 +1180,7 @@ public: void NetworkOPsImp::tryStartConsensus () { uint256 networkClosed; - bool ledgerChange = checkLastClosedLedger (getApp().getPeers ().getPeerVector (), networkClosed); + bool ledgerChange = checkLastClosedLedger (getApp().getPeers ().getActivePeers (), networkClosed); if (networkClosed.isZero ()) return; @@ -1213,7 +1212,7 @@ void NetworkOPsImp::tryStartConsensus () beginConsensus (networkClosed, m_ledgerMaster.getCurrentLedger ()); } -bool NetworkOPsImp::checkLastClosedLedger (const std::vector& peerList, uint256& networkClosed) +bool NetworkOPsImp::checkLastClosedLedger (const Peers::PeerSequence& peerList, uint256& networkClosed) { // Returns true if there's an *abnormal* ledger issue, normal changing in TRACKING mode should return false // Do we have sufficient validations for our last closed ledger? Or do sufficient nodes @@ -1385,8 +1384,9 @@ void NetworkOPsImp::switchLastClosedLedger (Ledger::pointer newLedger, bool duri s.set_ledgerhashprevious (hash.begin (), hash.size ()); hash = newLedger->getHash (); s.set_ledgerhash (hash.begin (), hash.size ()); - PackedMessage::pointer packet = boost::make_shared (s, protocol::mtSTATUS_CHANGE); - getApp().getPeers ().relayMessage (NULL, packet); + + getApp ().getPeers ().foreach (send_always ( + boost::make_shared (s, protocol::mtSTATUS_CHANGE))); } int NetworkOPsImp::beginConsensus (uint256 const& networkClosed, Ledger::pointer closingLedger) @@ -1436,7 +1436,7 @@ bool NetworkOPsImp::haveConsensusObject () { // we need to get into the consensus process uint256 networkClosed; - std::vector peerList = getApp().getPeers ().getPeerVector (); + Peers::PeerSequence peerList = getApp().getPeers ().getActivePeers (); bool ledgerChange = checkLastClosedLedger (peerList, networkClosed); if (!ledgerChange) @@ -1499,10 +1499,12 @@ void NetworkOPsImp::processTrustedProposal (LedgerProposal::pointer proposal, if (relay) { - std::set peers; - getApp().getHashRouter ().swapSet (proposal->getHashRouter (), peers, SF_RELAYED); - PackedMessage::pointer message = boost::make_shared (*set, protocol::mtPROPOSE_LEDGER); - getApp().getPeers ().relayMessageBut (peers, message); + std::set peers; + getApp().getHashRouter ().swapSet ( + proposal->getHashRouter (), peers, SF_RELAYED); + getApp ().getPeers ().foreach (send_if_not ( + boost::make_shared (*set, protocol::mtPROPOSE_LEDGER), + peer_in_set(peers))); } else { @@ -1595,7 +1597,7 @@ void NetworkOPsImp::endConsensus (bool correctLCL) { uint256 deadLedger = m_ledgerMaster.getClosedLedger ()->getParentHash (); - std::vector peerList = getApp().getPeers ().getPeerVector (); + std::vector peerList = getApp().getPeers ().getActivePeers (); BOOST_FOREACH (Peer::ref it, peerList) { @@ -2116,9 +2118,6 @@ Json::Value NetworkOPsImp::getServerInfo (bool human, bool admin) info ["build_version"] = BuildInfo::getVersionString (); - if (getConfig ().TESTNET) - info["testnet"] = getConfig ().TESTNET; - info["server_state"] = strOperatingMode (); if (mNeedNetworkLedger) @@ -2147,7 +2146,7 @@ Json::Value NetworkOPsImp::getServerInfo (bool human, bool admin) if (fp != 0) info["fetch_pack"] = Json::UInt (fp); - info["peers"] = getApp().getPeers ().getPeerCount (); + info["peers"] = Json::UInt (getApp ().getPeers ().size ()); Json::Value lastClose = Json::objectValue; lastClose["proposers"] = getApp().getOPs ().getPreviousProposers (); @@ -2705,9 +2704,6 @@ bool NetworkOPsImp::subServer (InfoSub::ref isrListener, Json::Value& jvResult) if (getConfig ().RUN_STANDALONE) jvResult["stand_alone"] = getConfig ().RUN_STANDALONE; - if (getConfig ().TESTNET) - jvResult["testnet"] = getConfig ().TESTNET; - RandomNumbers::getInstance ().fillBytes (uRandom.begin (), uRandom.size ()); jvResult["random"] = uRandom.ToString (); jvResult["server_status"] = strOperatingMode (); diff --git a/src/ripple_app/misc/Validations.cpp b/src/ripple_app/misc/Validations.cpp index c70ddfbab..bdca1e6a4 100644 --- a/src/ripple_app/misc/Validations.cpp +++ b/src/ripple_app/misc/Validations.cpp @@ -73,7 +73,7 @@ private: RippleAddress signer = val->getSignerPublic (); bool isCurrent = false; - if (getApp().getUNL ().nodeInUNL (signer) || val->isTrusted ()) + if (val->isTrusted () || getApp().getUNL ().nodeInUNL (signer)) { val->setTrusted (); uint32 now = getApp().getOPs ().getCloseTimeNC (); diff --git a/src/ripple_app/node/SqliteFactory.cpp b/src/ripple_app/node/SqliteFactory.cpp index 662b91edf..5fedf2bea 100644 --- a/src/ripple_app/node/SqliteFactory.cpp +++ b/src/ripple_app/node/SqliteFactory.cpp @@ -17,6 +17,8 @@ */ //============================================================================== +namespace ripple { + static const char* s_nodeStoreDBInit [] = { "PRAGMA synchronous=NORMAL;", @@ -247,3 +249,5 @@ std::unique_ptr make_SqliteFactory () { return std::make_unique (); } + +} diff --git a/src/ripple_app/node/SqliteFactory.h b/src/ripple_app/node/SqliteFactory.h index 3a3082667..057b0ca45 100644 --- a/src/ripple_app/node/SqliteFactory.h +++ b/src/ripple_app/node/SqliteFactory.h @@ -20,9 +20,13 @@ #ifndef RIPPLE_APP_SQLITEFACTORY_H_INCLUDED #define RIPPLE_APP_SQLITEFACTORY_H_INCLUDED +namespace ripple { + /** Factory to produce SQLite backends for the NodeStore. @see Database */ std::unique_ptr make_SqliteFactory (); +} + #endif diff --git a/src/ripple_app/paths/Pathfinder.cpp b/src/ripple_app/paths/Pathfinder.cpp index b322e0dc3..15c3bee13 100644 --- a/src/ripple_app/paths/Pathfinder.cpp +++ b/src/ripple_app/paths/Pathfinder.cpp @@ -857,8 +857,8 @@ std::string Pathfinder::pathTypeToString(PathType_t const& type) void Pathfinder::initPathTable() { // CAUTION: Do not include rules that build default paths { // XRP to XRP + // do not remove this - it's necessary to build the table. /*CostedPathList_t& list =*/ mPathTable[pt_XRP_to_XRP]; - // list.push_back(CostedPath_t(8, makePath("sbxd"))); // source -> book -> book_to_XRP -> destination // list.push_back(CostedPath_t(9, makePath("sbaxd"))); // source -> book -> gateway -> to_XRP ->destination } diff --git a/src/ripple_app/peers/PackedMessage.h b/src/ripple_app/peers/PackedMessage.h index bb84300ff..798c09120 100644 --- a/src/ripple_app/peers/PackedMessage.h +++ b/src/ripple_app/peers/PackedMessage.h @@ -45,7 +45,7 @@ public: public: /** Number of bytes in a message header. */ - static unsigned const kHeaderBytes = 6; + static size_t const kHeaderBytes = 6; PackedMessage (::google::protobuf::Message const& message, int type); diff --git a/src/ripple_app/peers/Peer.cpp b/src/ripple_app/peers/Peer.cpp index 26c2ae64a..8065ea23a 100644 --- a/src/ripple_app/peers/Peer.cpp +++ b/src/ripple_app/peers/Peer.cpp @@ -17,268 +17,691 @@ */ //============================================================================== +namespace ripple { + SETUP_LOG (Peer) class PeerImp; -// Don't try to run past receiving nonsense from a peer -// #define TRUST_NETWORK +std::string to_string (PeerImp const& peer); +std::ostream& operator<< (std::ostream& os, PeerImp const& peer); -// Node has this long to verify its identity from connection accepted or connection attempt. -#define NODE_VERIFY_SECONDS 15 +std::string to_string (PeerImp const* peer); +std::ostream& operator<< (std::ostream& os, PeerImp const* peer); -// Idle nodes are probed this often -#define NODE_IDLE_SECONDS 120 +//------------------------------------------------------------------------------ -class PeerImp : public Peer +struct get_usable_peers +{ + typedef Peers::PeerSequence return_type; + + Peers::PeerSequence usablePeers; + uint256 const& txHash; + Peer const* skip; + + get_usable_peers(uint256 const& hash, Peer const* s) + : txHash(hash), skip(s) + { } + + void operator() (Peer::ref peer) + { + if (peer->hasTxSet (txHash) && (peer.get () != skip)) + usablePeers.push_back (peer); + } + + return_type operator() () + { + return usablePeers; + } +}; + +//------------------------------------------------------------------------------ + +class PeerImp + : public Peer , public CountedObject { private: + /** Time alloted for a peer to send a HELLO message (DEPRECATED) */ + static const boost::posix_time::seconds nodeVerifySeconds; + + /** The clock drift we allow a remote peer to have */ + static const uint32 clockToleranceDeltaSeconds = 20; + + /** The length of the smallest valid finished message */ + static const size_t sslMinimumFinishedLength = 12; + +public: + boost::shared_ptr m_shared_socket; + + Journal m_journal; + + // A unique identifier (up to a restart of rippled) for this particular + // peer instance. A peer that disconnects will, upon reconnection, get a + // new ID. + ShortId m_shortId; + + // Updated at each stage of the connection process to reflect + // the current conditions as closely as possible. This includes + // the case where we learn the true IP via a PROXY handshake. + IPAddress m_remoteAddress; + // These is up here to prevent warnings about order of initializations // Resource::Manager& m_resourceManager; - bool m_isInbound; - -public: + PeerFinder::Manager& m_peerFinder; + Peers& m_peers; + bool m_inbound; std::unique_ptr m_socket; boost::asio::io_service::strand m_strand; + State m_state; // Current state + bool m_detaching; // True if detaching. + bool m_clusterNode; // True if peer is a node in our cluster + RippleAddress m_nodePublicKey; // Node public key of peer. + std::string m_nodeName; + + // Both sides of the peer calculate this value and verify that it matches + // to detect/prevent man-in-the-middle attacks. + // + uint256 m_secureCookie; + + // The indices of the smallest and largest ledgers this peer has available + // + LedgerIndex m_minLedger; + LedgerIndex m_maxLedger; + + uint256 m_closedLedgerHash; + uint256 m_previousLedgerHash; + + std::list m_recentLedgers; + std::list m_recentTxSets; + mutable boost::mutex m_recentLock; + + boost::asio::deadline_timer mActivityTimer; + + std::vector m_readBuffer; + std::list mSendQ; + PackedMessage::pointer mSendingPacket; + protocol::TMStatusChange mLastStatus; + protocol::TMHello mHello; + + Resource::Consumer m_usage; + + //--------------------------------------------------------------------------- + /** New incoming peer from the specified socket */ + PeerImp ( + boost::shared_ptr const& socket, + Peers& peers, + Resource::Manager& resourceManager, + PeerFinder::Manager& peerFinder, + boost::asio::ssl::context& ssl_context, + MultiSocket::Flag flags) + : m_shared_socket (socket) + , m_journal (LogPartition::getJournal ()) + , m_shortId (0) + , m_resourceManager (resourceManager) + , m_peerFinder (peerFinder) + , m_peers (peers) + , m_inbound (true) + , m_socket (MultiSocket::New ( + *socket, ssl_context, flags.asBits ())) + , m_strand (socket->get_io_service()) + , m_state (stateConnected) + , m_detaching (false) + , m_clusterNode (false) + , m_minLedger (0) + , m_maxLedger (0) + , mActivityTimer (socket->get_io_service()) + { + m_peers.peerCreated (this); + } + + /** New outgoing peer + @note Construction of outbound peers is a two step process: a second + call is needed (to connect or accept) but we cannot make it from + inside the constructor because you cannot call shared_from_this + from inside constructors. + */ + PeerImp ( + boost::asio::io_service& io_service, + Peers& peers, + Resource::Manager& resourceManager, + PeerFinder::Manager& peerFinder, + boost::asio::ssl::context& ssl_context, + MultiSocket::Flag flags) + : m_journal (LogPartition::getJournal ()) + , m_shortId (0) + , m_resourceManager (resourceManager) + , m_peerFinder (peerFinder) + , m_peers (peers) + , m_inbound (false) + , m_socket (MultiSocket::New ( + io_service, ssl_context, flags.asBits ())) + , m_strand (io_service) + , m_state (stateConnecting) + , m_detaching (false) + , m_clusterNode (false) + , m_minLedger (0) + , m_maxLedger (0) + , mActivityTimer (io_service) + { + m_peers.peerCreated (this); + } + + virtual ~PeerImp () + { + m_peers.peerDestroyed (this); + } + NativeSocketType& getNativeSocket () { return m_socket->next_layer (); } - MultiSocket& getHandshakeStream () - { - return *m_socket; - } - MultiSocket& getStream () { return *m_socket; } - PeerImp (Resource::Manager& resourceManager, - boost::asio::io_service& io_service, - boost::asio::ssl::context& ssl_context, - uint64 peerID, bool inbound, - MultiSocket::Flag flags) - : m_resourceManager (resourceManager) - , m_isInbound (inbound) - , m_socket (MultiSocket::New ( - io_service, ssl_context, flags.asBits ())) - , m_strand (io_service) - , mHelloed (false) - , mDetaching (false) - , mActive (2) - , mCluster (false) - , mPeerId (peerID) - , mPrivate (false) - , mMinLedger (0) - , mMaxLedger (0) - , mActivityTimer (io_service) - , m_remoteAddressSet (false) - { - WriteLog (lsDEBUG, Peer) << "CREATING PEER: " << addressToString (this); - } - - //--------------------------------------------------------------------------- -private: - bool mClientConnect; // In process of connecting as client. - bool mHelloed; // True, if hello accepted. - bool mDetaching; // True, if detaching. - int mActive; // 0=idle, 1=pingsent, 2=active - bool mCluster; // Node in our cluster - RippleAddress mNodePublic; // Node public key of peer. - std::string mNodeName; - IPAndPortNumber mIpPort; - IPAndPortNumber mIpPortConnect; - uint256 mCookieHash; - uint64 mPeerId; - bool mPrivate; // Keep peer IP private. - uint32 mMinLedger, mMaxLedger; - - uint256 mClosedLedgerHash; - uint256 mPreviousLedgerHash; - - std::list mRecentLedgers; - std::list mRecentTxSets; - mutable boost::mutex mRecentLock; - - - boost::asio::deadline_timer mActivityTimer; - - std::vector mReadbuf; - std::list mSendQ; - PackedMessage::pointer mSendingPacket; - protocol::TMStatusChange mLastStatus; - protocol::TMHello mHello; - - bool m_remoteAddressSet; - IPAddress m_remoteAddress; - Resource::Consumer m_usage; - -public: static char const* getCountedObjectName () { return "Peer"; } - void handleConnect (const boost::system::error_code & error, boost::asio::ip::tcp::resolver::iterator it); - - std::string const& getIP () + //-------------------------------------------------------------------------- + Peer::State state() const { - return mIpPort.first; + return m_state; } - std::string getDisplayName () + void state(Peer::State new_state) { - return mCluster ? mNodeName : mIpPort.first; - } - int getPort () - { - return mIpPort.second; - } - bool getConnectString(std::string& connect) const - { - if (!mHello.has_ipv4port() || mIpPortConnect.first.empty()) - return false; - connect = boost::str(boost::format("%s %d") % mIpPortConnect.first % mHello.ipv4port()); - return true; + m_state = new_state; } + //-------------------------------------------------------------------------- - void setIpPort (const std::string & strIP, int iPort); + /** Attempt an outbound connection. - void connect (const std::string & strIp, int iPort); - void connected (const boost::system::error_code & error); - void detach (const char*, bool onIOStrand); + The connection may fail (for a number of reasons) and we do not know + what will happen at this point. - // VFALCO Seems no one is using these - //bool samePeer (Peer::ref p) { return samePeer(*p); } - //bool samePeer (const Peer& p) { return this == &p; } + The connection state does not transition with this function and remains + as `stateConnecting`. - void sendPacket (const PackedMessage::pointer & packet, bool onStrand); + @param address the IP:port to which we want to connect. - void sendGetPeers (); + @note This should likely become a static member that creates the peer + object and begins the process of connection establishment instead + of requiring the caller to construct a Peer and call connect. + */ + void connect (IPAddress const& address) + { + m_remoteAddress = address; + m_peers.addPeer (shared_from_this ()); + + m_journal.info << "Connecting to " << m_remoteAddress; + + boost::system::error_code err; + + mActivityTimer.expires_from_now (nodeVerifySeconds, err); + + mActivityTimer.async_wait (m_strand.wrap (boost::bind ( + &PeerImp::handleVerifyTimer, + boost::static_pointer_cast (shared_from_this ()), + boost::asio::placeholders::error))); + + if (err) + { + m_journal.error << "Failed to set verify timer."; + detach ("c2", false); + return; + } + + m_peerFinder.onPeerConnect (m_remoteAddress); + + getNativeSocket ().async_connect ( + IPAddressConversion::to_asio_endpoint (address), + m_strand.wrap (boost::bind ( + &PeerImp::onConnect, + boost::static_pointer_cast (shared_from_this ()), + boost::asio::placeholders::error))); + } + + /** Outbound connection attempt has completed (not necessarily successfully) + + The connection may fail for a number of reasons. Perhaps we do not have + a route to the remote endpoint, or there is no server listening at that + address. + + If the connection succeeded, we transition to the `stateConnected` state + and move on. + + If the connection failed, we simply disconnect. + + @param ec indicates success or an error code. + */ + void onConnect (boost::system::error_code const& ec) + { + if (ec) + { + // VFALCO NOTE This log statement looks like ass + m_journal.info << "Connecting to " << m_remoteAddress << + " failed " << ec.message(); + + // This should end up calling onPeerClosed() + detach ("hc", true); + return; + } + + bassert (m_state == stateConnecting); + m_state = stateConnected; + + m_peerFinder.onPeerConnected (m_socket->local_endpoint(), + m_remoteAddress); + + m_socket->set_verify_mode (boost::asio::ssl::verify_none); + m_socket->async_handshake ( + boost::asio::ssl::stream_base::client, + m_strand.wrap (boost::bind (&PeerImp::handleStart, + boost::static_pointer_cast (shared_from_this ()), + boost::asio::placeholders::error))); + } + + /** We have accepted an inbound connection. + + The connection state transitions from `stateConnect` to `stateConnected` + as `stateConnect`. + + @param address the IP:port to which we want to connect. + */ + void accept () + { + m_remoteAddress = m_socket->remote_endpoint(); + + m_journal.info << "Accepted " << m_remoteAddress; + + m_peers.addPeer (shared_from_this ()); + + m_peerFinder.onPeerAccept (m_socket->local_endpoint(), m_remoteAddress); + + m_socket->set_verify_mode (boost::asio::ssl::verify_none); + m_socket->async_handshake ( + boost::asio::ssl::stream_base::server, + m_strand.wrap (boost::bind ( + &PeerImp::handleStart, + boost::static_pointer_cast (shared_from_this ()), + boost::asio::placeholders::error))); + } + + /** Indicates that the peer must be activated. + + A peer is activated after the handshake is completed and if it is not + a second connection from a peer that we already have. Once activated + the peer transitions to `stateActive` and begins operating. + */ + void activate () + { + bassert (m_state == stateHandshaked); + m_state = stateActive; + m_peers.onPeerActivated(shared_from_this ()); + } + + //-------------------------------------------------------------------------- + std::string getClusterNodeName() const + { + return m_nodeName; + } + + //-------------------------------------------------------------------------- + /** Disconnect a peer + + The peer transitions from its current state into `stateGracefulClose` + + @param rsn a code indicating why the peer was disconnected + @param onIOStrand true if called on an I/O strand. It if is not, then + a callback will be queued up. + */ + void detach (const char* rsn, bool onIOStrand) + { + // VFALCO NOTE So essentially, detach() is really two different functions + // depending on the value of onIOStrand. + // TODO Clean this up. + // + if (!onIOStrand) + { + m_strand.post (BIND_TYPE (&Peer::detach, shared_from_this (), rsn, true)); + return; + } + + if (!m_detaching) + { + // NIKB TODO No - a race is NOT ok. This needs to be fixed + // to have PeerFinder work reliably. + m_detaching = true; // Race is ok. + + m_peerFinder.onPeerClosed (m_remoteAddress); + + if (m_state == stateActive) + m_peers.onPeerDisconnect (shared_from_this ()); + + m_state = stateGracefulClose; + + if (m_clusterNode && m_journal.active(Journal::Severity::kWarning)) + m_journal.warning << "Cluster peer " << m_nodeName << + " detached: " << rsn; + + mSendQ.clear (); + + (void) mActivityTimer.cancel (); + m_socket->async_shutdown ( + m_strand.wrap ( boost::bind( + &PeerImp::handleShutdown, + boost::static_pointer_cast (shared_from_this ()), + boost::asio::placeholders::error))); + + if (m_nodePublicKey.isValid ()) + m_nodePublicKey.clear (); // Be idempotent. + } + } + + void sendPacket (const PackedMessage::pointer& packet, bool onStrand) + { + if (packet) + { + if (!onStrand) + { + m_strand.post (BIND_TYPE ( + &Peer::sendPacket, shared_from_this (), packet, true)); + return; + } + + if (mSendingPacket) + { + mSendQ.push_back (packet); + } + else + { + sendPacketForce (packet); + } + } + } + + void sendGetPeers () + { + // Ask peer for known other peers. + protocol::TMGetPeers getPeers; + + getPeers.set_doweneedthis (1); + + PackedMessage::pointer packet = boost::make_shared ( + getPeers, protocol::mtGET_PEERS); + + sendPacket (packet, true); + } void charge (Resource::Charge const& fee) { - if ((m_usage.charge (fee) == Resource::drop) && m_usage.disconnect ()) - detach ("resource", false); + if ((m_usage.charge (fee) == Resource::drop) && m_usage.disconnect ()) + detach ("resource", false); + } + + Json::Value json () + { + Json::Value ret (Json::objectValue); + + ret["public_key"] = m_nodePublicKey.ToString (); + ret["address"] = m_remoteAddress.to_string(); + + if (m_inbound) + ret["inbound"] = true; + + if (m_clusterNode) + { + ret["cluster"] = true; + + if (!m_nodeName.empty ()) + ret["name"] = m_nodeName; + } + + if (mHello.has_fullversion ()) + ret["version"] = mHello.fullversion (); + + if (mHello.has_protoversion () && + (mHello.protoversion () != BuildInfo::getCurrentProtocol().toPacked ())) + { + ret["protocol"] = BuildInfo::Protocol (mHello.protoversion ()).toStdString (); + } + + uint32 minSeq, maxSeq; + ledgerRange(minSeq, maxSeq); + + if ((minSeq != 0) || (maxSeq != 0)) + ret["complete_ledgers"] = boost::lexical_cast(minSeq) + " - " + + boost::lexical_cast(maxSeq); + + if (!!m_closedLedgerHash) + ret["ledger"] = m_closedLedgerHash.GetHex (); + + if (mLastStatus.has_newstatus ()) + { + switch (mLastStatus.newstatus ()) + { + case protocol::nsCONNECTING: + ret["status"] = "connecting"; + break; + + case protocol::nsCONNECTED: + ret["status"] = "connected"; + break; + + case protocol::nsMONITORING: + ret["status"] = "monitoring"; + break; + + case protocol::nsVALIDATING: + ret["status"] = "validating"; + break; + + case protocol::nsSHUTTING: + ret["status"] = "shutting"; + break; + + default: + // FIXME: do we really want this? + m_journal.warning << "Unknown status: " << mLastStatus.newstatus (); + } + } + + return ret; } - Json::Value getJson (); bool isConnected () const { - return mHelloed && !mDetaching; + // CHECKME should this be stateActive or something else? + return (m_state == stateActive) && !m_detaching; } bool isInCluster () const { - return mCluster; + return m_clusterNode; } bool isInbound () const { - return m_isInbound; + return m_inbound; } bool isOutbound () const { - return !m_isInbound; + return !m_inbound; } uint256 const& getClosedLedgerHash () const { - return mClosedLedgerHash; + return m_closedLedgerHash; } - bool hasLedger (uint256 const & hash, uint32 seq) const; - void ledgerRange (uint32& minSeq, uint32& maxSeq) const; - bool hasTxSet (uint256 const & hash) const; - uint64 getPeerId () const + + bool hasLedger (uint256 const& hash, uint32 seq) const { - return mPeerId; + boost::mutex::scoped_lock sl(m_recentLock); + + if ((seq != 0) && (seq >= m_minLedger) && (seq <= m_maxLedger)) + return true; + + BOOST_FOREACH (uint256 const & ledger, m_recentLedgers) + { + if (ledger == hash) + return true; + } + + return false; + } + + void ledgerRange (uint32& minSeq, uint32& maxSeq) const + { + boost::mutex::scoped_lock sl(m_recentLock); + + minSeq = m_minLedger; + maxSeq = m_maxLedger; + } + + bool hasTxSet (uint256 const& hash) const + { + boost::mutex::scoped_lock sl(m_recentLock); + BOOST_FOREACH (uint256 const & set, m_recentTxSets) + + if (set == hash) + return true; + + return false; + } + + void setShortId(Peer::ShortId shortId) + { + bassert((m_shortId == 0) && (shortId != 0)); + + m_shortId = shortId; + } + + Peer::ShortId getShortId () const + { + return m_shortId; } const RippleAddress& getNodePublic () const { - return mNodePublic; - } - void cycleStatus () - { - mPreviousLedgerHash = mClosedLedgerHash; - mClosedLedgerHash.zero (); - } - bool hasProto (int version); - bool hasRange (uint32 uMin, uint32 uMax) - { - return (uMin >= mMinLedger) && (uMax <= mMaxLedger); + return m_nodePublicKey; } - IPAddress getPeerEndpoint() const + void cycleStatus () + { + m_previousLedgerHash = m_closedLedgerHash; + m_closedLedgerHash.zero (); + } + + bool supportsVersion (int version) + { + return mHello.has_protoversion () && (mHello.protoversion () >= version); + } + + bool hasRange (uint32 uMin, uint32 uMax) + { + return (uMin >= m_minLedger) && (uMax <= m_maxLedger); + } + + IPAddress getRemoteAddress() const { return m_remoteAddress; } private: - void handleShutdown (const boost::system::error_code & error) + void handleShutdown (boost::system::error_code const& ec) { - ; - } - void handleWrite (const boost::system::error_code & error, size_t bytes_transferred); + if (ec == boost::asio::error::operation_aborted) + return; - void handleReadHeader (boost::system::error_code const& error, - std::size_t bytes_transferred) - { - if (mDetaching) - { - // Drop data or error if detaching. - nothing (); - } - else if (!error) - { - unsigned msg_len = PackedMessage::getLength (mReadbuf); - - // WRITEME: Compare to maximum message length, abort if too large - if ((msg_len > (32 * 1024 * 1024)) || (msg_len == 0)) - { - detach ("hrh", true); - return; - } - - startReadBody (msg_len); - } - else - { - if (mCluster) - { - WriteLog (lsINFO, Peer) << "Peer: Cluster connection lost to \"" << mNodeName << "\": " << - error.category ().name () << ": " << error.message () << ": " << error; - } - else - { - WriteLog (lsINFO, Peer) << "Peer: Header: Error: " << getIP () << ": " << error.category ().name () << ": " << error.message () << ": " << error; - } - - detach ("hrh2", true); - } - } - - void handleReadBody (boost::system::error_code const& error, - std::size_t bytes_transferred) - { - if (mDetaching) + if (m_detaching) { + m_peers.removePeer (shared_from_this()); return; } - else if (error) - { - if (mCluster) - { - WriteLog (lsINFO, Peer) << "Peer: Cluster connection lost to \"" << mNodeName << "\": " << - error.category ().name () << ": " << error.message () << ": " << error; - } - else - { - WriteLog (lsINFO, Peer) << "Peer: Body: Error: " << getIP () << ": " << error.category ().name () << ": " << error.message () << ": " << error; - } + if (ec) + { + m_journal.info << "Shutdown: " << ec.message (); + detach ("hsd", true); + return; + } + } + + void handleWrite (boost::system::error_code const& ec, size_t bytes) + { + // Call on IO strand + + mSendingPacket.reset (); + + if (ec == boost::asio::error::operation_aborted) + return; + + if (m_detaching) + return; + + if (ec) + { + m_journal.info << "Write: " << ec.message (); + detach ("hw", true); + return; + } + + if (!mSendQ.empty ()) + { + PackedMessage::pointer packet = mSendQ.front (); + + if (packet) + { + sendPacketForce (packet); + mSendQ.pop_front (); + } + } + } + + void handleReadHeader (boost::system::error_code const& ec, + std::size_t bytes) + { + if (ec == boost::asio::error::operation_aborted) + return; + + if (m_detaching) + return; + + if (ec) + { + m_journal.info << "ReadHeader: " << ec.message (); + detach ("hrh1", true); + return; + } + + unsigned msg_len = PackedMessage::getLength (m_readBuffer); + + // WRITEME: Compare to maximum message length, abort if too large + if ((msg_len > (32 * 1024 * 1024)) || (msg_len == 0)) + { + detach ("hrh2", true); + return; + } + + startReadBody (msg_len); + } + + void handleReadBody (boost::system::error_code const& ec, + std::size_t bytes) + { + if (ec == boost::asio::error::operation_aborted) + return; + + if (m_detaching) + return; + + if (ec) + { + m_journal.info << "ReadBody: " << ec.message (); detach ("hrb", true); return; } @@ -289,544 +712,97 @@ private: // We have an encrypted connection to the peer. // Have it say who it is so we know to avoid redundant connections. - // Establish that it really who we are talking to by having it sign a connection detail. - // Also need to establish no man in the middle attack is in progress. - void handleStart (const boost::system::error_code& error) + // Establish that it really who we are talking to by having it sign a + // connection detail. Also need to establish no man in the middle attack + // is in progress. + void handleStart (boost::system::error_code const& ec) { - if (error) + if (ec == boost::asio::error::operation_aborted) + return; + + if (m_detaching) + return; + + if (ec) { - WriteLog (lsINFO, Peer) << "Peer: Handshake: Error: " << error.category ().name () << ": " << error.message () << ": " << error; + m_journal.info << "Handshake: " << ec.message (); detach ("hs", true); - } - else - { - bool valid = false; - - try - { - if (m_socket->getFlags ().set (MultiSocket::Flag::proxy) && m_isInbound) - { - MultiSocket::ProxyInfo const proxyInfo (m_socket->getProxyInfo ()); - - if (proxyInfo.protocol == "TCP4") - { - m_remoteAddressSet = true; - m_remoteAddress = IPAddress (IPAddress::V4 ( - proxyInfo.sourceAddress.value [0], - proxyInfo.sourceAddress.value [1], - proxyInfo.sourceAddress.value [2], - proxyInfo.sourceAddress.value [3]), - proxyInfo.sourcePort); - - // Set remote IP and port number from PROXY handshake - mIpPort.first = proxyInfo.sourceAddress.toString ().toStdString (); - mIpPort.second = proxyInfo.sourcePort; - - if (m_isInbound) - m_usage = m_resourceManager.newInboundEndpoint (m_remoteAddress); - else - m_usage = m_resourceManager.newOutboundEndpoint (m_remoteAddress); - - valid = true; - - WriteLog (lsINFO, Peer) << "Peer: PROXY handshake from " << mIpPort.first; - } - else - { - if (proxyInfo.protocol != String::empty) - { - WriteLog (lsINFO, Peer) << "Peer: Unknown PROXY protocol " << - proxyInfo.protocol.toStdString (); - } - else - { - WriteLog (lsINFO, Peer) << "Peer: Missing PROXY handshake"; - } - - detach ("pi", true); - } - } - else - { - boost::asio::ip::address addr (getNativeSocket().remote_endpoint().address()); - - if (addr.is_v4()) - { - boost::asio::ip::address_v4::bytes_type bytes (addr.to_v4().to_bytes()); - m_remoteAddress = IPAddress (IPAddress::V4 ( - bytes[0], bytes[1], bytes[2], bytes[3]), 0); - if (! m_isInbound) - m_remoteAddress = m_remoteAddress.withPort ( - getNativeSocket().remote_endpoint().port()); - } - else - { - // TODO: Support ipv6 - bassertfalse; - } - m_remoteAddressSet = true; - - if (m_isInbound) - m_usage = m_resourceManager.newInboundEndpoint (m_remoteAddress); - else - m_usage = m_resourceManager.newOutboundEndpoint (m_remoteAddress); - - valid = true; - } - } - catch (...) - { - WriteLog (lsDEBUG, Peer) << "exception accepting peer"; - detach ("ex", true); - } - - if (valid) - { - if (m_usage.disconnect ()) - { - detach ("resource", true); - } - else - { - getApp ().getPeers ().peerConnected(m_remoteAddress, m_isInbound); - - // Must compute mCookieHash before receiving a hello. - sendHello (); - startReadHeader (); - } - } - - } - } - - void handleVerifyTimer (const boost::system::error_code & ecResult); - void handlePingTimer (const boost::system::error_code & ecResult); - - void processReadBuffer (); - void startReadHeader (); - void startReadBody (unsigned msg_len); - - void sendPacketForce (const PackedMessage::pointer & packet); - - void sendHello (); - - void recvHello (protocol::TMHello & packet); - void recvCluster (protocol::TMCluster & packet); - void recvTransaction (protocol::TMTransaction & packet); - void recvValidation (const boost::shared_ptr& packet); - void recvGetValidation (protocol::TMGetValidations & packet); - void recvContact (protocol::TMContact & packet); - void recvGetContacts (protocol::TMGetContacts & packet); - void recvGetPeers (protocol::TMGetPeers & packet); - void recvPeers (protocol::TMPeers & packet); - void recvEndpoints (protocol::TMEndpoints & packet); - void recvGetObjectByHash (const boost::shared_ptr& packet); - void recvPing (protocol::TMPing & packet); - void recvErrorMessage (protocol::TMErrorMsg & packet); - void recvSearchTransaction (protocol::TMSearchTransaction & packet); - void recvGetAccount (protocol::TMGetAccount & packet); - void recvAccount (protocol::TMAccount & packet); - void recvGetLedger (const boost::shared_ptr & packet); - void recvLedger (const boost::shared_ptr& packet); - void recvStatus (protocol::TMStatusChange & packet); - void recvPropose (const boost::shared_ptr& packet); - void recvHaveTxSet (protocol::TMHaveTransactionSet & packet); - void recvProofWork (protocol::TMProofWork & packet); - - void getSessionCookie (std::string & strDst); - - void addLedger (uint256 const & ledger); - void getLedger (protocol::TMGetLedger & packet); - void addTxSet (uint256 const & TxSet); - - void doFetchPack (const boost::shared_ptr& packet); - - static void doProofOfWork (Job&, boost::weak_ptr , ProofOfWork::pointer); -}; - -void PeerImp::handleWrite (const boost::system::error_code& error, size_t bytes_transferred) -{ - // Call on IO strand -#ifdef BEAST_DEBUG - // if (!error) - // Log::out() << "Peer::handleWrite bytes: "<< bytes_transferred; -#endif - - mSendingPacket.reset (); - - if (mDetaching) - { - // Ignore write requests when detatching. - nothing (); - } - else if (error) - { - WriteLog (lsINFO, Peer) << "Peer: Write: Error: " << addressToString (this) << ": bytes=" << bytes_transferred << ": " << error.category ().name () << ": " << error.message () << ": " << error; - - detach ("hw", true); - } - else if (!mSendQ.empty ()) - { - PackedMessage::pointer packet = mSendQ.front (); - - if (packet) - { - sendPacketForce (packet); - mSendQ.pop_front (); - } - } -} - -void PeerImp::setIpPort (const std::string& strIP, int iPort) -{ - mIpPort = make_pair (strIP, iPort); - - WriteLog (lsDEBUG, Peer) << "Peer: Set: " - << addressToString (this) << "> " - << (mNodePublic.isValid () ? mNodePublic.humanNodePublic () : "-") << " " << getIP () << " " << getPort (); -} - -void PeerImp::detach (const char* rsn, bool onIOStrand) -{ - // VFALCO NOTE So essentially, detach() is really two different functions - // depending on the value of onIOStrand. - // TODO Clean this up. - // - if (!onIOStrand) - { - m_strand.post (BIND_TYPE (&Peer::detach, shared_from_this (), rsn, true)); - return; - } - - if (!mDetaching) - { - mDetaching = true; // Race is ok. - - CondLog (mCluster, lsWARNING, Peer) << "Cluster peer detach \"" << mNodeName << "\": " << rsn; - /* - WriteLog (lsDEBUG, Peer) << "Peer: Detach: " - << addressToString(this) << "> " - << rsn << ": " - << (mNodePublic.isValid() ? mNodePublic.humanNodePublic() : "-") << " " << getIP() << " " << getPort(); - */ - - mSendQ.clear (); - - (void) mActivityTimer.cancel (); - getHandshakeStream ().async_shutdown (m_strand.wrap (boost::bind - (&PeerImp::handleShutdown, boost::static_pointer_cast (shared_from_this ()), - boost::asio::placeholders::error))); - - if (mNodePublic.isValid ()) - { - getApp().getPeers ().peerDisconnected (shared_from_this (), mNodePublic); - - mNodePublic.clear (); // Be idempotent. - } - - if (!mIpPort.first.empty ()) - { - // Connection might be part of scanning. Inform connect failed. - // Might need to scan. Inform connection closed. - getApp().getPeers ().peerClosed (shared_from_this (), mIpPort.first, mIpPort.second); - - mIpPort.first.clear (); // Be idempotent. - } - - /* - WriteLog (lsDEBUG, Peer) << "Peer: Detach: " - << addressToString(this) << "< " - << rsn << ": " - << (mNodePublic.isValid() ? mNodePublic.humanNodePublic() : "-") << " " << getIP() << " " << getPort(); - */ - } -} - -void PeerImp::handlePingTimer (const boost::system::error_code& ecResult) -{ - // called on IO strand - if (ecResult || mDetaching) - return; - - if (mActive == 1) - { - // ping out - detach ("pto", true); - return; - } - - if (mActive == 0) - { - // idle->pingsent - mActive = 1; - protocol::TMPing packet; - packet.set_type (protocol::TMPing::ptPING); - sendPacket (boost::make_shared (packet, protocol::mtPING), true); - } - else // active->idle - mActive = 0; - - mActivityTimer.expires_from_now (boost::posix_time::seconds (NODE_IDLE_SECONDS)); - mActivityTimer.async_wait (m_strand.wrap (boost::bind ( - &PeerImp::handlePingTimer, - boost::static_pointer_cast (shared_from_this ()), - boost::asio::placeholders::error))); -} - - -void PeerImp::handleVerifyTimer (const boost::system::error_code& ecResult) -{ - if (ecResult == boost::asio::error::operation_aborted) - { - // Timer canceled because deadline no longer needed. - // Log::out() << "Deadline cancelled."; - - nothing (); // Aborter is done. - } - else if (ecResult) - { - WriteLog (lsINFO, Peer) << "Peer verify timer error"; - } - else - { - //WriteLog (lsINFO, Peer) << "Peer: Verify: Peer failed to verify in time."; - - detach ("hvt", true); - } -} - -// Begin trying to connect. We are not connected till we know and accept peer's public key. -// Only takes IP addresses (not domains). -void PeerImp::connect (const std::string& strIp, int iPort) -{ - int iPortAct = (iPort <= 0) ? SYSTEM_PEER_PORT : iPort; - - mClientConnect = true; - - mIpPort = make_pair (strIp, iPort); - mIpPortConnect = mIpPort; - assert (!mIpPort.first.empty ()); - - boost::asio::ip::tcp::resolver::query query (strIp, lexicalCastThrow (iPortAct), - boost::asio::ip::resolver_query_base::numeric_host | boost::asio::ip::resolver_query_base::numeric_service); - boost::asio::ip::tcp::resolver resolver (getApp().getIOService ()); - boost::system::error_code err; - boost::asio::ip::tcp::resolver::iterator itrEndpoint = resolver.resolve (query, err); - - if (err || itrEndpoint == boost::asio::ip::tcp::resolver::iterator ()) - { - WriteLog (lsWARNING, Peer) << "Peer: Connect: Bad IP: " << strIp; - detach ("c", false); - return; - } - else - { - mActivityTimer.expires_from_now (boost::posix_time::seconds (NODE_VERIFY_SECONDS), err); - - mActivityTimer.async_wait (m_strand.wrap (boost::bind ( - &PeerImp::handleVerifyTimer, - boost::static_pointer_cast (shared_from_this ()), - boost::asio::placeholders::error))); - - if (err) - { - WriteLog (lsWARNING, Peer) << "Peer: Connect: Failed to set timer."; - detach ("c2", false); - return; - } - } - - if (!err) - { - WriteLog (lsINFO, Peer) << "Peer: Connect: Outbound: " << - addressToString (this) << ": " << - mIpPort.first << " " << mIpPort.second; - - // Notify peer finder that we have a connection attempt in-progress - getApp ().getPeers ().getPeerFinder ().onPeerConnectAttemptBegins( - IPAddress::from_string(strIp).withPort(iPortAct) ); - - boost::asio::async_connect ( - getNativeSocket (), - itrEndpoint, - m_strand.wrap (boost::bind ( - &PeerImp::handleConnect, - boost::static_pointer_cast (shared_from_this ()), - boost::asio::placeholders::error, - boost::asio::placeholders::iterator))); - } -} - -// Connect ssl as client. -void PeerImp::handleConnect (const boost::system::error_code& error, boost::asio::ip::tcp::resolver::iterator it) -{ - // Notify peer finder about the status of this in-progress connection attempt -#if RIPPLE_USE_PEERFINDER - getApp ().getPeers ().getPeerFinder ().onPeerConnectAttemptCompletes( - IPAddress::from_string(getIP()).withPort(getPort()), !error ); -#endif - - if (error) - { - WriteLog (lsINFO, Peer) << "Peer: Connect: Error: " << - getIP() << ":" << getPort() << - " (" << error.category ().name () << - ": " << error.message () << - ": " << error << ")"; - detach ("hc", true); - } - else - { - WriteLog (lsINFO, Peer) << "Peer: Connect: Success: " << - getIP() << ":" << getPort(); - - getHandshakeStream ().set_verify_mode (boost::asio::ssl::verify_none); - - getHandshakeStream ().async_handshake (boost::asio::ssl::stream_base::client, - m_strand.wrap (boost::bind (&PeerImp::handleStart, - boost::static_pointer_cast (shared_from_this ()), - boost::asio::placeholders::error))); - } -} - -// Connect ssl as server to an inbound connection. -// - We don't bother remembering the inbound IP or port. Only useful for debugging. -void PeerImp::connected (const boost::system::error_code& error) -{ - boost::asio::ip::tcp::endpoint ep; - int iPort; - std::string strIp; - - try - { - ep = getNativeSocket ().remote_endpoint (); - iPort = ep.port (); - strIp = ep.address ().to_string (); - } - catch (...) - { - detach ("edc", false); - return; - } - - mClientConnect = false; - mIpPortConnect = make_pair (strIp, iPort); - - if (iPort == SYSTEM_PEER_PORT) //TODO: Why are you doing this? - iPort = -1; - - if (!error) - { - // Not redundant ip and port, handshake, and start. - - WriteLog (lsINFO, Peer) << "Peer: Inbound: Accepted: " << addressToString (this) << ": " << strIp << " " << iPort; - - getHandshakeStream ().set_verify_mode (boost::asio::ssl::verify_none); - - getHandshakeStream ().async_handshake (boost::asio::ssl::stream_base::server, m_strand.wrap (boost::bind ( - &PeerImp::handleStart, boost::static_pointer_cast (shared_from_this ()), - boost::asio::placeholders::error))); - } - else if (!mDetaching) - { - WriteLog (lsINFO, Peer) << "Peer: Inbound: Error: " << addressToString (this) << ": " << strIp << " " << iPort << " : " << error.category ().name () << ": " << error.message () << ": " << error; - - detach ("ctd", false); - } -} - -void PeerImp::sendPacketForce (const PackedMessage::pointer& packet) -{ - // must be on IO strand - if (!mDetaching) - { - mSendingPacket = packet; - - boost::asio::async_write (getStream (), boost::asio::buffer (packet->getBuffer ()), - m_strand.wrap (boost::bind (&PeerImp::handleWrite, - boost::static_pointer_cast (shared_from_this ()), - boost::asio::placeholders::error, - boost::asio::placeholders::bytes_transferred))); - } -} - -void PeerImp::sendPacket (const PackedMessage::pointer& packet, bool onStrand) -{ - if (packet) - { - if (!onStrand) - { - m_strand.post (BIND_TYPE (&Peer::sendPacket, shared_from_this (), packet, true)); return; } - if (mSendingPacket) + m_remoteAddress = m_socket->remote_endpoint(); + + if (m_inbound) + m_usage = m_resourceManager.newInboundEndpoint (m_remoteAddress); + else + m_usage = m_resourceManager.newOutboundEndpoint (m_remoteAddress); + + if (m_usage.disconnect ()) { - mSendQ.push_back (packet); + detach ("resource", true); + return; + } + + if(!sendHello ()) + { + m_journal.error << "Unable to send HELLO to " << m_remoteAddress; + detach ("hello", true); + return; + } + + startReadHeader (); + } + + void handleVerifyTimer (boost::system::error_code const& ec) + { + if (ec == boost::asio::error::operation_aborted) + { + // Timer canceled because deadline no longer needed. + } + else if (ec) + { + m_journal.info << "Peer verify timer error"; } else { - sendPacketForce (packet); + // m_journal.info << "Verify: Peer failed to verify in time."; + + detach ("hvt", true); } } -} -void PeerImp::startReadHeader () -{ - if (!mDetaching) + void processReadBuffer () { - mReadbuf.clear (); - mReadbuf.resize (PackedMessage::kHeaderBytes); + // must not hold peer lock + int type = PackedMessage::getType (m_readBuffer); - boost::asio::async_read (getStream (), - boost::asio::buffer (mReadbuf), - m_strand.wrap (boost::bind (&PeerImp::handleReadHeader, - boost::static_pointer_cast (shared_from_this ()), - boost::asio::placeholders::error, - boost::asio::placeholders::bytes_transferred))); - } -} + LoadEvent::autoptr event ( + getApp().getJobQueue ().getLoadEventAP (jtPEER, "Peer::read")); -void PeerImp::startReadBody (unsigned msg_len) -{ - // m_readbuf already contains the header in its first PackedMessage::kHeaderBytes - // bytes. Expand it to fit in the body as well, and start async - // read into the body. - - if (!mDetaching) - { - mReadbuf.resize (PackedMessage::kHeaderBytes + msg_len); - - boost::asio::async_read (getStream (), - boost::asio::buffer (&mReadbuf [PackedMessage::kHeaderBytes], msg_len), - m_strand.wrap (boost::bind (&PeerImp::handleReadBody, - boost::static_pointer_cast (shared_from_this ()), - boost::asio::placeholders::error, - boost::asio::placeholders::bytes_transferred))); - } -} - -void PeerImp::processReadBuffer () -{ - // must not hold peer lock - int type = PackedMessage::getType (mReadbuf); -#ifdef BEAST_DEBUG - // Log::out() << "PRB(" << type << "), len=" << (mReadbuf.size()-PackedMessage::kHeaderBytes); -#endif - - // Log::out() << "Peer::processReadBuffer: " << mIpPort.first << " " << mIpPort.second; - - LoadEvent::autoptr event (getApp().getJobQueue ().getLoadEventAP (jtPEER, "Peer::read")); - - { - // If connected and get a mtHELLO or if not connected and get a non-mtHELLO, wrong message was sent. - if (mHelloed == (type == protocol::mtHELLO)) - { - WriteLog (lsWARNING, Peer) << "Wrong message type: " << type; - detach ("prb1", true); - } - else { + Application::ScopedLockType lock (getApp ().getMasterLock (), + __FILE__, __LINE__); + + // An mtHELLO message must be the first message receiced by a peer + // and it must be received *exactly* once during a connection; any + // other scenario constitutes a protocol violation. + + if ((m_state == stateHandshaked) && (type == protocol::mtHELLO)) + { + m_journal.warning << "Protocol: HELLO expected!"; + detach ("prb-hello-expected", true); + return; + } + + if ((m_state == stateActive) && (type == protocol::mtHELLO)) + { + m_journal.warning << "Protocol: HELLO unexpected!"; + detach ("prb-hello-unexpected", true); + return; + } + + size_t msgLen (m_readBuffer.size () - PackedMessage::kHeaderBytes); + switch (type) { case protocol::mtHELLO: @@ -834,22 +810,24 @@ void PeerImp::processReadBuffer () event->reName ("Peer::hello"); protocol::TMHello msg; - if (msg.ParseFromArray (&mReadbuf[PackedMessage::kHeaderBytes], mReadbuf.size () - PackedMessage::kHeaderBytes)) + if (msg.ParseFromArray (&m_readBuffer[PackedMessage::kHeaderBytes], + msgLen)) recvHello (msg); else - WriteLog (lsWARNING, Peer) << "parse error: " << type; + m_journal.warning << "parse error: " << type; } - break; + break; case protocol::mtCLUSTER: { event->reName ("Peer::cluster"); protocol::TMCluster msg; - if (msg.ParseFromArray (&mReadbuf[PackedMessage::kHeaderBytes], mReadbuf.size () - PackedMessage::kHeaderBytes)) + if (msg.ParseFromArray (&m_readBuffer[PackedMessage::kHeaderBytes], + msgLen)) recvCluster (msg); else - WriteLog (lsWARNING, Peer) << "parse error: " << type; + m_journal.warning << "parse error: " << type; } case protocol::mtERROR_MSG: @@ -857,1940 +835,1967 @@ void PeerImp::processReadBuffer () event->reName ("Peer::errormessage"); protocol::TMErrorMsg msg; - if (msg.ParseFromArray (&mReadbuf[PackedMessage::kHeaderBytes], mReadbuf.size () - PackedMessage::kHeaderBytes)) + if (msg.ParseFromArray (&m_readBuffer[PackedMessage::kHeaderBytes], + msgLen)) recvErrorMessage (msg); else - WriteLog (lsWARNING, Peer) << "parse error: " << type; + m_journal.warning << "parse error: " << type; } - break; + break; case protocol::mtPING: { event->reName ("Peer::ping"); protocol::TMPing msg; - if (msg.ParseFromArray (&mReadbuf[PackedMessage::kHeaderBytes], mReadbuf.size () - PackedMessage::kHeaderBytes)) + if (msg.ParseFromArray (&m_readBuffer[PackedMessage::kHeaderBytes], + msgLen)) recvPing (msg); else - WriteLog (lsWARNING, Peer) << "parse error: " << type; + m_journal.warning << "parse error: " << type; } - break; + break; case protocol::mtGET_CONTACTS: { event->reName ("Peer::getcontacts"); protocol::TMGetContacts msg; - if (msg.ParseFromArray (&mReadbuf[PackedMessage::kHeaderBytes], mReadbuf.size () - PackedMessage::kHeaderBytes)) + if (msg.ParseFromArray (&m_readBuffer[PackedMessage::kHeaderBytes], + msgLen)) recvGetContacts (msg); else - WriteLog (lsWARNING, Peer) << "parse error: " << type; + m_journal.warning << "parse error: " << type; } - break; + break; case protocol::mtCONTACT: { event->reName ("Peer::contact"); protocol::TMContact msg; - if (msg.ParseFromArray (&mReadbuf[PackedMessage::kHeaderBytes], mReadbuf.size () - PackedMessage::kHeaderBytes)) + if (msg.ParseFromArray (&m_readBuffer[PackedMessage::kHeaderBytes], + msgLen)) recvContact (msg); else - WriteLog (lsWARNING, Peer) << "parse error: " << type; + m_journal.warning << "parse error: " << type; } - break; + break; case protocol::mtGET_PEERS: { event->reName ("Peer::getpeers"); protocol::TMGetPeers msg; - if (msg.ParseFromArray (&mReadbuf[PackedMessage::kHeaderBytes], mReadbuf.size () - PackedMessage::kHeaderBytes)) - recvGetPeers (msg); + if (msg.ParseFromArray (&m_readBuffer[PackedMessage::kHeaderBytes], + msgLen)) + recvGetPeers (msg, lock); else - WriteLog (lsWARNING, Peer) << "parse error: " << type; + m_journal.warning << "parse error: " << type; } - break; + break; case protocol::mtPEERS: { event->reName ("Peer::peers"); protocol::TMPeers msg; - if (msg.ParseFromArray (&mReadbuf[PackedMessage::kHeaderBytes], mReadbuf.size () - PackedMessage::kHeaderBytes)) + if (msg.ParseFromArray (&m_readBuffer[PackedMessage::kHeaderBytes], + msgLen)) recvPeers (msg); else - WriteLog (lsWARNING, Peer) << "parse error: " << type; + m_journal.warning << "parse error: " << type; } - break; + break; case protocol::mtENDPOINTS: { event->reName ("Peer::endpoints"); protocol::TMEndpoints msg; - if(msg.ParseFromArray (&mReadbuf[PackedMessage::kHeaderBytes], mReadbuf.size() - PackedMessage::kHeaderBytes)) + if(msg.ParseFromArray (&m_readBuffer[PackedMessage::kHeaderBytes], + msgLen)) recvEndpoints (msg); else - WriteLog (lsWARNING, Peer) << "parse error: " << type;; + m_journal.warning << "parse error: " << type;; } - break; - + break; + case protocol::mtSEARCH_TRANSACTION: { event->reName ("Peer::searchtransaction"); protocol::TMSearchTransaction msg; - if (msg.ParseFromArray (&mReadbuf[PackedMessage::kHeaderBytes], mReadbuf.size () - PackedMessage::kHeaderBytes)) + if (msg.ParseFromArray (&m_readBuffer[PackedMessage::kHeaderBytes], + msgLen)) recvSearchTransaction (msg); else - WriteLog (lsWARNING, Peer) << "parse error: " << type; + m_journal.warning << "parse error: " << type; } - break; + break; case protocol::mtGET_ACCOUNT: { event->reName ("Peer::getaccount"); protocol::TMGetAccount msg; - if (msg.ParseFromArray (&mReadbuf[PackedMessage::kHeaderBytes], mReadbuf.size () - PackedMessage::kHeaderBytes)) + if (msg.ParseFromArray (&m_readBuffer[PackedMessage::kHeaderBytes], + msgLen)) recvGetAccount (msg); else - WriteLog (lsWARNING, Peer) << "parse error: " << type; + m_journal.warning << "parse error: " << type; } - break; + break; case protocol::mtACCOUNT: { event->reName ("Peer::account"); protocol::TMAccount msg; - if (msg.ParseFromArray (&mReadbuf[PackedMessage::kHeaderBytes], mReadbuf.size () - PackedMessage::kHeaderBytes)) + if (msg.ParseFromArray (&m_readBuffer[PackedMessage::kHeaderBytes], + msgLen)) recvAccount (msg); else - WriteLog (lsWARNING, Peer) << "parse error: " << type; + m_journal.warning << "parse error: " << type; } - break; + break; case protocol::mtTRANSACTION: { event->reName ("Peer::transaction"); protocol::TMTransaction msg; - if (msg.ParseFromArray (&mReadbuf[PackedMessage::kHeaderBytes], mReadbuf.size () - PackedMessage::kHeaderBytes)) - recvTransaction (msg); + if (msg.ParseFromArray (&m_readBuffer[PackedMessage::kHeaderBytes], + msgLen)) + recvTransaction (msg, lock); else - WriteLog (lsWARNING, Peer) << "parse error: " << type; + m_journal.warning << "parse error: " << type; } - break; + break; case protocol::mtSTATUS_CHANGE: { event->reName ("Peer::statuschange"); protocol::TMStatusChange msg; - if (msg.ParseFromArray (&mReadbuf[PackedMessage::kHeaderBytes], mReadbuf.size () - PackedMessage::kHeaderBytes)) + if (msg.ParseFromArray (&m_readBuffer[PackedMessage::kHeaderBytes], + msgLen)) recvStatus (msg); else - WriteLog (lsWARNING, Peer) << "parse error: " << type; + m_journal.warning << "parse error: " << type; } - break; + break; case protocol::mtPROPOSE_LEDGER: { event->reName ("Peer::propose"); boost::shared_ptr msg = boost::make_shared (); - if (msg->ParseFromArray (&mReadbuf[PackedMessage::kHeaderBytes], mReadbuf.size () - PackedMessage::kHeaderBytes)) + if (msg->ParseFromArray (&m_readBuffer[PackedMessage::kHeaderBytes], + msgLen)) recvPropose (msg); else - WriteLog (lsWARNING, Peer) << "parse error: " << type; + m_journal.warning << "parse error: " << type; } - break; + break; case protocol::mtGET_LEDGER: { event->reName ("Peer::getledger"); - boost::shared_ptr msg = boost::make_shared (); + protocol::TMGetLedger msg; - if (msg->ParseFromArray (&mReadbuf[PackedMessage::kHeaderBytes], mReadbuf.size () - PackedMessage::kHeaderBytes)) - recvGetLedger (msg); + if (msg.ParseFromArray (&m_readBuffer[PackedMessage::kHeaderBytes], + msgLen)) + recvGetLedger (msg, lock); else - WriteLog (lsWARNING, Peer) << "parse error: " << type; + m_journal.warning << "parse error: " << type; } - break; + break; case protocol::mtLEDGER_DATA: { event->reName ("Peer::ledgerdata"); boost::shared_ptr msg = boost::make_shared (); - if (msg->ParseFromArray (&mReadbuf[PackedMessage::kHeaderBytes], mReadbuf.size () - PackedMessage::kHeaderBytes)) - recvLedger (msg); + if (msg->ParseFromArray (&m_readBuffer[PackedMessage::kHeaderBytes], + msgLen)) + recvLedger (msg, lock); else - WriteLog (lsWARNING, Peer) << "parse error: " << type; + m_journal.warning << "parse error: " << type; } - break; + break; case protocol::mtHAVE_SET: { event->reName ("Peer::haveset"); protocol::TMHaveTransactionSet msg; - if (msg.ParseFromArray (&mReadbuf[PackedMessage::kHeaderBytes], mReadbuf.size () - PackedMessage::kHeaderBytes)) + if (msg.ParseFromArray (&m_readBuffer[PackedMessage::kHeaderBytes], + msgLen)) recvHaveTxSet (msg); else - WriteLog (lsWARNING, Peer) << "parse error: " << type; + m_journal.warning << "parse error: " << type; } - break; + break; case protocol::mtVALIDATION: { event->reName ("Peer::validation"); boost::shared_ptr msg = boost::make_shared (); - if (msg->ParseFromArray (&mReadbuf[PackedMessage::kHeaderBytes], mReadbuf.size () - PackedMessage::kHeaderBytes)) - recvValidation (msg); + if (msg->ParseFromArray (&m_readBuffer[PackedMessage::kHeaderBytes], + msgLen)) + recvValidation (msg, lock); else - WriteLog (lsWARNING, Peer) << "parse error: " << type; + m_journal.warning << "parse error: " << type; } - break; - #if 0 + break; +#if 0 case protocol::mtGET_VALIDATION: { protocol::TM msg; - if (msg.ParseFromArray (&mReadbuf[PackedMessage::kHeaderBytes], mReadbuf.size () - PackedMessage::kHeaderBytes)) + if (msg.ParseFromArray (&m_readBuffer[PackedMessage::kHeaderBytes], msgLen)) recv (msg); else - WriteLog (lsWARNING, Peer) << "parse error: " << type; + m_journal.warning << "parse error: " << type; } - break; + break; - #endif +#endif case protocol::mtGET_OBJECTS: { event->reName ("Peer::getobjects"); - boost::shared_ptr msg = boost::make_shared (); + boost::shared_ptr msg = + boost::make_shared (); - if (msg->ParseFromArray (&mReadbuf[PackedMessage::kHeaderBytes], mReadbuf.size () - PackedMessage::kHeaderBytes)) - recvGetObjectByHash (msg); + if (msg->ParseFromArray (&m_readBuffer[PackedMessage::kHeaderBytes], + msgLen)) + recvGetObjectByHash (msg, lock); else - WriteLog (lsWARNING, Peer) << "parse error: " << type; + m_journal.warning << "parse error: " << type; } - break; + break; case protocol::mtPROOFOFWORK: { event->reName ("Peer::proofofwork"); protocol::TMProofWork msg; - if (msg.ParseFromArray (&mReadbuf[PackedMessage::kHeaderBytes], mReadbuf.size () - PackedMessage::kHeaderBytes)) + if (msg.ParseFromArray (&m_readBuffer[PackedMessage::kHeaderBytes], + msgLen)) recvProofWork (msg); else - WriteLog (lsWARNING, Peer) << "parse error: " << type; + m_journal.warning << "parse error: " << type; } - break; + break; default: event->reName ("Peer::unknown"); - WriteLog (lsWARNING, Peer) << "Unknown Msg: " << type; - WriteLog (lsWARNING, Peer) << strHex (&mReadbuf[0], mReadbuf.size ()); + m_journal.warning << "Unknown Msg: " << type; + m_journal.warning << strHex (&m_readBuffer[0], m_readBuffer.size ()); } } } -} -void PeerImp::recvHello (protocol::TMHello& packet) -{ - bool bDetach = true; - - (void) mActivityTimer.cancel (); - mActivityTimer.expires_from_now (boost::posix_time::seconds (NODE_IDLE_SECONDS)); - mActivityTimer.async_wait (m_strand.wrap (boost::bind (&PeerImp::handlePingTimer, boost::static_pointer_cast (shared_from_this ()), - boost::asio::placeholders::error))); - - uint32 ourTime = getApp().getOPs ().getNetworkTimeNC (); - uint32 minTime = ourTime - 20; - uint32 maxTime = ourTime + 20; - -#ifdef BEAST_DEBUG - if (packet.has_nettime ()) + void startReadHeader () { - int64 to = ourTime; - to -= packet.nettime (); - WriteLog (lsDEBUG, Peer) << "Connect: time offset " << to; - } - -#endif - - if ((packet.has_testnet () && packet.testnet ()) != getConfig ().TESTNET) - { - // Format: actual/requested. - WriteLog (lsINFO, Peer) << boost::str (boost::format ("Recv(Hello): Network mismatch: %d/%d") - % packet.testnet () - % getConfig ().TESTNET); - } - else if (packet.has_nettime () && ((packet.nettime () < minTime) || (packet.nettime () > maxTime))) - { - if (packet.nettime () > maxTime) + if (!m_detaching) { - WriteLog (lsINFO, Peer) << "Recv(Hello): " << getIP () << " :Clock far off +" << packet.nettime () - ourTime; - } - else if (packet.nettime () < minTime) - { - WriteLog (lsINFO, Peer) << "Recv(Hello): " << getIP () << " :Clock far off -" << ourTime - packet.nettime (); + m_readBuffer.clear (); + m_readBuffer.resize (PackedMessage::kHeaderBytes); + + boost::asio::async_read (getStream (), + boost::asio::buffer (m_readBuffer), + m_strand.wrap (boost::bind (&PeerImp::handleReadHeader, + boost::static_pointer_cast (shared_from_this ()), + boost::asio::placeholders::error, + boost::asio::placeholders::bytes_transferred))); } } - else if (packet.protoversionmin () > BuildInfo::getCurrentProtocol().toPacked ()) - { - WriteLog (lsINFO, Peer) << - "Recv(Hello): Server requires protocol version " << BuildInfo::Protocol (packet.protoversion()).toStdString () << - ", we run " << BuildInfo::getCurrentProtocol().toStdString (); - } - else if (!mNodePublic.setNodePublic (packet.nodepublic ())) - { - WriteLog (lsINFO, Peer) << "Recv(Hello): Disconnect: Bad node public key."; - } - else if (!mNodePublic.verifyNodePublic (mCookieHash, packet.nodeproof ())) - { - // Unable to verify they have private key for claimed public key. - WriteLog (lsINFO, Peer) << "Recv(Hello): Disconnect: Failed to verify session."; - } - else - { - // Successful connection. - WriteLog (lsINFO, Peer) << "Recv(Hello): Connect: " << mNodePublic.humanNodePublic (); - CondLog (BuildInfo::Protocol (packet.protoversion()) != BuildInfo::getCurrentProtocol(), lsINFO, Peer) << - "Peer speaks version " << BuildInfo::Protocol (packet.protoversion()).toStdString (); - mHello = packet; - if (getApp().getUNL ().nodeInCluster (mNodePublic, mNodeName)) + void startReadBody (unsigned msg_len) + { + // The first PackedMessage::kHeaderBytes bytes of m_readbuf already + // contains the header. Expand it to fit in the body as well, and + // start async read into the body. + + if (!m_detaching) { - mCluster = true; - WriteLog (lsINFO, Peer) << "Cluster connection to \"" << (mNodeName.empty () ? getIP () : mNodeName) - << "\" established"; + m_readBuffer.resize (PackedMessage::kHeaderBytes + msg_len); + + boost::asio::async_read (getStream (), + boost::asio::buffer ( + &m_readBuffer [PackedMessage::kHeaderBytes], msg_len), + m_strand.wrap (boost::bind ( + &PeerImp::handleReadBody, + boost::static_pointer_cast (shared_from_this ()), + boost::asio::placeholders::error, + boost::asio::placeholders::bytes_transferred))); + } + } + + void sendPacketForce (const PackedMessage::pointer& packet) + { + // must be on IO strand + if (!m_detaching) + { + mSendingPacket = packet; + + boost::asio::async_write (getStream (), + boost::asio::buffer (packet->getBuffer ()), + m_strand.wrap (boost::bind ( + &PeerImp::handleWrite, + boost::static_pointer_cast (shared_from_this ()), + boost::asio::placeholders::error, + boost::asio::placeholders::bytes_transferred))); + } + } + + /** Hashes the latest finished message from an SSL stream + + @param sslSession the session to get the message from. + @param hash the buffer into which the hash of the retrieved + message will be saved. The buffer MUST be at least + 64 bytes long. + @param getMessage a pointer to the function to call to retrieve the + finished message. This be either: + `SSL_get_finished` or + `SSL_get_peer_finished`. + + @return `true` if successful, `false` otherwise. + + */ + bool hashLatestFinishedMessage (const SSL *sslSession, unsigned char *hash, + size_t (*getFinishedMessage)(const SSL *, void *buf, size_t)) + { + unsigned char buf[1024]; + + // Get our finished message and hash it. + std::memset(hash, 0, 64); + + size_t len = getFinishedMessage (sslSession, buf, sizeof (buf)); + + if(len < sslMinimumFinishedLength) + return false; + + SHA512 (buf, len, hash); + + return true; + } + + /** Generates a secure cookie to protect against man-in-the-middle attacks + + This function should never fail under normal circumstances and regular + server operation. + + A failure prevents the cookie value from being calculated which is an + important component of connection security. If this function fails, a + secure connection cannot be established and the link MUST be dropped. + + @return `true` if the cookie was generated, `false` otherwise. + + @note failure is an exceptional situation - it should never happen and + will almost always indicate an active man-in-the-middle attack is + taking place. + */ + bool calculateSessionCookie () + { + SSL* ssl = m_socket->ssl_handle (); + + if (!ssl) + { + m_journal.error << "Cookie generation: No underlying connection"; + return false; } - if (mClientConnect) + unsigned char sha1[64]; + unsigned char sha2[64]; + + if (!hashLatestFinishedMessage(ssl, sha1, SSL_get_finished)) { - // If we connected due to scan, no longer need to scan. - getApp().getPeers ().peerVerified (shared_from_this ()); + m_journal.error << "Cookie generation: local setup not complete"; + return false; } - if (! getApp().getPeers ().peerHandshake (shared_from_this (), mNodePublic, getIP (), getPort ())) + if (!hashLatestFinishedMessage(ssl, sha2, SSL_get_peer_finished)) { - // Already connected, self, or some other reason. - WriteLog (lsINFO, Peer) << "Recv(Hello): Disconnect: Extraneous connection."; + m_journal.error << "Cookie generation: peer setup not complete"; + return false; + } + + // If both messages hash to the same value (i.e. match) something is + // wrong. This would cause the resulting cookie to be 0. + if (memcmp (sha1, sha2, sizeof (sha1)) == 0) + { + m_journal.error << "Cookie generation: identical finished messages"; + return false; + } + + for (size_t i = 0; i < sizeof (sha1); ++i) + sha1[i] ^= sha2[i]; + + // Finally, derive the actual cookie for the values that we have + // calculated. + m_secureCookie = Serializer::getSHA512Half (sha1, sizeof(sha1)); + + return true; + } + + /** Perform a secure handshake with the peer at the other end. + + If this function returns false then we cannot guarantee that there + is no active man-in-the-middle attack taking place and the link + MUST be disconnected. + + @return true if successful, false otherwise. + */ + bool sendHello () + { + if (!calculateSessionCookie()) + return false; + + Blob vchSig; + getApp().getLocalCredentials ().getNodePrivate ().signNodePrivate (m_secureCookie, vchSig); + + protocol::TMHello h; + + h.set_protoversion (BuildInfo::getCurrentProtocol().toPacked ()); + h.set_protoversionmin (BuildInfo::getMinimumProtocol().toPacked ()); + h.set_fullversion (BuildInfo::getFullVersionString ()); + h.set_nettime (getApp().getOPs ().getNetworkTimeNC ()); + h.set_nodepublic (getApp().getLocalCredentials ().getNodePublic ().humanNodePublic ()); + h.set_nodeproof (&vchSig[0], vchSig.size ()); + h.set_ipv4port (getConfig ().peerListeningPort); + h.set_testnet (false); + + // We always advertise ourselves as private in the HELLO message. This + // suppresses the old peer advertising code and allows PeerFinder to + // take over the functionality. + h.set_nodeprivate (true); + + Ledger::pointer closedLedger = getApp().getLedgerMaster ().getClosedLedger (); + + if (closedLedger && closedLedger->isClosed ()) + { + uint256 hash = closedLedger->getHash (); + h.set_ledgerclosed (hash.begin (), hash.GetSerializeSize ()); + hash = closedLedger->getParentHash (); + h.set_ledgerprevious (hash.begin (), hash.GetSerializeSize ()); + } + + PackedMessage::pointer packet = boost::make_shared ( + h, protocol::mtHELLO); + sendPacket (packet, true); + + return true; + } + + void recvHello (protocol::TMHello& packet) + { + bool bDetach = true; + + (void) mActivityTimer.cancel (); + + uint32 const ourTime (getApp().getOPs ().getNetworkTimeNC ()); + uint32 const minTime (ourTime - clockToleranceDeltaSeconds); + uint32 const maxTime (ourTime + clockToleranceDeltaSeconds); + + #ifdef BEAST_DEBUG + if (packet.has_nettime ()) + { + int64 to = ourTime; + to -= packet.nettime (); + m_journal.debug << "Connect: time offset " << to; + } + + #endif + + BuildInfo::Protocol protocol (packet.protoversion()); + + if (packet.has_nettime () && + ((packet.nettime () < minTime) || (packet.nettime () > maxTime))) + { + if (packet.nettime () > maxTime) + { + m_journal.info << "Hello: Clock for " << *this << + " is off by +" << packet.nettime () - ourTime; + } + else if (packet.nettime () < minTime) + { + m_journal.info << "Hello: Clock for " << *this << + " is off by -" << ourTime - packet.nettime (); + } + } + else if (packet.protoversionmin () > BuildInfo::getCurrentProtocol().toPacked ()) + { + std::string reqVersion ( + protocol.toStdString ()); + + std::string curVersion ( + BuildInfo::getCurrentProtocol().toStdString ()); + + m_journal.info << "Hello: Disconnect: Protocol mismatch [" << + "Peer expects " << reqVersion << + " and we run " << curVersion << "]"; + } + else if (!m_nodePublicKey.setNodePublic (packet.nodepublic ())) + { + m_journal.info << "Hello: Disconnect: Bad node public key."; + } + else if (!m_nodePublicKey.verifyNodePublic (m_secureCookie, packet.nodeproof ())) + { + // Unable to verify they have private key for claimed public key. + m_journal.info << "Hello: Disconnect: Failed to verify session."; } else { - if (mClientConnect) - { - // No longer connecting as client. - mClientConnect = false; - } - else - { - try - { - // Take a guess at remotes address. - std::string strIP = getNativeSocket ().remote_endpoint ().address ().to_string (); - int iPort = packet.ipv4port (); + // Successful connection. + m_journal.info << "Hello: Connect: " << m_nodePublicKey.humanNodePublic (); - if (mHello.nodeprivate ()) - { - WriteLog (lsINFO, Peer) << boost::str (boost::format ("Recv(Hello): Private connection: %s %s") % strIP % iPort); - } - else - { - // Don't save IP address if the node wants privacy. - // Note: We don't go so far as to delete it. If a node which has previously announced itself now wants - // privacy, it should at least change its port. - getApp().getPeers ().savePeer (strIP, iPort, UniqueNodeList::vsInbound); - } - } - catch (boost::system::system_error const&) - { - } + if ((protocol != BuildInfo::getCurrentProtocol()) && + m_journal.active(Journal::Severity::kInfo)) + { + m_journal.info << "Peer protocol: " << protocol.toStdString (); } - // Consider us connected. No longer accepting mtHELLO. - mHelloed = true; + mHello = packet; + + // Determine if this peer belongs to our cluster and get it's name + m_clusterNode = getApp().getUNL().nodeInCluster (m_nodePublicKey, m_nodeName); + + if(m_clusterNode) + m_journal.info << "Connected to cluster node " << m_nodeName; + + bassert (m_state == stateConnected); + m_state = stateHandshaked; + + m_peerFinder.onPeerHandshake (m_remoteAddress, + RipplePublicKey(m_nodePublicKey), m_clusterNode); // XXX Set timer: connection is in grace period to be useful. // XXX Set timer: connection idle (idle may vary depending on connection type.) - - if ((packet.has_ledgerclosed ()) && (packet.ledgerclosed ().size () == (256 / 8))) + if ((mHello.has_ledgerclosed ()) && (mHello.ledgerclosed ().size () == (256 / 8))) { - memcpy (mClosedLedgerHash.begin (), packet.ledgerclosed ().data (), 256 / 8); + memcpy (m_closedLedgerHash.begin (), mHello.ledgerclosed ().data (), 256 / 8); - if ((packet.has_ledgerprevious ()) && (packet.ledgerprevious ().size () == (256 / 8))) + if ((mHello.has_ledgerprevious ()) && (mHello.ledgerprevious ().size () == (256 / 8))) { - memcpy (mPreviousLedgerHash.begin (), packet.ledgerprevious ().data (), 256 / 8); - addLedger (mPreviousLedgerHash); + memcpy (m_previousLedgerHash.begin (), mHello.ledgerprevious ().data (), 256 / 8); + addLedger (m_previousLedgerHash); + } + else + { + m_previousLedgerHash.zero (); } - else mPreviousLedgerHash.zero (); } bDetach = false; } - } - if (bDetach) - { - mNodePublic.clear (); - detach ("recvh", true); - } - else - { - sendGetPeers (); - } -} - -static void checkTransaction (Job&, int flags, SerializedTransaction::pointer stx, boost::weak_ptr peer) -{ - -#ifndef TRUST_NETWORK - - try - { -#endif - Transaction::pointer tx; - - if (isSetBit (flags, SF_SIGGOOD)) - tx = boost::make_shared (stx, false); - else - tx = boost::make_shared (stx, true); - - if (tx->getStatus () == INVALID) + if (bDetach) { - getApp().getHashRouter ().setFlag (stx->getTransactionID (), SF_BAD); - Peer::charge (peer, Resource::feeInvalidSignature); - return; + m_nodePublicKey.clear (); + detach ("recvh", true); } else - getApp().getHashRouter ().setFlag (stx->getTransactionID (), SF_SIGGOOD); - - getApp().getOPs ().processTransaction (tx, isSetBit (flags, SF_TRUSTED), false); - -#ifndef TRUST_NETWORK - } - catch (...) - { - getApp().getHashRouter ().setFlag (stx->getTransactionID (), SF_BAD); - Peer::charge (peer, Resource::feeInvalidRequest); - } - -#endif -} - -void PeerImp::recvTransaction (protocol::TMTransaction& packet) -{ - Transaction::pointer tx; -#ifndef TRUST_NETWORK - - try - { -#endif - Serializer s (packet.rawtransaction ()); - SerializerIterator sit (s); - SerializedTransaction::pointer stx = boost::make_shared (boost::ref (sit)); - uint256 txID = stx->getTransactionID(); - - int flags; - - if (! getApp().getHashRouter ().addSuppressionPeer (txID, mPeerId, flags)) { - // we have seen this transaction recently - if (isSetBit (flags, SF_BAD)) + sendGetPeers (); + } + } + + void recvCluster (protocol::TMCluster& packet) + { + if (!m_clusterNode) + { + charge (Resource::feeUnwantedData); + return; + } + + for (int i = 0; i < packet.clusternodes().size(); ++i) + { + protocol::TMClusterNode const& node = packet.clusternodes(i); + + std::string name; + if (node.has_nodename()) + name = node.nodename(); + ClusterNodeStatus s(name, node.nodeload(), node.reporttime()); + + RippleAddress nodePub; + nodePub.setNodePublic(node.publickey()); + + getApp().getUNL().nodeUpdate(nodePub, s); + } + + int loadSources = packet.loadsources().size(); + if (loadSources != 0) + { + Resource::Gossip gossip; + gossip.items.reserve (loadSources); + for (int i = 0; i < packet.loadsources().size(); ++i) { - charge (Resource::feeInvalidSignature); + protocol::TMLoadSource const& node = packet.loadsources (i); + Resource::Gossip::Item item; + item.address = IPAddress::from_string (node.name()); + item.balance = node.cost(); + if (item.address != IPAddress()) + gossip.items.push_back(item); + } + m_resourceManager.importConsumers (m_nodeName, gossip); + } + + getApp().getFeeTrack().setClusterFee(getApp().getUNL().getClusterFee()); + } + + + void recvTransaction (protocol::TMTransaction& packet, Application::ScopedLockType& masterLockHolder) + { + masterLockHolder.unlock (); + Transaction::pointer tx; + + #ifndef TRUST_NETWORK + try + { + #endif + Serializer s (packet.rawtransaction ()); + SerializerIterator sit (s); + SerializedTransaction::pointer stx = boost::make_shared (boost::ref (sit)); + uint256 txID = stx->getTransactionID(); + + int flags; + + if (! getApp().getHashRouter ().addSuppressionPeer (txID, m_shortId, flags)) + { + // we have seen this transaction recently + if (isSetBit (flags, SF_BAD)) + { + charge (Resource::feeInvalidSignature); + return; + } + + if (!isSetBit (flags, SF_RETRY)) + return; + } + + m_journal.debug << "Got transaction from peer " << *this << ": " << txID; + + if (m_clusterNode) + flags |= SF_TRUSTED | SF_SIGGOOD; + + if (getApp().getJobQueue().getJobCount(jtTRANSACTION) > 100) + m_journal.info << "Transaction queue is full"; + else if (getApp().getLedgerMaster().getValidatedLedgerAge() > 240) + m_journal.info << "No new transactions until synchronized"; + else + getApp().getJobQueue ().addJob (jtTRANSACTION, + "recvTransaction->checkTransaction", + BIND_TYPE ( + &PeerImp::checkTransaction, P_1, flags, stx, + boost::weak_ptr (shared_from_this ()))); + + #ifndef TRUST_NETWORK + } + catch (...) + { + #ifdef BEAST_DEBUG + Log::out() << "Transaction from peer fails validity tests"; + Json::StyledStreamWriter w; + // VFALCO NOTE This bypasses the Log bottleneck + w.write (std::cerr, tx->getJson (0)); + #endif + return; + } + #endif + } + + void recvValidation (const boost::shared_ptr& packet, Application::ScopedLockType& masterLockHolder) + { + uint32 closeTime = getApp().getOPs().getCloseTimeNC(); + masterLockHolder.unlock (); + + if (packet->validation ().size () < 50) + { + m_journal.warning << "Too small validation from peer"; + charge (Resource::feeInvalidRequest); + return; + } + + #ifndef TRUST_NETWORK + + try + #endif + { + Serializer s (packet->validation ()); + SerializerIterator sit (s); + SerializedValidation::pointer val = boost::make_shared (boost::ref (sit), false); + + if (closeTime > (120 + val->getFieldU32(sfSigningTime))) + { + m_journal.trace << "Validation is more than two minutes old"; + charge (Resource::feeUnwantedData); return; } - if (!isSetBit (flags, SF_RETRY)) + if (! getApp().getHashRouter ().addSuppressionPeer (s.getSHA512Half(), m_shortId)) + { + m_journal.trace << "Validation is duplicate"; return; + } + + bool isTrusted = getApp().getUNL ().nodeInUNL (val->getSignerPublic ()); + if (isTrusted || !getApp().getFeeTrack ().isLoadedLocal ()) + { + getApp().getJobQueue ().addJob ( + isTrusted ? jtVALIDATION_t : jtVALIDATION_ut, + "recvValidation->checkValidation", + BIND_TYPE ( + &PeerImp::checkValidation, P_1, &m_peers, val, + isTrusted, m_clusterNode, packet, + boost::weak_ptr (shared_from_this ()))); + } + else + m_journal.debug << "Dropping UNTRUSTED validation due to load"; } - WriteLog (lsDEBUG, Peer) << "Got transaction from peer " << getDisplayName () << " : " << txID; - - if (mCluster) - flags |= SF_TRUSTED | SF_SIGGOOD; - - if (getApp().getJobQueue().getJobCount(jtTRANSACTION) > 100) - WriteLog(lsINFO, Peer) << "Transaction queue is full"; - else if (getApp().getLedgerMaster().getValidatedLedgerAge() > 240) - WriteLog(lsINFO, Peer) << "No new transactions until synchronized"; - else - getApp().getJobQueue ().addJob (jtTRANSACTION, "recvTransaction->checkTransaction", - BIND_TYPE (&checkTransaction, P_1, flags, stx, boost::weak_ptr (shared_from_this ()))); - -#ifndef TRUST_NETWORK - } - catch (...) - { -#ifdef BEAST_DEBUG - Log::out() << "Transaction from peer fails validity tests"; - Json::StyledStreamWriter w; - // VFALCO NOTE This bypasses the Log bottleneck - w.write (std::cerr, tx->getJson (0)); -#endif - return; - } - -#endif - -} - -// Called from our JobQueue -static void checkPropose (Job& job, boost::shared_ptr packet, - LedgerProposal::pointer proposal, uint256 consensusLCL, RippleAddress nodePublic, - boost::weak_ptr peer, bool fromCluster) -{ - bool sigGood = false; - bool isTrusted = (job.getType () == jtPROPOSAL_t); - - WriteLog (lsTRACE, Peer) << "Checking " << (isTrusted ? "trusted" : "UNtrusted") << " proposal"; - - assert (packet); - protocol::TMProposeSet& set = *packet; - - uint256 prevLedger; - - if (set.has_previousledger ()) - { - // proposal includes a previous ledger - WriteLog (lsTRACE, Peer) << "proposal with previous ledger"; - memcpy (prevLedger.begin (), set.previousledger ().data (), 256 / 8); - - if (!fromCluster && !proposal->checkSign (set.signature ())) + #ifndef TRUST_NETWORK + catch (...) { - Peer::pointer p = peer.lock (); - WriteLog (lsWARNING, Peer) << "proposal with previous ledger fails signature check: " << - (p ? p->getIP () : std::string ("???")); - Peer::charge (peer, Resource::feeInvalidSignature); + m_journal.warning << "Exception processing validation"; + charge (Resource::feeInvalidRequest); + } + + #endif + } + + void recvGetValidation (protocol::TMGetValidations& packet) + { + } + + void recvContact (protocol::TMContact& packet) + { + } + + void recvGetContacts (protocol::TMGetContacts& packet) + { + } + + // Return a list of your favorite people + // TODO: filter out all the LAN peers + void recvGetPeers (protocol::TMGetPeers& packet, Application::ScopedLockType& masterLockHolder) + { + masterLockHolder.unlock (); + +#if 0 + protocol::TMPeers peers; + + // CODEME: This is deprecated because of PeerFinder, but populate the + // response with some data here anyways, and send if non-empty. + + sendPacket ( + boost::make_shared (peers, protocol::mtPEERS), + true); +#endif + } + + // TODO: filter out all the LAN peers + void recvPeers (protocol::TMPeers& packet) + { + std::vector list; + list.reserve (packet.nodes().size()); + for (int i = 0; i < packet.nodes ().size (); ++i) + { + in_addr addr; + + addr.s_addr = packet.nodes (i).ipv4 (); + + { + IP::AddressV4 v4 (ntohl (addr.s_addr)); + IPAddress address (v4, packet.nodes (i).ipv4port ()); + list.push_back (address); + } + } + + if (! list.empty()) + m_peerFinder.onLegacyEndpoints (list); + } + + void recvEndpoints (protocol::TMEndpoints& packet) + { + std::vector endpoints; + + endpoints.reserve (packet.endpoints().size()); + + for (int i = 0; i < packet.endpoints ().size (); ++i) + { + PeerFinder::Endpoint endpoint; + protocol::TMEndpoint const& tm (packet.endpoints(i)); + + // hops + endpoint.hops = tm.hops(); + + // ipv4 + if (endpoint.hops > 0) + { + in_addr addr; + addr.s_addr = tm.ipv4().ipv4(); + IP::AddressV4 v4 (ntohl (addr.s_addr)); + endpoint.address = IPAddress (v4, tm.ipv4().ipv4port ()); + } + else + { + // This Endpoint describes the peer we are connected to. + // We will take the remote address seen on the socket and + // store that in the IPAddress. If this is the first time, + // then we'll verify that their listener can receive incoming + // by performing a connectivity test. + // + endpoint.address = m_remoteAddress.at_port ( + tm.ipv4().ipv4port ()); + } + + endpoints.push_back (endpoint); + } + + if (! endpoints.empty()) + m_peerFinder.onPeerEndpoints (m_remoteAddress, endpoints); + } + + void recvGetObjectByHash (const boost::shared_ptr& ptr, + Application::ScopedLockType& masterLockHolder) + { + masterLockHolder.unlock(); + + protocol::TMGetObjectByHash& packet = *ptr; + + if (packet.query ()) + { + // this is a query + if (packet.type () == protocol::TMGetObjectByHash::otFETCH_PACK) + { + doFetchPack (ptr); + return; + } + + protocol::TMGetObjectByHash reply; + + reply.set_query (false); + + if (packet.has_seq ()) + reply.set_seq (packet.seq ()); + + reply.set_type (packet.type ()); + + if (packet.has_ledgerhash ()) + reply.set_ledgerhash (packet.ledgerhash ()); + + // This is a very minimal implementation + for (int i = 0; i < packet.objects_size (); ++i) + { + uint256 hash; + const protocol::TMIndexedObject& obj = packet.objects (i); + + if (obj.has_hash () && (obj.hash ().size () == (256 / 8))) + { + memcpy (hash.begin (), obj.hash ().data (), 256 / 8); + NodeObject::pointer hObj = getApp().getNodeStore ().fetch (hash); + + if (hObj) + { + protocol::TMIndexedObject& newObj = *reply.add_objects (); + newObj.set_hash (hash.begin (), hash.size ()); + newObj.set_data (&hObj->getData ().front (), hObj->getData ().size ()); + + if (obj.has_nodeid ()) + newObj.set_index (obj.nodeid ()); + + if (!reply.has_seq () && (hObj->getIndex () != 0)) + reply.set_seq (hObj->getIndex ()); + } + } + } + + m_journal.trace << "GetObjByHash had " << reply.objects_size () << + " of " << packet.objects_size () << + " for " << to_string (this); + sendPacket ( + boost::make_shared (reply, protocol::mtGET_OBJECTS), + true); + } + else + { + // this is a reply + uint32 pLSeq = 0; + bool pLDo = true; + bool progress = false; + + for (int i = 0; i < packet.objects_size (); ++i) + { + const protocol::TMIndexedObject& obj = packet.objects (i); + + if (obj.has_hash () && (obj.hash ().size () == (256 / 8))) + { + + if (obj.has_ledgerseq ()) + { + if (obj.ledgerseq () != pLSeq) + { + if ((pLDo && (pLSeq != 0)) && + m_journal.active(Journal::Severity::kDebug)) + m_journal.debug << "Received full fetch pack for " << pLSeq; + + pLSeq = obj.ledgerseq (); + pLDo = !getApp().getOPs ().haveLedger (pLSeq); + + if (!pLDo) + m_journal.debug << "Got pack for " << pLSeq << " too late"; + else + progress = true; + } + } + + if (pLDo) + { + uint256 hash; + memcpy (hash.begin (), obj.hash ().data (), 256 / 8); + + boost::shared_ptr< Blob > data ( + boost::make_shared< Blob > ( + obj.data ().begin (), obj.data ().end ())); + + getApp().getOPs ().addFetchPack (hash, data); + } + } + } + + if ((pLDo && (pLSeq != 0)) && + m_journal.active(Journal::Severity::kDebug)) + m_journal.debug << "Received partial fetch pack for " << pLSeq; + + if (packet.type () == protocol::TMGetObjectByHash::otFETCH_PACK) + getApp().getOPs ().gotFetchPack (progress, pLSeq); + } + } + + void recvPing (protocol::TMPing& packet) + { + if (packet.type () == protocol::TMPing::ptPING) + { + packet.set_type (protocol::TMPing::ptPONG); + sendPacket (boost::make_shared (packet, protocol::mtPING), true); + } + } + + void recvErrorMessage (protocol::TMErrorMsg& packet) + { + } + + void recvSearchTransaction (protocol::TMSearchTransaction& packet) + { + } + + void recvGetAccount (protocol::TMGetAccount& packet) + { + } + + void recvAccount (protocol::TMAccount& packet) + { + } + + void recvGetLedger (protocol::TMGetLedger& packet, + Application::ScopedLockType& masterLockHolder) + { + SHAMap::pointer map; + protocol::TMLedgerData reply; + bool fatLeaves = true, fatRoot = false; + + if (packet.has_requestcookie ()) + reply.set_requestcookie (packet.requestcookie ()); + + std::string logMe; + + if (packet.itype () == protocol::liTS_CANDIDATE) + { + // Request is for a transaction candidate set + m_journal.trace << "Received request for TX candidate set data " << + to_string (this); + + if ((!packet.has_ledgerhash () || packet.ledgerhash ().size () != 32)) + { + charge (Resource::feeInvalidRequest); + m_journal.warning << "invalid request for TX candidate set data"; + return; + } + + uint256 txHash; + memcpy (txHash.begin (), packet.ledgerhash ().data (), 32); + map = getApp().getOPs ().getTXMap (txHash); + masterLockHolder.unlock(); + + if (!map) + { + if (packet.has_querytype () && !packet.has_requestcookie ()) + { + m_journal.debug << "Trying to route TX set request"; + + Peers::PeerSequence usablePeers (m_peers.foreach ( + get_usable_peers (txHash, this))); + + if (usablePeers.empty ()) + { + m_journal.info << "Unable to route TX set request"; + return; + } + + Peer::ref selectedPeer = usablePeers[rand () % usablePeers.size ()]; + packet.set_requestcookie (getShortId ()); + selectedPeer->sendPacket ( + boost::make_shared (packet, protocol::mtGET_LEDGER), + false); + return; + } + + m_journal.error << "We do not have the map our peer wants " << + to_string (this); + charge (Resource::feeInvalidRequest); + return; + } + + reply.set_ledgerseq (0); + reply.set_ledgerhash (txHash.begin (), txHash.size ()); + reply.set_type (protocol::liTS_CANDIDATE); + fatLeaves = false; // We'll already have most transactions + fatRoot = true; // Save a pass + } + else + { + masterLockHolder.unlock (); + + if (getApp().getFeeTrack().isLoadedLocal() && !m_clusterNode) + { + m_journal.debug << "Too busy to fetch ledger data"; + return; + } + + // Figure out what ledger they want + m_journal.trace << "Received request for ledger data " << + to_string (this); + Ledger::pointer ledger; + + if (packet.has_ledgerhash ()) + { + uint256 ledgerhash; + + if (packet.ledgerhash ().size () != 32) + { + charge (Resource::feeInvalidRequest); + m_journal.warning << "Invalid request"; + return; + } + + memcpy (ledgerhash.begin (), packet.ledgerhash ().data (), 32); + logMe += "LedgerHash:"; + logMe += ledgerhash.GetHex (); + ledger = getApp().getLedgerMaster ().getLedgerByHash (ledgerhash); + + CondLog (!ledger, lsTRACE, Peer) << "Don't have ledger " << ledgerhash; + + if (!ledger && (packet.has_querytype () && !packet.has_requestcookie ())) + { + uint32 seq = 0; + + if (packet.has_ledgerseq ()) + seq = packet.ledgerseq (); + + Peers::PeerSequence peerList = m_peers.getActivePeers (); + Peers::PeerSequence usablePeers; + BOOST_FOREACH (Peer::ref peer, peerList) + { + if (peer->hasLedger (ledgerhash, seq) && (peer.get () != this)) + usablePeers.push_back (peer); + } + + if (usablePeers.empty ()) + { + m_journal.trace << "Unable to route ledger request"; + return; + } + + Peer::ref selectedPeer = usablePeers[rand () % usablePeers.size ()]; + packet.set_requestcookie (getShortId ()); + selectedPeer->sendPacket (boost::make_shared (packet, protocol::mtGET_LEDGER), false); + m_journal.debug << "Ledger request routed"; + return; + } + } + else if (packet.has_ledgerseq ()) + { + ledger = getApp().getLedgerMaster ().getLedgerBySeq (packet.ledgerseq ()); + CondLog (!ledger, lsDEBUG, Peer) << "Don't have ledger " << packet.ledgerseq (); + } + else if (packet.has_ltype () && (packet.ltype () == protocol::ltCURRENT)) + ledger = getApp().getLedgerMaster ().getCurrentLedger (); + else if (packet.has_ltype () && (packet.ltype () == protocol::ltCLOSED) ) + { + ledger = getApp().getLedgerMaster ().getClosedLedger (); + + if (ledger && !ledger->isClosed ()) + ledger = getApp().getLedgerMaster ().getLedgerBySeq (ledger->getLedgerSeq () - 1); + } + else + { + charge (Resource::feeInvalidRequest); + m_journal.warning << "Can't figure out what ledger they want"; + return; + } + + if ((!ledger) || (packet.has_ledgerseq () && (packet.ledgerseq () != ledger->getLedgerSeq ()))) + { + charge (Resource::feeInvalidRequest); + + if (ShouldLog (lsWARNING, Peer)) + { + if (ledger) + Log (lsWARNING) << "Ledger has wrong sequence"; + } + + return; + } + + // Fill out the reply + uint256 lHash = ledger->getHash (); + reply.set_ledgerhash (lHash.begin (), lHash.size ()); + reply.set_ledgerseq (ledger->getLedgerSeq ()); + reply.set_type (packet.itype ()); + + if (packet.itype () == protocol::liBASE) + { + // they want the ledger base data + m_journal.trace << "They want ledger base data"; + Serializer nData (128); + ledger->addRaw (nData); + reply.add_nodes ()->set_nodedata (nData.getDataPtr (), nData.getLength ()); + + SHAMap::pointer map = ledger->peekAccountStateMap (); + + if (map && map->getHash ().isNonZero ()) + { + // return account state root node if possible + Serializer rootNode (768); + + if (map->getRootNode (rootNode, snfWIRE)) + { + reply.add_nodes ()->set_nodedata (rootNode.getDataPtr (), rootNode.getLength ()); + + if (ledger->getTransHash ().isNonZero ()) + { + map = ledger->peekTransactionMap (); + + if (map && map->getHash ().isNonZero ()) + { + rootNode.erase (); + + if (map->getRootNode (rootNode, snfWIRE)) + reply.add_nodes ()->set_nodedata (rootNode.getDataPtr (), rootNode.getLength ()); + } + } + } + } + + PackedMessage::pointer oPacket = boost::make_shared (reply, protocol::mtLEDGER_DATA); + sendPacket (oPacket, true); + return; + } + + if (packet.itype () == protocol::liTX_NODE) + { + map = ledger->peekTransactionMap (); + logMe += " TX:"; + logMe += map->getHash ().GetHex (); + } + else if (packet.itype () == protocol::liAS_NODE) + { + map = ledger->peekAccountStateMap (); + logMe += " AS:"; + logMe += map->getHash ().GetHex (); + } + } + + if ((!map) || (packet.nodeids_size () == 0)) + { + m_journal.warning << "Can't find map or empty request"; + charge (Resource::feeInvalidRequest); return; } - else - sigGood = true; - } - else - { - if (consensusLCL.isNonZero () && proposal->checkSign (set.signature ())) + + m_journal.trace << "Request: " << logMe; + + for (int i = 0; i < packet.nodeids ().size (); ++i) { - prevLedger = consensusLCL; - sigGood = true; + SHAMapNode mn (packet.nodeids (i).data (), packet.nodeids (i).size ()); + + if (!mn.isValid ()) + { + m_journal.warning << "Request for invalid node: " << logMe; + charge (Resource::feeInvalidRequest); + return; + } + + std::vector nodeIDs; + std::list< Blob > rawNodes; + + try + { + if (map->getNodeFat (mn, nodeIDs, rawNodes, fatRoot, fatLeaves)) + { + assert (nodeIDs.size () == rawNodes.size ()); + m_journal.trace << "getNodeFat got " << rawNodes.size () << " nodes"; + std::vector::iterator nodeIDIterator; + std::list< Blob >::iterator rawNodeIterator; + + for (nodeIDIterator = nodeIDs.begin (), rawNodeIterator = rawNodes.begin (); + nodeIDIterator != nodeIDs.end (); ++nodeIDIterator, ++rawNodeIterator) + { + Serializer nID (33); + nodeIDIterator->addIDRaw (nID); + protocol::TMLedgerNode* node = reply.add_nodes (); + node->set_nodeid (nID.getDataPtr (), nID.getLength ()); + node->set_nodedata (&rawNodeIterator->front (), rawNodeIterator->size ()); + } + } + else + m_journal.warning << "getNodeFat returns false"; + } + catch (std::exception&) + { + std::string info; + + if (packet.itype () == protocol::liTS_CANDIDATE) + info = "TS candidate"; + else if (packet.itype () == protocol::liBASE) + info = "Ledger base"; + else if (packet.itype () == protocol::liTX_NODE) + info = "TX node"; + else if (packet.itype () == protocol::liAS_NODE) + info = "AS node"; + + if (!packet.has_ledgerhash ()) + info += ", no hash specified"; + + m_journal.warning << "getNodeFat( " << mn << ") throws exception: " << info; + } } - else + + PackedMessage::pointer oPacket = boost::make_shared (reply, protocol::mtLEDGER_DATA); + sendPacket (oPacket, true); + } + + void recvLedger (const boost::shared_ptr& packet_ptr, Application::ScopedLockType& masterLockHolder) + { + masterLockHolder.unlock (); + protocol::TMLedgerData& packet = *packet_ptr; + + if (packet.nodes ().size () <= 0) { - WriteLog (lsWARNING, Peer) << "Ledger proposal fails signature check"; // Could be mismatched prev ledger - proposal->setSignature (set.signature ()); + m_journal.warning << "Ledger/TXset data with no nodes"; + charge (Resource::feeInvalidRequest); + return; + } + + if (packet.has_requestcookie ()) + { + Peer::pointer target = m_peers.findPeerByShortID (packet.requestcookie ()); + + if (target) + { + packet.clear_requestcookie (); + target->sendPacket (boost::make_shared (packet, protocol::mtLEDGER_DATA), false); + } + else + { + m_journal.info << "Unable to route TX/ledger data reply"; + charge (Resource::feeUnwantedData); + } + + return; + } + + uint256 hash; + + if (packet.ledgerhash ().size () != 32) + { + m_journal.warning << "TX candidate reply with invalid hash size"; + charge (Resource::feeInvalidRequest); + return; + } + + memcpy (hash.begin (), packet.ledgerhash ().data (), 32); + + if (packet.type () == protocol::liTS_CANDIDATE) + { + // got data for a candidate transaction set + std::list nodeIDs; + std::list< Blob > nodeData; + + for (int i = 0; i < packet.nodes ().size (); ++i) + { + const protocol::TMLedgerNode& node = packet.nodes (i); + + if (!node.has_nodeid () || !node.has_nodedata () || (node.nodeid ().size () != 33)) + { + m_journal.warning << "LedgerData request with invalid node ID"; + charge (Resource::feeInvalidRequest); + return; + } + + nodeIDs.push_back (SHAMapNode (node.nodeid ().data (), node.nodeid ().size ())); + nodeData.push_back (Blob (node.nodedata ().begin (), node.nodedata ().end ())); + } + + SHAMapAddNode san = getApp().getOPs ().gotTXData (shared_from_this (), hash, nodeIDs, nodeData); + + if (san.isInvalid ()) + { + charge (Resource::feeUnwantedData); + } + + return; + } + + if (!getApp().getInboundLedgers ().gotLedgerData (hash, shared_from_this(), packet_ptr)) + { + WriteLog (lsINFO, Peer) << "Got data for unwanted ledger"; + charge (Resource::feeUnwantedData); } } - if (isTrusted) - getApp().getOPs ().processTrustedProposal (proposal, packet, nodePublic, prevLedger, sigGood); - else if (sigGood && (prevLedger == consensusLCL)) + void recvStatus (protocol::TMStatusChange& packet) { - // relay untrusted proposal - WriteLog (lsTRACE, Peer) << "relaying untrusted proposal"; - std::set peers; - getApp().getHashRouter ().swapSet (proposal->getHashRouter (), peers, SF_RELAYED); - PackedMessage::pointer message = boost::make_shared (set, protocol::mtPROPOSE_LEDGER); - getApp().getPeers ().relayMessageBut (peers, message); - } - else - WriteLog (lsDEBUG, Peer) << "Not relaying untrusted proposal"; -} + m_journal.trace << "Received status change from peer " << + to_string (this); -void PeerImp::recvPropose (const boost::shared_ptr& packet) -{ - assert (packet); - protocol::TMProposeSet& set = *packet; + if (!packet.has_networktime ()) + packet.set_networktime (getApp().getOPs ().getNetworkTimeNC ()); - if ((set.currenttxhash ().size () != 32) || (set.nodepubkey ().size () < 28) || - (set.signature ().size () < 56) || (set.nodepubkey ().size () > 128) || (set.signature ().size () > 128)) - { - WriteLog (lsWARNING, Peer) << "Received proposal is malformed"; - charge (Resource::feeInvalidSignature); - return; + if (!mLastStatus.has_newstatus () || packet.has_newstatus ()) + mLastStatus = packet; + else + { + // preserve old status + protocol::NodeStatus status = mLastStatus.newstatus (); + mLastStatus = packet; + packet.set_newstatus (status); + } + + if (packet.newevent () == protocol::neLOST_SYNC) + { + if (!m_closedLedgerHash.isZero ()) + { + m_journal.trace << "peer has lost sync " << to_string (this); + m_closedLedgerHash.zero (); + } + + m_previousLedgerHash.zero (); + return; + } + + if (packet.has_ledgerhash () && (packet.ledgerhash ().size () == (256 / 8))) + { + // a peer has changed ledgers + memcpy (m_closedLedgerHash.begin (), packet.ledgerhash ().data (), 256 / 8); + addLedger (m_closedLedgerHash); + m_journal.trace << "peer LCL is " << m_closedLedgerHash << + " " << to_string (this); + } + else + { + m_journal.trace << "peer has no ledger hash" << to_string (this); + m_closedLedgerHash.zero (); + } + + if (packet.has_ledgerhashprevious () && packet.ledgerhashprevious ().size () == (256 / 8)) + { + memcpy (m_previousLedgerHash.begin (), packet.ledgerhashprevious ().data (), 256 / 8); + addLedger (m_previousLedgerHash); + } + else m_previousLedgerHash.zero (); + + if (packet.has_firstseq () && packet.has_lastseq()) + { + m_minLedger = packet.firstseq (); + m_maxLedger = packet.lastseq (); + + // Work around some servers that report sequences incorrectly + if (m_minLedger == 0) + m_maxLedger = 0; + if (m_maxLedger == 0) + m_minLedger = 0; + } } - if (set.has_previousledger () && (set.previousledger ().size () != 32)) + void recvPropose (const boost::shared_ptr& packet) { - WriteLog (lsWARNING, Peer) << "Received proposal is malformed"; - charge (Resource::feeInvalidRequest); - return; + assert (packet); + protocol::TMProposeSet& set = *packet; + + if ((set.currenttxhash ().size () != 32) || (set.nodepubkey ().size () < 28) || + (set.signature ().size () < 56) || (set.nodepubkey ().size () > 128) || (set.signature ().size () > 128)) + { + m_journal.warning << "Received proposal is malformed"; + charge (Resource::feeInvalidSignature); + return; + } + + if (set.has_previousledger () && (set.previousledger ().size () != 32)) + { + m_journal.warning << "Received proposal is malformed"; + charge (Resource::feeInvalidRequest); + return; + } + + uint256 proposeHash, prevLedger; + memcpy (proposeHash.begin (), set.currenttxhash ().data (), 32); + + if (set.has_previousledger ()) + memcpy (prevLedger.begin (), set.previousledger ().data (), 32); + + Serializer s (512); + s.add256 (proposeHash); + s.add32 (set.proposeseq ()); + s.add32 (set.closetime ()); + s.addVL (set.nodepubkey ()); + s.addVL (set.signature ()); + + if (set.has_previousledger ()) + s.add256 (prevLedger); + + uint256 suppression = s.getSHA512Half (); + + if (! getApp().getHashRouter ().addSuppressionPeer (suppression, m_shortId)) + { + m_journal.trace << "Received duplicate proposal from peer " << m_shortId; + return; + } + + RippleAddress signerPublic = RippleAddress::createNodePublic (strCopy (set.nodepubkey ())); + + if (signerPublic == getConfig ().VALIDATION_PUB) + { + m_journal.trace << "Received our own proposal from peer " << m_shortId; + return; + } + + bool isTrusted = getApp().getUNL ().nodeInUNL (signerPublic); + if (!isTrusted && getApp().getFeeTrack ().isLoadedLocal ()) + { + m_journal.debug << "Dropping UNTRUSTED proposal due to load"; + return; + } + + m_journal.trace << "Received " << (isTrusted ? "trusted" : "UNTRUSTED") << + " proposal from " << m_shortId; + + uint256 consensusLCL = getApp().getOPs ().getConsensusLCL (); + LedgerProposal::pointer proposal = boost::make_shared ( + prevLedger.isNonZero () ? prevLedger : consensusLCL, + set.proposeseq (), proposeHash, set.closetime (), signerPublic, suppression); + + getApp().getJobQueue ().addJob (isTrusted ? jtPROPOSAL_t : jtPROPOSAL_ut, + "recvPropose->checkPropose", BIND_TYPE ( + &PeerImp::checkPropose, P_1, &m_peers, packet, proposal, consensusLCL, + m_nodePublicKey, boost::weak_ptr (shared_from_this ()), m_clusterNode)); } - uint256 proposeHash, prevLedger; - memcpy (proposeHash.begin (), set.currenttxhash ().data (), 32); - - if (set.has_previousledger ()) - memcpy (prevLedger.begin (), set.previousledger ().data (), 32); - - Serializer s (512); - s.add256 (proposeHash); - s.add32 (set.proposeseq ()); - s.add32 (set.closetime ()); - s.addVL (set.nodepubkey ()); - s.addVL (set.signature ()); - - if (set.has_previousledger ()) - s.add256 (prevLedger); - - uint256 suppression = s.getSHA512Half (); - - if (! getApp().getHashRouter ().addSuppressionPeer (suppression, mPeerId)) + void recvHaveTxSet (protocol::TMHaveTransactionSet& packet) { - WriteLog (lsTRACE, Peer) << "Received duplicate proposal from peer " << mPeerId; - return; - } + uint256 hashes; - RippleAddress signerPublic = RippleAddress::createNodePublic (strCopy (set.nodepubkey ())); + if (packet.hash ().size () != (256 / 8)) + { + charge (Resource::feeInvalidRequest); + return; + } - if (signerPublic == getConfig ().VALIDATION_PUB) - { - WriteLog (lsTRACE, Peer) << "Received our own proposal from peer " << mPeerId; - return; - } + uint256 hash; - bool isTrusted = getApp().getUNL ().nodeInUNL (signerPublic); - if (!isTrusted && getApp().getFeeTrack ().isLoadedLocal ()) - { - WriteLog (lsDEBUG, Peer) << "Dropping untrusted proposal due to load"; - return; - } + // VFALCO TODO There should be no use of memcpy() throughout the program. + // TODO Clean up this magic number + // + memcpy (hash.begin (), packet.hash ().data (), 32); - WriteLog (lsTRACE, Peer) << "Received " << (isTrusted ? "trusted" : "UNtrusted") << " proposal from " << mPeerId; + if (packet.status () == protocol::tsHAVE) + addTxSet (hash); - uint256 consensusLCL; - - { - Application::ScopedLockType lock (getApp ().getMasterLock (), __FILE__, __LINE__); - consensusLCL = getApp().getOPs ().getConsensusLCL (); - } - LedgerProposal::pointer proposal = boost::make_shared ( - prevLedger.isNonZero () ? prevLedger : consensusLCL, - set.proposeseq (), proposeHash, set.closetime (), signerPublic, suppression); - - getApp().getJobQueue ().addJob (isTrusted ? jtPROPOSAL_t : jtPROPOSAL_ut, "recvPropose->checkPropose", - BIND_TYPE (&checkPropose, P_1, packet, proposal, consensusLCL, - mNodePublic, boost::weak_ptr (shared_from_this ()), mCluster)); -} - -void PeerImp::recvHaveTxSet (protocol::TMHaveTransactionSet& packet) -{ - uint256 hashes; - - if (packet.hash ().size () != (256 / 8)) - { - charge (Resource::feeInvalidRequest); - return; - } - - uint256 hash; - - // VFALCO TODO There should be no use of memcpy() throughout the program. - // TODO Clean up this magic number - // - memcpy (hash.begin (), packet.hash ().data (), 32); - - if (packet.status () == protocol::tsHAVE) - addTxSet (hash); - - { - Application::ScopedLockType lock (getApp ().getMasterLock (), __FILE__, __LINE__); if (!getApp().getOPs ().hasTXSet (shared_from_this (), hash, packet.status ())) { charge (Resource::feeUnwantedData); } } -} -static void checkValidation (Job&, SerializedValidation::pointer val, bool isTrusted, bool isCluster, - boost::shared_ptr packet, boost::weak_ptr peer) -{ -#ifndef TRUST_NETWORK - - try -#endif + void recvProofWork (protocol::TMProofWork& packet) { - uint256 signingHash = val->getSigningHash(); - if (!isCluster && !val->isValid (signingHash)) + if (packet.has_response ()) { - WriteLog (lsWARNING, Peer) << "Validation is invalid"; - Peer::charge (peer, Resource::feeInvalidRequest); - return; - } - - std::string source; - Peer::pointer lp = peer.lock (); - - if (lp) - source = lp->getDisplayName (); - else - source = "unknown"; - - std::set peers; - - //---------------------------------------------------------------------- - // - { - SerializedValidation const& sv (*val); - Validators::ReceivedValidation rv; - rv.ledgerHash = sv.getLedgerHash (); - rv.publicKey = sv.getSignerPublic(); - getApp ().getValidators ().receiveValidation (rv); - } - // - //---------------------------------------------------------------------- - - if (getApp().getOPs ().recvValidation (val, source) && - getApp().getHashRouter ().swapSet (signingHash, peers, SF_RELAYED)) - { - PackedMessage::pointer message = boost::make_shared (*packet, protocol::mtVALIDATION); - getApp().getPeers ().relayMessageBut (peers, message); - } - } - -#ifndef TRUST_NETWORK - catch (...) - { - WriteLog (lsWARNING, Peer) << "Exception processing validation"; - Peer::charge (peer, Resource::feeInvalidRequest); - } - -#endif -} - -void PeerImp::recvValidation (const boost::shared_ptr& packet) -{ - uint32 closeTime = getApp().getOPs().getCloseTimeNC(); - - if (packet->validation ().size () < 50) - { - WriteLog (lsWARNING, Peer) << "Too small validation from peer"; - charge (Resource::feeInvalidRequest); - return; - } - -#ifndef TRUST_NETWORK - - try -#endif - { - Serializer s (packet->validation ()); - SerializerIterator sit (s); - SerializedValidation::pointer val = boost::make_shared (boost::ref (sit), false); - - if (closeTime > (120 + val->getFieldU32(sfSigningTime))) - { - WriteLog (lsTRACE, Peer) << "Validation is more than two minutes old"; - charge (Resource::feeUnwantedData); - return; - } - - if (! getApp().getHashRouter ().addSuppressionPeer (s.getSHA512Half(), mPeerId)) - { - WriteLog (lsTRACE, Peer) << "Validation is duplicate"; - return; - } - - bool isTrusted = getApp().getUNL ().nodeInUNL (val->getSignerPublic ()); - if (isTrusted || !getApp().getFeeTrack ().isLoadedLocal ()) - getApp().getJobQueue ().addJob (isTrusted ? jtVALIDATION_t : jtVALIDATION_ut, "recvValidation->checkValidation", - BIND_TYPE (&checkValidation, P_1, val, isTrusted, mCluster, packet, - boost::weak_ptr (shared_from_this ()))); - else - WriteLog(lsDEBUG, Peer) << "Dropping untrusted validation due to load"; - } - -#ifndef TRUST_NETWORK - catch (...) - { - WriteLog (lsWARNING, Peer) << "Exception processing validation"; - charge (Resource::feeInvalidRequest); - } - -#endif -} - -void PeerImp::recvCluster (protocol::TMCluster& packet) -{ - if (!mCluster) - { - charge (Resource::feeUnwantedData); - return; - } - - for (int i = 0; i < packet.clusternodes().size(); ++i) - { - protocol::TMClusterNode const& node = packet.clusternodes(i); - - std::string name; - if (node.has_nodename()) - name = node.nodename(); - ClusterNodeStatus s(name, node.nodeload(), node.reporttime()); - - RippleAddress nodePub; - nodePub.setNodePublic(node.publickey()); - - getApp().getUNL().nodeUpdate(nodePub, s); - } - - int loadSources = packet.loadsources().size(); - if (loadSources != 0) - { - Resource::Gossip gossip; - gossip.items.reserve (loadSources); - for (int i = 0; i < packet.loadsources().size(); ++i) - { - protocol::TMLoadSource const& node = packet.loadsources (i); - Resource::Gossip::Item item; - item.address = IPAddress::from_string (node.name()); - item.balance = node.cost(); - if (item.address != IPAddress()) - gossip.items.push_back(item); - } - m_resourceManager.importConsumers (mNodeName, gossip); - } - - getApp().getFeeTrack().setClusterFee(getApp().getUNL().getClusterFee()); -} - -void PeerImp::recvGetValidation (protocol::TMGetValidations& packet) -{ -} - -void PeerImp::recvContact (protocol::TMContact& packet) -{ -} - -void PeerImp::recvGetContacts (protocol::TMGetContacts& packet) -{ -} - -// Return a list of your favorite people -// TODO: filter out all the LAN peers -void PeerImp::recvGetPeers (protocol::TMGetPeers& packet) -{ - std::vector addrs; - - getApp().getPeers ().getTopNAddrs (30, addrs); - - if (!addrs.empty ()) - { - protocol::TMPeers peers; - - for (unsigned int n = 0; n < addrs.size (); n++) - { - std::string strIP; - int iPort; - - try - { - splitIpPort (addrs[n], strIP, iPort); - // XXX This should also ipv6 - protocol::TMIPv4EndPoint* addr = peers.add_nodes (); - addr->set_ipv4 (inet_addr (strIP.c_str ())); - addr->set_ipv4port (iPort); - } - catch (...) - { - WriteLog (lsWARNING, Peer) << "Bad peer in list: " << addrs[n]; - } - - - - //WriteLog (lsINFO, Peer) << "Peer: Teaching: " << addressToString(this) << ": " << n << ": " << strIP << " " << iPort; - } - - PackedMessage::pointer message = boost::make_shared (peers, protocol::mtPEERS); - sendPacket (message, true); - } -} - -// TODO: filter out all the LAN peers -void PeerImp::recvPeers (protocol::TMPeers& packet) -{ - for (int i = 0; i < packet.nodes ().size (); ++i) - { - in_addr addr; - - addr.s_addr = packet.nodes (i).ipv4 (); - - { - IPAddress::V4 v4 (ntohl (addr.s_addr)); - IPAddress ep (v4, packet.nodes (i).ipv4port ()); - getApp().getPeers().getPeerFinder().onPeerLegacyEndpoint (ep); - } - - std::string strIP (inet_ntoa (addr)); - int iPort = packet.nodes (i).ipv4port (); - - if (strIP != "0.0.0.0" && strIP != "127.0.0.1") - { - WriteLog (lsDEBUG, Peer) << "Peer: Learning: " << addressToString(this) << ": " << i << ": " << strIP << " " << iPort; - - getApp().getPeers ().savePeer (strIP, iPort, UniqueNodeList::vsTold); - } - } -} - -void PeerImp::recvEndpoints (protocol::TMEndpoints& packet) -{ - std::vector endpoints; - - endpoints.reserve (packet.endpoints().size()); - - for (int i = 0; i < packet.endpoints ().size (); ++i) - { - PeerFinder::Endpoint endpoint; - protocol::TMEndpoint const& tm (packet.endpoints(i)); - - // hops - endpoint.hops = tm.hops(); - - // ipv4 - if (endpoint.hops > 0) - { - in_addr addr; - addr.s_addr = tm.ipv4().ipv4(); - IPAddress::V4 v4 (ntohl (addr.s_addr)); - endpoint.address = IPAddress (v4, tm.ipv4().ipv4port ()); - } - else - { - // This Endpoint describes the peer we are connected to. - // We will take the remote address seen on the socket and - // store that in the Endpoint. If this is the first time, - // then we'll verify that their listener can receive incoming - // by performing a connectivity test. - // - bassert (m_remoteAddressSet); - endpoint.address = m_remoteAddress.withPort ( - tm.ipv4().ipv4port ()); - } - - // slots - endpoint.incomingSlotsAvailable = tm.slots(); - - // maxSlots - endpoint.incomingSlotsMax = tm.maxslots(); - - // uptimeSeconds - endpoint.uptimeSeconds = tm.uptimeseconds(); - - endpoints.push_back (endpoint); - } - - getApp().getPeers().getPeerFinder().onPeerEndpoints ( - PeerFinder::PeerID (mNodePublic), endpoints); -} - -void PeerImp::recvGetObjectByHash (const boost::shared_ptr& ptr) -{ - protocol::TMGetObjectByHash& packet = *ptr; - - if (packet.query ()) - { - // this is a query - if (packet.type () == protocol::TMGetObjectByHash::otFETCH_PACK) - { - doFetchPack (ptr); - return; - } - - protocol::TMGetObjectByHash reply; - - reply.set_query (false); - - if (packet.has_seq ()) - reply.set_seq (packet.seq ()); - - reply.set_type (packet.type ()); - - if (packet.has_ledgerhash ()) - reply.set_ledgerhash (packet.ledgerhash ()); - - // This is a very minimal implementation - for (int i = 0; i < packet.objects_size (); ++i) - { - uint256 hash; - const protocol::TMIndexedObject& obj = packet.objects (i); - - if (obj.has_hash () && (obj.hash ().size () == (256 / 8))) - { - memcpy (hash.begin (), obj.hash ().data (), 256 / 8); - NodeObject::pointer hObj = getApp().getNodeStore ().fetch (hash); - - if (hObj) - { - protocol::TMIndexedObject& newObj = *reply.add_objects (); - newObj.set_hash (hash.begin (), hash.size ()); - newObj.set_data (&hObj->getData ().front (), hObj->getData ().size ()); - - if (obj.has_nodeid ()) - newObj.set_index (obj.nodeid ()); - - if (!reply.has_seq () && (hObj->getIndex () != 0)) - reply.set_seq (hObj->getIndex ()); - } - } - } - - WriteLog (lsTRACE, Peer) << "GetObjByHash had " << reply.objects_size () << " of " << packet.objects_size () - << " for " << getIP (); - sendPacket (boost::make_shared (reply, protocol::mtGET_OBJECTS), true); - } - else - { - // this is a reply - uint32 pLSeq = 0; - bool pLDo = true; - bool progress = false; - - for (int i = 0; i < packet.objects_size (); ++i) - { - const protocol::TMIndexedObject& obj = packet.objects (i); - - if (obj.has_hash () && (obj.hash ().size () == (256 / 8))) - { - - if (obj.has_ledgerseq ()) - { - if (obj.ledgerseq () != pLSeq) - { - CondLog (pLDo && (pLSeq != 0), lsDEBUG, Peer) << "Received full fetch pack for " << pLSeq; - pLSeq = obj.ledgerseq (); - pLDo = !getApp().getOPs ().haveLedger (pLSeq); - - if (!pLDo) - { - WriteLog (lsDEBUG, Peer) << "Got pack for " << pLSeq << " too late"; - } - else - progress = true; - } - } - - if (pLDo) - { - uint256 hash; - memcpy (hash.begin (), obj.hash ().data (), 256 / 8); - - boost::shared_ptr< Blob > data = boost::make_shared< Blob > - (obj.data ().begin (), obj.data ().end ()); - - getApp().getOPs ().addFetchPack (hash, data); - } - } - } - - CondLog (pLDo && (pLSeq != 0), lsDEBUG, Peer) << "Received partial fetch pack for " << pLSeq; - - if (packet.type () == protocol::TMGetObjectByHash::otFETCH_PACK) - getApp().getOPs ().gotFetchPack (progress, pLSeq); - } -} - -void PeerImp::recvPing (protocol::TMPing& packet) -{ - if (packet.type () == protocol::TMPing::ptPING) - { - packet.set_type (protocol::TMPing::ptPONG); - sendPacket (boost::make_shared (packet, protocol::mtPING), true); - } - else if (packet.type () == protocol::TMPing::ptPONG) - { - mActive = 2; - } -} - -void PeerImp::recvErrorMessage (protocol::TMErrorMsg& packet) -{ -} - -void PeerImp::recvSearchTransaction (protocol::TMSearchTransaction& packet) -{ -} - -void PeerImp::recvGetAccount (protocol::TMGetAccount& packet) -{ -} - -void PeerImp::recvAccount (protocol::TMAccount& packet) -{ -} - -void PeerImp::recvProofWork (protocol::TMProofWork& packet) -{ - if (packet.has_response ()) - { - // this is an answer to a proof of work we requested - if (packet.response ().size () != (256 / 8)) - { - charge (Resource::feeInvalidRequest); - return; - } - - uint256 response; - memcpy (response.begin (), packet.response ().data (), 256 / 8); - PowResult r = getApp().getProofOfWorkFactory ().checkProof (packet.token (), response); - - if (r == powOK) - { - // credit peer - // WRITEME - return; - } - - // return error message - // WRITEME - if (r != powTOOEASY) - { - charge (Resource::feeBadProofOfWork); - } - - return; - } - - if (packet.has_result ()) - { - // this is a reply to a proof of work we sent - // WRITEME - } - - if (packet.has_target () && packet.has_challenge () && packet.has_iterations ()) - { - // this is a challenge - // WRITEME: Reject from inbound connections - - uint256 challenge, target; - - if ((packet.challenge ().size () != (256 / 8)) || (packet.target ().size () != (256 / 8))) - { - charge (Resource::feeInvalidRequest); - return; - } - - memcpy (challenge.begin (), packet.challenge ().data (), 256 / 8); - memcpy (target.begin (), packet.target ().data (), 256 / 8); - ProofOfWork::pointer pow = boost::make_shared (packet.token (), packet.iterations (), - challenge, target); - - if (!pow->isValid ()) - { - charge (Resource::feeInvalidRequest); - return; - } - -#if 0 // Until proof of work is completed, don't do it - getApp().getJobQueue ().addJob ( - jtPROOFWORK, - "recvProof->doProof", - BIND_TYPE (&PeerImp::doProofOfWork, P_1, boost::weak_ptr (shared_from_this ()), pow)); -#endif - - return; - } - - WriteLog (lsINFO, Peer) << "Received in valid proof of work object from peer"; -} - -void PeerImp::recvStatus (protocol::TMStatusChange& packet) -{ - WriteLog (lsTRACE, Peer) << "Received status change from peer " << getIP (); - - if (!packet.has_networktime ()) - packet.set_networktime (getApp().getOPs ().getNetworkTimeNC ()); - - if (!mLastStatus.has_newstatus () || packet.has_newstatus ()) - mLastStatus = packet; - else - { - // preserve old status - protocol::NodeStatus status = mLastStatus.newstatus (); - mLastStatus = packet; - packet.set_newstatus (status); - } - - if (packet.newevent () == protocol::neLOST_SYNC) - { - if (!mClosedLedgerHash.isZero ()) - { - WriteLog (lsTRACE, Peer) << "peer has lost sync " << getIP (); - mClosedLedgerHash.zero (); - } - - mPreviousLedgerHash.zero (); - return; - } - - if (packet.has_ledgerhash () && (packet.ledgerhash ().size () == (256 / 8))) - { - // a peer has changed ledgers - memcpy (mClosedLedgerHash.begin (), packet.ledgerhash ().data (), 256 / 8); - addLedger (mClosedLedgerHash); - WriteLog (lsTRACE, Peer) << "peer LCL is " << mClosedLedgerHash << " " << getIP (); - } - else - { - WriteLog (lsTRACE, Peer) << "peer has no ledger hash" << getIP (); - mClosedLedgerHash.zero (); - } - - if (packet.has_ledgerhashprevious () && packet.ledgerhashprevious ().size () == (256 / 8)) - { - memcpy (mPreviousLedgerHash.begin (), packet.ledgerhashprevious ().data (), 256 / 8); - addLedger (mPreviousLedgerHash); - } - else mPreviousLedgerHash.zero (); - - if (packet.has_firstseq () && packet.has_lastseq()) - { - mMinLedger = packet.firstseq (); - mMaxLedger = packet.lastseq (); - - // Work around some servers that report sequences incorrectly - if (mMinLedger == 0) - mMaxLedger = 0; - if (mMaxLedger == 0) - mMinLedger = 0; - } -} - -/** Handle a TMGetLedger received from a peer - Dispatched by the JobQueue -*/ -static void sGetLedger (boost::weak_ptr wPeer, boost::shared_ptr pPacket) -{ - boost::shared_ptr peer = wPeer.lock (); - if (peer) - peer->getLedger (*pPacket); -} - -void PeerImp::getLedger (protocol::TMGetLedger & packet) -{ - SHAMap::pointer map; - protocol::TMLedgerData reply; - bool fatLeaves = true, fatRoot = false; - - if (packet.has_requestcookie ()) - reply.set_requestcookie (packet.requestcookie ()); - - std::string logMe; - - if (packet.itype () == protocol::liTS_CANDIDATE) - { - // Request is for a transaction candidate set - WriteLog (lsTRACE, Peer) << "Received request for TX candidate set data " << getIP (); - - if ((!packet.has_ledgerhash () || packet.ledgerhash ().size () != 32)) - { - charge (Resource::feeInvalidRequest); - WriteLog (lsWARNING, Peer) << "invalid request for TX candidate set data"; - return; - } - - uint256 txHash; - memcpy (txHash.begin (), packet.ledgerhash ().data (), 32); - - { - Application::ScopedLockType lock (getApp ().getMasterLock (), __FILE__, __LINE__); - map = getApp().getOPs ().getTXMap (txHash); - } - - if (!map) - { - if (packet.has_querytype () && !packet.has_requestcookie ()) - { - WriteLog (lsDEBUG, Peer) << "Trying to route TX set request"; - std::vector peerList = getApp().getPeers ().getPeerVector (); - std::vector usablePeers; - BOOST_FOREACH (Peer::ref peer, peerList) - { - if (peer->hasTxSet (txHash) && (peer.get () != this)) - usablePeers.push_back (peer); - } - - if (usablePeers.empty ()) - { - WriteLog (lsINFO, Peer) << "Unable to route TX set request"; - return; - } - - Peer::ref selectedPeer = usablePeers[rand () % usablePeers.size ()]; - packet.set_requestcookie (getPeerId ()); - selectedPeer->sendPacket (boost::make_shared (packet, protocol::mtGET_LEDGER), false); - return; - } - - WriteLog (lsERROR, Peer) << "We do not have the map our peer wants " << getIP (); - charge (Resource::feeInvalidRequest); - return; - } - - reply.set_ledgerseq (0); - reply.set_ledgerhash (txHash.begin (), txHash.size ()); - reply.set_type (protocol::liTS_CANDIDATE); - fatLeaves = false; // We'll already have most transactions - fatRoot = true; // Save a pass - } - else - { - if (getApp().getFeeTrack().isLoadedLocal() && !mCluster) - { - WriteLog (lsDEBUG, Peer) << "Too busy to fetch ledger data"; - return; - } - - // Figure out what ledger they want - WriteLog (lsTRACE, Peer) << "Received request for ledger data " << getIP (); - Ledger::pointer ledger; - - if (packet.has_ledgerhash ()) - { - uint256 ledgerhash; - - if (packet.ledgerhash ().size () != 32) + // this is an answer to a proof of work we requested + if (packet.response ().size () != (256 / 8)) { charge (Resource::feeInvalidRequest); - WriteLog (lsWARNING, Peer) << "Invalid request"; return; } - memcpy (ledgerhash.begin (), packet.ledgerhash ().data (), 32); - logMe += "LedgerHash:"; - logMe += ledgerhash.GetHex (); - ledger = getApp().getLedgerMaster ().getLedgerByHash (ledgerhash); + uint256 response; + memcpy (response.begin (), packet.response ().data (), 256 / 8); + PowResult r = getApp().getProofOfWorkFactory ().checkProof (packet.token (), response); - CondLog (!ledger, lsTRACE, Peer) << "Don't have ledger " << ledgerhash; - - if (!ledger && (packet.has_querytype () && !packet.has_requestcookie ())) + if (r == powOK) { - uint32 seq = 0; - - if (packet.has_ledgerseq ()) - seq = packet.ledgerseq (); - - std::vector peerList = getApp().getPeers ().getPeerVector (); - std::vector usablePeers; - BOOST_FOREACH (Peer::ref peer, peerList) - { - if (peer->hasLedger (ledgerhash, seq) && (peer.get () != this)) - usablePeers.push_back (peer); - } - - if (usablePeers.empty ()) - { - WriteLog (lsTRACE, Peer) << "Unable to route ledger request"; - return; - } - - Peer::ref selectedPeer = usablePeers[rand () % usablePeers.size ()]; - packet.set_requestcookie (getPeerId ()); - selectedPeer->sendPacket (boost::make_shared (packet, protocol::mtGET_LEDGER), false); - WriteLog (lsDEBUG, Peer) << "Ledger request routed"; + // credit peer + // WRITEME return; } - } - else if (packet.has_ledgerseq ()) - { - ledger = getApp().getLedgerMaster ().getLedgerBySeq (packet.ledgerseq ()); - CondLog (!ledger, lsDEBUG, Peer) << "Don't have ledger " << packet.ledgerseq (); - } - else if (packet.has_ltype () && (packet.ltype () == protocol::ltCURRENT)) - ledger = getApp().getLedgerMaster ().getCurrentLedger (); - else if (packet.has_ltype () && (packet.ltype () == protocol::ltCLOSED) ) - { - ledger = getApp().getLedgerMaster ().getClosedLedger (); - if (ledger && !ledger->isClosed ()) - ledger = getApp().getLedgerMaster ().getLedgerBySeq (ledger->getLedgerSeq () - 1); + // return error message + // WRITEME + if (r != powTOOEASY) + { + charge (Resource::feeBadProofOfWork); + } + + return; + } + + if (packet.has_result ()) + { + // this is a reply to a proof of work we sent + // WRITEME + } + + if (packet.has_target () && packet.has_challenge () && packet.has_iterations ()) + { + // this is a challenge + // WRITEME: Reject from inbound connections + + uint256 challenge, target; + + if ((packet.challenge ().size () != (256 / 8)) || (packet.target ().size () != (256 / 8))) + { + charge (Resource::feeInvalidRequest); + return; + } + + memcpy (challenge.begin (), packet.challenge ().data (), 256 / 8); + memcpy (target.begin (), packet.target ().data (), 256 / 8); + ProofOfWork::pointer pow = boost::make_shared (packet.token (), packet.iterations (), + challenge, target); + + if (!pow->isValid ()) + { + charge (Resource::feeInvalidRequest); + return; + } + + #if 0 // Until proof of work is completed, don't do it + getApp().getJobQueue ().addJob ( + jtPROOFWORK, + "recvProof->doProof", + BIND_TYPE (&PeerImp::doProofOfWork, P_1, boost::weak_ptr (shared_from_this ()), pow)); + #endif + + return; + } + + WriteLog (lsINFO, Peer) << "Received in valid proof of work object from peer"; + } + + void addLedger (uint256 const& hash) + { + boost::mutex::scoped_lock sl(m_recentLock); + BOOST_FOREACH (uint256 const & ledger, m_recentLedgers) + + if (ledger == hash) + return; + + if (m_recentLedgers.size () == 128) + m_recentLedgers.pop_front (); + + m_recentLedgers.push_back (hash); + } + + void addTxSet (uint256 const& hash) + { + boost::mutex::scoped_lock sl(m_recentLock); + BOOST_FOREACH (uint256 const & set, m_recentTxSets) + + if (set == hash) + return; + + if (m_recentTxSets.size () == 128) + m_recentTxSets.pop_front (); + + m_recentTxSets.push_back (hash); + } + + void doFetchPack (const boost::shared_ptr& packet) + { + // VFALCO TODO Invert this dependency using an observer and shared state object. + if (getApp().getFeeTrack ().isLoadedLocal ()) + { + m_journal.info << "Too busy to make fetch pack"; + return; + } + + if (packet->ledgerhash ().size () != 32) + { + m_journal.warning << "FetchPack hash size malformed"; + charge (Resource::feeInvalidRequest); + return; + } + + uint256 hash; + memcpy (hash.begin (), packet->ledgerhash ().data (), 32); + + Ledger::pointer haveLedger = getApp().getOPs ().getLedgerByHash (hash); + + if (!haveLedger) + { + m_journal.info << "Peer requests fetch pack for ledger we don't have: " << hash; + charge (Resource::feeRequestNoReply); + return; + } + + if (!haveLedger->isClosed ()) + { + m_journal.warning << "Peer requests fetch pack from open ledger: " << hash; + charge (Resource::feeInvalidRequest); + return; + } + + Ledger::pointer wantLedger = getApp().getOPs ().getLedgerByHash (haveLedger->getParentHash ()); + + if (!wantLedger) + { + m_journal.info << "Peer requests fetch pack for ledger whose predecessor we don't have: " << hash; + charge (Resource::feeRequestNoReply); + return; + } + + getApp().getJobQueue ().addJob (jtPACK, "MakeFetchPack", + BIND_TYPE (&NetworkOPs::makeFetchPack, &getApp().getOPs (), P_1, + boost::weak_ptr (shared_from_this ()), packet, wantLedger, haveLedger, UptimeTimer::getInstance ().getElapsedSeconds ())); + } + + void doProofOfWork (Job&, boost::weak_ptr peer, ProofOfWork::pointer pow) + { + if (peer.expired ()) + return; + + uint256 solution = pow->solve (); + + if (solution.isZero ()) + { + m_journal.warning << "Failed to solve proof of work"; } else { - charge (Resource::feeInvalidRequest); - WriteLog (lsWARNING, Peer) << "Can't figure out what ledger they want"; - return; - } + Peer::pointer pptr (peer.lock ()); - if ((!ledger) || (packet.has_ledgerseq () && (packet.ledgerseq () != ledger->getLedgerSeq ()))) - { - charge (Resource::feeInvalidRequest); - - if (ShouldLog (lsWARNING, Peer)) + if (pptr) { - if (ledger) - Log (lsWARNING) << "Ledger has wrong sequence"; - } - - return; - } - - // Fill out the reply - uint256 lHash = ledger->getHash (); - reply.set_ledgerhash (lHash.begin (), lHash.size ()); - reply.set_ledgerseq (ledger->getLedgerSeq ()); - reply.set_type (packet.itype ()); - - if (packet.itype () == protocol::liBASE) - { - // they want the ledger base data - WriteLog (lsTRACE, Peer) << "They want ledger base data"; - Serializer nData (128); - ledger->addRaw (nData); - reply.add_nodes ()->set_nodedata (nData.getDataPtr (), nData.getLength ()); - - SHAMap::pointer map = ledger->peekAccountStateMap (); - - if (map && map->getHash ().isNonZero ()) - { - // return account state root node if possible - Serializer rootNode (768); - - if (map->getRootNode (rootNode, snfWIRE)) - { - reply.add_nodes ()->set_nodedata (rootNode.getDataPtr (), rootNode.getLength ()); - - if (ledger->getTransHash ().isNonZero ()) - { - map = ledger->peekTransactionMap (); - - if (map && map->getHash ().isNonZero ()) - { - rootNode.erase (); - - if (map->getRootNode (rootNode, snfWIRE)) - reply.add_nodes ()->set_nodedata (rootNode.getDataPtr (), rootNode.getLength ()); - } - } - } - } - - PackedMessage::pointer oPacket = boost::make_shared (reply, protocol::mtLEDGER_DATA); - sendPacket (oPacket, false); - return; - } - - if (packet.itype () == protocol::liTX_NODE) - { - map = ledger->peekTransactionMap (); - logMe += " TX:"; - logMe += map->getHash ().GetHex (); - } - else if (packet.itype () == protocol::liAS_NODE) - { - map = ledger->peekAccountStateMap (); - logMe += " AS:"; - logMe += map->getHash ().GetHex (); - } - } - - if ((!map) || (packet.nodeids_size () == 0)) - { - WriteLog (lsWARNING, Peer) << "Can't find map or empty request"; - charge (Resource::feeInvalidRequest); - return; - } - - WriteLog (lsTRACE, Peer) << "Request: " << logMe; - - for (int i = 0; i < packet.nodeids ().size (); ++i) - { - SHAMapNode mn (packet.nodeids (i).data (), packet.nodeids (i).size ()); - - if (!mn.isValid ()) - { - WriteLog (lsWARNING, Peer) << "Request for invalid node: " << logMe; - charge (Resource::feeInvalidRequest); - return; - } - - std::vector nodeIDs; - std::list< Blob > rawNodes; - - try - { - if (map->getNodeFat (mn, nodeIDs, rawNodes, fatRoot, fatLeaves)) - { - assert (nodeIDs.size () == rawNodes.size ()); - WriteLog (lsTRACE, Peer) << "getNodeFat got " << rawNodes.size () << " nodes"; - std::vector::iterator nodeIDIterator; - std::list< Blob >::iterator rawNodeIterator; - - for (nodeIDIterator = nodeIDs.begin (), rawNodeIterator = rawNodes.begin (); - nodeIDIterator != nodeIDs.end (); ++nodeIDIterator, ++rawNodeIterator) - { - Serializer nID (33); - nodeIDIterator->addIDRaw (nID); - protocol::TMLedgerNode* node = reply.add_nodes (); - node->set_nodeid (nID.getDataPtr (), nID.getLength ()); - node->set_nodedata (&rawNodeIterator->front (), rawNodeIterator->size ()); - } + protocol::TMProofWork reply; + reply.set_token (pow->getToken ()); + reply.set_response (solution.begin (), solution.size ()); + pptr->sendPacket (boost::make_shared (reply, protocol::mtPROOFOFWORK), false); } else - WriteLog (lsWARNING, Peer) << "getNodeFat returns false"; - } - catch (std::exception&) - { - std::string info; - - if (packet.itype () == protocol::liTS_CANDIDATE) - info = "TS candidate"; - else if (packet.itype () == protocol::liBASE) - info = "Ledger base"; - else if (packet.itype () == protocol::liTX_NODE) - info = "TX node"; - else if (packet.itype () == protocol::liAS_NODE) - info = "AS node"; - - if (!packet.has_ledgerhash ()) - info += ", no hash specified"; - - WriteLog (lsWARNING, Peer) << "getNodeFat( " << mn << ") throws exception: " << info; + { + // WRITEME: Save solved proof of work for new connection + } } } - PackedMessage::pointer oPacket = boost::make_shared (reply, protocol::mtLEDGER_DATA); - sendPacket (oPacket, false); -} - -void PeerImp::recvGetLedger (boost::shared_ptr const& packet) -{ - getApp().getJobQueue().addJob (jtPACK, "recvGetLedger", - std::bind (&sGetLedger, boost::weak_ptr (shared_from_this ()), packet)); -} - -void PeerImp::recvLedger (const boost::shared_ptr& packet_ptr) -{ - protocol::TMLedgerData& packet = *packet_ptr; - - if (packet.nodes ().size () <= 0) + static void checkTransaction (Job&, int flags, SerializedTransaction::pointer stx, boost::weak_ptr peer) { - WriteLog (lsWARNING, Peer) << "Ledger/TXset data with no nodes"; - charge (Resource::feeInvalidRequest); - return; + #ifndef TRUST_NETWORK + try + { + #endif + Transaction::pointer tx; + + if (isSetBit (flags, SF_SIGGOOD)) + tx = boost::make_shared (stx, false); + else + tx = boost::make_shared (stx, true); + + if (tx->getStatus () == INVALID) + { + getApp().getHashRouter ().setFlag (stx->getTransactionID (), SF_BAD); + Peer::charge (peer, Resource::feeInvalidSignature); + return; + } + else + getApp().getHashRouter ().setFlag (stx->getTransactionID (), SF_SIGGOOD); + + getApp().getOPs ().processTransaction (tx, isSetBit (flags, SF_TRUSTED), false); + + #ifndef TRUST_NETWORK + } + catch (...) + { + getApp().getHashRouter ().setFlag (stx->getTransactionID (), SF_BAD); + Peer::charge (peer, Resource::feeInvalidRequest); + } + + #endif } - if (packet.has_requestcookie ()) + // Called from our JobQueue + static void checkPropose (Job& job, Peers* pPeers, boost::shared_ptr packet, + LedgerProposal::pointer proposal, uint256 consensusLCL, RippleAddress nodePublic, + boost::weak_ptr peer, bool fromCluster) { - Peer::pointer target = getApp().getPeers ().getPeerById (packet.requestcookie ()); + bool sigGood = false; + bool isTrusted = (job.getType () == jtPROPOSAL_t); - if (target) + WriteLog (lsTRACE, Peer) << "Checking " << + (isTrusted ? "trusted" : "UNTRUSTED") << + " proposal"; + + assert (packet); + protocol::TMProposeSet& set = *packet; + + uint256 prevLedger; + + if (set.has_previousledger ()) { - packet.clear_requestcookie (); - target->sendPacket (boost::make_shared (packet, protocol::mtLEDGER_DATA), false); + // proposal includes a previous ledger + WriteLog(lsTRACE, Peer) << "proposal with previous ledger"; + memcpy (prevLedger.begin (), set.previousledger ().data (), 256 / 8); + + if (!fromCluster && !proposal->checkSign (set.signature ())) + { + Peer::pointer p = peer.lock (); + WriteLog(lsWARNING, Peer) << "proposal with previous ledger fails sig check: " << + *p; + Peer::charge (peer, Resource::feeInvalidSignature); + return; + } + else + sigGood = true; } else { - WriteLog (lsINFO, Peer) << "Unable to route TX/ledger data reply"; - charge (Resource::feeUnwantedData); + if (consensusLCL.isNonZero () && proposal->checkSign (set.signature ())) + { + prevLedger = consensusLCL; + sigGood = true; + } + else + { + // Could be mismatched prev ledger + WriteLog(lsWARNING, Peer) << "Ledger proposal fails signature check"; + proposal->setSignature (set.signature ()); + } } - return; - } - - uint256 hash; - - if (packet.ledgerhash ().size () != 32) - { - WriteLog (lsWARNING, Peer) << "TX candidate reply with invalid hash size"; - charge (Resource::feeInvalidRequest); - return; - } - - memcpy (hash.begin (), packet.ledgerhash ().data (), 32); - - if (packet.type () == protocol::liTS_CANDIDATE) - { - // got data for a candidate transaction set - std::list nodeIDs; - std::list< Blob > nodeData; - - for (int i = 0; i < packet.nodes ().size (); ++i) + if (isTrusted) { - const protocol::TMLedgerNode& node = packet.nodes (i); + getApp().getOPs ().processTrustedProposal (proposal, packet, nodePublic, prevLedger, sigGood); + } + else if (sigGood && (prevLedger == consensusLCL)) + { + // relay untrusted proposal + WriteLog(lsTRACE, Peer) << "relaying UNTRUSTED proposal"; + std::set peers; + getApp().getHashRouter ().swapSet ( + proposal->getHashRouter (), peers, SF_RELAYED); + + pPeers->foreach (send_if_not ( + boost::make_shared (set, protocol::mtPROPOSE_LEDGER), + peer_in_set(peers))); + } + else + { + WriteLog(lsDEBUG, Peer) << "Not relaying UNTRUSTED proposal"; + } + } - if (!node.has_nodeid () || !node.has_nodedata () || (node.nodeid ().size () != 33)) + static void checkValidation (Job&, Peers* pPeers, SerializedValidation::pointer val, bool isTrusted, bool isCluster, + boost::shared_ptr packet, boost::weak_ptr peer) + { + #ifndef TRUST_NETWORK + + try + #endif + { + uint256 signingHash = val->getSigningHash(); + if (!isCluster && !val->isValid (signingHash)) { - WriteLog (lsWARNING, Peer) << "LedgerData request with invalid node ID"; - charge (Resource::feeInvalidRequest); + WriteLog(lsWARNING, Peer) << "Validation is invalid"; + Peer::charge (peer, Resource::feeInvalidRequest); return; } - nodeIDs.push_back (SHAMapNode (node.nodeid ().data (), node.nodeid ().size ())); - nodeData.push_back (Blob (node.nodedata ().begin (), node.nodedata ().end ())); + std::string source; + Peer::pointer lp = peer.lock (); + + if (lp) + source = to_string(*lp); + else + source = "unknown"; + + std::set peers; + + //---------------------------------------------------------------------- + // + { + SerializedValidation const& sv (*val); + Validators::ReceivedValidation rv; + rv.ledgerHash = sv.getLedgerHash (); + rv.publicKey = sv.getSignerPublic(); + getApp ().getValidators ().receiveValidation (rv); + } + // + //---------------------------------------------------------------------- + + if (getApp().getOPs ().recvValidation (val, source) && + getApp().getHashRouter ().swapSet (signingHash, peers, SF_RELAYED)) + { + pPeers->foreach (send_if_not ( + boost::make_shared (*packet, protocol::mtVALIDATION), + peer_in_set(peers))); + } } - bool doCharge = false; + #ifndef TRUST_NETWORK + catch (...) { - Application::ScopedLockType lock (getApp ().getMasterLock (), __FILE__, __LINE__); - if (getApp().getOPs ().gotTXData (shared_from_this (), hash, nodeIDs, nodeData).isInvalid ()) - doCharge = true; + WriteLog(lsTRACE, Peer) << "Exception processing validation"; + Peer::charge (peer, Resource::feeInvalidRequest); } - - if (doCharge) - { - charge (Resource::feeUnwantedData); - } - - return; + #endif } - - if (!getApp().getInboundLedgers ().gotLedgerData (hash, shared_from_this(), packet_ptr)) - { - WriteLog (lsINFO, Peer) << "Got data for unwanted ledger"; - charge (Resource::feeUnwantedData); - } -} - -void PeerImp::ledgerRange (uint32& minSeq, uint32& maxSeq) const -{ - boost::mutex::scoped_lock sl(mRecentLock); - - minSeq = mMinLedger; - maxSeq = mMaxLedger; -} - -bool PeerImp::hasLedger (uint256 const& hash, uint32 seq) const -{ - boost::mutex::scoped_lock sl(mRecentLock); - - if ((seq != 0) && (seq >= mMinLedger) && (seq <= mMaxLedger)) - return true; - - BOOST_FOREACH (uint256 const & ledger, mRecentLedgers) - { - if (ledger == hash) - return true; - } - - return false; -} - -void PeerImp::addLedger (uint256 const& hash) -{ - boost::mutex::scoped_lock sl(mRecentLock); - BOOST_FOREACH (uint256 const & ledger, mRecentLedgers) - { - if (ledger == hash) - return; - } - - if (mRecentLedgers.size () == 128) - mRecentLedgers.pop_front (); - - mRecentLedgers.push_back (hash); -} - -bool PeerImp::hasTxSet (uint256 const& hash) const -{ - boost::mutex::scoped_lock sl(mRecentLock); - BOOST_FOREACH (uint256 const & set, mRecentTxSets) - { - if (set == hash) - return true; - } - - return false; -} - -void PeerImp::addTxSet (uint256 const& hash) -{ - boost::mutex::scoped_lock sl(mRecentLock); - BOOST_FOREACH (uint256 const & set, mRecentTxSets) - { - if (set == hash) - return; - } - - if (mRecentTxSets.size () == 128) - mRecentTxSets.pop_front (); - - mRecentTxSets.push_back (hash); -} - -// Get session information we can sign to prevent man in the middle attack. -// (both sides get the same information, neither side controls it) -void PeerImp::getSessionCookie (std::string& strDst) -{ - SSL* ssl (getHandshakeStream ().ssl_handle ()); - - if (!ssl) throw std::runtime_error ("No underlying connection"); - - // Get both finished messages - unsigned char s1[1024], s2[1024]; - int l1 = SSL_get_finished (ssl, s1, sizeof (s1)); - int l2 = SSL_get_peer_finished (ssl, s2, sizeof (s2)); - - if ((l1 < 12) || (l2 < 12)) - throw std::runtime_error (str (boost::format ("Connection setup not complete: %d %d") % l1 % l2)); - - // Hash them and XOR the results - unsigned char sha1[64], sha2[64]; - - SHA512 (s1, l1, sha1); - SHA512 (s2, l2, sha2); - - if (memcmp (s1, s2, sizeof (sha1)) == 0) - throw std::runtime_error ("Identical finished messages"); - - for (int i = 0; i < sizeof (sha1); ++i) - sha1[i] ^= sha2[i]; - - strDst.assign ((char*) &sha1[0], sizeof (sha1)); -} - -void PeerImp::sendHello () -{ - std::string strCookie; - Blob vchSig; - - getSessionCookie (strCookie); - mCookieHash = Serializer::getSHA512Half (strCookie); - - getApp().getLocalCredentials ().getNodePrivate ().signNodePrivate (mCookieHash, vchSig); - - protocol::TMHello h; - - h.set_protoversion (BuildInfo::getCurrentProtocol().toPacked ()); - h.set_protoversionmin (BuildInfo::getMinimumProtocol().toPacked ()); - h.set_fullversion (BuildInfo::getFullVersionString ()); - h.set_nettime (getApp().getOPs ().getNetworkTimeNC ()); - h.set_nodepublic (getApp().getLocalCredentials ().getNodePublic ().humanNodePublic ()); - h.set_nodeproof (&vchSig[0], vchSig.size ()); - h.set_ipv4port (getConfig ().peerListeningPort); - h.set_nodeprivate (getConfig ().PEER_PRIVATE); - h.set_testnet (getConfig ().TESTNET); - - Ledger::pointer closedLedger = getApp().getLedgerMaster ().getClosedLedger (); - - if (closedLedger && closedLedger->isClosed ()) - { - uint256 hash = closedLedger->getHash (); - h.set_ledgerclosed (hash.begin (), hash.GetSerializeSize ()); - hash = closedLedger->getParentHash (); - h.set_ledgerprevious (hash.begin (), hash.GetSerializeSize ()); - } - - PackedMessage::pointer packet = boost::make_shared (h, protocol::mtHELLO); - sendPacket (packet, true); -} - -void PeerImp::sendGetPeers () -{ - // Ask peer for known other peers. - protocol::TMGetPeers getPeers; - - getPeers.set_doweneedthis (1); - - PackedMessage::pointer packet = boost::make_shared (getPeers, protocol::mtGET_PEERS); - - sendPacket (packet, true); -} - -void PeerImp::doProofOfWork (Job&, boost::weak_ptr peer, ProofOfWork::pointer pow) -{ - if (peer.expired ()) - return; - - uint256 solution = pow->solve (); - - if (solution.isZero ()) - { - WriteLog (lsWARNING, Peer) << "Failed to solve proof of work"; - } - else - { - Peer::pointer pptr (peer.lock ()); - - if (pptr) - { - protocol::TMProofWork reply; - reply.set_token (pow->getToken ()); - reply.set_response (solution.begin (), solution.size ()); - pptr->sendPacket (boost::make_shared (reply, protocol::mtPROOFOFWORK), false); - } - else - { - // WRITEME: Save solved proof of work for new connection - } - } -} - -void PeerImp::doFetchPack (const boost::shared_ptr& packet) -{ - // VFALCO TODO Invert this dependency using an observer and shared state object. - if (getApp().getFeeTrack ().isLoadedLocal ()) - { - WriteLog (lsINFO, Peer) << "Too busy to make fetch pack"; - return; - } - - if (packet->ledgerhash ().size () != 32) - { - WriteLog (lsWARNING, Peer) << "FetchPack hash size malformed"; - charge (Resource::feeInvalidRequest); - return; - } - - uint256 hash; - memcpy (hash.begin (), packet->ledgerhash ().data (), 32); - - Ledger::pointer haveLedger = getApp().getOPs ().getLedgerByHash (hash); - - if (!haveLedger) - { - WriteLog (lsINFO, Peer) << "Peer requests fetch pack for ledger we don't have: " << hash; - charge (Resource::feeRequestNoReply); - return; - } - - if (!haveLedger->isClosed ()) - { - WriteLog (lsWARNING, Peer) << "Peer requests fetch pack from open ledger: " << hash; - charge (Resource::feeInvalidRequest); - return; - } - - Ledger::pointer wantLedger = getApp().getOPs ().getLedgerByHash (haveLedger->getParentHash ()); - - if (!wantLedger) - { - WriteLog (lsINFO, Peer) << "Peer requests fetch pack for ledger whose predecessor we don't have: " << hash; - charge (Resource::feeRequestNoReply); - return; - } - - getApp().getJobQueue ().addJob (jtPACK, "MakeFetchPack", - BIND_TYPE (&NetworkOPs::makeFetchPack, &getApp().getOPs (), P_1, - boost::weak_ptr (shared_from_this ()), packet, wantLedger, haveLedger, UptimeTimer::getInstance ().getElapsedSeconds ())); -} - -bool PeerImp::hasProto (int version) -{ - return mHello.has_protoversion () && (mHello.protoversion () >= version); -} - -Json::Value PeerImp::getJson () -{ - Json::Value ret (Json::objectValue); - - //ret["this"] = addressToString(this); - ret["public_key"] = mNodePublic.ToString (); - ret["ip"] = mIpPortConnect.first; - //ret["port"] = mIpPortConnect.second; - ret["port"] = mIpPort.second; - - if (m_isInbound) - ret["inbound"] = true; - - if (mCluster) - { - ret["cluster"] = true; - - if (!mNodeName.empty ()) - ret["name"] = mNodeName; - } - - if (mHello.has_fullversion ()) - ret["version"] = mHello.fullversion (); - - if (mHello.has_protoversion () && - (mHello.protoversion () != BuildInfo::getCurrentProtocol().toPacked ())) - { - ret["protocol"] = BuildInfo::Protocol (mHello.protoversion ()).toStdString (); - } - - uint32 minSeq, maxSeq; - ledgerRange(minSeq, maxSeq); - if ((minSeq != 0) || (maxSeq != 0)) - ret["complete_ledgers"] = boost::lexical_cast(minSeq) + " - " + - boost::lexical_cast(maxSeq); - - if (!!mClosedLedgerHash) - ret["ledger"] = mClosedLedgerHash.GetHex (); - - if (mLastStatus.has_newstatus ()) - { - switch (mLastStatus.newstatus ()) - { - case protocol::nsCONNECTING: - ret["status"] = "connecting"; - break; - - case protocol::nsCONNECTED: - ret["status"] = "connected"; - break; - - case protocol::nsMONITORING: - ret["status"] = "monitoring"; - break; - - case protocol::nsVALIDATING: - ret["status"] = "validating"; - break; - - case protocol::nsSHUTTING: - ret["status"] = "shutting"; - break; - - default: - WriteLog (lsWARNING, Peer) << "Peer has unknown status: " << mLastStatus.newstatus (); - } - } - - /* - if (!mIpPort.first.empty()) - { - ret["verified_ip"] = mIpPort.first; - ret["verified_port"] = mIpPort.second; - }*/ - - return ret; -} - -//------------------------------------------------------------------------------ - -Peer::pointer Peer::New (Resource::Manager& resourceManager, - boost::asio::io_service& io_service, - boost::asio::ssl::context& ssl_context, uint64 id, - bool inbound, bool requirePROXYHandshake) -{ - MultiSocket::Flag flags; - - if (inbound) - { - flags = MultiSocket::Flag::server_role | MultiSocket::Flag::ssl_required; - - if (requirePROXYHandshake) - { - flags = flags.with (MultiSocket::Flag::proxy); - } - } - else - { - flags = MultiSocket::Flag::client_role | MultiSocket::Flag::ssl; - - bassert (! requirePROXYHandshake); - } - - return Peer::pointer (new PeerImp (resourceManager, - io_service, ssl_context, id, inbound, flags)); -} +}; //------------------------------------------------------------------------------ void Peer::charge (boost::weak_ptr & peer, Resource::Charge const& fee) { Peer::pointer p (peer.lock()); + if (p != nullptr) p->charge (fee); } + +//------------------------------------------------------------------------------ + +void Peer::accept ( + boost::shared_ptr const& socket, + Peers& peers, + Resource::Manager& resourceManager, + PeerFinder::Manager& peerFinder, + boost::asio::ssl::context& ssl_context, + bool proxyHandshake) +{ + MultiSocket::Flag flags ( + MultiSocket::Flag::server_role | MultiSocket::Flag::ssl_required); + + if (proxyHandshake) + flags = flags.with (MultiSocket::Flag::proxy); + + boost::shared_ptr peer (boost::make_shared ( + socket, + peers, + resourceManager, + peerFinder, + ssl_context, + flags)); + + peer->accept (); +} + +//------------------------------------------------------------------------------ + +void Peer::connect ( + IP::Endpoint const& address, + boost::asio::io_service& io_service, + Peers& peers, + Resource::Manager& resourceManager, + PeerFinder::Manager& peerFinder, + boost::asio::ssl::context& ssl_context) +{ + MultiSocket::Flag flags ( + MultiSocket::Flag::client_role | MultiSocket::Flag::ssl); + + boost::shared_ptr peer (boost::make_shared ( + io_service, + peers, + resourceManager, + peerFinder, + ssl_context, + flags)); + + peer->connect (address); +} + +//------------------------------------------------------------------------------ +const boost::posix_time::seconds PeerImp::nodeVerifySeconds (15); + +//------------------------------------------------------------------------------ +std::string to_string (PeerImp const& peer) +{ + if (peer.isInCluster()) + return peer.getClusterNodeName(); + + return peer.getRemoteAddress().to_string(); +} + +std::string to_string (PeerImp const* peer) +{ + return to_string (*peer); +} + +std::ostream& operator<< (std::ostream& os, PeerImp const& peer) +{ + os << to_string (peer); + + return os; +} + +std::ostream& operator<< (std::ostream& os, PeerImp const* peer) +{ + os << to_string (peer); + + return os; +} + +//------------------------------------------------------------------------------ +std::string to_string (Peer const& peer) +{ + if (peer.isInCluster()) + return peer.getClusterNodeName(); + + return peer.getRemoteAddress().to_string(); +} + +std::string to_string (Peer const* peer) +{ + return to_string (*peer); +} + +std::ostream& operator<< (std::ostream& os, Peer const& peer) +{ + os << to_string (peer); + + return os; +} + +std::ostream& operator<< (std::ostream& os, Peer const* peer) +{ + os << to_string (peer); + + return os; +} + +} diff --git a/src/ripple_app/peers/Peer.h b/src/ripple_app/peers/Peer.h index 11f347626..4fc91e126 100644 --- a/src/ripple_app/peers/Peer.h +++ b/src/ripple_app/peers/Peer.h @@ -20,56 +20,93 @@ #ifndef RIPPLE_PEER_H_INCLUDED #define RIPPLE_PEER_H_INCLUDED +namespace ripple { + +typedef boost::asio::ip::tcp::socket NativeSocketType; + namespace Resource { class Charge; class Manager; } -// VFALCO TODO Couldn't this be a struct? -typedef std::pair IPAndPortNumber; +namespace PeerFinder { +class Manager; +} + +class Peers; /** Represents a peer connection in the overlay. */ class Peer : public boost::enable_shared_from_this - , LeakChecked + , public List ::Node + , private LeakChecked { public: + typedef boost::shared_ptr Ptr; + + // DEPRECATED typedefs. typedef boost::shared_ptr pointer; typedef pointer const& ref; + /** Uniquely identifies a particular connection of a peer + This works upto a restart of rippled. + */ + typedef uint32 ShortId; + + /** Current state */ + enum State + { + /** An connection is being established (outbound) */ + stateConnecting + + /** Connection has been successfully established */ + ,stateConnected + + /** Handshake has been received from this peer */ + ,stateHandshaked + + /** Running the Ripple protocol actively */ + ,stateActive + + /** Gracefully closing */ + ,stateGracefulClose + }; + public: - static pointer New (Resource::Manager& resourceManager, - boost::asio::io_service& io_service, - boost::asio::ssl::context& ctx, - uint64 id, - bool inbound, - bool requirePROXYHandshake); + static void accept ( + boost::shared_ptr const& socket, + Peers& peers, + Resource::Manager& resourceManager, + PeerFinder::Manager& peerFinder, + boost::asio::ssl::context& ctx, + bool proxyHandshake); - // VFALCO TODO see if this and below can be private - virtual void handleConnect (const boost::system::error_code& error, - boost::asio::ip::tcp::resolver::iterator it) = 0; + static void connect ( + IP::Endpoint const& address, + boost::asio::io_service& io_service, + Peers& peers, + Resource::Manager& resourceManager, + PeerFinder::Manager& peerFinder, + boost::asio::ssl::context& ssl_context); - virtual std::string const& getIP () = 0; + //-------------------------------------------------------------------------- + /** Called when an open slot is assigned to a handshaked peer. */ + virtual void activate () = 0; - virtual std::string getDisplayName () = 0; + //-------------------------------------------------------------------------- + //virtual void connect (IPAddress const &address) = 0; - virtual int getPort () = 0; + //-------------------------------------------------------------------------- + virtual State state () const = 0; - virtual void setIpPort (const std::string& strIP, int iPort) = 0; - - virtual void connect (const std::string& strIp, int iPort) = 0; - - virtual void connected (const boost::system::error_code& error) = 0; + virtual void state (State new_state) = 0; + //-------------------------------------------------------------------------- virtual void detach (const char*, bool onIOStrand) = 0; virtual void sendPacket (const PackedMessage::pointer& packet, bool onStrand) = 0; - virtual void sendGetPeers () = 0; - - virtual void getLedger (protocol::TMGetLedger &) = 0; - // VFALCO NOTE what's with this odd parameter passing? Why the static member? // /** Adjust this peer's load balance based on the type of load imposed. @@ -79,18 +116,18 @@ public: virtual void charge (Resource::Charge const& fee) = 0; static void charge (boost::weak_ptr & peer, Resource::Charge const& fee); - virtual Json::Value getJson () = 0; + virtual Json::Value json () = 0; virtual bool isConnected () const = 0; virtual bool isInCluster () const = 0; + virtual std::string getClusterNodeName() const = 0; + virtual bool isInbound () const = 0; virtual bool isOutbound () const = 0; - virtual bool getConnectString(std::string&) const = 0; - virtual uint256 const& getClosedLedgerHash () const = 0; virtual bool hasLedger (uint256 const& hash, uint32 seq) const = 0; @@ -99,23 +136,29 @@ public: virtual bool hasTxSet (uint256 const& hash) const = 0; - virtual uint64 getPeerId () const = 0; + virtual void setShortId(Peer::ShortId shortId) = 0; + + virtual ShortId getShortId () const = 0; virtual const RippleAddress& getNodePublic () const = 0; virtual void cycleStatus () = 0; - virtual bool hasProto (int version) = 0; + virtual bool supportsVersion (int version) = 0; virtual bool hasRange (uint32 uMin, uint32 uMax) = 0; - virtual IPAddress getPeerEndpoint() const = 0; + virtual IPAddress getRemoteAddress() const = 0; - //-------------------------------------------------------------------------- - - typedef boost::asio::ip::tcp::socket NativeSocketType; - virtual NativeSocketType& getNativeSocket () = 0; }; +std::string to_string (Peer const& peer); +std::ostream& operator<< (std::ostream& os, Peer const& peer); + +std::string to_string (Peer const* peer); +std::ostream& operator<< (std::ostream& os, Peer const* peer); + +} + #endif diff --git a/src/ripple_app/peers/PeerDoor.cpp b/src/ripple_app/peers/PeerDoor.cpp index e1d24adc0..c0c6375bc 100644 --- a/src/ripple_app/peers/PeerDoor.cpp +++ b/src/ripple_app/peers/PeerDoor.cpp @@ -17,6 +17,10 @@ */ //============================================================================== +#include "PeerDoor.h" + +namespace ripple { + SETUP_LOG (PeerDoor) class PeerDoorImp @@ -24,23 +28,23 @@ class PeerDoorImp , public LeakChecked { public: - PeerDoorImp (Stoppable& parent, Resource::Manager& resourceManager, - Kind kind, std::string const& ip, int port, - boost::asio::io_service& io_service, boost::asio::ssl::context& ssl_context) - : PeerDoor (parent) - , m_resourceManager (resourceManager) + PeerDoorImp (Kind kind, Peers& peers, + boost::asio::ip::tcp::endpoint const &ep, + boost::asio::io_service& io_service) + : PeerDoor (static_cast(peers)) + , m_peers (peers) + , m_journal (LogPartition::getJournal ()) , m_kind (kind) - , m_ssl_context (ssl_context) - , mAcceptor (io_service, boost::asio::ip::tcp::endpoint ( - boost::asio::ip::address ().from_string (ip.empty () ? "0.0.0.0" : ip), port)) - , mDelayTimer (io_service) + , m_acceptor (io_service, ep) + , m_acceptDelay (io_service) { - if (! ip.empty () && port != 0) - { - Log (lsINFO) << "Peer port: " << ip << " " << port; - - async_accept (); - } + m_journal.info << + "Listening on " << + IPAddressConversion::from_asio ( + m_acceptor.local_endpoint()) << + ((m_kind == sslAndPROXYRequired) ? " (proxy)" : ""); + + async_accept (); } ~PeerDoorImp () @@ -53,18 +57,14 @@ public: // void async_accept () { - bool const isInbound (true); - bool const requirePROXYHandshake (m_kind == sslAndPROXYRequired); + boost::shared_ptr socket ( + boost::make_shared ( + m_acceptor.get_io_service())); - Peer::pointer new_connection (Peer::New ( - m_resourceManager, mAcceptor.get_io_service (), - m_ssl_context, getApp().getPeers ().assignPeerId (), - isInbound, requirePROXYHandshake)); - - mAcceptor.async_accept (new_connection->getNativeSocket (), + m_acceptor.async_accept (*socket, boost::bind (&PeerDoorImp::handleAccept, this, boost::asio::placeholders::error, - new_connection)); + socket)); } //-------------------------------------------------------------------------- @@ -78,26 +78,29 @@ public: // Called when the accept socket wait completes // - void handleAccept (boost::system::error_code ec, Peer::pointer new_connection) + void handleAccept (boost::system::error_code ec, + boost::shared_ptr const& socket) { bool delay = false; if (! ec) { - // VFALCO NOTE the error code doesnt seem to be used in connected() - new_connection->connected (ec); + bool const proxyHandshake (m_kind == sslAndPROXYRequired); + + m_peers.accept (proxyHandshake, socket); } else { if (ec == boost::system::errc::too_many_files_open) delay = true; - WriteLog (lsERROR, PeerDoor) << ec; + + m_journal.info << "Error " << ec; } if (delay) { - mDelayTimer.expires_from_now (boost::posix_time::milliseconds (500)); - mDelayTimer.async_wait (boost::bind (&PeerDoorImp::handleTimer, + m_acceptDelay.expires_from_now (boost::posix_time::milliseconds (500)); + m_acceptDelay.async_wait (boost::bind (&PeerDoorImp::handleTimer, this, boost::asio::placeholders::error)); } else @@ -112,23 +115,23 @@ public: { { boost::system::error_code ec; - mDelayTimer.cancel (ec); + m_acceptDelay.cancel (ec); } { boost::system::error_code ec; - mAcceptor.cancel (ec); + m_acceptor.cancel (ec); } stopped (); } private: - Resource::Manager& m_resourceManager; + Peers& m_peers; + Journal m_journal; Kind m_kind; - boost::asio::ssl::context& m_ssl_context; - boost::asio::ip::tcp::acceptor mAcceptor; - boost::asio::deadline_timer mDelayTimer; + boost::asio::ip::tcp::acceptor m_acceptor; + boost::asio::deadline_timer m_acceptDelay; }; //------------------------------------------------------------------------------ @@ -139,13 +142,19 @@ PeerDoor::PeerDoor (Stoppable& parent) } //------------------------------------------------------------------------------ - -PeerDoor* PeerDoor::New (Stoppable& parent, - Resource::Manager& resourceManager, - Kind kind, std::string const& ip, int port, - boost::asio::io_service& io_service, - boost::asio::ssl::context& ssl_context) +PeerDoor* PeerDoor::New ( + Kind kind, Peers& peers, + std::string const& ip, int port, + boost::asio::io_service& io_service) { - return new PeerDoorImp (parent, resourceManager, - kind, ip, port, io_service, ssl_context); + // You have to listen on something! + bassert(port != 0); + + boost::asio::ip::tcp::endpoint ep( + boost::asio::ip::address ().from_string ( + ip.empty () ? "0.0.0.0" : ip), port); + + return new PeerDoorImp (kind, peers, ep, io_service); +} + } diff --git a/src/ripple_app/peers/PeerDoor.h b/src/ripple_app/peers/PeerDoor.h index 77cfe200b..061f3a266 100644 --- a/src/ripple_app/peers/PeerDoor.h +++ b/src/ripple_app/peers/PeerDoor.h @@ -20,9 +20,7 @@ #ifndef RIPPLE_PEERDOOR_H_INCLUDED #define RIPPLE_PEERDOOR_H_INCLUDED -namespace Resource { -class Manager; -} +namespace ripple { /** Handles incoming connections from peers. */ class PeerDoor : public Stoppable @@ -39,13 +37,11 @@ public: sslAndPROXYRequired }; - static PeerDoor* New (Stoppable& parent, - Resource::Manager& resourceManager, - Kind kind, std::string const& ip, int port, - boost::asio::io_service& io_service, - boost::asio::ssl::context& ssl_context); - - //virtual boost::asio::ssl::context& getSSLContext () = 0; + static PeerDoor* New (Kind kind, Peers& peers, + std::string const& ip, int port, + boost::asio::io_service& io_service); }; +} + #endif diff --git a/src/ripple_app/peers/PeerSet.cpp b/src/ripple_app/peers/PeerSet.cpp index c479fb264..f11dbaf4a 100644 --- a/src/ripple_app/peers/PeerSet.cpp +++ b/src/ripple_app/peers/PeerSet.cpp @@ -53,7 +53,7 @@ bool PeerSet::peerHas (Peer::ref ptr) { ScopedLockType sl (mLock, __FILE__, __LINE__); - if (!mPeers.insert (std::make_pair (ptr->getPeerId (), 0)).second) + if (!mPeers.insert (std::make_pair (ptr->getShortId (), 0)).second) return false; newPeer (ptr); @@ -63,7 +63,7 @@ bool PeerSet::peerHas (Peer::ref ptr) void PeerSet::badPeer (Peer::ref ptr) { ScopedLockType sl (mLock, __FILE__, __LINE__); - mPeers.erase (ptr->getPeerId ()); + mPeers.erase (ptr->getShortId ()); } void PeerSet::setTimer () @@ -157,9 +157,9 @@ void PeerSet::sendRequest (const protocol::TMGetLedger& tmGL) PackedMessage::pointer packet = boost::make_shared (tmGL, protocol::mtGET_LEDGER); - for (Map::iterator it = mPeers.begin (), end = mPeers.end (); it != end; ++it) + for (PeerSetMap::iterator it = mPeers.begin (), end = mPeers.end (); it != end; ++it) { - Peer::pointer peer = getApp().getPeers ().getPeerById (it->first); + Peer::pointer peer = getApp().getPeers ().findPeerByShortID (it->first); if (peer) peer->sendPacket (packet, false); @@ -171,7 +171,7 @@ int PeerSet::takePeerSetFrom (const PeerSet& s) int ret = 0; mPeers.clear (); - for (Map::const_iterator it = s.mPeers.begin (), end = s.mPeers.end (); + for (PeerSetMap::const_iterator it = s.mPeers.begin (), end = s.mPeers.end (); it != end; ++it) { mPeers.insert (std::make_pair (it->first, 0)); @@ -181,12 +181,12 @@ int PeerSet::takePeerSetFrom (const PeerSet& s) return ret; } -int PeerSet::getPeerCount () const +std::size_t PeerSet::getPeerCount () const { - int ret = 0; + std::size_t ret (0); - for (Map::const_iterator it = mPeers.begin (), end = mPeers.end (); it != end; ++it) - if (getApp().getPeers ().hasPeer (it->first)) + for (PeerSetMap::const_iterator it = mPeers.begin (), end = mPeers.end (); it != end; ++it) + if (getApp().getPeers ().findPeerByShortID (it->first)) ++ret; return ret; diff --git a/src/ripple_app/peers/PeerSet.h b/src/ripple_app/peers/PeerSet.h index 8e81c13bc..1db58045e 100644 --- a/src/ripple_app/peers/PeerSet.h +++ b/src/ripple_app/peers/PeerSet.h @@ -92,7 +92,7 @@ public: void setTimer (); int takePeerSetFrom (const PeerSet& s); - int getPeerCount () const; + std::size_t getPeerCount () const; virtual bool isDone () const { return mComplete || mFailed; @@ -149,10 +149,11 @@ protected: boost::asio::deadline_timer mTimer; // VFALCO TODO Verify that these are used in the way that the names suggest. - typedef uint64 PeerIdentifier; + typedef Peer::ShortId PeerIdentifier; typedef int ReceivedChunkCount; - typedef boost::unordered_map Map; - Map mPeers; + typedef boost::unordered_map PeerSetMap; + + PeerSetMap mPeers; }; #endif diff --git a/src/ripple_app/peers/Peers.cpp b/src/ripple_app/peers/Peers.cpp index cf33affad..83c406e5c 100644 --- a/src/ripple_app/peers/Peers.cpp +++ b/src/ripple_app/peers/Peers.cpp @@ -17,7 +17,15 @@ */ //============================================================================== -SETUP_LOG (Peers) +#include "PeerDoor.h" +#include +#include +#include + +namespace ripple { + +class PeersLog; +template <> char const* LogPartition::getPartitionName () { return "Peers"; } class PeerFinderLog; template <> char const* LogPartition::getPartitionName () { return "PeerFinder"; } @@ -43,80 +51,98 @@ static static_call init_NameResolverLog (&LogPartition::get ); //------------------------------------------------------------------------------ +/** A functor to visit all active peers and retrieve their JSON data */ +struct get_peer_json +{ + typedef Json::Value return_type; + + Json::Value json; + + get_peer_json () + { } + + void operator() (Peer::ref peer) + { + json.append (peer->json ()); + } + + Json::Value operator() () + { + return json; + } +}; + +//------------------------------------------------------------------------------ + class PeersImp : public Peers - , public Stoppable , public PeerFinder::Callback , public LeakChecked -{ +{ public: - enum - { - /** Frequency of policy enforcement. */ - policyIntervalSeconds = 5 - }; + typedef boost::unordered_map PeerByIP; - typedef RippleRecursiveMutex LockType; - typedef LockType::ScopedLockType ScopedLockType; - typedef std::pair naPeer; - typedef std::pair pipPeer; - typedef std::map::value_type vtPeer; - typedef boost::unordered_map::value_type vtConMap; + typedef boost::unordered_map < + RippleAddress, Peer::pointer> PeerByPublicKey; + typedef boost::unordered_map < + Peer::ShortId, Peer::pointer> PeerByShortId; + + std::recursive_mutex m_mutex; + + // Blocks us until dependent objects have been destroyed + std::condition_variable_any m_cond; + + // Number of dependencies that must be destroyed before we can stop + std::size_t m_child_count; + + Journal m_journal; Resource::Manager& m_resourceManager; + std::unique_ptr m_peerFinder; boost::asio::io_service& m_io_service; boost::asio::ssl::context& m_ssl_context; - LockType mPeerLock; + /** Tracks peers by their IP address and port */ + PeerByIP m_ipMap; - uint64 mLastPeer; - int mPhase; + /** Tracks peers by their public key */ + PeerByPublicKey m_publicKeyMap; - // PeersImp we are connecting with and non-thin peers we are connected to. - // Only peers we know the connection ip for are listed. - // We know the ip and port for: - // - All outbound connections - // - Some inbound connections (which we figured out). - boost::unordered_map mIpMap; + /** Tracks peers by their session ID */ + PeerByShortId m_shortIdMap; - // Non-thin peers which we are connected to. - // PeersImp we have the public key for. - boost::unordered_map mConnectedMap; + /** Tracks all instances of peer objects */ + List m_list; - // Connections with have a 64-bit identifier - boost::unordered_map mPeerIdMap; + /** The peer door for regular SSL connections */ + std::unique_ptr m_doorDirect; - Peer::pointer mScanning; - boost::asio::deadline_timer mScanTimer; - std::string mScanIp; - int mScanPort; + /** The peer door for proxy connections */ + std::unique_ptr m_doorProxy; - void scanHandler (const boost::system::error_code& ecResult); + /** The resolver we use for peer hostnames */ + Resolver& m_resolver; - boost::asio::deadline_timer mPolicyTimer; - - void policyHandler (const boost::system::error_code& ecResult); - - // PeersImp we are establishing a connection with as a client. - // int miConnectStarting; - - bool peerAvailable (std::string& strIp, int& iPort); - bool peerScanSet (const std::string& strIp, int iPort); - - Peer::pointer peerConnect (const std::string& strIp, int iPort); - - std::unique_ptr m_resolver; + /** Monotically increasing identifiers for peers */ + Atomic m_nextShortId; + //-------------------------------------------------------------------------- + // + // Peers + // //-------------------------------------------------------------------------- PeersImp (Stoppable& parent, Resource::Manager& resourceManager, SiteFiles::Manager& siteFiles, - boost::asio::io_service& io_service, - boost::asio::ssl::context& ssl_context) - : Stoppable ("Peers", parent) + Resolver& resolver, + boost::asio::io_service& io_service, + boost::asio::ssl::context& ssl_context) + : Peers (parent) + , m_child_count (1) + , m_journal (LogPartition::getJournal ()) , m_resourceManager (resourceManager) , m_peerFinder (add (PeerFinder::Manager::New ( *this, @@ -125,122 +151,170 @@ public: LogPartition::getJournal ()))) , m_io_service (io_service) , m_ssl_context (ssl_context) - , mPeerLock (this, "PeersImp", __FILE__, __LINE__) - , mLastPeer (0) - , mPhase (0) - , mScanTimer (io_service) - , mPolicyTimer (io_service) - -// Disable the Resolver since it is broken in this version -#if 0 - , m_resolver (NameResolver::New ( - io_service, - Journal())) -#endif + , m_resolver (resolver) { } + ~PeersImp () + { + // Block until dependent objects have been destroyed. + // This is just to catch improper use of the Stoppable API. + // + std::unique_lock lock (m_mutex); +#ifdef BOOST_NO_CXX11_LAMBDAS + while (m_child_count != 0) + m_cond.wait (lock); +#else + m_cond.wait (lock, [this] { + return this->m_child_count == 0; }); +#endif + + } + + void accept ( + bool proxyHandshake, + boost::shared_ptr const& socket) + { + Peer::accept ( + socket, + *this, + m_resourceManager, + *m_peerFinder, + m_ssl_context, + proxyHandshake); + } + + void connect (IP::Endpoint const& address) + { + Peer::connect ( + address, + m_io_service, + *this, + m_resourceManager, + *m_peerFinder, + m_ssl_context); + } + + //-------------------------------------------------------------------------- + + // Check for the stopped condition + // Caller must hold the mutex + void check_stopped () + { + // To be stopped, child Stoppable objects must be stopped + // and the count of dependent objects must be zero + if (areChildrenStopped () && m_child_count == 0) + { + m_cond.notify_all (); + stopped (); + } + } + + // Increment the count of dependent objects + // Caller must hold the mutex + void addref () + { + ++m_child_count; + } + + // Decrement the count of dependent objects + // Caller must hold the mutex + void release () + { + if (--m_child_count == 0) + check_stopped (); + } + + void peerCreated (Peer* peer) + { + std::lock_guard lock (m_mutex); + m_list.push_back (*peer); + addref(); + } + + void peerDestroyed (Peer* peer) + { + std::lock_guard lock (m_mutex); + m_list.erase (m_list.iterator_to (*peer)); + release(); + } + //-------------------------------------------------------------------------- // - // PeerFinder + // PeerFinder::Callback // + //-------------------------------------------------------------------------- - // Maps Config settings to PeerFinder::Config - void preparePeerFinder() + void connectPeers (std::vector const& list) { - PeerFinder::Config config; - - config.maxPeerCount = getConfig ().PEERS_MAX; - - config.wantIncoming = - (! getConfig ().PEER_PRIVATE) && - (getConfig().peerListeningPort != 0); - - config.listeningPort = getConfig().peerListeningPort; - - // if it's a private peer or we are running as standalone - // automatic connections would defeat the purpose. - config.connectAutomatically = - !getConfig().RUN_STANDALONE && - !getConfig().PEER_PRIVATE; - - config.featureList = ""; - - m_peerFinder->setConfig (config); - - // Add the static IPs from the rippled.cfg file - m_peerFinder->addFallbackStrings ("rippled.cfg", getConfig().IPS); - - // Add the ips_fixed from the rippled.cfg file - if (! getConfig().RUN_STANDALONE) - m_peerFinder->addFixedPeers (getConfig().IPS_FIXED); + for (std::vector ::const_iterator iter (list.begin()); + iter != list.end(); ++iter) + connect (*iter); } - void sendPeerEndpoints (PeerFinder::PeerID const& id, + void disconnectPeer (IPAddress const& address, bool graceful) + { + m_journal.trace << + "disconnectPeer (" << address << + ", " << graceful << ")"; + + std::lock_guard lock (m_mutex); + + PeerByIP::iterator const it (m_ipMap.find (address)); + + if (it != m_ipMap.end ()) + it->second->detach ("disc", false); + } + + void activatePeer (IPAddress const& remote_address) + { + m_journal.trace << + "activatePeer (" << remote_address << ")"; + + std::lock_guard lock (m_mutex); + + PeerByIP::iterator const it (m_ipMap.find (remote_address)); + + if (it != m_ipMap.end ()) + it->second->activate(); + } + + void sendEndpoints (IPAddress const& remote_address, std::vector const& endpoints) { bassert (! endpoints.empty()); - typedef std::vector List; protocol::TMEndpoints tm; - for (List::const_iterator iter (endpoints.begin()); iter != endpoints.end(); ++iter) { PeerFinder::Endpoint const& ep (*iter); protocol::TMEndpoint& tme (*tm.add_endpoints()); - - if (ep.address.isV4()) + if (ep.address.is_v4()) tme.mutable_ipv4()->set_ipv4( - toNetworkByteOrder (ep.address.v4().value)); + toNetworkByteOrder (ep.address.to_v4().value)); else tme.mutable_ipv4()->set_ipv4(0); tme.mutable_ipv4()->set_ipv4port (ep.address.port()); tme.set_hops (ep.hops); - tme.set_slots (ep.incomingSlotsAvailable); - tme.set_maxslots (ep.incomingSlotsMax); - tme.set_uptimeseconds (ep.uptimeSeconds); - tme.set_features (ep.featureList); + tme.set_version (1); } PackedMessage::pointer msg ( boost::make_shared ( tm, protocol::mtENDPOINTS)); - std::vector list = getPeerVector (); - BOOST_FOREACH (Peer::ref peer, list) { - if (peer->isConnected() && - PeerFinder::PeerID (peer->getNodePublic()) == id) - { + std::lock_guard lock (m_mutex); + PeerByIP::iterator const iter (m_ipMap.find (remote_address)); + // Address must exist! + check_postcondition (iter != m_ipMap.end()); + Peer::pointer peer (iter->second); + // VFALCO TODO Why are we checking isConnected? That should not be needed + if (peer->isConnected()) peer->sendPacket (msg, false); - break; - } - } - } - - void connectPeerEndpoints (std::vector const& list) - { - typedef std::vector List; - - for (List::const_iterator iter (list.begin()); - iter != list.end(); ++iter) - peerConnect (iter->withPort (0), iter->port()); - } - - void chargePeerLoadPenalty (PeerFinder::PeerID const& id) - { - std::vector list = getPeerVector (); - BOOST_FOREACH (Peer::ref peer, list) - { - if (peer->isConnected() && - PeerFinder::PeerID (peer->getNodePublic()) == id) - { - peer->charge (Resource::feeUnwantedData); - break; - } } } @@ -248,928 +322,266 @@ public: // // Stoppable // + //-------------------------------------------------------------------------- void onPrepare () { - preparePeerFinder(); + PeerFinder::Config config; + + config.maxPeers = getConfig ().PEERS_MAX; + + config.outPeers = config.calcOutPeers(); + + config.wantIncoming = + (! getConfig ().PEER_PRIVATE) && + (getConfig().peerListeningPort != 0); + + // if it's a private peer or we are running as standalone + // automatic connections would defeat the purpose. + config.autoConnect = + !getConfig().RUN_STANDALONE && + !getConfig().PEER_PRIVATE; + + config.listeningPort = getConfig().peerListeningPort; + + config.features = ""; + + // Enforce business rules + config.applyTuning (); + + m_peerFinder->setConfig (config); + + // Add the static IPs from the rippled.cfg file + m_peerFinder->addFallbackStrings ("rippled.cfg", getConfig().IPS); + + // Add the ips_fixed from the rippled.cfg file + if (! getConfig ().RUN_STANDALONE && !getConfig ().IPS_FIXED.empty ()) + { + struct resolve_fixed_peers + { + PeerFinder::Manager* m_peerFinder; + + resolve_fixed_peers (PeerFinder::Manager* peerFinder) + : m_peerFinder (peerFinder) + { } + + void operator()(std::string const& name, + std::vector const& address) + { + if (!address.empty()) + m_peerFinder->addFixedPeer (name, address); + } + }; + + m_resolver.resolve (getConfig ().IPS_FIXED, + resolve_fixed_peers (m_peerFinder.get ())); + } + + // Configure the peer doors, which allow the server to accept incoming + // peer connections: + // Create the listening sockets for peers + // + m_doorDirect.reset (PeerDoor::New ( + PeerDoor::sslRequired, + *this, + getConfig ().PEER_IP, + getConfig ().peerListeningPort, + m_io_service)); + + if (getConfig ().peerPROXYListeningPort != 0) + { + m_doorProxy.reset (PeerDoor::New ( + PeerDoor::sslAndPROXYRequired, + *this, + getConfig ().PEER_IP, + getConfig ().peerPROXYListeningPort, + m_io_service)); + } } void onStart () { } + // Close all peer connections. If graceful is true then the peer objects + // will wait for pending i/o before closing the socket. No new data will + // be sent. + // + // The caller must hold the mutex + // + // VFALCO TODO implement the graceful flag + // + void close_all (bool graceful) + { + for (List ::iterator iter (m_list.begin ()); + iter != m_list.end(); ++iter) + iter->detach ("stop", false); + } + void onStop () { -// Disable the Resolver since it is broken in this version -#if 0 - m_resolver->stop_async(); -#endif + m_resolver.stop_async(); + + std::lock_guard lock (m_mutex); + // Take off the extra count we added in the constructor + release(); + // Close all peers + close_all (true); } void onChildrenStopped () { -// Disable the Resolver since it is broken in this version -#if 0 - m_resolver->stop (); -#endif + m_resolver.stop (); - // VFALCO TODO Clean this up and do it right, based on sockets - stopped(); + std::lock_guard lock (m_mutex); + check_stopped (); } //-------------------------------------------------------------------------- // // PropertyStream // + //-------------------------------------------------------------------------- void onWrite (PropertyStream& stream) { } //-------------------------------------------------------------------------- - - PeerFinder::Manager& getPeerFinder() + /** A peer has connected successfully + This is called after the peer handshake has been completed and during + peer activation. At this point, the peer address and the public key + are known. + */ + void onPeerActivated (Peer::ref peer) { - return *m_peerFinder; + // First assign this peer a new short ID + peer->setShortId(++m_nextShortId); + + std::lock_guard lock (m_mutex); + + // Now track this peer + std::pair idResult( + m_shortIdMap.emplace ( + boost::unordered::piecewise_construct, + boost::make_tuple (peer->getShortId()), + boost::make_tuple (peer))); + check_postcondition(idResult.second); + + std::pair keyResult( + m_publicKeyMap.emplace ( + boost::unordered::piecewise_construct, + boost::make_tuple (peer->getNodePublic()), + boost::make_tuple (peer))); + check_postcondition(keyResult.second); + + m_journal.debug << + "activated " << peer->getRemoteAddress() << + " (" << peer->getShortId() << + ":" << RipplePublicKey(peer->getNodePublic()) << ")"; + + // We just accepted this peer so we have non-zero active peers + check_postcondition(size() != 0); } - // Begin enforcing connection policy. - void start (); - - // Send message to network. - int relayMessage (Peer* fromPeer, const PackedMessage::pointer& msg); - int relayMessageCluster (Peer* fromPeer, const PackedMessage::pointer& msg); - void relayMessageTo (const std::set& fromPeers, const PackedMessage::pointer& msg); - void relayMessageBut (const std::set& fromPeers, const PackedMessage::pointer& msg); - - // Manual connection request. - // Queue for immediate scanning. - void connectTo (const std::string& strIp, int iPort); - - // - // Peer connectivity notification. - // - bool getTopNAddrs (int n, std::vector& addrs); - bool savePeer (const std::string& strIp, int iPort, char code); - - // disconnect the specified peer - void disconnectPeer (PeerFinder::PeerID const &id, bool graceful) + /** A peer is being disconnected + This is called during the disconnection of a known, activated peer. It + will not be called for outbound peer connections that don't succeed or + for connections of peers that are dropped prior to being activated. + */ + void onPeerDisconnect (Peer::ref peer) { - // NIKB TODO + std::lock_guard lock (m_mutex); + m_shortIdMap.erase (peer->getShortId ()); + m_publicKeyMap.erase (peer->getNodePublic ()); } - // A peer connected but we only have the IP address so far. - void peerConnected (const IPAddress& address, bool incoming); + /** The number of active peers on the network + Active peers are only those peers that have completed the handshake + and are running the Ripple protocol. + */ + std::size_t size () + { + std::lock_guard lock (m_mutex); + return m_publicKeyMap.size (); + } - // We know peers node public key. - // <-- bool: false=reject - bool peerHandshake (Peer::ref peer, const RippleAddress& naPeer, const std::string& strIP, int iPort); + // Returns information on verified peers. + Json::Value json () + { + return foreach (get_peer_json()); + } - // No longer connected. - void peerDisconnected (Peer::ref peer, const RippleAddress& naPeer); + Peers::PeerSequence getActivePeers () + { + Peers::PeerSequence ret; - // As client accepted. - void peerVerified (Peer::ref peer); + std::lock_guard lock (m_mutex); - // As client failed connect and be accepted. - void peerClosed (Peer::ref peer, const std::string& strIp, int iPort); + ret.reserve (m_publicKeyMap.size ()); - int getPeerCount (); - Json::Value getPeersJson (); - std::vector getPeerVector (); + BOOST_FOREACH (PeerByPublicKey::value_type const& pair, m_publicKeyMap) + { + assert (!!pair.second); + ret.push_back (pair.second); + } - // Peer 64-bit ID function - uint64 assignPeerId (); - Peer::pointer getPeerById (const uint64& id); - bool hasPeer (const uint64& id); + return ret; + } - // - // Scanning - // + Peer::pointer findPeerByShortID (Peer::ShortId const& id) + { + std::lock_guard lock (m_mutex); + PeerByShortId::iterator const iter ( + m_shortIdMap.find (id)); + if (iter != m_shortIdMap.end ()) + iter->second; + return Peer::pointer(); + } - void scanRefresh (); + // TODO NIKB Rename these two functions. It's not immediately clear + // what they do: create a tracking entry for a peer by + // the peer's remote IP. + /** Start tracking a peer */ + void addPeer (Peer::Ptr const& peer) + { + std::lock_guard lock (m_mutex); - // - // Connection policy - // - void policyLowWater (); - void policyEnforce (); + check_precondition (! isStopping ()); - // configured connections - void legacyConnectFixedIPs (); + m_journal.error << "Adding peer: " << peer->getRemoteAddress(); + + std::pair result (m_ipMap.emplace ( + boost::unordered::piecewise_construct, + boost::make_tuple (peer->getRemoteAddress()), + boost::make_tuple (peer))); + + check_postcondition (result.second); + } + + /** Stop tracking a peer */ + void removePeer (Peer::Ptr const& peer) + { + std::lock_guard lock (m_mutex); + m_ipMap.erase (peer->getRemoteAddress()); + } }; -void splitIpPort (const std::string& strIpPort, std::string& strIp, int& iPort) -{ - std::vector vIpPort; - boost::split (vIpPort, strIpPort, boost::is_any_of (" :")); - - strIp = vIpPort[0]; - iPort = lexicalCastThrow (vIpPort[1]); -} - -void PeersImp::start () -{ - if (getConfig ().RUN_STANDALONE) - return; - -#if ! RIPPLE_USE_PEERFINDER - // Start running policy. - policyEnforce (); - - // Start scanning. - scanRefresh (); -#endif -} - -bool PeersImp::getTopNAddrs (int n, std::vector& addrs) -{ -#if ! RIPPLE_USE_PEERFINDER - // Try current connections first - std::vector peers = getPeerVector(); - BOOST_FOREACH(Peer::ref peer, peers) - { - if (peer->isConnected()) - { - std::string connectString; - if (peer->getConnectString(connectString)) - addrs.push_back(connectString); - } - } - - if (addrs.size() < n) - { - // XXX Filter out other local addresses (like ipv6) - Database* db = getApp().getWalletDB ()->getDB (); - DeprecatedScopedLock sl (getApp().getWalletDB ()->getDBLock ()); - - SQL_FOREACH (db, str (boost::format ("SELECT IpPort FROM PeerIps LIMIT %d") % n)) - { - std::string str; - - db->getStr (0, str); - - addrs.push_back (str); - } - } - - // FIXME: Should uniqify addrs -#endif - - return true; -} - -bool PeersImp::savePeer (const std::string& strIp, int iPort, char code) -{ - bool bNew = false; - -#if ! RIPPLE_USE_PEERFINDER - Database* db = getApp().getWalletDB ()->getDB (); - - std::string ipAndPort = sqlEscape (str (boost::format ("%s %d") % strIp % iPort)); - - DeprecatedScopedLock sl (getApp().getWalletDB ()->getDBLock ()); - std::string sql = str (boost::format ("SELECT COUNT(*) FROM PeerIps WHERE IpPort=%s;") % ipAndPort); - - if (db->executeSQL (sql) && db->startIterRows ()) - { - if (!db->getInt (0)) - { - db->executeSQL (str (boost::format ("INSERT INTO PeerIps (IpPort,Score,Source) values (%s,0,'%c');") % ipAndPort % code)); - bNew = true; - } - else - { - // We already had this peer. - // We will eventually verify its address if it is possible. - // YYY If it is vsInbound, then we might make verification immediate so we can connect back sooner if the connection - // is lost. - nothing (); - } - - db->endIterRows (); - } - else - { - Log::out() << "Error saving Peer"; - } - - if (bNew) - scanRefresh (); -#endif - - return bNew; -} - -Peer::pointer PeersImp::getPeerById (const uint64& id) -{ - ScopedLockType sl (mPeerLock, __FILE__, __LINE__); - const boost::unordered_map::iterator& it = mPeerIdMap.find (id); - - if (it == mPeerIdMap.end ()) - return Peer::pointer (); - - return it->second; -} - -bool PeersImp::hasPeer (const uint64& id) -{ - ScopedLockType sl (mPeerLock, __FILE__, __LINE__); - return mPeerIdMap.find (id) != mPeerIdMap.end (); -} - -// An available peer is one we had no trouble connect to last time and that we are not currently knowingly connected or connecting -// too. -// -// <-- true, if a peer is available to connect to -bool PeersImp::peerAvailable (std::string& strIp, int& iPort) -{ - Database* db = getApp().getWalletDB ()->getDB (); - std::vector vstrIpPort; - - // Convert mIpMap (list of open connections) to a vector of " ". - { - ScopedLockType sl (mPeerLock, __FILE__, __LINE__); - - vstrIpPort.reserve (mIpMap.size ()); - - BOOST_FOREACH (const vtPeer & ipPeer, mIpMap) - { - const std::string& strIp = ipPeer.first.first; - int iPort = ipPeer.first.second; - - vstrIpPort.push_back (sqlEscape (str (boost::format ("%s %d") % strIp % iPort))); - } - } - - // Get the first IpPort entry which is not in vector and which is not scheduled for scanning. - std::string strIpPort; - - { - DeprecatedScopedLock sl (getApp().getWalletDB ()->getDBLock ()); - - if (db->executeSQL (str (boost::format ("SELECT IpPort FROM PeerIps WHERE ScanNext IS NULL AND IpPort NOT IN (%s) LIMIT 1;") - % strJoin (vstrIpPort.begin (), vstrIpPort.end (), ","))) - && db->startIterRows ()) - { - strIpPort = db->getStrBinary ("IpPort"); - db->endIterRows (); - } - } - - bool bAvailable = !strIpPort.empty (); - - if (bAvailable) - splitIpPort (strIpPort, strIp, iPort); - - return bAvailable; -} - -// Make sure we have at least low water connections. -void PeersImp::policyLowWater () -{ - std::string strIp; - int iPort; - - // Find an entry to connect to. - if (getPeerCount () > getConfig ().PEER_CONNECT_LOW_WATER) - { - // Above low water mark, don't need more connections. - WriteLog (lsTRACE, Peers) << "Pool: Low water: sufficient connections: " << mConnectedMap.size () << "/" << getConfig ().PEER_CONNECT_LOW_WATER; - - nothing (); - } - -#if 0 - else if (miConnectStarting == getConfig ().PEER_START_MAX) - { - // Too many connections starting to start another. - nothing (); - } - -#endif - else if (!peerAvailable (strIp, iPort)) - { - // No more connections available to start. - WriteLog (lsTRACE, Peers) << "Pool: Low water: no peers available."; - - // XXX Might ask peers for more ips. - nothing (); - } - else - { - // Try to start connection. - WriteLog (lsTRACE, Peers) << "Pool: Low water: start connection."; - - if (!peerConnect (strIp, iPort)) - { - WriteLog (lsINFO, Peers) << "Pool: Low water: already connected."; - } - - // Check if we need more. - policyLowWater (); - } -} - -void PeersImp::policyEnforce () -{ -#if ! RIPPLE_USE_PEERFINDER - // Cancel any in progress timer. - (void) mPolicyTimer.cancel (); - - // Enforce policies. - if (!getConfig ().PEER_PRIVATE) - policyLowWater (); - - if (((++mPhase) % 12) == 0) - { - WriteLog (lsTRACE, Peers) << "Making configured connections"; - legacyConnectFixedIPs (); - } - - // Schedule next enforcement. - mPolicyTimer.expires_at (boost::posix_time::second_clock::universal_time () + boost::posix_time::seconds (policyIntervalSeconds)); - mPolicyTimer.async_wait (BIND_TYPE (&PeersImp::policyHandler, this, P_1)); -#endif -} - -void PeersImp::policyHandler (const boost::system::error_code& ecResult) -{ - if (ecResult == boost::asio::error::operation_aborted) - { - nothing (); - } - else if (!ecResult) - { - policyEnforce (); - } - else - { - throw std::runtime_error ("Internal error: unexpected deadline error."); - } -} - -// YYY: Should probably do this in the background. -// YYY: Might end up sending to disconnected peer? -int PeersImp::relayMessage (Peer* fromPeer, const PackedMessage::pointer& msg) -{ - int sentTo = 0; - std::vector peerVector = getPeerVector (); - BOOST_FOREACH (Peer::ref peer, peerVector) - { - if ((!fromPeer || ! (peer.get () == fromPeer)) && peer->isConnected ()) - { - ++sentTo; - peer->sendPacket (msg, false); - } - } - - return sentTo; -} - -int PeersImp::relayMessageCluster (Peer* fromPeer, const PackedMessage::pointer& msg) -{ - int sentTo = 0; - std::vector peerVector = getPeerVector (); - BOOST_FOREACH (Peer::ref peer, peerVector) - { - if ((!fromPeer || ! (peer.get () == fromPeer)) && peer->isConnected () && peer->isInCluster ()) - { - ++sentTo; - peer->sendPacket (msg, false); - } - } - - return sentTo; -} - -void PeersImp::relayMessageBut (const std::set& fromPeers, const PackedMessage::pointer& msg) -{ - // Relay message to all but the specified peers - std::vector peerVector = getPeerVector (); - BOOST_FOREACH (Peer::ref peer, peerVector) - { - if (peer->isConnected () && (fromPeers.count (peer->getPeerId ()) == 0)) - peer->sendPacket (msg, false); - } - -} - -void PeersImp::relayMessageTo (const std::set& fromPeers, const PackedMessage::pointer& msg) -{ - // Relay message to the specified peers - std::vector peerVector = getPeerVector (); - BOOST_FOREACH (Peer::ref peer, peerVector) - { - if (peer->isConnected () && (fromPeers.count (peer->getPeerId ()) != 0)) - peer->sendPacket (msg, false); - } - -} - -// Schedule a connection via scanning. -//addr.to_v4().to_bytes() -// Add or modify into PeerIps as a manual entry for immediate scanning. -// Requires sane IP and port. -void PeersImp::connectTo (const std::string& strIp, int iPort) -{ - { - Database* db = getApp().getWalletDB ()->getDB (); - DeprecatedScopedLock sl (getApp().getWalletDB ()->getDBLock ()); - - db->executeSQL (str (boost::format ("REPLACE INTO PeerIps (IpPort,Score,Source,ScanNext) values (%s,%d,'%c',0);") - % sqlEscape (str (boost::format ("%s %d") % strIp % iPort)) - % getApp().getUNL ().iSourceScore (UniqueNodeList::vsManual) - % char (UniqueNodeList::vsManual))); - } - - scanRefresh (); -} - -// Start a connection, if not already known connected or connecting. -// -// <-- true, if already connected. -Peer::pointer PeersImp::peerConnect (const std::string& strIp, int iPort) -{ - IPAndPortNumber pipPeer = make_pair (strIp, iPort); - Peer::pointer ppResult; - - { - ScopedLockType sl (mPeerLock, __FILE__, __LINE__); - - if (mIpMap.find (pipPeer) == mIpMap.end ()) - { - bool const isInbound (false); - bool const requirePROXYHandshake (false); - - ppResult = Peer::New (m_resourceManager, m_io_service, m_ssl_context, - ++mLastPeer, isInbound, requirePROXYHandshake); - - mIpMap [pipPeer] = ppResult; - } - } - - if (ppResult) - { - ppResult->connect (strIp, iPort); - WriteLog (lsDEBUG, Peers) << "Pool: Connecting: " << strIp << " " << iPort; - } - else - { - WriteLog (lsTRACE, Peers) << "Pool: Already connected: " << strIp << " " << iPort; - } - - return ppResult; -} - -// Returns information on verified peers. -Json::Value PeersImp::getPeersJson () -{ - Json::Value ret (Json::arrayValue); - std::vector vppPeers = getPeerVector (); - - BOOST_FOREACH (Peer::ref peer, vppPeers) - { - ret.append (peer->getJson ()); - } - - return ret; -} - -int PeersImp::getPeerCount () -{ - ScopedLockType sl (mPeerLock, __FILE__, __LINE__); - - return mConnectedMap.size (); -} - -std::vector PeersImp::getPeerVector () -{ - std::vector ret; - - ScopedLockType sl (mPeerLock, __FILE__, __LINE__); - - ret.reserve (mConnectedMap.size ()); - - BOOST_FOREACH (const vtConMap & pair, mConnectedMap) - { - assert (!!pair.second); - ret.push_back (pair.second); - } - - return ret; -} - -uint64 PeersImp::assignPeerId () -{ - ScopedLockType sl (mPeerLock, __FILE__, __LINE__); - return ++mLastPeer; -} - -void PeersImp::peerConnected (const IPAddress& address, bool incoming) -{ - getPeerFinder ().onPeerConnected (address, incoming); -} - -// Now know peer's node public key. Determine if we want to stay connected. -// <-- bNew: false = redundant -bool PeersImp::peerHandshake (Peer::ref peer, const RippleAddress& naPeer, - const std::string& strIP, int iPort) -{ - bool bNew = false; - - assert (!!peer); - - if (naPeer == getApp().getLocalCredentials ().getNodePublic ()) - { - WriteLog (lsINFO, Peers) << "Pool: Connected: self: " << addressToString (peer.get()) << ": " << naPeer.humanNodePublic () << " " << strIP << " " << iPort; - } - else - { - ScopedLockType sl (mPeerLock, __FILE__, __LINE__); - const boost::unordered_map::iterator& itCm = mConnectedMap.find (naPeer); - - if (itCm == mConnectedMap.end ()) - { - // New connection. - //WriteLog (lsINFO, Peers) << "Pool: Connected: new: " << addressToString (peer.get()) << ": " << naPeer.humanNodePublic() << " " << strIP << " " << iPort; - - mConnectedMap[naPeer] = peer; - bNew = true; - - // Notify peerfinder since this is a connection that we didn't - // know about and are keeping - // - getPeerFinder ().onPeerHandshake (RipplePublicKey ( - peer->getNodePublic()), peer->getPeerEndpoint(), - peer->isInbound()); - - assert (peer->getPeerId () != 0); - mPeerIdMap.insert (std::make_pair (peer->getPeerId (), peer)); - } - // Found in map, already connected. - else if (!strIP.empty ()) - { - // Was an outbound connection, we know IP and port. - // Note in previous connection how to reconnect. - if (itCm->second->getIP ().empty ()) - { - // Old peer did not know it's IP. - //WriteLog (lsINFO, Peers) << "Pool: Connected: redundant: outbound: " << addressToString (peer.get()) << " discovered: " << addressToString(itCm->second) << ": " << strIP << " " << iPort; - - itCm->second->setIpPort (strIP, iPort); - - // Add old connection to identified connection list. - mIpMap[make_pair (strIP, iPort)] = itCm->second; - } - else - { - // Old peer knew its IP. Do nothing. - //WriteLog (lsINFO, Peers) << "Pool: Connected: redundant: outbound: rediscovered: " << addressToString (peer.get()) << " " << strIP << " " << iPort; - - nothing (); - } - } - else - { - //WriteLog (lsINFO, Peers) << "Pool: Connected: redundant: inbound: " << addressToString (peer.get()) << " " << strIP << " " << iPort; - - nothing (); - } - } - - return bNew; -} - -// We maintain a map of public key to peer for connected and verified peers. Maintain it. -void PeersImp::peerDisconnected (Peer::ref peer, const RippleAddress& naPeer) -{ - ScopedLockType sl (mPeerLock, __FILE__, __LINE__); - - if (naPeer.isValid ()) - { - const boost::unordered_map::iterator& itCm = mConnectedMap.find (naPeer); - - if (itCm == mConnectedMap.end ()) - { - // Did not find it. Not already connecting or connected. - WriteLog (lsWARNING, Peers) << "Pool: disconnected: Internal Error: mConnectedMap was inconsistent."; - // XXX Maybe bad error, considering we have racing connections, may not so bad. - } - else if (itCm->second != peer) - { - WriteLog (lsWARNING, Peers) << "Pool: disconected: non canonical entry"; - - nothing (); - } - else - { - // Found it. Notify peerfinder, then delete it. - getPeerFinder ().onPeerDisconnected (RipplePublicKey (itCm->first)); - - mConnectedMap.erase (itCm); - - //WriteLog (lsINFO, Peers) << "Pool: disconnected: " << naPeer.humanNodePublic() << " " << peer->getIP() << " " << peer->getPort(); - } - } - else - { - //WriteLog (lsINFO, Peers) << "Pool: disconnected: anonymous: " << peer->getIP() << " " << peer->getPort(); - } - - assert (peer->getPeerId () != 0); - mPeerIdMap.erase (peer->getPeerId ()); -} - -// Schedule for immediate scanning, if not already scheduled. -// -// <-- true, scanRefresh needed. -bool PeersImp::peerScanSet (const std::string& strIp, int iPort) -{ - std::string strIpPort = str (boost::format ("%s %d") % strIp % iPort); - bool bScanDirty = false; - - DeprecatedScopedLock sl (getApp().getWalletDB ()->getDBLock ()); - Database* db = getApp().getWalletDB ()->getDB (); - - if (db->executeSQL (str (boost::format ("SELECT ScanNext FROM PeerIps WHERE IpPort=%s;") - % sqlEscape (strIpPort))) - && db->startIterRows ()) - { - if (db->getNull ("ScanNext")) - { - // Non-scanning connection terminated. Schedule for scanning. - int iInterval = getConfig ().PEER_SCAN_INTERVAL_MIN; - boost::posix_time::ptime tpNow = boost::posix_time::second_clock::universal_time (); - boost::posix_time::ptime tpNext = tpNow + boost::posix_time::seconds (iInterval); - - //WriteLog (lsINFO, Peers) << str(boost::format("Pool: Scan: schedule create: %s %s (next %s, delay=%d)") - // % mScanIp % mScanPort % tpNext % (tpNext-tpNow).total_seconds()); - - db->executeSQL (str (boost::format ("UPDATE PeerIps SET ScanNext=%d,ScanInterval=%d WHERE IpPort=%s;") - % iToSeconds (tpNext) - % iInterval - % sqlEscape (strIpPort))); - - bScanDirty = true; - } - else - { - // Scan connection terminated, already scheduled for retry. - // boost::posix_time::ptime tpNow = boost::posix_time::second_clock::universal_time(); - // boost::posix_time::ptime tpNext = ptFromSeconds(db->getInt("ScanNext")); - - //WriteLog (lsINFO, Peers) << str(boost::format("Pool: Scan: schedule exists: %s %s (next %s, delay=%d)") - // % mScanIp % mScanPort % tpNext % (tpNext-tpNow).total_seconds()); - } - - db->endIterRows (); - } - else - { - //WriteLog (lsWARNING, Peers) << "Pool: Scan: peer wasn't in PeerIps: " << strIp << " " << iPort; - } - - return bScanDirty; -} - -// --> strIp: not empty -void PeersImp::peerClosed (Peer::ref peer, const std::string& strIp, int iPort) -{ - IPAndPortNumber ipPeer = make_pair (strIp, iPort); - bool bScanRefresh = false; - - // If the connection was our scan, we are no longer scanning. - if (mScanning && mScanning == peer) - { - //WriteLog (lsINFO, Peers) << "Pool: Scan: scan fail: " << strIp << " " << iPort; - - mScanning.reset (); // No longer scanning. - bScanRefresh = true; // Look for more to scan. - } - - // Determine if closed peer was redundant. - bool bRedundant = true; - { - ScopedLockType sl (mPeerLock, __FILE__, __LINE__); - const boost::unordered_map::iterator& itIp = mIpMap.find (ipPeer); - - if (itIp == mIpMap.end ()) - { - // Did not find it. Not already connecting or connected. - WriteLog (lsWARNING, Peers) << "Pool: Closed: UNEXPECTED: " << addressToString (peer.get()) << ": " << strIp << " " << iPort; - // XXX Internal error. - } - else if (mIpMap[ipPeer] == peer) - { - // We were the identified connection. - //WriteLog (lsINFO, Peers) << "Pool: Closed: identified: " << addressToString (peer.get()) << ": " << strIp << " " << iPort; - - // Delete our entry. - mIpMap.erase (itIp); - - bRedundant = false; - } - else - { - // Found it. But, we were redundant. - //WriteLog (lsINFO, Peers) << "Pool: Closed: redundant: " << addressToString (peer.get()) << ": " << strIp << " " << iPort; - } - } - - if (!bRedundant) - { - // If closed was not redundant schedule if not already scheduled. - bScanRefresh = peerScanSet (ipPeer.first, ipPeer.second) || bScanRefresh; - } - - if (bScanRefresh) - scanRefresh (); -} - -void PeersImp::peerVerified (Peer::ref peer) -{ - if (mScanning && mScanning == peer) - { - // Scan completed successfully. - std::string strIp = peer->getIP (); - int iPort = peer->getPort (); - - std::string strIpPort = str (boost::format ("%s %d") % strIp % iPort); - - //WriteLog (lsINFO, Peers) << str(boost::format("Pool: Scan: connected: %s %s %s (scanned)") % addressToString (peer.get()) % strIp % iPort); - - if (peer->getNodePublic () == getApp().getLocalCredentials ().getNodePublic ()) - { - // Talking to ourself. We will just back off. This lets us maybe advertise our outside address. - - nothing (); // Do nothing, leave scheduled scanning. - } - else - { - // Talking with a different peer. - DeprecatedScopedLock sl (getApp().getWalletDB ()->getDBLock ()); - Database* db = getApp().getWalletDB ()->getDB (); - - db->executeSQL (boost::str (boost::format ("UPDATE PeerIps SET ScanNext=NULL,ScanInterval=0 WHERE IpPort=%s;") - % sqlEscape (strIpPort))); - // XXX Check error. - } - - mScanning.reset (); - - scanRefresh (); // Continue scanning. - } -} - -void PeersImp::scanHandler (const boost::system::error_code& ecResult) -{ - if (ecResult == boost::asio::error::operation_aborted) - { - nothing (); - } - else if (!ecResult) - { - scanRefresh (); - } - else - { - throw std::runtime_error ("Internal error: unexpected deadline error."); - } -} - -// Legacy policy enforcement: Maintain peer connections -// to the configured set of fixed IP addresses. Note that this -// is replaced by the new PeerFinder. -// -void PeersImp::legacyConnectFixedIPs () -{ - if (getConfig ().RUN_STANDALONE) - return; - - BOOST_FOREACH (const std::string & strPeer, getConfig ().IPS_FIXED) - { - std::string strIP; - int iPort; - - if (parseIpPort (strPeer, strIP, iPort)) - peerConnect (strIP, iPort); - } -} - -// Scan ips as per db entries. -void PeersImp::scanRefresh () -{ -#if ! RIPPLE_USE_PEERFINDER - if (getConfig ().RUN_STANDALONE) - { - nothing (); - } - else if (mScanning) - { - // Currently scanning, will scan again after completion. - WriteLog (lsTRACE, Peers) << "Pool: Scan: already scanning"; - - nothing (); - } - else - { - // Discover if there are entries that need scanning. - boost::posix_time::ptime tpNext; - boost::posix_time::ptime tpNow; - std::string strIpPort; - int iInterval; - - { - DeprecatedScopedLock sl (getApp().getWalletDB ()->getDBLock ()); - Database* db = getApp().getWalletDB ()->getDB (); - - if (db->executeSQL ("SELECT * FROM PeerIps INDEXED BY PeerScanIndex WHERE ScanNext NOT NULL ORDER BY ScanNext LIMIT 1;") - && db->startIterRows ()) - { - // Have an entry to scan. - int iNext = db->getInt ("ScanNext"); - - tpNext = ptFromSeconds (iNext); - tpNow = boost::posix_time::second_clock::universal_time (); - - db->getStr ("IpPort", strIpPort); - iInterval = db->getInt ("ScanInterval"); - db->endIterRows (); - } - else - { - // No entries to scan. - tpNow = boost::posix_time::ptime (boost::posix_time::not_a_date_time); - } - } - - if (tpNow.is_not_a_date_time ()) - { - //WriteLog (lsINFO, Peers) << "Pool: Scan: stop."; - - (void) mScanTimer.cancel (); - } - else if (tpNext <= tpNow) - { - // Scan it. - splitIpPort (strIpPort, mScanIp, mScanPort); - - (void) mScanTimer.cancel (); - - iInterval = std::max (iInterval, getConfig ().PEER_SCAN_INTERVAL_MIN); - - tpNext = tpNow + boost::posix_time::seconds (iInterval); - - //WriteLog (lsINFO, Peers) << str(boost::format("Pool: Scan: Now: %s %s (next %s, delay=%d)") - // % mScanIp % mScanPort % tpNext % (tpNext-tpNow).total_seconds()); - - iInterval *= 2; - - { - DeprecatedScopedLock sl (getApp().getWalletDB ()->getDBLock ()); - Database* db = getApp().getWalletDB ()->getDB (); - - db->executeSQL (boost::str (boost::format ("UPDATE PeerIps SET ScanNext=%d,ScanInterval=%d WHERE IpPort=%s;") - % iToSeconds (tpNext) - % iInterval - % sqlEscape (strIpPort))); - // XXX Check error. - } - - mScanning = peerConnect (mScanIp, mScanPort); - - if (!mScanning) - { - // Already connected. Try again. - scanRefresh (); - } - } - else - { - //WriteLog (lsINFO, Peers) << str(boost::format("Pool: Scan: Next: %s (next %s, delay=%d)") - // % strIpPort % tpNext % (tpNext-tpNow).total_seconds()); - - mScanTimer.expires_at (tpNext); - mScanTimer.async_wait (BIND_TYPE (&PeersImp::scanHandler, this, P_1)); - } - } -#endif -} - //------------------------------------------------------------------------------ -Peers::Peers () - : PropertyStream::Source ("peers") +Peers::~Peers () { } Peers* Peers::New (Stoppable& parent, Resource::Manager& resourceManager, SiteFiles::Manager& siteFiles, - boost::asio::io_service& io_service, - boost::asio::ssl::context& ssl_context) + Resolver& resolver, + boost::asio::io_service& io_service, + boost::asio::ssl::context& ssl_context) { - return new PeersImp (parent, resourceManager, siteFiles, io_service, ssl_context); + return new PeersImp (parent, resourceManager, siteFiles, + resolver, io_service, ssl_context); } +} diff --git a/src/ripple_app/peers/Peers.h b/src/ripple_app/peers/Peers.h index 798c3cc26..dd637a5ee 100644 --- a/src/ripple_app/peers/Peers.h +++ b/src/ripple_app/peers/Peers.h @@ -20,7 +20,10 @@ #ifndef RIPPLE_PEERS_H_INCLUDED #define RIPPLE_PEERS_H_INCLUDED +namespace ripple { + namespace PeerFinder { +struct Endpoint; class Manager; } @@ -32,84 +35,263 @@ namespace SiteFiles { class Manager; } +//------------------------------------------------------------------------------ + /** Manages the set of connected peers. */ -class Peers : public PropertyStream::Source +class Peers + : public Stoppable + , public PropertyStream::Source { +protected: + // VFALCO NOTE The requirement of this constructor is an + // unfortunate problem with the API for + // Stoppable and PropertyStream + // + Peers (Stoppable& parent) + : Stoppable ("Peers", parent) + , PropertyStream::Source ("peers") + { + + } + public: + typedef std::vector PeerSequence; + static Peers* New (Stoppable& parent, Resource::Manager& resourceManager, SiteFiles::Manager& siteFiles, - boost::asio::io_service& io_service, - boost::asio::ssl::context& context); + Resolver& resolver, + boost::asio::io_service& io_service, + boost::asio::ssl::context& context); - Peers (); - - virtual ~Peers () { } - - virtual PeerFinder::Manager& getPeerFinder() = 0; - - // Begin enforcing connection policy. - virtual void start () = 0; - - // Send message to network. - virtual int relayMessage (Peer* fromPeer, const PackedMessage::pointer& msg) = 0; - virtual int relayMessageCluster (Peer* fromPeer, const PackedMessage::pointer& msg) = 0; - virtual void relayMessageTo (const std::set& fromPeers, const PackedMessage::pointer& msg) = 0; - virtual void relayMessageBut (const std::set& fromPeers, const PackedMessage::pointer& msg) = 0; - - // Manual connection request. - // Queue for immediate scanning. - virtual void connectTo (const std::string& strIp, int iPort) = 0; + virtual ~Peers () = 0; + // NIKB TODO This is an implementation detail - a private + // interface between Peers and Peer. It should + // be split out and moved elsewhere. // - // Peer connectivity notification. + // VFALCO NOTE PeerImp should have visbility to PeersImp // - virtual bool getTopNAddrs (int n, std::vector& addrs) = 0; - virtual bool savePeer (const std::string& strIp, int iPort, char code) = 0; + virtual void peerCreated (Peer* peer) = 0; + virtual void peerDestroyed (Peer *peer) = 0; - // A peer connection has been established, but we know nothing about it at - // this point beyond the IP address. - virtual void peerConnected (const IPAddress& address, bool incoming) = 0; + virtual void accept (bool proxyHandshake, + boost::shared_ptr const& socket) = 0; - // We know peers node public key. - // <-- bool: false=reject - virtual bool peerHandshake (Peer::ref peer, const RippleAddress& naPeer, const std::string& strIP, int iPort) = 0; + virtual void connect (IP::Endpoint const& address) = 0; - // No longer connected. - virtual void peerDisconnected (Peer::ref peer, const RippleAddress& naPeer) = 0; + // Notification that a peer has connected. + virtual void onPeerActivated (Peer::ref peer) = 0; - // As client accepted. - virtual void peerVerified (Peer::ref peer) = 0; + // Notification that a peer has disconnected. + virtual void onPeerDisconnect (Peer::ref peer) = 0; - // As client failed connect and be accepted. - virtual void peerClosed (Peer::ref peer, const std::string& strIp, int iPort) = 0; - - virtual int getPeerCount () = 0; - virtual Json::Value getPeersJson () = 0; - virtual std::vector getPeerVector () = 0; + virtual std::size_t size () = 0; + virtual Json::Value json () = 0; + virtual PeerSequence getActivePeers () = 0; // Peer 64-bit ID function - virtual uint64 assignPeerId () = 0; - virtual Peer::pointer getPeerById (const uint64& id) = 0; - virtual bool hasPeer (const uint64& id) = 0; + virtual Peer::pointer findPeerByShortID (Peer::ShortId const& id) = 0; - // - // Scanning - // + virtual void addPeer (Peer::Ptr const& peer) = 0; + virtual void removePeer (Peer::Ptr const& peer) = 0; - virtual void scanRefresh () = 0; + /** Visit every active peer and return a value + The functor must: + - Be callable as: + void operator()(Peer::ref peer); + - Must have the following typedef: + typedef void return_type; + - Be callable as: + Function::return_type operator()() const; - // - // Connection policy - // - virtual void policyLowWater () = 0; - virtual void policyEnforce () = 0; // VFALCO This and others can be made private + @param f the functor to call with every peer + @returns `f()` - // configured connections - virtual void legacyConnectFixedIPs () = 0; + @note The functor is passed by value! + */ + template + typename boost::disable_if < + boost::is_void , + typename Function::return_type>::type + foreach(Function f) + { + PeerSequence peers (getActivePeers()); + + for(PeerSequence::const_iterator i = peers.begin(); i != peers.end(); ++i) + f (*i); + + return f(); + } + + /** Visit every active peer + The visitor functor must: + - Be callable as: + void operator()(Peer::ref peer); + - Must have the following typedef: + typedef void return_type; + + @param f the functor to call with every peer + */ + template + typename boost::enable_if < + boost::is_void , + typename Function::return_type>::type + foreach(Function f) + { + PeerSequence peers (getActivePeers()); + + for(PeerSequence::const_iterator i = peers.begin(); i != peers.end(); ++i) + f (*i); + } }; -// VFALCO TODO Put this in some group of utilities -extern void splitIpPort (const std::string& strIpPort, std::string& strIp, int& iPort); +/** Sends a message to all peers */ +struct send_always +{ + typedef void return_type; + + PackedMessage::pointer const& msg; + + send_always(PackedMessage::pointer const& m) + : msg(m) + { } + + void operator()(Peer::ref peer) const + { + peer->sendPacket (msg, false); + } +}; + +//------------------------------------------------------------------------------ + +/** Sends a message to match peers */ +template +struct send_if_pred +{ + typedef void return_type; + + PackedMessage::pointer const& msg; + Predicate const& predicate; + + send_if_pred(PackedMessage::pointer const& m, Predicate const& p) + : msg(m), predicate(p) + { } + + void operator()(Peer::ref peer) const + { + if (predicate (peer)) + peer->sendPacket (msg, false); + } +}; + +/** Helper function to aid in type deduction */ +template +send_if_pred send_if ( + PackedMessage::pointer const& m, + Predicate const &f) +{ + return send_if_pred(m, f); +} + +//------------------------------------------------------------------------------ + +/** Sends a message to non-matching peers */ +template +struct send_if_not_pred +{ + typedef void return_type; + + PackedMessage::pointer const& msg; + Predicate const& predicate; + + send_if_not_pred(PackedMessage::pointer const& m, Predicate const& p) + : msg(m), predicate(p) + { } + + void operator()(Peer::ref peer) const + { + if (!predicate (peer)) + peer->sendPacket (msg, false); + } +}; + +/** Helper function to aid in type deduction */ +template +send_if_not_pred send_if_not ( + PackedMessage::pointer const& m, + Predicate const &f) +{ + return send_if_not_pred(m, f); +} + +//------------------------------------------------------------------------------ + +/** Select the specific peer */ +struct match_peer +{ + Peer const* matchPeer; + + match_peer (Peer const* match = nullptr) + : matchPeer (match) + { } + + bool operator() (Peer::ref peer) const + { + bassert(peer->isConnected()); + + if(matchPeer && (peer.get () == matchPeer)) + return true; + + return false; + } +}; + +//------------------------------------------------------------------------------ + +/** Select all peers (except optional excluded) that are in our cluster */ +struct peer_in_cluster +{ + match_peer skipPeer; + + peer_in_cluster (Peer const* skip = nullptr) + : skipPeer (skip) + { } + + bool operator() (Peer::ref peer) const + { + if (skipPeer (peer)) + return false; + + if (!peer->isInCluster ()) + return false; + + return true; + } +}; + +//------------------------------------------------------------------------------ + +/** Select all peers that are in the specified set */ +struct peer_in_set +{ + std::set const& peerSet; + + peer_in_set (std::set const& peers) + : peerSet (peers) + { } + + bool operator() (Peer::ref peer) const + { + bassert(peer->isConnected()); + + if (peerSet.count (peer->getShortId ()) == 0) + return false; + + return true; + } +}; + +} #endif diff --git a/src/ripple_app/peers/TODO.md b/src/ripple_app/peers/TODO.md new file mode 100644 index 000000000..6ca873551 --- /dev/null +++ b/src/ripple_app/peers/TODO.md @@ -0,0 +1,66 @@ +# Peer.cpp + +- Move magic number constants into Tuning.h / Peer constants enum + +- Wrap lines to 80 columns + +- Use Journal + +- Pass Journal in the ctor argument list + +- Use m_socket->remote_endpoint() instead of m_remoteAddress in all cases. + For async_connect pass the IPAddress in the bind to onConnect completion + handler (so we know the address if the connect fails). For PROXY, to recover + the original remote address (of the ELB) use m_socket->next_layer()->remote_endpoint(), + and use m_socket->remote_endpoint() to get the "real IP" reported in the PROXY + handshake. + +- Handle operation_aborted correctly, work with Peers.cpp to properly handle + a stop. Peers need to be gracefully disconnected, the listening socket closed + on the stop to prevent new connections (and new connections that slip + through should be refused). The Peers object needs to know when the last + Peer has finished closing either gracefully or from an expired graceful + close timer. + +- Handle graceful connection closure (With a graceful close timeout). During + a graceful close, throttle incoming data (by not issuing new socket reads), + discard any received messages, drain the outbound queue, and tear down + the socket when the last send completes. + +- PeerImp should construct with the socket, using a move-assign or swap. + PeerDoor should not have to create a Peer object, it should just create a + socket, accept the connection, and then construct the Peer object. + +- No more strings for IP addresses and ports. Always use IPAddress. We can + have a version of connect() that takes a string but it should either convert + it to IPAddress if its parseable, else perform a name resolution. + +- Stop calling getNativeSocket() this and that, just go through m_socket. + +- Properly handle operation_aborted in all the callbacks. + +- Move all the function definitions into the class declaration. + +- Replace macros with language constants. + +- Stop checking for exceptions, just handle errors correctly. + +- Move the business logic out of the Peer class. Socket operations and business + logic should not be mixed together. We can declare a new class PeerLogic to + abstract the details of message processing. + +- Change m_nodePublicKey from RippleAddress to RipplePublicKey, change the + header, and modify all call sites to use the new type instead of the old. This + might require adding some compatibility functions to RipplePublicKey. + +- Remove public functions that are not used outside of Peer.cpp + +# Peers.cpp + +- Add Peer::Config instead of using getConfig() to pass in configuration data + +# PeerSet.cpp + +- Remove to cyclic dependency on InboundLedger (logging only) + +- Convert to Journal diff --git a/src/ripple_app/peers/UniqueNodeList.cpp b/src/ripple_app/peers/UniqueNodeList.cpp index 963eada0b..6e188dc58 100644 --- a/src/ripple_app/peers/UniqueNodeList.cpp +++ b/src/ripple_app/peers/UniqueNodeList.cpp @@ -143,9 +143,6 @@ public: // Score again if needed. scoreNext (false); - - // Scan may be dirty due to new ips. - getApp().getPeers ().scanRefresh (); } void doFetch () @@ -201,7 +198,7 @@ public: } // Promote source, if needed. - if (!bFound || iSourceScore (vsWhy) >= iSourceScore (snCurrent.vsSource)) + if (!bFound /*|| iSourceScore (vsWhy) >= iSourceScore (snCurrent.vsSource)*/) { snCurrent.vsSource = vsWhy; snCurrent.strComment = strComment; @@ -442,6 +439,7 @@ public: int iNodes = 0; Database* db = getApp().getWalletDB ()->getDB (); +#if 0 { DeprecatedScopedLock sl (getApp().getWalletDB ()->getDBLock ()); @@ -455,6 +453,7 @@ public: db->endIterRows (); } +#endif bool bLoaded = iDomains || iNodes; @@ -494,41 +493,6 @@ public: nodeNetwork (); } - - // Take the set of entries in IPS and insert them into the - // "legacy endpoint" database so they will be served as IP addresses - // in the legacy mtPEERS message. Note that this is all replaced by - // the new PeerFinder. - // - if (!getConfig ().IPS.empty ()) - { - std::vector vstrValues; - - vstrValues.reserve (getConfig ().IPS.size ()); - - BOOST_FOREACH (const std::string & strPeer, getConfig ().IPS) - { - std::string strIP; - int iPort; - - if (parseIpPort (strPeer, strIP, iPort)) - { - vstrValues.push_back (str (boost::format ("(%s,'%c')") - % sqlEscape (str (boost::format ("%s %d") % strIP % iPort)) - % static_cast (vsConfig))); - } - } - - if (!vstrValues.empty ()) - { - DeprecatedScopedLock sl (getApp().getWalletDB ()->getDBLock ()); - - db->executeSQL (str (boost::format ("REPLACE INTO PeerIps (IpPort,Source) VALUES %s;") - % strJoin (vstrValues.begin (), vstrValues.end (), ","))); - } - - fetchDirty (); - } } //-------------------------------------------------------------------------- @@ -1087,9 +1051,6 @@ private: mUNL.swap (usUNL); } - // Score IPs. - db->executeSQL ("UPDATE PeerIps SET Score = 0 WHERE Score != 0;"); - boost::unordered_map umValidators; if (!vsnNodes.empty ()) @@ -1142,31 +1103,6 @@ private: } } - // Apply validator scores to each IP. - if (umScore.size ()) - { - std::vector vstrValues; - - vstrValues.reserve (umScore.size ()); - - typedef boost::unordered_map, score>::value_type ipScoreType; - BOOST_FOREACH (ipScoreType & ipScore, umScore) - { - IPAndPortNumber ipEndpoint = ipScore.first; - std::string strIpPort = str (boost::format ("%s %d") % ipEndpoint.first % ipEndpoint.second); - score iPoints = ipScore.second; - - vstrValues.push_back (str (boost::format ("(%s,%d,'%c')") - % sqlEscape (strIpPort) - % iPoints - % vsValidator)); - } - - // Set scores for each IP. - db->executeSQL (str (boost::format ("REPLACE INTO PeerIps (IpPort,Score,Source) VALUES %s;") - % strJoin (vstrValues.begin (), vstrValues.end (), ","))); - } - db->executeSQL ("COMMIT;"); } @@ -1338,15 +1274,15 @@ private: { ScopedFetchLockType sl (mFetchLock, __FILE__, __LINE__); - bFull = mFetchActive == NODE_FETCH_JOBS; + bFull = (mFetchActive == NODE_FETCH_JOBS); } if (!bFull) { // Determine next scan. - std::string strDomain; - boost::posix_time::ptime tpNext; - boost::posix_time::ptime tpNow; + std::string strDomain; + boost::posix_time::ptime tpNext (boost::posix_time::min_date_time); + boost::posix_time::ptime tpNow (boost::posix_time::second_clock::universal_time ()); DeprecatedScopedLock sl (getApp().getWalletDB ()->getDBLock ()); Database* db = getApp().getWalletDB ()->getDB (); @@ -1354,13 +1290,12 @@ private: if (db->executeSQL ("SELECT Domain,Next FROM SeedDomains INDEXED BY SeedDomainNext ORDER BY Next LIMIT 1;") && db->startIterRows ()) { - int iNext = db->getInt ("Next"); + int iNext (db->getInt ("Next")); tpNext = ptFromSeconds (iNext); - tpNow = boost::posix_time::second_clock::universal_time (); WriteLog (lsTRACE, UniqueNodeList) << str (boost::format ("fetchNext: iNext=%s tpNext=%s tpNow=%s") % iNext % tpNext % tpNow); - strDomain = db->getStrBinary ("Domain"); + strDomain = db->getStrBinary ("Domain"); db->endIterRows (); } @@ -1369,7 +1304,7 @@ private: { ScopedFetchLockType sl (mFetchLock, __FILE__, __LINE__); - bFull = mFetchActive == NODE_FETCH_JOBS; + bFull = (mFetchActive == NODE_FETCH_JOBS); if (!bFull && tpNext <= tpNow) { @@ -1380,8 +1315,6 @@ private: if (strDomain.empty () || bFull) { WriteLog (lsTRACE, UniqueNodeList) << str (boost::format ("fetchNext: strDomain=%s bFull=%d") % strDomain % bFull); - - nothing (); } else if (tpNext > tpNow) { @@ -1389,7 +1322,11 @@ private: // Fetch needs to happen in the future. Set a timer to wake us. mtpFetchNext = tpNext; - double const seconds = (tpNext - tpNow).seconds (); + double seconds = (tpNext - tpNow).seconds (); + + // VFALCO check this. + if (seconds == 0) + seconds = 1; m_fetchTimer.setExpiration (seconds); } @@ -1722,7 +1659,6 @@ private: if (naValidator.setSeedGeneric (strRefered)) { - WriteLog (lsWARNING, UniqueNodeList) << str (boost::format ("Bad validator: domain or public key required: %s %s") % strRefered % strComment); } else if (naValidator.setNodePublic (strRefered)) diff --git a/src/ripple_app/ripple_app.cpp b/src/ripple_app/ripple_app.cpp index 9c3f15c76..048a19374 100644 --- a/src/ripple_app/ripple_app.cpp +++ b/src/ripple_app/ripple_app.cpp @@ -59,12 +59,6 @@ namespace ripple { # include "main/FatalErrorReporter.h" #include "main/FatalErrorReporter.cpp" -# include "node/SqliteFactory.h" -#include "node/SqliteFactory.cpp" - -# include "peers/PeerDoor.h" -#include "peers/PeerDoor.cpp" - # include "rpc/RPCHandler.h" # include "misc/PowResult.h" # include "misc/ProofOfWork.h" @@ -91,6 +85,11 @@ namespace ripple { +#include "../ripple/common/ResolverAsio.h" + +# include "node/SqliteFactory.h" +#include "node/SqliteFactory.cpp" + #include "main/Application.cpp" diff --git a/src/ripple_app/ripple_app.h b/src/ripple_app/ripple_app.h index 24a04a48f..8026aaec0 100644 --- a/src/ripple_app/ripple_app.h +++ b/src/ripple_app/ripple_app.h @@ -54,6 +54,8 @@ #include "../ripple_data/ripple_data.h" #include "../ripple_net/ripple_net.h" +#include "../ripple/common/ResolverAsio.h" + //#include "beast/modules/beast_sqdb/beast_sqdb.h" #include "beast/modules/beast_sqlite/beast_sqlite.h" @@ -101,8 +103,11 @@ namespace ripple { #include "misc/IFeatures.h" #include "misc/IFeeVote.h" #include "misc/IHashRouter.h" +} +// escape the ripple namespace #include "peers/Peer.h" #include "peers/Peers.h" +namespace ripple { #include "peers/ClusterNodeStatus.h" #include "peers/UniqueNodeList.h" #include "misc/Validations.h" diff --git a/src/ripple_app/ripple_app_pt5.cpp b/src/ripple_app/ripple_app_pt5.cpp index 86a0dc129..6710c260e 100644 --- a/src/ripple_app/ripple_app_pt5.cpp +++ b/src/ripple_app/ripple_app_pt5.cpp @@ -27,10 +27,9 @@ #include "../ripple/resource/ripple_resource.h" #include "../ripple/validators/ripple_validators.h" -#include +#include "../ripple/common/ResolverAsio.h" -#include "peers/NameResolver.h" -#include "peers/NameResolver.cpp" +#include namespace ripple { @@ -44,7 +43,9 @@ namespace ripple { # include "misc/PowResult.h" # include "misc/ProofOfWork.h" # include "misc/ProofOfWorkFactory.h" -#include "peers/Peer.cpp" #include "peers/PackedMessage.cpp" -#include "peers/Peers.cpp" } + +#include "peers/Peer.cpp" +#include "peers/PeerDoor.cpp" +#include "peers/Peers.cpp" diff --git a/src/ripple_app/rpc/RPCHandler.cpp b/src/ripple_app/rpc/RPCHandler.cpp index 7b3e3d9c4..5675dfd08 100644 --- a/src/ripple_app/rpc/RPCHandler.cpp +++ b/src/ripple_app/rpc/RPCHandler.cpp @@ -827,7 +827,9 @@ Json::Value RPCHandler::doBlackList (Json::Value params, Resource::Charge& loadT // port: // } // XXX Might allow domain for manual connections. -Json::Value RPCHandler::doConnect (Json::Value params, Resource::Charge& loadType, Application::ScopedLockType& masterLockHolder) +Json::Value RPCHandler::doConnect (Json::Value params, + Resource::Charge& loadType, + Application::ScopedLockType& masterLockHolder) { if (getConfig ().RUN_STANDALONE) return "cannot connect in standalone mode"; @@ -835,11 +837,20 @@ Json::Value RPCHandler::doConnect (Json::Value params, Resource::Charge& loadTyp if (!params.isMember ("ip")) return RPC::missing_field_error ("ip"); - std::string strIp = params["ip"].asString (); - int iPort = params.isMember ("port") ? params["port"].asInt () : -1; + if (params.isMember ("port") && !params["port"].isConvertibleTo (Json::intValue)) + return rpcError (rpcINVALID_PARAMS); - // XXX Validate legal IP and port - getApp().getPeers ().connectTo (strIp, iPort); + int iPort; + + if(params.isMember ("port")) + iPort = params["port"].asInt (); + else + iPort = SYSTEM_PEER_PORT; + + IPAddress ip (IPAddress::from_string(params["ip"].asString ())); + + if (! is_unspecified (ip)) + getApp().getPeers ().connect (ip.at_port(iPort)); return "connecting"; } @@ -989,7 +1000,7 @@ Json::Value RPCHandler::doPeers (Json::Value, Resource::Charge& loadType, Applic { Json::Value jvResult (Json::objectValue); - jvResult["peers"] = getApp().getPeers ().getPeersJson (); + jvResult["peers"] = getApp().getPeers ().json (); getApp().getUNL().addClusterStatus(jvResult); diff --git a/src/ripple_app/tx/TransactionAcquire.cpp b/src/ripple_app/tx/TransactionAcquire.cpp index 261e35022..a67140439 100644 --- a/src/ripple_app/tx/TransactionAcquire.cpp +++ b/src/ripple_app/tx/TransactionAcquire.cpp @@ -89,7 +89,7 @@ void TransactionAcquire::onTimer (bool progress, ScopedLockType& psl) WriteLog (lsWARNING, TransactionAcquire) << "Still need it"; mTimeouts = 0; aggressive = true; - } + } } psl.lock(__FILE__, __LINE__); @@ -107,7 +107,7 @@ void TransactionAcquire::onTimer (bool progress, ScopedLockType& psl) WriteLog (lsWARNING, TransactionAcquire) << "Out of peers for TX set " << getHash (); bool found = false; - std::vector peerList = getApp().getPeers ().getPeerVector (); + Peers::PeerSequence peerList = getApp().getPeers ().getActivePeers (); BOOST_FOREACH (Peer::ref peer, peerList) { if (peer->hasTxSet (getHash ())) diff --git a/src/ripple_app/websocket/WSConnection.cpp b/src/ripple_app/websocket/WSConnection.cpp index 11fe3f0ce..3facd608b 100644 --- a/src/ripple_app/websocket/WSConnection.cpp +++ b/src/ripple_app/websocket/WSConnection.cpp @@ -139,7 +139,7 @@ Json::Value WSConnection::invokeCommand (Json::Value& jvRequest) Config::Role const role = m_isPublic ? Config::GUEST // Don't check on the public interface. : getConfig ().getAdminRole ( - jvRequest, m_remoteAddress.withPort(0)); + jvRequest, m_remoteAddress); if (Config::FORBID == role) { diff --git a/src/ripple_app/websocket/WSServerHandler.h b/src/ripple_app/websocket/WSServerHandler.h index 88fb21d62..1bd0f8182 100644 --- a/src/ripple_app/websocket/WSServerHandler.h +++ b/src/ripple_app/websocket/WSServerHandler.h @@ -299,7 +299,7 @@ public: try { WriteLog (lsDEBUG, WSServerHandlerLog) << - "Ws:: Rejected(" << cpClient->get_socket ().remote_endpoint ().to_string () << + "Ws:: Rejected(" << to_string (cpClient->get_socket ().remote_endpoint ()) << ") '" << mpMessage->get_payload () << "'"; } catch (...) @@ -351,7 +351,7 @@ public: try { WriteLog (lsDEBUG, WSServerHandlerLog) << - "Ws:: Receiving(" << cpClient->get_socket ().remote_endpoint ().to_string () << + "Ws:: Receiving(" << to_string (cpClient->get_socket ().remote_endpoint ()) << ") '" << mpMessage->get_payload () << "'"; } catch (...) diff --git a/src/ripple_basics/utility/StringUtilities.cpp b/src/ripple_basics/utility/StringUtilities.cpp index bd54212fc..394b077d7 100644 --- a/src/ripple_basics/utility/StringUtilities.cpp +++ b/src/ripple_basics/utility/StringUtilities.cpp @@ -271,14 +271,6 @@ bool parseQuality (const std::string& strSource, uint32& uQuality) return !!uQuality; } -std::string addressToString (void const* address) -{ - // VFALCO TODO Clean this up, use uintptr_t and only produce a 32 bit - // output on 32 bit platforms - // - return strHex (static_cast (address) - static_cast (0)); -} - StringPairArray parseDelimitedKeyValueString (String parameters, beast_wchar delimiter) { StringPairArray keyValues; diff --git a/src/ripple_basics/utility/StringUtilities.h b/src/ripple_basics/utility/StringUtilities.h index a66175781..d97fe8d39 100644 --- a/src/ripple_basics/utility/StringUtilities.h +++ b/src/ripple_basics/utility/StringUtilities.h @@ -165,10 +165,6 @@ bool parseUrl (const std::string& strUrl, std::string& strScheme, std::string& s #define ADDRESS(p) strHex(uint64( ((char*) p) - ((char*) 0))) -/** Convert a pointer address to a string for display purposes. -*/ -extern std::string addressToString (void const* address); - /** Create a Parameters from a String. Parameter strings have the format: diff --git a/src/ripple_core/functional/Config.cpp b/src/ripple_core/functional/Config.cpp index 3751f91d9..d574170f1 100644 --- a/src/ripple_core/functional/Config.cpp +++ b/src/ripple_core/functional/Config.cpp @@ -47,7 +47,7 @@ void parseAddresses (OutputSequence& out, InputIterator first, InputIterator las ++first; { IPAddress const addr (IPAddress::from_string (str)); - if (! addr.empty ()) + if (! is_unspecified (addr)) { out.push_back (addr); continue; @@ -55,7 +55,7 @@ void parseAddresses (OutputSequence& out, InputIterator first, InputIterator las } { IPAddress const addr (IPAddress::from_string_altform (str)); - if (! addr.empty ()) + if (! is_unspecified (addr)) { out.push_back (addr); continue; @@ -89,7 +89,6 @@ Config::Config () // Defaults // - TESTNET = false; NETWORK_START_TIME = 1319844908; RPC_SECURE = 0; @@ -149,7 +148,7 @@ Config::Config () START_UP = NORMAL; } -void Config::setup (const std::string& strConf, bool bTestNet, bool bQuiet) +void Config::setup (const std::string& strConf, bool bQuiet) { boost::system::error_code ec; std::string strDbPath, strConfFile; @@ -160,36 +159,19 @@ void Config::setup (const std::string& strConf, bool bTestNet, bool bQuiet) // that with "db" as the data directory. // - TESTNET = bTestNet; QUIET = bQuiet; NODE_SIZE = 0; - // VFALCO NOTE TESTNET forces a "testnet-" prefix on the conf - // file and db directory, unless --conf is specified - // in which case there is no forced prefix. + strDbPath = Helpers::getDatabaseDirName (); + strConfFile = strConf.empty () ? Helpers::getConfigFileName () : strConf; - strDbPath = Helpers::getDatabaseDirName (TESTNET); - strConfFile = strConf.empty () ? Helpers::getConfigFileName (TESTNET) : strConf; - - VALIDATORS_BASE = Helpers::getValidatorsFileName (TESTNET); + VALIDATORS_BASE = Helpers::getValidatorsFileName (); VALIDATORS_URI = boost::str (boost::format ("/%s") % VALIDATORS_BASE); - if (TESTNET) - { - SIGN_TRANSACTION = HashPrefix::txSignTestnet; - SIGN_VALIDATION = HashPrefix::validationTestnet; - SIGN_PROPOSAL = HashPrefix::proposalTestnet; - } - else - { - SIGN_TRANSACTION = HashPrefix::txSign; - SIGN_VALIDATION = HashPrefix::validation; - SIGN_PROPOSAL = HashPrefix::proposal; - } - - if (TESTNET) - Base58::setCurrentAlphabet (Base58::getTestnetAlphabet ()); + SIGN_TRANSACTION = HashPrefix::txSign; + SIGN_VALIDATION = HashPrefix::validation; + SIGN_PROPOSAL = HashPrefix::proposal; if (!strConf.empty ()) { @@ -641,139 +623,6 @@ Config& getConfig () //------------------------------------------------------------------------------ -/* The location of the configuration file is checked as follows, - in order of descending priority: - - 1. In the path specified by --conf command line option if present - (which is relative to the current directory) - 2. In the current user's "home" directory - 3. In the same directory as the rippled executable - 4. In the "current" directory defined by the process which launched rippled -*/ -File Config::findConfigFile (String commandLineLocation, bool forTestNetwork) -{ - File file (File::nonexistent ()); - -#if 0 - // Highest priority goes to commandLineLocation - // - if (file == File::nonexistent() && commandLineLocation != String::empty) - { - // If commandLineLocation is a full path, - // this will just assign the full path to file. - // - file = File::getCurrentDirectory().getChildFile (commandLineLocation); - - if (! file.existsAsFile ()) - file = File::nonexistent (); - } - - // Next, we will look in the user's home directory - // -#else - -#if 0 - // VFALCO NOTE This is the original legacy code... - - std::string strConfFile; - - // Determine the config and data directories. - // If the config file is found in the current working directory, - // use the current working directory as the config directory and - // that with "db" as the data directory. - { - String s; - - if (forTestNetwork) - s += "testnet-"; - - if (commandLineLocation != String::empty) - s += Helpers::getConfigFileName (forTestNetwork); - - strConfFile = boost::str (boost::format (forTestNetwork ? "testnet-%s" : "%s") - % (strConf.empty () ? Helpers::getConfigFileName() : strConf)); - - VALIDATORS_BASE = boost::str (boost::format (TESTNET ? "testnet-%s" : "%s") - % VALIDATORS_FILE_NAME); - VALIDATORS_URI = boost::str (boost::format ("/%s") % VALIDATORS_BASE); - - if (TESTNET) - { - SIGN_TRANSACTION = HashPrefix::txSignTestnet; - SIGN_VALIDATION = HashPrefix::validationTestnet; - SIGN_PROPOSAL = HashPrefix::proposalTestnet; - } - else - { - SIGN_TRANSACTION = HashPrefix::txSign; - SIGN_VALIDATION = HashPrefix::validation; - SIGN_PROPOSAL = HashPrefix::proposal; - } - - if (TESTNET) - Base58::setCurrentAlphabet (Base58::getTestnetAlphabet ()); - - if (!strConf.empty ()) - { - // --conf= : everything is relative that file. - CONFIG_FILE = strConfFile; - CONFIG_DIR = boost::filesystem::absolute (CONFIG_FILE); - CONFIG_DIR.remove_filename (); - DATA_DIR = CONFIG_DIR / strDbPath; - } - else - { - CONFIG_DIR = boost::filesystem::current_path (); - CONFIG_FILE = CONFIG_DIR / strConfFile; - DATA_DIR = CONFIG_DIR / strDbPath; - - if (exists (CONFIG_FILE) - // Can we figure out XDG dirs? - || (!getenv ("HOME") && (!getenv ("XDG_CONFIG_HOME") || !getenv ("XDG_DATA_HOME")))) - { - // Current working directory is fine, put dbs in a subdir. - nothing (); - } - else - { - // Construct XDG config and data home. - // http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html - std::string strHome = strGetEnv ("HOME"); - std::string strXdgConfigHome = strGetEnv ("XDG_CONFIG_HOME"); - std::string strXdgDataHome = strGetEnv ("XDG_DATA_HOME"); - - if (strXdgConfigHome.empty ()) - { - // $XDG_CONFIG_HOME was not set, use default based on $HOME. - strXdgConfigHome = boost::str (boost::format ("%s/.config") % strHome); - } - - if (strXdgDataHome.empty ()) - { - // $XDG_DATA_HOME was not set, use default based on $HOME. - strXdgDataHome = boost::str (boost::format ("%s/.local/share") % strHome); - } - - CONFIG_DIR = boost::str (boost::format ("%s/" SYSTEM_NAME) % strXdgConfigHome); - CONFIG_FILE = CONFIG_DIR / strConfFile; - DATA_DIR = boost::str (boost::format ("%s/" SYSTEM_NAME) % strXdgDataHome); - - boost::filesystem::create_directories (CONFIG_DIR, ec); - - if (ec) - throw std::runtime_error (boost::str (boost::format ("Can not create %s") % CONFIG_DIR)); - } - } - -#endif - -#endif - - return file; -} - -//------------------------------------------------------------------------------ - File Config::getConfigDir () const { String const s (CONFIG_FILE.native().c_str ()); diff --git a/src/ripple_core/functional/Config.h b/src/ripple_core/functional/Config.h index 6e3ae644c..f08057694 100644 --- a/src/ripple_core/functional/Config.h +++ b/src/ripple_core/functional/Config.h @@ -102,19 +102,19 @@ public: struct Helpers { // This replaces CONFIG_FILE_NAME - static char const* getConfigFileName (bool forTestNetwork = false) + static char const* getConfigFileName () { - return forTestNetwork ? "testnet-rippled.cfg" : "rippled.cfg"; + return "rippled.cfg"; } - static char const* getDatabaseDirName (bool forTestNetwork = false) + static char const* getDatabaseDirName () { - return forTestNetwork ? "testnet-db" : "db"; + return "db"; } - static char const* getValidatorsFileName (bool forTestNetwork = false) + static char const* getValidatorsFileName () { - return forTestNetwork ? "testnet-validators.txt" : "validators.txt"; + return "validators.txt"; } }; @@ -178,18 +178,6 @@ public: //-------------------------------------------------------------------------- - /** Determine the location of the config file. - This searches the location provided on the command line first, - followed by the platform specific "user's home" directory. - @param commandLineLocation an optional location passed from the command line. - @param forTestNetwork Whether or not we are operating on the test network (DEPRECATATED) - */ - static File findConfigFile ( - String commandLineLocation = String::empty, - bool forTestNetwork = false); - - //-------------------------------------------------------------------------- - // Settings related to the configuration file location and directories /** Returns the directory from which the configuration file was loaded. */ @@ -339,7 +327,6 @@ public: public: // Configuration parameters bool QUIET; - bool TESTNET; boost::filesystem::path DEBUG_LOGFILE; std::string CONSOLE_LOG_OUTPUT; @@ -348,7 +335,7 @@ public: std::string VALIDATORS_SITE; // Where to find validators.txt on the Internet. std::string VALIDATORS_URI; // URI of validators.txt. - std::string VALIDATORS_BASE; // Name with testnet-, if needed. + std::string VALIDATORS_BASE; // Name std::vector IPS; // Peer IPs from rippled.cfg. std::vector IPS_FIXED; // Fixed Peer IPs from rippled.cfg. std::vector SNTP_SERVERS; // SNTP servers from rippled.cfg. @@ -483,7 +470,7 @@ public: Config (); int getSize (SizedItemName); - void setup (const std::string& strConf, bool bTestNet, bool bQuiet); + void setup (const std::string& strConf, bool bQuiet); void load (); }; diff --git a/src/ripple_data/protocol/HashPrefix.h b/src/ripple_data/protocol/HashPrefix.h index a567bcb7e..c00d070fa 100644 --- a/src/ripple_data/protocol/HashPrefix.h +++ b/src/ripple_data/protocol/HashPrefix.h @@ -68,15 +68,6 @@ struct HashPrefix // proposal for signing static uint32 const proposal = 0x50525000; // 'PRP' - - // inner transaction to sign (TESTNET) - static uint32 const txSignTestnet = 0x73747800; // 'stx' - - // validation for signing (TESTNET) - static uint32 const validationTestnet = 0x76616C00; // 'val' - - // proposal for signing (TESTNET) - static uint32 const proposalTestnet = 0x70727000; // 'prp' }; #endif diff --git a/src/ripple_data/protocol/ripple.proto b/src/ripple_data/protocol/ripple.proto index 979eab928..ed45edcd4 100644 --- a/src/ripple_data/protocol/ripple.proto +++ b/src/ripple_data/protocol/ripple.proto @@ -244,12 +244,12 @@ message TMPeers // An Endpoint describes a network peer that can accept incoming connections message TMEndpoint { - required TMIPv4EndPoint ipv4 = 1; - required uint32 hops = 2; - required uint32 slots = 3; // the number of available incoming slots - required uint32 maxSlots = 4; // the maximum number of incoming slots - required uint32 uptimeSeconds = 5; // uptime in seconds - required string features = 6; + required TMIPv4EndPoint ipv4 = 1; + required uint32 hops = 2; + + // This field is used to allow the TMEndpoint message format to be modified + // as necessary in the future. + required uint32 version = 3; } // An array of Endpoint messages diff --git a/src/ripple_net/basics/MultiSocket.cpp b/src/ripple_net/basics/MultiSocket.cpp index aa7133d70..c9d8a5528 100644 --- a/src/ripple_net/basics/MultiSocket.cpp +++ b/src/ripple_net/basics/MultiSocket.cpp @@ -17,6 +17,17 @@ */ //============================================================================== +MultiSocket* MultiSocket::New ( + boost::asio::ip::tcp::socket& socket, + boost::asio::ssl::context& ssl_context, + int flags) +{ + return new MultiSocketType ( + socket, ssl_context, flags); +} + +//------------------------------------------------------------------------------ + MultiSocket* MultiSocket::New ( boost::asio::io_service& io_service, boost::asio::ssl::context& ssl_context, diff --git a/src/ripple_net/basics/MultiSocket.h b/src/ripple_net/basics/MultiSocket.h index 6edf5ba30..86a980251 100644 --- a/src/ripple_net/basics/MultiSocket.h +++ b/src/ripple_net/basics/MultiSocket.h @@ -110,6 +110,13 @@ public: /** Returns a pointer to the SSL handle or nullptr if no SSL. */ virtual SSL* ssl_handle () = 0; + // Caller owns the socket + static MultiSocket* New ( + boost::asio::ip::tcp::socket& socket, + boost::asio::ssl::context& ssl_context, + int flags = 0); + + // Caller owns the io_service static MultiSocket* New ( boost::asio::io_service& io_service, boost::asio::ssl::context& ssl_context, diff --git a/src/ripple_net/basics/impl/MultiSocketType.h b/src/ripple_net/basics/impl/MultiSocketType.h index a8139b742..03de8ad7e 100644 --- a/src/ripple_net/basics/impl/MultiSocketType.h +++ b/src/ripple_net/basics/impl/MultiSocketType.h @@ -102,7 +102,7 @@ protected: if (m_proxyInfo.protocol == "TCP4") { return IPAddress ( - IPAddress::V4 ( + IP::AddressV4 ( m_proxyInfo.destAddress.value [0], m_proxyInfo.destAddress.value [1], m_proxyInfo.destAddress.value [2],