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