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); + +} +}