From 818130a8c0cadf6cca8981a38948710506b7ab5a Mon Sep 17 00:00:00 2001 From: Nik Bougalis Date: Wed, 7 Oct 2015 01:40:31 -0700 Subject: [PATCH] Separate cluster tracking from UNL: * Simplify code * Leverage C++14 transparent comparators --- Builds/VisualStudio2015/RippleD.vcxproj | 12 +- .../VisualStudio2015/RippleD.vcxproj.filters | 11 +- src/ripple/app/main/Application.cpp | 9 + src/ripple/app/main/Application.h | 2 + src/ripple/app/misc/NetworkOPs.cpp | 40 ++-- src/ripple/app/misc/UniqueNodeList.cpp | 129 ------------ src/ripple/app/misc/UniqueNodeList.h | 7 - src/ripple/overlay/Cluster.h | 115 +++++++++++ .../{ClusterNodeStatus.h => ClusterNode.h} | 53 +++-- src/ripple/overlay/impl/Cluster.cpp | 143 ++++++++++++++ src/ripple/overlay/impl/ConnectAttempt.cpp | 11 +- src/ripple/overlay/impl/OverlayImpl.cpp | 8 +- src/ripple/overlay/impl/PeerImp.cpp | 46 ++++- src/ripple/overlay/tests/cluster_test.cpp | 187 ++++++++++++++++++ src/ripple/protocol/JsonFields.h | 4 +- src/ripple/rpc/handlers/Peers.cpp | 32 ++- src/ripple/unity/overlay.cpp | 2 + 17 files changed, 603 insertions(+), 208 deletions(-) create mode 100644 src/ripple/overlay/Cluster.h rename src/ripple/overlay/{ClusterNodeStatus.h => ClusterNode.h} (59%) create mode 100644 src/ripple/overlay/impl/Cluster.cpp create mode 100644 src/ripple/overlay/tests/cluster_test.cpp diff --git a/Builds/VisualStudio2015/RippleD.vcxproj b/Builds/VisualStudio2015/RippleD.vcxproj index caf586417..dd78d653a 100644 --- a/Builds/VisualStudio2015/RippleD.vcxproj +++ b/Builds/VisualStudio2015/RippleD.vcxproj @@ -2582,8 +2582,14 @@ - + + + + + True + True + True True @@ -2648,6 +2654,10 @@ + + True + True + True True diff --git a/Builds/VisualStudio2015/RippleD.vcxproj.filters b/Builds/VisualStudio2015/RippleD.vcxproj.filters index fb23071b8..df06558c4 100644 --- a/Builds/VisualStudio2015/RippleD.vcxproj.filters +++ b/Builds/VisualStudio2015/RippleD.vcxproj.filters @@ -3255,9 +3255,15 @@ ripple\nodestore - + ripple\overlay + + ripple\overlay + + + ripple\overlay\impl + ripple\overlay\impl @@ -3330,6 +3336,9 @@ ripple\overlay + + ripple\overlay\tests + ripple\overlay\tests diff --git a/src/ripple/app/main/Application.cpp b/src/ripple/app/main/Application.cpp index 57b55669e..eb2803bc6 100644 --- a/src/ripple/app/main/Application.cpp +++ b/src/ripple/app/main/Application.cpp @@ -59,6 +59,7 @@ #include #include #include +#include #include #include #include @@ -320,6 +321,7 @@ public: std::unique_ptr m_inboundTransactions; TaggedCache m_acceptedLedgerCache; std::unique_ptr m_networkOPs; + std::unique_ptr cluster_; std::unique_ptr m_deprecatedUNL; std::unique_ptr serverHandler_; std::unique_ptr m_amendmentTable; @@ -660,6 +662,11 @@ public: return *m_deprecatedUNL; } + Cluster& cluster () override + { + return *cluster_; + } + SHAMapStore& getSHAMapStore () override { return *m_shaMapStore; @@ -982,6 +989,8 @@ void ApplicationImp::setup() m_orderBookDB.setup (getLedgerMaster ().getCurrentLedger ()); + cluster_ = make_Cluster (config (), logs_->journal("Overlay")); + // Begin validation and ip maintenance. // // - LocalCredentials maintains local information: including identity diff --git a/src/ripple/app/main/Application.h b/src/ripple/app/main/Application.h index 5a0f8b493..77a53c067 100644 --- a/src/ripple/app/main/Application.h +++ b/src/ripple/app/main/Application.h @@ -64,6 +64,7 @@ class TimeKeeper; class TransactionMaster; class TxQ; class Validations; +class Cluster; class DatabaseCon; class SHAMapStore; @@ -117,6 +118,7 @@ public: virtual Overlay& overlay () = 0; virtual TxQ& getTxQ() = 0; virtual UniqueNodeList& getUNL () = 0; + virtual Cluster& cluster () = 0; virtual Validations& getValidations () = 0; virtual NodeStore::Database& getNodeStore () = 0; virtual InboundLedgers& getInboundLedgers () = 0; diff --git a/src/ripple/app/misc/NetworkOPs.cpp b/src/ripple/app/misc/NetworkOPs.cpp index 7c688e6af..6efd2abb7 100644 --- a/src/ripple/app/misc/NetworkOPs.cpp +++ b/src/ripple/app/misc/NetworkOPs.cpp @@ -55,7 +55,8 @@ #include #include #include -#include +#include +#include #include #include #include @@ -669,28 +670,31 @@ void NetworkOPsImp::processHeartbeatTimer () void NetworkOPsImp::processClusterTimer () { - bool synced = (m_ledgerMaster.getValidatedLedgerAge() <= 240); - ClusterNodeStatus us("", synced ? app_.getFeeTrack().getLocalFee() : 0, - app_.timeKeeper().now().time_since_epoch().count()); - auto& unl = app_.getUNL(); - if (!unl.nodeUpdate(app_.getLocalCredentials().getNodePublic(), us)) + bool const update = app_.cluster().update( + app_.getLocalCredentials().getNodePublic(), + "", + (m_ledgerMaster.getValidatedLedgerAge() <= 240) + ? app_.getFeeTrack().getLocalFee() + : 0, + app_.timeKeeper().now().time_since_epoch().count()); + + if (!update) { - m_journal.debug << "To soon to send cluster update"; + m_journal.debug << "Too soon to send cluster update"; return; } - auto nodes = unl.getClusterStatus(); - protocol::TMCluster cluster; - for (auto& it: nodes) - { - protocol::TMClusterNode& node = *cluster.add_clusternodes(); - node.set_publickey(it.first.humanNodePublic()); - node.set_reporttime(it.second.getReportTime()); - node.set_nodeload(it.second.getLoadFee()); - if (!it.second.getName().empty()) - node.set_nodename(it.second.getName()); - } + app_.cluster().for_each( + [&cluster](ClusterNode const& node) + { + protocol::TMClusterNode& n = *cluster.add_clusternodes(); + n.set_publickey(node.identity().humanNodePublic()); + n.set_reporttime(node.getReportTime()); + n.set_nodeload(node.getLoadFee()); + if (!node.name().empty()) + n.set_nodename(node.name()); + }); Resource::Gossip gossip = app_.getResourceManager().exportConsumers(); for (auto& item: gossip.items) diff --git a/src/ripple/app/misc/UniqueNodeList.cpp b/src/ripple/app/misc/UniqueNodeList.cpp index 8172fecec..d584b3af8 100644 --- a/src/ripple/app/misc/UniqueNodeList.cpp +++ b/src/ripple/app/misc/UniqueNodeList.cpp @@ -22,7 +22,6 @@ #include #include #include -#include #include #include #include @@ -228,8 +227,6 @@ private: boost::posix_time::ptime mtpFetchNext; // Time of to start next fetch. beast::DeadlineTimer m_fetchTimer; // Timer to start fetching. - std::map m_clusterNodes; - std::string node_file_name_; std::string node_file_path_; @@ -271,17 +268,6 @@ public: bool nodeInUNL (RippleAddress const& naNodePublic); - bool nodeInCluster (RippleAddress const& naNodePublic); - bool nodeInCluster (RippleAddress const& naNodePublic, std::string& name); - - bool nodeUpdate (RippleAddress const& naNodePublic, ClusterNodeStatus const& cnsStatus); - - std::map getClusterStatus(); - - std::uint32_t getClusterFee(); - - void addClusterStatus (Json::Value& obj); - void nodeBootstrap(); bool nodeLoad (boost::filesystem::path pConfig); @@ -654,105 +640,6 @@ bool UniqueNodeListImp::nodeInUNL (RippleAddress const& naNodePublic) //-------------------------------------------------------------------------- -bool UniqueNodeListImp::nodeInCluster (RippleAddress const& naNodePublic) -{ - std::lock_guard sl (mUNLLock); - return m_clusterNodes.end () != m_clusterNodes.find (naNodePublic); -} - -//-------------------------------------------------------------------------- - -bool UniqueNodeListImp::nodeInCluster (RippleAddress const& naNodePublic, std::string& name) -{ - std::lock_guard sl (mUNLLock); - std::map::iterator it = m_clusterNodes.find (naNodePublic); - - if (it == m_clusterNodes.end ()) - return false; - - name = it->second.getName(); - return true; -} - -//-------------------------------------------------------------------------- - -bool UniqueNodeListImp::nodeUpdate (RippleAddress const& naNodePublic, ClusterNodeStatus const& cnsStatus) -{ - std::lock_guard sl (mUNLLock); - return m_clusterNodes[naNodePublic].update(cnsStatus); -} - -//-------------------------------------------------------------------------- - -std::map -UniqueNodeListImp::getClusterStatus() -{ - std::map ret; - { - std::lock_guard sl (mUNLLock); - ret = m_clusterNodes; - } - return ret; -} - -//-------------------------------------------------------------------------- - -std::uint32_t UniqueNodeListImp::getClusterFee() -{ - auto const thresh = app_.timeKeeper().now().time_since_epoch().count() - 90; - - std::vector fees; - { - std::lock_guard sl (mUNLLock); - { - for (std::map::iterator it = m_clusterNodes.begin(), - end = m_clusterNodes.end(); it != end; ++it) - { - if (it->second.getReportTime() >= thresh) - fees.push_back(it->second.getLoadFee()); - } - } - } - - if (fees.empty()) - return 0; - std::sort (fees.begin(), fees.end()); - return fees[fees.size() / 2]; -} - -//-------------------------------------------------------------------------- - -void UniqueNodeListImp::addClusterStatus (Json::Value& obj) -{ - std::lock_guard sl (mUNLLock); - if (m_clusterNodes.size() > 1) // nodes other than us - { - auto const now = app_.timeKeeper().now().time_since_epoch().count(); - std::uint32_t ref = app_.getFeeTrack().getLoadBase(); - Json::Value& nodes = (obj[jss::cluster] = Json::objectValue); - - for (std::map::iterator it = m_clusterNodes.begin(), - end = m_clusterNodes.end(); it != end; ++it) - { - if (it->first != app_.getLocalCredentials().getNodePublic()) - { - Json::Value& node = nodes[it->first.humanNodePublic()]; - - if (!it->second.getName().empty()) - node["tag"] = it->second.getName(); - - if ((it->second.getLoadFee() != ref) && (it->second.getLoadFee() != 0)) - node["fee"] = static_cast(it->second.getLoadFee()) / ref; - - if (it->second.getReportTime() != 0) - node["age"] = (it->second.getReportTime() >= now) ? 0 : (now - it->second.getReportTime()); - } - } - } -} - -//-------------------------------------------------------------------------- - void UniqueNodeListImp::nodeBootstrap() { int iDomains = 0; @@ -1017,22 +904,6 @@ bool UniqueNodeListImp::miscSave() void UniqueNodeListImp::trustedLoad() { - boost::regex rNode ("\\`\\s*(\\S+)[\\s]*(.*)\\'"); - for (auto const& c : app_.config().CLUSTER_NODES) - { - boost::smatch match; - - if (boost::regex_match (c, match, rNode)) - { - RippleAddress a = RippleAddress::createNodePublic (match[1]); - - if (a.isValid ()) - m_clusterNodes.insert (std::make_pair (a, ClusterNodeStatus(match[2]))); - } - else - JLOG (j_.warning) << "Entry in cluster list invalid: '" << c << "'"; - } - auto db = app_.getWalletDB ().checkoutDb (); std::lock_guard slUNL (mUNLLock); diff --git a/src/ripple/app/misc/UniqueNodeList.h b/src/ripple/app/misc/UniqueNodeList.h index c7db1baf0..370ae2e42 100644 --- a/src/ripple/app/misc/UniqueNodeList.h +++ b/src/ripple/app/misc/UniqueNodeList.h @@ -21,7 +21,6 @@ #define RIPPLE_APP_PEERS_UNIQUENODELIST_H_INCLUDED #include -#include #include #include #include @@ -69,12 +68,6 @@ public: virtual void nodeScore () = 0; virtual bool nodeInUNL (RippleAddress const& naNodePublic) = 0; - virtual bool nodeInCluster (RippleAddress const& naNodePublic) = 0; - virtual bool nodeInCluster (RippleAddress const& naNodePublic, std::string& name) = 0; - virtual bool nodeUpdate (RippleAddress const& naNodePublic, ClusterNodeStatus const& cnsStatus) = 0; - virtual std::map getClusterStatus () = 0; - virtual std::uint32_t getClusterFee () = 0; - virtual void addClusterStatus (Json::Value&) = 0; virtual void nodeBootstrap () = 0; virtual bool nodeLoad (boost::filesystem::path pConfig) = 0; diff --git a/src/ripple/overlay/Cluster.h b/src/ripple/overlay/Cluster.h new file mode 100644 index 000000000..cede510ba --- /dev/null +++ b/src/ripple/overlay/Cluster.h @@ -0,0 +1,115 @@ +//------------------------------------------------------------------------------ +/* + 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_OVERLAY_CLUSTER_H_INCLUDED +#define RIPPLE_OVERLAY_CLUSTER_H_INCLUDED + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace ripple { + +class Cluster +{ +private: + struct Comparator + { + using is_transparent = std::true_type; + + bool + operator() ( + ClusterNode const& lhs, + ClusterNode const& rhs) const + { + return lhs.identity() < rhs.identity(); + } + + bool + operator() ( + ClusterNode const& lhs, + RippleAddress const& rhs) const + { + return lhs.identity() < rhs; + } + + bool + operator() ( + RippleAddress const& lhs, + ClusterNode const& rhs) const + { + return lhs < rhs.identity(); + } + }; + + std::set nodes_; + std::mutex mutable mutex_; + beast::Journal mutable j_; + +public: + Cluster (beast::Journal j); + + /** Determines whether a node belongs in the cluster + @return empty optional if the node isn't a member, + otherwise, the node's name (which may be + empty). + */ + boost::optional + member (RippleAddress const& node) const; + + /** The number of nodes in the cluster list. */ + std::size_t + size() const; + + /** Store information about the state of a cluster node. + @param identity The node's public identity + @param name The node's name (may be empty) + @return true if we updated our information + */ + bool + update ( + RippleAddress const& identity, + std::string name, + std::uint32_t loadFee = 0, + std::uint32_t reportTime = 0); + + /** Invokes the callback once for every cluster node. + @note You are not allowed to call `update` from + within the callback. + */ + void + for_each ( + std::function func) const; +}; + +std::unique_ptr +make_Cluster (Config const& config, beast::Journal j); + +} // ripple + +#endif diff --git a/src/ripple/overlay/ClusterNodeStatus.h b/src/ripple/overlay/ClusterNode.h similarity index 59% rename from src/ripple/overlay/ClusterNodeStatus.h rename to src/ripple/overlay/ClusterNode.h index 7e88c5e7e..cf12c5453 100644 --- a/src/ripple/overlay/ClusterNodeStatus.h +++ b/src/ripple/overlay/ClusterNode.h @@ -20,59 +20,54 @@ #ifndef RIPPLE_APP_PEERS_CLUSTERNODESTATUS_H_INCLUDED #define RIPPLE_APP_PEERS_CLUSTERNODESTATUS_H_INCLUDED +#include #include #include namespace ripple { -class ClusterNodeStatus +class ClusterNode { public: + ClusterNode() = delete; - ClusterNodeStatus() : mLoadFee(0), mReportTime(0) - { ; } + ClusterNode( + RippleAddress const& identity, + std::string const& name, + std::uint32_t fee = 0, + std::uint32_t rtime = 0) + : identity_ (identity) + , name_(name) + , mLoadFee(fee) + , mReportTime(rtime) + { } - explicit ClusterNodeStatus(std::string const& name) : - mNodeName(name), mLoadFee(0), mReportTime(0) - { ; } - - ClusterNodeStatus( - std::string const& name, std::uint32_t fee, std::uint32_t rtime) : - mNodeName(name), - mLoadFee(fee), - mReportTime(rtime) - { ; } - - std::string const& getName() + std::string const& name() const { - return mNodeName; + return name_; } - std::uint32_t getLoadFee() + std::uint32_t getLoadFee() const { return mLoadFee; } - std::uint32_t getReportTime() + std::uint32_t getReportTime() const { return mReportTime; } - bool update(ClusterNodeStatus const& status) + RippleAddress const& + identity () const { - if (status.mReportTime <= mReportTime) - return false; - mLoadFee = status.mLoadFee; - mReportTime = status.mReportTime; - if (mNodeName.empty() || !status.mNodeName.empty()) - mNodeName = status.mNodeName; - return true; + return identity_; } private: - std::string mNodeName; - std::uint32_t mLoadFee; - std::uint32_t mReportTime; + RippleAddress identity_; + std::string name_; + std::uint32_t mLoadFee = 0; + std::uint32_t mReportTime = 0; }; } // ripple diff --git a/src/ripple/overlay/impl/Cluster.cpp b/src/ripple/overlay/impl/Cluster.cpp new file mode 100644 index 000000000..526f8891b --- /dev/null +++ b/src/ripple/overlay/impl/Cluster.cpp @@ -0,0 +1,143 @@ +//------------------------------------------------------------------------------ +/* + 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. +*/ +//============================================================================== + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace ripple { + +Cluster::Cluster (beast::Journal j) + : j_ (j) +{ +} + +boost::optional +Cluster::member (RippleAddress const& identity) const +{ + std::lock_guard lock(mutex_); + + auto iter = nodes_.find (identity); + if (iter == nodes_.end ()) + return boost::none; + return iter->name (); +} + +std::size_t +Cluster::size() const +{ + std::lock_guard lock(mutex_); + return nodes_.size(); +} + +bool +Cluster::update ( + RippleAddress const& identity, + std::string name, + std::uint32_t loadFee, + std::uint32_t reportTime) +{ + std::lock_guard lock(mutex_); + + // We can't use auto here yet due to the libstdc++ issue + // described at https://gcc.gnu.org/bugzilla/show_bug.cgi?id=68190 + std::set::iterator iter = + nodes_.find (identity); + + if (iter != nodes_.end ()) + { + if (reportTime <= iter->getReportTime()) + return false; + + if (name.empty()) + name = iter->name(); + + iter = nodes_.erase (iter); + } + + nodes_.emplace_hint (iter, identity, name, loadFee, reportTime); + return true; +} + +void +Cluster::for_each ( + std::function func) const +{ + std::lock_guard lock(mutex_); + for (auto const& ni : nodes_) + func (ni); +} + +std::unique_ptr +make_Cluster (Config const& config, beast::Journal j) +{ + static boost::regex const re ( + "^" // start of line + "(?:\\s*)" // whitespace (optional) + "([a-zA-Z0-9]*)" // Node identity + "(?:\\s*)" // whitespace (optional) + "(.*\\S*)" // + "(?:\\s*)" // whitespace (optional) + ); + + auto cluster = std::make_unique (j); + + for (auto const& n : config.CLUSTER_NODES) + { + boost::smatch match; + + if (!boost::regex_match (n, match, re)) + { + JLOG (j.error) << + "Malformed entry: '" << n << "'"; + continue; + } + + auto const nid = RippleAddress::createNodePublic (match[1]); + + if (!nid.isValid()) + { + JLOG (j.error) << + "Invalid node identity: " << match[1]; + continue; + } + + if (cluster->member (nid)) + { + JLOG (j.warning) << + "Duplicate node identity: " << match[1]; + continue; + } + + cluster->update(nid, match[2]); + } + + return cluster; +} + +} // ripple diff --git a/src/ripple/overlay/impl/ConnectAttempt.cpp b/src/ripple/overlay/impl/ConnectAttempt.cpp index da47368b3..8ae98ea95 100644 --- a/src/ripple/overlay/impl/ConnectAttempt.cpp +++ b/src/ripple/overlay/impl/ConnectAttempt.cpp @@ -18,6 +18,7 @@ //============================================================================== #include +#include #include #include #include @@ -417,15 +418,13 @@ ConnectAttempt::processResponse (beast::http::message const& m, if(journal_.info) journal_.info << "Protocol: " << to_string(protocol); - std::string name; - bool const clusterNode = - app_.getUNL().nodeInCluster(publicKey, name); - if (clusterNode) + auto member = app_.cluster().member(publicKey); + if (member) if (journal_.info) journal_.info << - "Cluster name: " << name; + "Cluster name: " << *member; auto const result = overlay_.peerFinder().activate (slot_, - publicKey.toPublicKey(), clusterNode); + publicKey.toPublicKey(), static_cast(member)); if (result != PeerFinder::Result::success) return fail("Outbound slots full"); diff --git a/src/ripple/overlay/impl/OverlayImpl.cpp b/src/ripple/overlay/impl/OverlayImpl.cpp index 3eb269e09..7be3cf980 100644 --- a/src/ripple/overlay/impl/OverlayImpl.cpp +++ b/src/ripple/overlay/impl/OverlayImpl.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -252,12 +253,9 @@ OverlayImpl::onHandoff (std::unique_ptr && ssl_bundle, if(! success) return handoff; - std::string name; - bool const cluster = app_.getUNL().nodeInCluster( - publicKey, name); - auto const result = m_peerFinder->activate (slot, - publicKey.toPublicKey(), cluster); + publicKey.toPublicKey(), + static_cast(app_.cluster().member(publicKey))); if (result != PeerFinder::Result::success) { m_peerFinder->on_closed(slot); diff --git a/src/ripple/overlay/impl/PeerImp.cpp b/src/ripple/overlay/impl/PeerImp.cpp index 9e0a6b9e1..a7ef59f92 100644 --- a/src/ripple/overlay/impl/PeerImp.cpp +++ b/src/ripple/overlay/impl/PeerImp.cpp @@ -27,7 +27,6 @@ #include #include #include -#include #include #include #include @@ -38,6 +37,8 @@ #include #include #include +#include +#include #include #include #include @@ -613,10 +614,13 @@ void PeerImp::doAccept() "Protocol: " << to_string(protocol); if(journal_.info) journal_.info << "Public Key: " << publicKey_.humanNodePublic(); - bool const cluster = app_.getUNL().nodeInCluster(publicKey_, name_); - if (cluster) + + if (auto member = app_.cluster().member(publicKey_)) + { + name_ = *member; if (journal_.info) journal_.info << "Cluster name: " << name_; + } overlay_.activate(shared_from_this()); @@ -912,12 +916,12 @@ PeerImp::onMessage (std::shared_ptr const& m) std::string name; if (node.has_nodename()) name = node.nodename(); - ClusterNodeStatus s(name, node.nodeload(), node.reporttime()); - RippleAddress nodePub; - nodePub.setNodePublic(node.publickey()); - - app_.getUNL().nodeUpdate(nodePub, s); + app_.cluster().update( + RippleAddress::createNodePublic(node.publickey()), + name, + node.nodeload(), + node.reporttime()); } int loadSources = m->loadsources().size(); @@ -937,7 +941,31 @@ PeerImp::onMessage (std::shared_ptr const& m) overlay_.resourceManager().importConsumers (name_, gossip); } - app_.getFeeTrack().setClusterFee(app_.getUNL().getClusterFee()); + // Calculate the cluster fee: + auto const thresh = app_.timeKeeper().now().time_since_epoch().count() - 90; + std::uint32_t clusterFee = 0; + + std::vector fees; + fees.reserve (app_.cluster().size()); + + app_.cluster().for_each( + [&fees,thresh](ClusterNode const& status) + { + if (status.getReportTime() >= thresh) + fees.push_back (status.getLoadFee ()); + }); + + if (!fees.empty()) + { + auto const index = fees.size() / 2; + std::nth_element ( + fees.begin(), + fees.begin () + index, + fees.end()); + clusterFee = fees[index]; + } + + app_.getFeeTrack().setClusterFee(clusterFee); } void diff --git a/src/ripple/overlay/tests/cluster_test.cpp b/src/ripple/overlay/tests/cluster_test.cpp new file mode 100644 index 000000000..2bc4454f5 --- /dev/null +++ b/src/ripple/overlay/tests/cluster_test.cpp @@ -0,0 +1,187 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright 2015 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. +*/ +//============================================================================== + +#include +#include +#include +#include + +namespace ripple { +namespace tests { + +class cluster_test : public ripple::TestSuite +{ +public: + std::unique_ptr + make_Cluster (std::vector const& nodes) + { + auto cluster = std::make_unique (beast::Journal ()); + + for (auto const& n : nodes) + cluster->update (n, n.humanNodePublic()); + + return cluster; + } + + RippleAddress + randomNode () + { + return RippleAddress::createNodePublic ( + RippleAddress::createSeedRandom ()); + } + + void + testMembership () + { + // The servers on the network + std::vector network; + + while (network.size () != 128) + network.push_back (randomNode()); + + { + testcase ("Membership: Empty cluster"); + + auto c = make_Cluster ({}); + + for (auto const& n : network) + expect (!c->member (n)); + } + + { + testcase ("Membership: Non-empty cluster and none present"); + + std::vector cluster; + while (cluster.size () != 32) + cluster.push_back (randomNode()); + + auto c = make_Cluster (cluster); + + for (auto const& n : network) + expect (!c->member (n)); + } + + { + testcase ("Membership: Non-empty cluster and some present"); + + std::vector cluster ( + network.begin (), network.begin () + 16); + + while (cluster.size () != 32) + cluster.push_back (randomNode()); + + auto c = make_Cluster (cluster); + + for (auto const& n : cluster) + expect (c->member (n)); + + for (auto const& n : network) + { + auto found = std::find ( + cluster.begin (), cluster.end (), n); + expect (static_cast(c->member (n)) == + (found != cluster.end ())); + } + } + + { + testcase ("Membership: Non-empty cluster and all present"); + + std::vector cluster ( + network.begin (), network.begin () + 32); + + auto c = make_Cluster (cluster); + + for (auto const& n : cluster) + expect (c->member (n)); + + for (auto const& n : network) + { + auto found = std::find ( + cluster.begin (), cluster.end (), n); + expect (static_cast(c->member (n)) == + (found != cluster.end ())); + } + } + } + + void + testUpdating () + { + testcase ("Updating"); + + auto c = make_Cluster ({}); + + auto const node = randomNode (); + std::uint32_t load = 0; + std::uint32_t tick = 0; + + // Initial update + expect (c->update (node, "", load, tick)); + { + auto member = c->member (node); + expect (static_cast(member)); + expect (member->empty ()); + } + + // Updating too quickly: should fail + expect (! c->update (node, node.humanNodePublic (), load, tick)); + { + auto member = c->member (node); + expect (static_cast(member)); + expect (member->empty ()); + } + + // Updating the name (empty updates to non-empty) + expect (c->update (node, node.humanNodePublic (), load, ++tick)); + { + auto member = c->member (node); + expect (static_cast(member)); + expect (member->compare(node.humanNodePublic ()) == 0); + } + + // Updating the name (non-empty doesn't go to empty) + expect (c->update (node, "", load, ++tick)); + { + auto member = c->member (node); + expect (static_cast(member)); + expect (member->compare(node.humanNodePublic ()) == 0); + } + + // Updating the name (non-empty updates to new non-empty) + expect (c->update (node, "test", load, ++tick)); + { + auto member = c->member (node); + expect (static_cast(member)); + expect (member->compare("test") == 0); + } + } + + void + run() override + { + testMembership (); + testUpdating (); + } +}; + +BEAST_DEFINE_TESTSUITE(cluster,overlay,ripple); + +} // tests +} // ripple diff --git a/src/ripple/protocol/JsonFields.h b/src/ripple/protocol/JsonFields.h index 53c580e14..a0e50aeb1 100644 --- a/src/ripple/protocol/JsonFields.h +++ b/src/ripple/protocol/JsonFields.h @@ -74,7 +74,7 @@ JSS ( accounts_proposed ); // in: Subscribe, Unsubscribe JSS ( action ); JSS ( address ); // out: PeerImp JSS ( affected ); // out: AcceptedLedgerTx -JSS ( age ); // out: UniqueNodeList, NetworkOPs +JSS ( age ); // out: UniqueNodeList, NetworkOPs, Peers JSS ( alternatives ); // out: PathRequest, RipplePathFind JSS ( amendment_blocked ); // out: NetworkOPs JSS ( asks ); // out: Subscribe @@ -154,6 +154,7 @@ JSS ( fail_hard ); // in: Sign, Submit JSS ( failed ); // out: InboundLedger JSS ( feature ); // in: Feature JSS ( features ); // out: Feature +JSS ( fee ); // out: NetworkOPs, Peers JSS ( fee_base ); // out: NetworkOPs JSS ( fee_mult_max ); // in: TransactionSign JSS ( fee_ref ); // out: NetworkOPs @@ -351,6 +352,7 @@ JSS ( subcommand ); // in: PathFind JSS ( success ); // rpc JSS ( supported ); // out: AmendmentTableImpl JSS ( system_time_offset ); // out: NetworkOPs +JSS ( tag ); // out: Peers JSS ( taker ); // in: Subscribe, BookOffers JSS ( taker_gets ); // in: Subscribe, Unsubscribe, BookOffers JSS ( taker_gets_funded ); // out: NetworkOPs diff --git a/src/ripple/rpc/handlers/Peers.cpp b/src/ripple/rpc/handlers/Peers.cpp index f5a4f6651..2c5317ff0 100644 --- a/src/ripple/rpc/handlers/Peers.cpp +++ b/src/ripple/rpc/handlers/Peers.cpp @@ -19,7 +19,10 @@ #include #include -#include +#include +#include +#include +#include #include #include #include @@ -36,7 +39,32 @@ Json::Value doPeers (RPC::Context& context) auto lock = beast::make_lock(context.app.getMasterMutex()); jvResult[jss::peers] = context.app.overlay ().json (); - context.app.getUNL().addClusterStatus(jvResult); + + auto const now = context.app.timeKeeper().now().time_since_epoch().count(); + auto const self = context.app.getLocalCredentials().getNodePublic(); + + Json::Value& cluster = (jvResult[jss::cluster] = Json::objectValue); + std::uint32_t ref = context.app.getFeeTrack().getLoadBase(); + + context.app.cluster().for_each ([&cluster, now, ref, &self] + (ClusterNode const& node) + { + if (node.identity() == self) + return; + + Json::Value& json = cluster[node.identity().humanNodePublic()]; + + if (!node.name().empty()) + json[jss::tag] = node.name(); + + if ((node.getLoadFee() != ref) && (node.getLoadFee() != 0)) + json[jss::fee] = static_cast(node.getLoadFee()) / ref; + + if (node.getReportTime()) + json[jss::age] = (node.getReportTime() >= now) + ? 0 + : (now - node.getReportTime()); + }); } return jvResult; diff --git a/src/ripple/unity/overlay.cpp b/src/ripple/unity/overlay.cpp index 877199052..3c469c1d9 100644 --- a/src/ripple/unity/overlay.cpp +++ b/src/ripple/unity/overlay.cpp @@ -20,6 +20,7 @@ #include #include +#include #include #include #include @@ -28,6 +29,7 @@ #include #include +#include #include #include #include