diff --git a/Builds/VisualStudio2013/RippleD.vcxproj b/Builds/VisualStudio2013/RippleD.vcxproj
index bc104f0ba..643d34d7e 100644
--- a/Builds/VisualStudio2013/RippleD.vcxproj
+++ b/Builds/VisualStudio2013/RippleD.vcxproj
@@ -3950,6 +3950,16 @@
+
+
+
+ True
+ True
+
+
+ True
+ True
+
diff --git a/Builds/VisualStudio2013/RippleD.vcxproj.filters b/Builds/VisualStudio2013/RippleD.vcxproj.filters
index ecd220f76..91100583f 100644
--- a/Builds/VisualStudio2013/RippleD.vcxproj.filters
+++ b/Builds/VisualStudio2013/RippleD.vcxproj.filters
@@ -442,6 +442,9 @@
{416459B4-BDA4-31D6-834A-88932E767F37}
+
+ {2C910562-8D0A-B115-B829-556779436F2F}
+
{44780F86-42D3-2F2B-0846-5AEE2CA6D7FE}
@@ -4602,6 +4605,15 @@
ripple\unl
+
+ ripple\unl\tests
+
+
+ ripple\unl\tests
+
+
+ ripple\unl\tests
+
ripple\unl
diff --git a/src/ripple/unity/unl.cpp b/src/ripple/unity/unl.cpp
index aa473b1c8..71929aa10 100644
--- a/src/ripple/unity/unl.cpp
+++ b/src/ripple/unity/unl.cpp
@@ -23,3 +23,6 @@
#include
#include
#include
+
+#include
+#include
diff --git a/src/ripple/unl/tests/BasicNetwork.h b/src/ripple/unl/tests/BasicNetwork.h
new file mode 100644
index 000000000..a0a1e2d5d
--- /dev/null
+++ b/src/ripple/unl/tests/BasicNetwork.h
@@ -0,0 +1,593 @@
+//------------------------------------------------------------------------------
+/*
+ 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_SIM_BASICNETWORK_H_INCLUDED
+#define RIPPLE_SIM_BASICNETWORK_H_INCLUDED
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include //
+#include
+#include
+#include
+
+namespace ripple {
+namespace test {
+
+template
+class BasicNetwork
+{
+public:
+ using peer_type = Peer;
+
+ using clock_type =
+ beast::manual_clock<
+ std::chrono::system_clock>;
+
+ using duration =
+ typename clock_type::duration;
+
+ using time_point =
+ typename clock_type::time_point;
+
+private:
+ struct invoke
+ {
+ virtual ~invoke() = default;
+ virtual void operator()(Peer& from) const = 0;
+ };
+
+ template
+ class invoke_impl;
+
+ struct msg
+ {
+ msg (Peer& from_, Peer& to_, time_point when_,
+ std::unique_ptr op_)
+ : to(&to_), from(&from_),
+ when(when_), op(std::move(op_))
+ {
+ }
+
+ Peer* to;
+ Peer* from;
+ time_point when;
+ std::unique_ptr mutable op;
+ };
+
+ struct link_type
+ {
+ bool inbound;
+ duration delay;
+
+ link_type (bool inbound_,
+ duration delay_)
+ : inbound (inbound_)
+ , delay (delay_)
+ {
+ }
+ };
+
+ using msg_list = boost::multi_index_container>,
+ boost::multi_index::hashed_non_unique<
+ boost::multi_index::member>,
+ boost::multi_index::ordered_non_unique<
+ boost::multi_index::member>>>;
+
+ using links_type =
+ boost::container::flat_map;
+
+ class link_transform
+ {
+ public:
+ using argument_type =
+ typename links_type::value_type;
+
+ class result_type
+ {
+ public:
+ Peer& to;
+ bool inbound;
+
+ result_type (result_type const&) = default;
+
+ result_type (BasicNetwork& net, Peer& from,
+ Peer& to_, bool inbound_)
+ : to(to_)
+ , inbound(inbound_)
+ , net_(net)
+ , from_(from)
+ {
+ }
+
+ /** Disconnect this link.
+
+ Effects:
+
+ The connection is removed at both ends.
+ */
+ void
+ disconnect() const
+ {
+ net_.disconnect(from_, to);
+ }
+
+ private:
+ BasicNetwork& net_;
+ Peer& from_;
+ };
+
+ link_transform (BasicNetwork& net, Peer& from)
+ : net_(net)
+ , from_(from)
+ {
+ }
+
+ result_type const
+ operator()(argument_type const& v) const
+ {
+ return result_type(net_, from_,
+ *v.first, v.second.inbound);
+ }
+
+ private:
+ BasicNetwork& net_;
+ Peer& from_;
+ };
+
+ struct peer_transform
+ {
+ using argument_type =
+ typename links_type::value_type;
+
+ using result_type = Peer&;
+
+ result_type
+ operator()(argument_type const& v) const
+ {
+ return *v.first;
+ }
+ };
+
+ msg_list msgs_;
+ clock_type clock_;
+ std::unordered_map links_;
+
+public:
+ /** Connect two peers.
+
+ The link is directed, with `from` establishing
+ the outbound connection and `to` receiving the
+ incoming connection.
+
+ Preconditions:
+
+ &from != &to (self connect disallowed).
+
+ A link between from and to does not
+ already exist (duplicates disallowed).
+
+ Effects:
+
+ Creates a link between from and to.
+
+ @param `from` The source of the outgoing connection
+ @param `to` The recipient of the incoming connection
+ @param `delay` The time delay of all delivered messages
+ @return `true` if a new connection was established
+ */
+ bool
+ connect (Peer& from, Peer& to,
+ duration const& delay = std::chrono::seconds{0});
+
+ /** Break a link.
+
+ Effects:
+
+ If a connection is present, both ends are
+ disconnected.
+
+ Any pending messages on the connection
+ are discarded.
+
+ @return `true` if a connection was broken.
+ */
+ bool
+ disconnect (Peer& peer1, Peer& peer2);
+
+ /** Return the range of active links.
+
+ @return A random access range.
+ */
+ boost::transformed_range<
+ link_transform, links_type>
+ links (Peer& from);
+
+ /** Returns the range of connected peers.
+
+ @return A random access range.
+ */
+ boost::transformed_range<
+ peer_transform, links_type>
+ peers (Peer& from);
+
+ /** Send a message to a peer.
+
+ Preconditions:
+
+ A link exists between from and to.
+
+ Effects:
+
+ If the link is not broken when the
+ link's `delay` time has elapsed,
+ to.receive() will be called with the
+ message.
+
+ The peer will be called with this signature:
+
+ void Peer::receive(Net&, Peer& from, Message&&)
+ */
+ template
+ void
+ send (Peer& from, Peer& to, Message&& m);
+
+ /** Perform breadth-first search.
+
+ Function will be called with this signature:
+
+ void(Derived&, std::size_t, Peer&);
+
+ The second argument is the distance of the
+ peer from the start peer, in hops.
+ */
+ template
+ void
+ bfs (Peer& start, Function&& f);
+
+ /** Run the network for up to one message.
+
+ Effects:
+
+ The clock is advanced to the time
+ of the last delivered message.
+
+ @return `true` if a message was processed.
+ */
+ bool
+ run_one();
+
+ /** Run the network until no messages remain.
+
+ Effects:
+
+ The clock is advanced to the time
+ of the last delivered message.
+
+ @return `true` if any message was processed.
+ */
+ bool
+ run();
+
+ /** Run the network until the specified time.
+
+ Effects:
+
+ The clock is advanced to the
+ specified time.
+
+ @return `true` if any message was processed.
+ */
+ bool
+ run_until (time_point const& until);
+
+ /** Run the network until time has elapsed.
+
+ Effects:
+
+ The clock is advanced by the
+ specified duration.
+
+ @return `true` if any message was processed.
+ */
+ template
+ bool
+ run_for (std::chrono::duration<
+ Period, Rep> const& amount);
+
+private:
+ Derived&
+ derived()
+ {
+ return *static_cast(this);
+ }
+};
+
+//------------------------------------------------------------------------------
+
+template
+template
+class BasicNetwork::invoke_impl
+ : public invoke
+{
+public:
+ invoke_impl (Derived& net,
+ Peer& peer, Message&& m)
+ : peer_(peer)
+ , net_(net)
+ , m_(std::forward(m))
+ {
+ }
+
+ void
+ operator()(Peer& from) const override
+ {
+ peer_.receive(
+ net_, from, std::move(m_));
+ };
+
+private:
+ Peer& peer_;
+ Derived& net_;
+ std::decay_t mutable m_;
+};
+
+//------------------------------------------------------------------------------
+
+template
+bool
+BasicNetwork::connect(
+ Peer& from, Peer& to, duration const& delay)
+{
+ if (&to == &from)
+ return false;
+ using namespace std;
+ if (! links_[&from].emplace(&to,
+ link_type{ false, delay }).second)
+ return false;
+ auto const result = links_[&to].emplace(
+ &from, link_type{ true, delay });
+ (void)result;
+ assert(result.second);
+ return true;
+}
+
+template
+bool
+BasicNetwork::disconnect(
+ Peer& peer1, Peer& peer2)
+{
+ if (links_[&peer1].erase(&peer2) == 0)
+ return false;
+ auto const n =
+ links_[&peer2].erase(&peer1);
+ (void)n;
+ assert(n);
+ using boost::multi_index::get;
+ {
+ auto& by_to = get<0>(msgs_);
+ auto const r = by_to.equal_range(&peer1);
+ by_to.erase(r.first, r.second);
+ }
+ {
+ auto& by_from = get<1>(msgs_);
+ auto const r = by_from.equal_range(&peer2);
+ by_from.erase(r.first, r.second);
+ }
+ return true;
+}
+
+template
+inline
+auto
+BasicNetwork::links(
+ Peer& from) ->
+ boost::transformed_range<
+ link_transform, links_type>
+{
+ return boost::adaptors::transform(
+ links_[&from],
+ link_transform{ *this, from });
+}
+
+template
+inline
+auto
+BasicNetwork::peers(
+ Peer& from) ->
+ boost::transformed_range<
+ peer_transform, links_type>
+{
+ return boost::adaptors::transform(
+ links_[&from], peer_transform{});
+}
+
+template
+template
+inline
+void
+BasicNetwork::send(
+ Peer& from, Peer& to, Message&& m)
+{
+ auto const iter = links_[&from].find(&to);
+ msgs_.emplace(from, to, clock_.now() + iter->second.delay,
+ std::make_unique>(
+ derived(), to, std::forward(m)));
+}
+
+template
+bool
+BasicNetwork::run_one()
+{
+ using boost::multi_index::get;
+ auto& by_when = get<2>(msgs_);
+ if (by_when.empty())
+ return false;
+ auto const iter = by_when.begin();
+ auto const from = iter->from;
+ auto const op = std::move(iter->op);
+ clock_.set(iter->when);
+ by_when.erase(iter);
+ (*op)(*from);
+ return true;
+}
+
+template
+bool
+BasicNetwork::run()
+{
+ if (! run_one())
+ return false;
+ for(;;)
+ if (! run_one())
+ break;
+ return true;
+}
+
+template
+bool
+BasicNetwork::run_until(
+ time_point const& until)
+{
+ using boost::multi_index::get;
+ auto& by_when = get<2>(msgs_);
+ if(by_when.empty() ||
+ by_when.begin()->when > until)
+ {
+ clock_.set(until);
+ return false;
+ }
+ do
+ {
+ run_one();
+ }
+ while(! by_when.empty() &&
+ by_when.begin()->when <= until);
+ clock_.set(until);
+ return true;
+}
+
+template
+template
+bool
+BasicNetwork::run_for(
+ std::chrono::duration const& amount)
+{
+ return run_until(
+ clock_.now() + amount);
+}
+
+template
+template
+void
+BasicNetwork::bfs(
+ Peer& start, Function&& f)
+{
+ std::deque> q;
+ std::unordered_set seen;
+ q.emplace_back(&start, 0);
+ seen.insert(&start);
+ while(! q.empty())
+ {
+ auto v = q.front();
+ q.pop_front();
+ f(derived(), v.second, *v.first);
+ for(auto const& link : links_[v.first])
+ {
+ auto w = link.first;
+ if (seen.count(w) == 0)
+ {
+ q.emplace_back(w, v.second + 1);
+ seen.insert(w);
+ }
+ }
+ }
+}
+
+//------------------------------------------------------------------------------
+
+template
+struct SimpleNetwork
+ : BasicNetwork>
+{
+};
+
+template
+std::string
+seq_string (FwdRange const& r)
+{
+ std::stringstream ss;
+ auto iter = std::begin(r);
+ if (iter == std::end(r))
+ return ss.str();
+ ss << *iter++;
+ while(iter != std::end(r))
+ ss << ", " << *iter++;
+ return ss.str();
+}
+
+template
+typename FwdRange::value_type
+seq_sum (FwdRange const& r)
+{
+ typename FwdRange::value_type sum = 0;
+ for (auto const& n : r)
+ sum += n;
+ return sum;
+}
+
+template
+double
+diameter (RanRange const& r)
+{
+ if (r.empty())
+ return 0;
+ if (r.size() == 1)
+ return r.front();
+ auto h0 = *(r.end() - 2);
+ auto h1 = r.back();
+ return (r.size() - 2) +
+ double(h1) / (h0 + h1);
+}
+
+template
+typename Container::value_type&
+nth (Container& c, std::size_t n)
+{
+ c.resize(std::max(c.size(), n + 1));
+ return c[n];
+}
+
+} // test
+} // ripple
+
+#endif
diff --git a/src/ripple/unl/tests/Network_test.cpp b/src/ripple/unl/tests/Network_test.cpp
new file mode 100644
index 000000000..cbaacd365
--- /dev/null
+++ b/src/ripple/unl/tests/Network_test.cpp
@@ -0,0 +1,217 @@
+//------------------------------------------------------------------------------
+/*
+ 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
+
+namespace ripple {
+namespace test {
+
+class Net_test : public beast::unit_test::suite
+{
+public:
+ struct Ping
+ {
+ int hops = 0;
+ };
+
+ struct InstantPeer
+ {
+ using Peer = InstantPeer;
+ bool set = false;
+ int hops = 0;
+
+ InstantPeer() = default;
+
+ template
+ std::chrono::seconds
+ delay(Net&) const
+ {
+ return {};
+ }
+
+ template
+ void
+ send (Net& net, Peer& from, Message&& m)
+ {
+ net.send (from, *this,
+ std::forward(m));
+ }
+
+ template
+ void
+ receive (Net& net, Peer& from, Ping p)
+ {
+ if (set)
+ return;
+ ++p.hops;
+ set = true;
+ hops = p.hops;
+ for(auto& peer : net.peers(*this))
+ peer.send(net, *this, p);
+ }
+ };
+
+ struct LatencyPeer
+ {
+ using Peer = LatencyPeer;
+ int hops = 0;
+ bool set = false;
+
+ LatencyPeer() = default;
+
+ template
+ std::chrono::milliseconds
+ delay(Net& net) const
+ {
+ using namespace std::chrono;
+ return milliseconds(net.rand(5, 200));
+ }
+
+ template
+ void
+ send (Net& net, Peer& from, Message&& m)
+ {
+ net.send (from, *this,
+ std::forward(m));
+ }
+
+ template
+ void
+ receive (Net& net, Peer& from, Ping p)
+ {
+ if (set)
+ return;
+ ++p.hops;
+ set = true;
+ hops = p.hops;
+ for(auto& peer : net.peers(*this))
+ peer.send(net, *this, p);
+ }
+ };
+
+ template
+ struct Network
+ : BasicNetwork>
+ {
+ static std::size_t const nPeer = 10000;
+ static std::size_t const nDegree = 10;
+
+ std::vector pv;
+ std::mt19937_64 rng;
+
+ Network()
+ {
+ pv.resize(nPeer);
+ for (auto& peer : pv)
+ for (auto i = 0; i < nDegree; ++i)
+ connect_one(peer);
+ }
+
+ // Return int in range [0, n)
+ std::size_t
+ rand (std::size_t n)
+ {
+ return std::uniform_int_distribution<
+ std::size_t>(0, n - 1)(rng);
+ }
+
+ // Return int in range [base, base+n)
+ std::size_t
+ rand (std::size_t base, std::size_t n)
+ {
+ return std::uniform_int_distribution<
+ std::size_t>(base, base + n - 1)(rng);
+ }
+
+ // Add one random connection
+ void
+ connect_one (Peer& peer)
+ {
+ using namespace std::chrono;
+ for(;;)
+ if (this->connect(peer, pv[rand(pv.size())],
+ peer.delay(*this)))
+ break;
+ }
+ };
+
+ template
+ void
+ testDiameter(std::string const& name)
+ {
+ using Net = Network;
+ using namespace std::chrono;
+ log << name << ":";
+ Net net;
+ net.pv[0].set = true;
+ net.pv[0].hops = 0;
+ for(auto& peer : net.peers(net.pv[0]))
+ peer.send(net, net.pv[0], Ping{});
+ net.run();
+ std::size_t reach = 0;
+ std::vector dist;
+ std::vector hops;
+ std::vector degree;
+ for(auto& peer : net.pv)
+ {
+ hops.resize(std::max(
+ peer.hops + 1, hops.size()));
+ ++hops[peer.hops];
+ }
+ net.bfs(net.pv[0],
+ [&](Net& net, std::size_t d, Peer& peer)
+ {
+ ++reach;
+ dist.resize(std::max(
+ d + 1, dist.size()));
+ ++dist[d];
+ auto const n = net.links(peer).size();
+ degree.resize(std::max(
+ n + 1, degree.size()));
+ ++degree[n];
+ });
+ log << "reach: " << net.pv.size();
+ log << "size: " << reach;
+ log << "hops: " << seq_string(hops);
+ log << "dist: " << seq_string(dist);
+ log << "degree: " << seq_string(degree);
+ log << "diameter: " << diameter(dist);
+ log << "hop diam: " << diameter(hops);
+ }
+
+ void
+ run()
+ {
+ testDiameter("InstantPeer");
+ testDiameter("LatencyPeer");
+ pass();
+ }
+};
+
+BEAST_DEFINE_TESTSUITE_MANUAL(Net,sim,ripple);
+
+}
+}
diff --git a/src/ripple/unl/tests/SlotPeer_test.cpp b/src/ripple/unl/tests/SlotPeer_test.cpp
new file mode 100644
index 000000000..0935b5785
--- /dev/null
+++ b/src/ripple/unl/tests/SlotPeer_test.cpp
@@ -0,0 +1,386 @@
+//------------------------------------------------------------------------------
+/*
+ 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
+
+namespace ripple {
+namespace test {
+
+struct Config
+{
+ static int const nStep = 100; // # of steps
+ static int const nPeer = 1001; // # of peers
+ static int const nDegree = 10; // outdegree
+ static int const nChurn = 5; // churn per step
+ static int const nValidator = 100; // # of validators
+ static int const nTrusted = 5; // # of trusted validators
+ static int const nAllowed = 5; // # of allowed validators
+ static int const nTrustedUplinks = 3; // # of uplinks for trusted
+ static int const nAllowedUplinks = 1; // # of uplinks for allowed
+
+ struct Peer
+ {
+ struct ValMsg
+ {
+ int id;
+ int seq;
+ ValMsg (int id_, int seq_)
+ : id(id_), seq(seq_) { }
+ };
+
+ struct SquelchMsg
+ {
+ int id;
+ SquelchMsg (int id_)
+ : id(id_) { }
+ };
+
+ struct UnsquelchMsg
+ {
+ int id;
+ UnsquelchMsg (int id_)
+ : id(id_) { }
+ };
+
+ struct Policy
+ {
+ struct Slot
+ {
+ std::unordered_set up;
+ std::unordered_set down;
+ };
+
+ std::unordered_set allowed;
+ std::unordered_map slots;
+
+ // Returns a slot or nullptr
+ template
+ Slot*
+ get (int id, Peer& from,
+ Peers const& peers)
+ {
+ auto iter = slots.find(id);
+ if (iter != slots.end())
+ return &iter->second;
+ if (id > nTrusted &&
+ allowed.size() >= nAllowed)
+ return nullptr;
+ if (id > nTrusted)
+ allowed.insert(id);
+ auto& slot = slots[id];
+ for(auto& peer : peers)
+ if (&peer != &from)
+ slot.down.insert(&peer);
+ return &slot;
+ }
+
+ // Returns `true` accepting Peer as uplink
+ bool
+ uplink (int id, Peer& from, Slot& slot)
+ {
+ if (slot.up.count(&from) > 0)
+ return true;
+ if (id <= nTrusted)
+ {
+ if (slot.up.size() >= nTrustedUplinks)
+ return false;
+ slot.up.insert(&from);
+ return true;
+ }
+ if (slot.up.size() >= nAllowedUplinks)
+ return false;
+ slot.up.insert(&from);
+ return true;
+ }
+
+ // Squelch a downlink
+ void
+ squelch (int id, Peer& from)
+ {
+ auto iter = slots.find(id);
+ if (iter == slots.end())
+ return;
+ iter->second.down.erase(&from);
+ }
+
+ // Unsquelch a downlink
+ void
+ unsquelch (int id, Peer& from)
+ {
+ auto iter = slots.find(id);
+ if (iter == slots.end())
+ return;
+ iter->second.down.insert(&from);
+ }
+
+ // Called when we hear a validation
+ void
+ heard (int id, int seq)
+ {
+ }
+ };
+
+ int id = 0; // validator id or 0
+ int seq = 0;
+ Policy policy;
+ std::map seen;
+ std::chrono::milliseconds delay;
+
+ // Called when a peer disconnects
+ template
+ void
+ disconnect (Net& net, Peer& from)
+ {
+ std::vector v;
+ for(auto const& item : policy.slots)
+ if (item.second.up.count(&from) > 0)
+ v.push_back(item.first);
+ for(auto id : v)
+ for(auto& peer : net.peers(*this))
+ peer.send(net, *this,
+ UnsquelchMsg{ id });
+ }
+
+ // Send a message to this peer
+ template
+ void
+ send (Net& net, Peer& from, Message&& m)
+ {
+ ++net.sent;
+ using namespace std::chrono;
+ net.send (from, *this,
+ std::forward(m));
+ }
+
+ // Relay a message to all links
+ template
+ void
+ relay (Net& net, Message&& m)
+ {
+ for(auto& peer : net.peers(*this))
+ peer.send(net, *this,
+ std::forward(m));
+ }
+
+ // Broadcast a validation
+ template
+ void
+ broadcast (Net& net)
+ {
+ relay(net, ValMsg{ id, ++seq });
+ }
+
+ // Receive a validation
+ template
+ void
+ receive (Net& net, Peer& from, ValMsg const& m)
+ {
+ if (m.id == id)
+ {
+ ++nth(net.dup, m.id - 1);
+ return from.send(net, *this,
+ SquelchMsg(m.id));
+ }
+ auto slot = policy.get(
+ m.id, from, net.peers(*this));
+ if (! slot || ! policy.uplink(
+ m.id, from, *slot))
+ return from.send(net, *this,
+ SquelchMsg(m.id));
+ auto& last = seen[m.id];
+ if (last >= m.seq)
+ {
+ ++nth(net.dup, m.id - 1);
+ return;
+ }
+ last = m.seq;
+ policy.heard(m.id, m.seq);
+ ++nth(net.heard, m.id - 1);
+ for(auto peer : slot->down)
+ peer->send(net, *this, m);
+ }
+
+ // Receive a squelch message
+ template
+ void
+ receive (Net& net, Peer& from, SquelchMsg const& m)
+ {
+ policy.squelch (m.id, from);
+ }
+
+ // Receive an unsquelch message
+ template
+ void
+ receive (Net& net, Peer& from, UnsquelchMsg const& m)
+ {
+ policy.unsquelch (m.id, from);
+ }
+ };
+
+ //--------------------------------------------------------------------------
+
+ struct Network
+ : BasicNetwork
+ {
+ std::size_t sent = 0;
+ std::vector pv;
+ std::mt19937_64 rng;
+ std::vector heard;
+ std::vector dup;
+
+ Network()
+ {
+ pv.resize(nPeer);
+ for (int i = 0; i < nValidator; ++i)
+ pv[i].id = i + 1;
+ for (auto& peer : pv)
+ {
+ using namespace std::chrono;
+ peer.delay =
+ milliseconds(rand(5, 45));
+ for (auto i = 0; i < nDegree; ++i)
+ connect_one(peer);
+ }
+ }
+
+ // Return int in range [0, n)
+ std::size_t
+ rand (std::size_t n)
+ {
+ return std::uniform_int_distribution<
+ std::size_t>(0, n - 1)(rng);
+ }
+
+ // Return int in range [base, base+n)
+ std::size_t
+ rand (std::size_t base, std::size_t n)
+ {
+ return std::uniform_int_distribution<
+ std::size_t>(base, base + n - 1)(rng);
+ }
+
+ // Add one random connection
+ void
+ connect_one (Peer& peer)
+ {
+ using namespace std::chrono;
+ for(;;)
+ if (connect(peer, pv[rand(pv.size())],
+ peer.delay + milliseconds(rand(5, 200))))
+ break;
+ }
+
+ // Redo one random connection
+ void
+ churn_one()
+ {
+ auto& peer = pv[rand(pv.size())];
+ auto const link = links(peer)[
+ rand(links(peer).size())];
+ link.disconnect();
+ link.to.disconnect(*this, peer);
+ peer.disconnect(*this, link.to);
+ link.to.disconnect(*this, peer);
+ // preserve outbound counts, otherwise
+ // the outdegree invariant will break.
+ if (link.inbound)
+ connect_one (link.to);
+ else
+ connect_one (peer);
+ }
+
+ // Redo several random connections
+ void
+ churn()
+ {
+ auto n = nChurn;
+ while(n--)
+ churn_one();
+ }
+
+ // Iterate the network
+ template
+ void
+ step (Log& log)
+ {
+ for (int i = nStep; i--;)
+ {
+ churn();
+ for(int j = 0; j < nValidator; ++j)
+ pv[j].broadcast(*this);
+ run();
+ }
+ }
+ };
+};
+
+//------------------------------------------------------------------------------
+
+class SlotPeer_test : public beast::unit_test::suite
+{
+public:
+ void
+ test(std::string const& name)
+ {
+ log << name << ":";
+ using Peer = Config::Peer;
+ using Network = Config::Network;
+ Network net;
+ net.step(log);
+ std::size_t reach = 0;
+ std::vector dist;
+ std::vector degree;
+ net.bfs(net.pv[0],
+ [&](Network& net, std::size_t d, Peer& peer)
+ {
+ ++reach;
+ ++nth(dist, d);
+ ++nth(degree, net.links(peer).size());
+ });
+ log << "reach: " << net.pv.size();
+ log << "size: " << reach;
+ log << "sent: " << net.sent;
+ log << "diameter: " << diameter(dist);
+ log << "dist: " << seq_string(dist);
+ log << "heard: " << seq_string(net.heard);
+ log << "dup: " << seq_string(net.dup);
+ log << "degree: " << seq_string(degree);
+ }
+
+ void
+ run()
+ {
+ test("SlotPeer");
+ pass();
+ }
+};
+
+BEAST_DEFINE_TESTSUITE_MANUAL(SlotPeer,sim,ripple);
+
+}
+}