New peer to peer network simulators

This commit is contained in:
Vinnie Falco
2015-07-18 13:09:09 -07:00
committed by Nik Bougalis
parent 2bfae2f0ac
commit ceeb36039e
6 changed files with 1221 additions and 0 deletions

View File

@@ -3950,6 +3950,16 @@
</ClCompile>
<ClInclude Include="..\..\src\ripple\unl\make_Manager.h">
</ClInclude>
<ClInclude Include="..\..\src\ripple\unl\tests\BasicNetwork.h">
</ClInclude>
<ClCompile Include="..\..\src\ripple\unl\tests\Network_test.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\src\ripple\unl\tests\SlotPeer_test.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
</ClCompile>
<ClInclude Include="..\..\src\ripple\unl\UNLManager.h">
</ClInclude>
<ClInclude Include="..\..\src\ripple\websocket\AutoSocket.h">

View File

@@ -442,6 +442,9 @@
<Filter Include="ripple\unl\impl">
<UniqueIdentifier>{416459B4-BDA4-31D6-834A-88932E767F37}</UniqueIdentifier>
</Filter>
<Filter Include="ripple\unl\tests">
<UniqueIdentifier>{2C910562-8D0A-B115-B829-556779436F2F}</UniqueIdentifier>
</Filter>
<Filter Include="ripple\websocket">
<UniqueIdentifier>{44780F86-42D3-2F2B-0846-5AEE2CA6D7FE}</UniqueIdentifier>
</Filter>
@@ -4602,6 +4605,15 @@
<ClInclude Include="..\..\src\ripple\unl\make_Manager.h">
<Filter>ripple\unl</Filter>
</ClInclude>
<ClInclude Include="..\..\src\ripple\unl\tests\BasicNetwork.h">
<Filter>ripple\unl\tests</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ripple\unl\tests\Network_test.cpp">
<Filter>ripple\unl\tests</Filter>
</ClCompile>
<ClCompile Include="..\..\src\ripple\unl\tests\SlotPeer_test.cpp">
<Filter>ripple\unl\tests</Filter>
</ClCompile>
<ClInclude Include="..\..\src\ripple\unl\UNLManager.h">
<Filter>ripple\unl</Filter>
</ClInclude>

View File

@@ -23,3 +23,6 @@
#include <ripple/unl/impl/Logic.cpp>
#include <ripple/unl/impl/UNLManager.cpp>
#include <ripple/unl/impl/StoreSqdb.cpp>
#include <ripple/unl/tests/Network_test.cpp>
#include <ripple/unl/tests/SlotPeer_test.cpp>

View File

