mirror of
https://github.com/XRPLF/rippled.git
synced 2025-12-06 17:27:55 +00:00
New peer to peer network simulators
This commit is contained in:
committed by
Nik Bougalis
parent
2bfae2f0ac
commit
ceeb36039e
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
593
src/ripple/unl/tests/BasicNetwork.h
Normal file
593
src/ripple/unl/tests/BasicNetwork.h
Normal 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
|
||||
217
src/ripple/unl/tests/Network_test.cpp
Normal file
217
src/ripple/unl/tests/Network_test.cpp
Normal 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);
|
||||
|
||||
}
|
||||
}
|
||||
386
src/ripple/unl/tests/SlotPeer_test.cpp
Normal file
386
src/ripple/unl/tests/SlotPeer_test.cpp
Normal 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);
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user