@@ -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 <beast/chrono/manual_clock.h>
#include <beast/hash/hash_append.h>
#include <beast/hash/uhash.h>
#include <boost/container/flat_map.hpp>
#include <boost/bimap.hpp>
#include <boost/multi_index_container.hpp>
#include <boost/multi_index/hashed_index.hpp>
#include <boost/multi_index/member.hpp>
#include <boost/range/adaptor/transformed.hpp>
#include <boost/range/iterator_range.hpp>
#include <boost/tuple/tuple.hpp>
#include <deque>
#include <beast/cxx14/memory.h> // <memory>
#include <cassert>
#include <unordered_map>
#include <unordered_set>
namespace ripple {
namespace test {
template <class Peer, class Derived>
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 Message>
class invoke_impl;
struct msg
{
msg (Peer& from_, Peer& to_, time_point when_,
std::unique_ptr<invoke> op_)
: to(&to_), from(&from_),
when(when_), op(std::move(op_))
{
}
Peer* to;
Peer* from;
time_point when;
std::unique_ptr<invoke> 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<msg,
boost::multi_index::indexed_by<
boost::multi_index::hashed_non_unique<
boost::multi_index::member<msg, Peer*, &msg::to>>,
boost::multi_index::hashed_non_unique<
boost::multi_index::member<msg, Peer*, &msg::from>>,
boost::multi_index::ordered_non_unique<
boost::multi_index::member<msg, time_point, &msg::when>>>>;
using links_type =
boost::container::flat_map<Peer*, link_type>;
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<Peer*, links_type> 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 <class Message>
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 <class Function>
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 <class Period, class Rep>
bool
run_for (std::chrono::duration<
Period, Rep> const& amount);
private:
Derived&
derived()
{
return *static_cast<Derived*>(this);
}
};
//------------------------------------------------------------------------------
template <class Peer, class Derived>
template <class Message>
class BasicNetwork<Peer,Derived>::invoke_impl
: public invoke
{
public:
invoke_impl (Derived& net,
Peer& peer, Message&& m)
: peer_(peer)
, net_(net)
, m_(std::forward<Message>(m))
{
}
void
operator()(Peer& from) const override
{
peer_.receive(
net_, from, std::move(m_));
};
private:
Peer& peer_;
Derived& net_;
std::decay_t<Message> mutable m_;
};
//------------------------------------------------------------------------------
template <class Peer, class Derived>
bool
BasicNetwork<Peer, Derived>::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 <class Peer, class Derived>
bool
BasicNetwork<Peer, Derived>::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 <class Peer, class Derived>
inline
auto
BasicNetwork<Peer, Derived>::links(
Peer& from) ->
boost::transformed_range<
link_transform, links_type>
{
return boost::adaptors::transform(
links_[&from],
link_transform{ *this, from });
}
template <class Peer, class Derived>
inline
auto
BasicNetwork<Peer, Derived>::peers(
Peer& from) ->
boost::transformed_range<
peer_transform, links_type>
{
return boost::adaptors::transform(
links_[&from], peer_transform{});
}
template <class Peer, class Derived>
template <class Message>
inline
void
BasicNetwork<Peer, Derived>::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<invoke_impl<Message>>(
derived(), to, std::forward<Message>(m)));
}
template <class Peer, class Derived>
bool
BasicNetwork<Peer, Derived>::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 <class Peer, class Derived>
bool
BasicNetwork<Peer, Derived>::run()
{
if (! run_one())
return false;
for(;;)
if (! run_one())
break;
return true;
}
template <class Peer, class Derived>
bool
BasicNetwork<Peer, Derived>::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 <class Peer, class Derived>
template <class Period, class Rep>
bool
BasicNetwork<Peer, Derived>::run_for(
std::chrono::duration<Period, Rep> const& amount)
{
return run_until(
clock_.now() + amount);
}
template <class Peer, class Derived>
template <class Function>
void
BasicNetwork<Peer, Derived>::bfs(
Peer& start, Function&& f)
{
std::deque<std::pair<Peer*, std::size_t>> q;
std::unordered_set<Peer*> 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 <class Peer>
struct SimpleNetwork
: BasicNetwork<Peer, SimpleNetwork<Peer>>
{
};
template <class FwdRange>
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 <class FwdRange>
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 <class RanRange>
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 <class Container>
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

View File

@@ -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 <BeastConfig.h>
#include <ripple/unl/tests/BasicNetwork.h>
#include <beast/unit_test/suite.h>
#include <boost/optional.hpp>
#include <algorithm>
#include <random>
#include <sstream>
#include <vector>
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 <class Net>
std::chrono::seconds
delay(Net&) const
{
return {};
}
template <class Net, class Message>
void
send (Net& net, Peer& from, Message&& m)
{
net.send (from, *this,
std::forward<Message>(m));
}
template <class Net>
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 <class Net>
std::chrono::milliseconds
delay(Net& net) const
{
using namespace std::chrono;
return milliseconds(net.rand(5, 200));
}
template <class Net, class Message>
void
send (Net& net, Peer& from, Message&& m)
{
net.send (from, *this,
std::forward<Message>(m));
}
template <class Net>
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 <class Peer>
struct Network
: BasicNetwork<Peer, Network<Peer>>
{
static std::size_t const nPeer = 10000;
static std::size_t const nDegree = 10;
std::vector<Peer> 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 <class Peer>
void
testDiameter(std::string const& name)
{
using Net = Network<Peer>;
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<int> dist;
std::vector<int> hops;
std::vector<int> degree;
for(auto& peer : net.pv)
{
hops.resize(std::max<std::size_t>(
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<std::size_t>(
d + 1, dist.size()));
++dist[d];
auto const n = net.links(peer).size();
degree.resize(std::max<std::size_t>(
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>("InstantPeer");
testDiameter<LatencyPeer>("LatencyPeer");
pass();
}
};
BEAST_DEFINE_TESTSUITE_MANUAL(Net,sim,ripple);
}
}

View File

@@ -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 <BeastConfig.h>
#include <ripple/unl/tests/BasicNetwork.h>
#include <beast/unit_test/suite.h>
#include <boost/container/flat_set.hpp>
#include <algorithm>
#include <random>
#include <sstream>
#include <unordered_map>
#include <unordered_set>
#include <vector>
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<Peer*> up;
std::unordered_set<Peer*> down;
};
std::unordered_set<int> allowed;
std::unordered_map<int, Slot> slots;
// Returns a slot or nullptr
template <class Peers>
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<int, int> seen;
std::chrono::milliseconds delay;
// Called when a peer disconnects
template <class Net>
void
disconnect (Net& net, Peer& from)
{
std::vector<int> 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 <class Net, class Message>
void
send (Net& net, Peer& from, Message&& m)
{
++net.sent;
using namespace std::chrono;
net.send (from, *this,
std::forward<Message>(m));
}
// Relay a message to all links
template <class Net, class Message>
void
relay (Net& net, Message&& m)
{
for(auto& peer : net.peers(*this))
peer.send(net, *this,
std::forward<Message>(m));
}
// Broadcast a validation
template <class Net>
void
broadcast (Net& net)
{
relay(net, ValMsg{ id, ++seq });
}
// Receive a validation
template <class Net>
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 <class Net>
void
receive (Net& net, Peer& from, SquelchMsg const& m)
{
policy.squelch (m.id, from);
}
// Receive an unsquelch message
template <class Net>
void
receive (Net& net, Peer& from, UnsquelchMsg const& m)
{
policy.unsquelch (m.id, from);
}
};
//--------------------------------------------------------------------------
struct Network
: BasicNetwork<Peer, Network>
{
std::size_t sent = 0;
std::vector<Peer> pv;
std::mt19937_64 rng;
std::vector<std::size_t> heard;
std::vector<std::size_t> 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 <class Log>
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<int> dist;
std::vector<int> 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);
}
}