Peer to peer network simulator:

* Refine the Peer concept
* Remove incremental consensus simulations (coverage)
* Add unit test (coverage)
* Fix BasicNetwork::remove
This commit is contained in:
Vinnie Falco
2016-01-13 14:46:58 -05:00
committed by Edward Hennis
parent 0fca91c6c1
commit 49c86768e6
16 changed files with 238 additions and 2911 deletions

View File

@@ -3594,6 +3594,12 @@
</ClCompile>
<ClInclude Include="..\..\src\ripple\shamap\TreeNodeCache.h">
</ClInclude>
<ClInclude Include="..\..\src\ripple\test\BasicNetwork.h">
</ClInclude>
<ClCompile Include="..\..\src\ripple\test\impl\BasicNetwork_test.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\src\ripple\test\impl\ManualTimeKeeper.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
@@ -3910,10 +3916,6 @@
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug.classic|x64'">True</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release.classic|x64'">True</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\src\ripple\unity\unl.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug.classic|x64'">True</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release.classic|x64'">True</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\src\ripple\unity\websocket02.cpp">
</ClCompile>
<ClCompile Include="..\..\src\ripple\unity\websocket04.cpp">
@@ -3922,30 +3924,6 @@
<AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='release.classic|x64'">..\..\src\websocketpp;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='release|x64'">..\..\src\websocketpp;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
<ClInclude Include="..\..\src\ripple\unl\tests\BasicNetwork.h">
</ClInclude>
<ClCompile Include="..\..\src\ripple\unl\tests\Consensus_test.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
</ClCompile>
<ClInclude Include="..\..\src\ripple\unl\tests\metrics.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>
<ClInclude Include="..\..\src\ripple\unl\tests\Sim1.h">
</ClInclude>
<ClInclude Include="..\..\src\ripple\unl\tests\Sim2.h">
</ClInclude>
<ClInclude Include="..\..\src\ripple\unl\tests\Sim3.h">
</ClInclude>
<ClInclude Include="..\..\src\ripple\unl\tests\Sim4.h">
</ClInclude>
<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\websocket\AutoSocket.h">
</ClInclude>
<ClInclude Include="..\..\src\ripple\websocket\Config04.h">

View File

@@ -424,12 +424,6 @@
<Filter Include="ripple\unity">
<UniqueIdentifier>{5DB3CD0B-B361-B301-9562-697CA8A52B68}</UniqueIdentifier>
</Filter>
<Filter Include="ripple\unl">
<UniqueIdentifier>{843C622F-AA52-E6C5-D3EB-D4B6D564B395}</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>
@@ -4245,6 +4239,12 @@
<ClInclude Include="..\..\src\ripple\shamap\TreeNodeCache.h">
<Filter>ripple\shamap</Filter>
</ClInclude>
<ClInclude Include="..\..\src\ripple\test\BasicNetwork.h">
<Filter>ripple\test</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ripple\test\impl\BasicNetwork_test.cpp">
<Filter>ripple\test\impl</Filter>
</ClCompile>
<ClCompile Include="..\..\src\ripple\test\impl\ManualTimeKeeper.cpp">
<Filter>ripple\test\impl</Filter>
</ClCompile>
@@ -4539,42 +4539,12 @@
<ClCompile Include="..\..\src\ripple\unity\test.cpp">
<Filter>ripple\unity</Filter>
</ClCompile>
<ClCompile Include="..\..\src\ripple\unity\unl.cpp">
<Filter>ripple\unity</Filter>
</ClCompile>
<ClCompile Include="..\..\src\ripple\unity\websocket02.cpp">
<Filter>ripple\unity</Filter>
</ClCompile>
<ClCompile Include="..\..\src\ripple\unity\websocket04.cpp">
<Filter>ripple\unity</Filter>
</ClCompile>
<ClInclude Include="..\..\src\ripple\unl\tests\BasicNetwork.h">
<Filter>ripple\unl\tests</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ripple\unl\tests\Consensus_test.cpp">
<Filter>ripple\unl\tests</Filter>
</ClCompile>
<ClInclude Include="..\..\src\ripple\unl\tests\metrics.h">
<Filter>ripple\unl\tests</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ripple\unl\tests\Network_test.cpp">
<Filter>ripple\unl\tests</Filter>
</ClCompile>
<ClInclude Include="..\..\src\ripple\unl\tests\Sim1.h">
<Filter>ripple\unl\tests</Filter>
</ClInclude>
<ClInclude Include="..\..\src\ripple\unl\tests\Sim2.h">
<Filter>ripple\unl\tests</Filter>
</ClInclude>
<ClInclude Include="..\..\src\ripple\unl\tests\Sim3.h">
<Filter>ripple\unl\tests</Filter>
</ClInclude>
<ClInclude Include="..\..\src\ripple\unl\tests\Sim4.h">
<Filter>ripple\unl\tests</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ripple\unl\tests\SlotPeer_test.cpp">
<Filter>ripple\unl\tests</Filter>
</ClCompile>
<ClInclude Include="..\..\src\ripple\websocket\AutoSocket.h">
<Filter>ripple\websocket</Filter>
</ClInclude>

View File

@@ -884,7 +884,6 @@ def get_classic_sources(toolchain):
append_sources(result, *list_sources('src/ripple/rpc', '.cpp'))
append_sources(result, *list_sources('src/ripple/shamap', '.cpp'))
append_sources(result, *list_sources('src/ripple/test', '.cpp'))
append_sources(result, *list_sources('src/ripple/unl', '.cpp'))
if use_shp(toolchain):
cc_flags = {'CCFLAGS': ['--system-header-prefix=rocksdb2']}
@@ -928,7 +927,6 @@ def get_unity_sources(toolchain):
'src/ripple/unity/rpcx.cpp',
'src/ripple/unity/shamap.cpp',
'src/ripple/unity/test.cpp',
'src/ripple/unity/unl.cpp',
)
if use_shp(toolchain):

View File

@@ -17,8 +17,8 @@
*/
//==============================================================================
#ifndef RIPPLE_SIM_BASICNETWORK_H_INCLUDED
#define RIPPLE_SIM_BASICNETWORK_H_INCLUDED
#ifndef RIPPLE_TEST_BASICNETWORK_H_INCLUDED
#define RIPPLE_TEST_BASICNETWORK_H_INCLUDED
#include <ripple/basics/qalloc.h>
#include <beast/chrono/manual_clock.h>
@@ -76,7 +76,11 @@ namespace test {
of the step, step_one, step_for, and step_until functions
to iterate the network,
Peer Concept:
Peer Requirements:
Peer should be a lightweight type, cheap to copy
and/or move. A good candidate is a simple pointer to
the underlying user defined type in the simulation.
Expression Type Requirements
---------- ---- ------------
@@ -87,6 +91,7 @@ namespace test {
u == v bool EqualityComparable
u < v bool LessThanComparable
std::hash<P> class std::hash is defined for P
! u bool true if u is not-a-peer
*/
template <class Peer>
class BasicNetwork
@@ -96,7 +101,7 @@ public:
using clock_type =
beast::manual_clock<
std::chrono::system_clock>;
std::chrono::steady_clock>;
using duration =
typename clock_type::duration;
@@ -129,8 +134,8 @@ private:
struct msg
: by_to_hook, by_from_hook, by_when_hook
{
Peer* to;
Peer* from;
Peer to;
Peer from;
time_point when;
msg (msg const&) = delete;
@@ -138,7 +143,8 @@ private:
virtual ~msg() = default;
virtual void operator()() const = 0;
msg (Peer* from_, Peer* to_, time_point when_)
msg (Peer const& from_, Peer const& to_,
time_point when_)
: to(to_), from(from_), when(when_)
{
}
@@ -160,14 +166,14 @@ private:
msg_impl (msg_impl const&) = delete;
msg_impl& operator= (msg_impl const&) = delete;
msg_impl (Peer* from_, Peer* to_,
msg_impl (Peer const& from_, Peer const& to_,
time_point when_, Handler&& h)
: msg (from_, to_, when_)
, h_ (std::move(h))
{
}
msg_impl (Peer* from_, Peer* to_,
msg_impl (Peer const& from_, Peer const& to_,
time_point when_, Handler const& h)
: msg (from_, to_, when_)
, h_ (h)
@@ -197,10 +203,10 @@ private:
boost::intrusive::make_multiset<msg,
boost::intrusive::constant_time_size<false>>::type;
std::unordered_map<Peer*, by_to_list> by_to_;
std::unordered_map<Peer*, by_from_list> by_from_;
by_when_set by_when_;
qalloc alloc_;
by_when_set by_when_;
std::unordered_map<Peer, by_to_list> by_to_;
std::unordered_map<Peer, by_from_list> by_from_;
public:
using iterator =
@@ -225,14 +231,14 @@ private:
template <class Handler>
typename by_when_set::iterator
emplace (Peer* from, Peer* to,
emplace (Peer const& from, Peer const& to,
time_point when, Handler&& h);
void
erase (iterator iter);
void
remove (Peer* from, Peer* to);
remove (Peer const& from, Peer const& to);
};
struct link_type
@@ -240,8 +246,7 @@ private:
bool inbound;
duration delay;
link_type (bool inbound_,
duration delay_)
link_type (bool inbound_, duration delay_)
: inbound (inbound_)
, delay (delay_)
{
@@ -249,15 +254,17 @@ private:
};
using links_type =
boost::container::flat_map<Peer*, link_type>;
boost::container::flat_map<Peer, link_type>;
class link_transform;
qalloc alloc_;
queue_type queue_;
clock_type clock_;
// VFALCO This is an ugly wart, aged containers
// want a non-const reference to a clock.
clock_type mutable clock_;
std::mt19937_64 rng_;
std::unordered_map<Peer*, links_type> links_;
std::unordered_map<Peer, links_type> links_;
public:
BasicNetwork (BasicNetwork const&) = delete;
@@ -273,6 +280,10 @@ public:
qalloc const&
alloc() const;
/** Return the clock. */
clock_type&
clock() const;
/** Return the current network time.
@note The epoch is unspecified
@@ -311,7 +322,7 @@ public:
@return `true` if a new connection was established
*/
bool
connect (Peer& from, Peer& to,
connect (Peer const& from, Peer const& to,
duration const& delay = std::chrono::seconds{0});
/** Break a link.
@@ -327,7 +338,7 @@ public:
@return `true` if a connection was broken.
*/
bool
disconnect (Peer& peer1, Peer& peer2);
disconnect (Peer const& peer1, Peer const& peer2);
/** Return the range of active links.
@@ -335,7 +346,7 @@ public:
*/
boost::transformed_range<
link_transform, links_type>
links (Peer& from);
links (Peer const& from);
/** Send a message to a peer.
@@ -357,7 +368,8 @@ public:
*/
template <class Function>
void
send (Peer& from, Peer& to, Function&& f);
send (Peer const& from, Peer const& to,
Function&& f);
// Used to cancel timers
struct cancel_token;
@@ -409,7 +421,7 @@ public:
*/
template <class Function>
void
bfs (Peer& start, Function&& f);
bfs (Peer const& start, Function&& f);
/** Run the network for up to one message.
@@ -514,7 +526,7 @@ template <class Peer>
template <class Handler>
auto
BasicNetwork<Peer>::queue_type::emplace(
Peer* from, Peer* to, time_point when,
Peer const& from, Peer const& to, time_point when,
Handler&& h) ->
typename by_when_set::iterator
{
@@ -554,42 +566,26 @@ BasicNetwork<Peer>::queue_type::erase(
template <class Peer>
void
BasicNetwork<Peer>::queue_type::remove(
Peer* from, Peer* to)
Peer const& from, Peer const& to)
{
{
auto& list = by_to_[to];
for(auto iter = list.begin();
iter != list.end();)
{
if (iter->from == from)
{
auto& m = *iter;
iter = list.erase(iter);
m.~msg();
alloc_.dealloc(&m, 1);
}
else
{
++iter;
}
auto& m = *iter++;
if (m.from == from)
erase(by_when_.iterator_to(m));
}
}
{
auto& list = by_from_[from];
auto& list = by_to_[from];
for(auto iter = list.begin();
iter != list.end();)
{
if (iter->to == to)
{
auto& m = *iter;
iter = list.erase(iter);
m.~msg();
alloc_.dealloc(&m, 1);
}
else
{
++iter;
}
auto& m = *iter++;
if (m.from == to)
erase(by_when_.iterator_to(m));
}
}
}
@@ -601,7 +597,7 @@ class BasicNetwork<Peer>::link_transform
{
private:
BasicNetwork& net_;
Peer& from_;
Peer from_;
public:
using argument_type =
@@ -610,13 +606,14 @@ public:
class result_type
{
public:
Peer& to;
Peer to;
bool inbound;
result_type (result_type const&) = default;
result_type (BasicNetwork& net, Peer& from,
Peer& to_, bool inbound_)
result_type (BasicNetwork& net,
Peer const& from, Peer const& to_,
bool inbound_)
: to(to_)
, inbound(inbound_)
, net_(net)
@@ -630,18 +627,19 @@ public:
The connection is removed at both ends.
*/
void
bool
disconnect() const
{
net_.disconnect(from_, to);
return net_.disconnect(from_, to);
}
private:
BasicNetwork& net_;
Peer& from_;
Peer from_;
};
link_transform (BasicNetwork& net, Peer& from)
link_transform (BasicNetwork& net,
Peer const& from)
: net_(net)
, from_(from)
{
@@ -651,7 +649,7 @@ public:
operator()(argument_type const& v) const
{
return result_type(net_, from_,
*v.first, v.second.inbound);
v.first, v.second.inbound);
}
};
@@ -702,6 +700,16 @@ BasicNetwork<Peer>::alloc() const
}
template <class Peer>
inline
auto
BasicNetwork<Peer>::clock() const ->
clock_type&
{
return clock_;
}
template <class Peer>
inline
auto
BasicNetwork<Peer>::now() const ->
time_point
@@ -730,16 +738,17 @@ BasicNetwork<Peer>::rand(
template <class Peer>
bool
BasicNetwork<Peer>::connect(
Peer& from, Peer& to, duration const& delay)
Peer const& from, Peer const& to,
duration const& delay)
{
if (&to == &from)
if (to == from)
return false;
using namespace std;
if (! links_[&from].emplace(&to,
if (! links_[from].emplace(to,
link_type{ false, delay }).second)
return false;
auto const result = links_[&to].emplace(
&from, link_type{ true, delay });
auto const result = links_[to].emplace(
from, link_type{ true, delay });
(void)result;
assert(result.second);
return true;
@@ -748,27 +757,27 @@ BasicNetwork<Peer>::connect(
template <class Peer>
bool
BasicNetwork<Peer>::disconnect(
Peer& peer1, Peer& peer2)
Peer const& peer1, Peer const& peer2)
{
if (links_[&peer1].erase(&peer2) == 0)
if (links_[peer1].erase(peer2) == 0)
return false;
auto const n =
links_[&peer2].erase(&peer1);
links_[peer2].erase(peer1);
(void)n;
assert(n);
queue_.remove(&peer1, &peer2);
queue_.remove(peer1, peer2);
return true;
}
template <class Peer>
inline
auto
BasicNetwork<Peer>::links(Peer& from) ->
BasicNetwork<Peer>::links(Peer const& from) ->
boost::transformed_range<
link_transform, links_type>
{
return boost::adaptors::transform(
links_[&from],
links_[from],
link_transform{ *this, from });
}
@@ -777,12 +786,13 @@ template <class Function>
inline
void
BasicNetwork<Peer>::send(
Peer& from, Peer& to, Function&& f)
Peer const& from, Peer const& to,
Function&& f)
{
using namespace std;
auto const iter =
links_[&from].find(&to);
queue_.emplace(&from, &to,
links_[from].find(to);
queue_.emplace(from, to,
clock_.now() + iter->second.delay,
forward<Function>(f));
}
@@ -852,14 +862,14 @@ bool
BasicNetwork<Peer>::step_until(
time_point const& until)
{
// VFALCO This routine needs optimize
// VFALCO This routine needs optimizing
if(queue_.empty())
{
clock_.set(until);
return false;
}
auto iter = queue_.begin();
if (iter->when > until)
if(iter->when > until)
{
clock_.set(until);
return true;
@@ -882,28 +892,27 @@ bool
BasicNetwork<Peer>::step_for(
std::chrono::duration<Period, Rep> const& amount)
{
return step_until(
clock_.now() + amount);
return step_until(now() + amount);
}
template <class Peer>
template <class Function>
void
BasicNetwork<Peer>::bfs(
Peer& start, Function&& f)
Peer const& 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);
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(v.second, *v.first);
f(v.second, v.first);
for(auto const& link : links_[v.first])
{
auto w = link.first;
auto const& w = link.first;
if (seen.count(w) == 0)
{
q.emplace_back(w, v.second + 1);

View File

@@ -0,0 +1,133 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012-2015 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include <BeastConfig.h>
#include <ripple/test/BasicNetwork.h>
#include <beast/unit_test/suite.h>
#include <set>
#include <vector>
namespace ripple {
namespace test {
class BasicNetwork_test : public beast::unit_test::suite
{
public:
struct Peer
{
int id;
std::set<int> set;
Peer (Peer const&) = default;
Peer (Peer&&) = default;
explicit Peer(int id_)
: id(id_)
{
}
template <class Net>
void start(Net& net)
{
using namespace std::chrono_literals;
auto t = net.timer(1s,
[&]{ set.insert(0); });
if (id == 0)
{
for(auto const& link : net.links(this))
net.send(this, link.to,
[&, to = link.to]
{
to->receive(net, this, 1);
});
}
else
{
net.cancel(t);
}
}
template <class Net>
void receive(Net& net, Peer* from, int m)
{
set.insert(m);
++m;
if (m < 5)
{
for(auto const& link : net.links(this))
net.send(this, link.to,
[&, mm = m, to = link.to]
{
to->receive(net, this, mm);
});
}
}
};
void run() override
{
using namespace std::chrono_literals;
std::vector<Peer> pv;
pv.emplace_back(0);
pv.emplace_back(1);
pv.emplace_back(2);
BasicNetwork<Peer*> net;
expect(net.rand(0, 1) == 0);
expect(! net.connect(&pv[0], &pv[0]));
expect(net.connect(&pv[0], &pv[1], 1s));
expect(net.connect(&pv[1], &pv[2], 1s));
expect(! net.connect(&pv[0], &pv[1]));
std::size_t diameter = 0;
net.bfs(&pv[0],
[&](auto d, Peer*)
{ diameter = std::max(d, diameter); });
expect(diameter == 2);
for(auto& peer : pv)
peer.start(net);
expect(net.step_for(0s));
expect(net.step_for(1s));
expect(net.step());
expect(! net.step());
expect(! net.step_for(1s));
net.send(&pv[0], &pv[1], []{});
net.send(&pv[1], &pv[0], []{});
expect(net.disconnect(&pv[0], &pv[1]));
expect(! net.disconnect(&pv[0], &pv[1]));
for(;;)
{
auto const links = net.links(&pv[1]);
if(links.empty())
break;
expect(links[0].disconnect());
}
expect(pv[0].set ==
std::set<int>({0, 2, 4}));
expect(pv[1].set ==
std::set<int>({1, 3}));
expect(pv[2].set ==
std::set<int>({2, 4}));
net.timer(0s, []{});
}
};
BEAST_DEFINE_TESTSUITE(BasicNetwork, test, ripple)
} // test
} // ripple

View File

@@ -48,4 +48,5 @@
#include <ripple/test/mao/impl/Net.cpp>
#include <ripple/test/mao/impl/Net_test.cpp>
#include <ripple/test/impl/BasicNetwork_test.cpp>
#include <ripple/test/impl/ManualTimeKeeper.cpp>

View File

@@ -1,24 +0,0 @@
//------------------------------------------------------------------------------
/*
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/Consensus_test.cpp>
#include <ripple/unl/tests/Network_test.cpp>
#include <ripple/unl/tests/SlotPeer_test.cpp>

View File

@@ -1,62 +0,0 @@
# Validators
The Validators module has these responsibilities:
- Provide an administrative interface for maintaining the list _Source_
locations.
- Report performance statistics on _Source_ locations
- Report performance statistics on _validators_ provided by _Source_ locations.
- Choose a suitable random subset of observed _Validators_ to become the
_Chosen Validators_ set.
- Update the _Chosen Validators_ set as needed to meet performance requirements.
## Description
The consensus process used by the Ripple payment protocol requires that ledger
hashes be signed by _Validators_, producing a _Validation_. The integrity of
the process is mathematically assured when each node chooses a random subset
of _Validators_ to trust, where each _Validator_ is a public verifiable entity
that is independent. Or more specifically, no entity should be in control of
any significant number of _validators_ chosen by each node.
The list of _Validators_ a node chooses to trust is called the _Chosen
Validators_. The **Validators** module implements business logic to automate the
selection of _Chosen Validators_ by allowing the administrator to provide one
or more trusted _Sources_, from which _Validators_ are learned. Performance
statistics are tracked for these _Validators_, and the module chooses a
suitable subset from which to form the _Chosen Validators_ list.
The module looks for these criteria to determine suitability:
- Different validators are not controlled by the same entity.
- Each validator participates in a majority of ledgers.
- A validator does not sign ledgers that fail the consensus process.
## Terms
<table>
<tr>
<td>Chosen Validators</td>
<td>A set of validators chosen by the Validators module. This is the new term
for what was formerly known as the Unique Node List.
</td>
</tr>
<tr>
<td>Source</td>
<td>A trusted source of validator descriptors. Examples: the rippled
configuration file, a local text file, or a trusted URL such
as https://ripple.com/validators.txt.
</td></tr>
</tr>
<tr>
<td>Validation</td>
<td>A closed ledger hash signed by a validator.
</td>
</tr>
<tr>
<td>Validator</td>
<td>A publicly verifiable entity which signs ledger hashes with its private
key, and makes its public key available through out of band means.
</td>
</tr>
</table>

View File

@@ -1,48 +0,0 @@
//------------------------------------------------------------------------------
/*
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/metrics.h>
#include <ripple/unl/tests/Sim1.h>
#include <ripple/unl/tests/Sim2.h>
#include <ripple/unl/tests/Sim3.h>
#include <ripple/unl/tests/Sim4.h>
#include <beast/unit_test/suite.h>
namespace ripple {
namespace test {
class Consensus_test : public beast::unit_test::suite
{
public:
void
run()
{
Sim4<log_t>::run(log);
//Sim3<log_t>::run(log);
//Sim2<log_t>::run(log);
//Sim1::run(log);
pass();
}
};
BEAST_DEFINE_TESTSUITE_MANUAL(Consensus,sim,ripple);
}
}

View File

@@ -1,217 +0,0 @@
//------------------------------------------------------------------------------
/*
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 <ripple/unl/tests/metrics.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,
[&, m]() { receive(net, from, m); });
}
template <class Net>
void
receive (Net& net, Peer& from, Ping p)
{
if (set)
return;
++p.hops;
set = true;
hops = p.hops;
for(auto& link : net.links(*this))
link.to.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,
[&, m]() { receive(net, from, m); });
}
template <class Net>
void
receive (Net& net, Peer& from, Ping p)
{
if (set)
return;
++p.hops;
set = true;
hops = p.hops;
for(auto& link : net.links(*this))
link.to.send(net, *this, p);
}
};
template <class Peer>
struct Network : BasicNetwork<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& link : net.links(net.pv[0]))
link.to.send(net, net.pv[0], Ping{});
net.step();
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],
[&](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

@@ -1,376 +0,0 @@
//------------------------------------------------------------------------------
/*
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_UNL_SIM1_H_INCLUDED
#define RIPPLE_UNL_SIM1_H_INCLUDED
#include <ripple/unl/tests/BasicNetwork.h>
#include <boost/optional.hpp>
#include <algorithm>
#include <sstream>
#include <unordered_map>
#include <vector>
namespace ripple {
namespace test {
struct Sim1
{
struct Config
{
};
static int const nPeer = 100; // # of peers
static int const nDegree = 10; // outdegree
static int const nTrial = 10; // number of trials
static int const nRound = 1; // number of rounds
static int const nUNLMin = 20;
static int const nUNLMax = 30;
using clock_type = std::chrono::system_clock;
struct Network;
// A round of consensus.
// Each round consists of a series of votes,
// terminating when a supermajority is reached.
class Round
{
public:
static int const nPercent = 80; // % of agreement
int id_;
bool consensus_ = false;
std::unordered_map<int, std::pair<
std::size_t, bool>> pos_;
std::size_t count_ = 0;
clock_type::time_point t0_;
public:
// Create a new round with initial position
Round (int id, bool value,
clock_type::time_point now)
: id_ (id)
, t0_ (now)
{
pos_.emplace(std::make_pair(id,
std::make_pair(0, value)));
}
// Returns our value
bool
value() const
{
auto const iter = pos_.find(id_);
return iter->second.second;
}
// Return our position
// This increments the sequence number
std::pair<std::size_t, bool>
pos()
{
auto const iter = pos_.find(id_);
return { ++iter->second.first,
iter->second.second };
}
// Update a peer's position
// Return `true` if we should relay
bool
receive (int id,
std::size_t seq, bool value)
{
if (id == id_)
return false;
auto const result = pos_.emplace(
std::make_pair(id,
std::make_pair(seq, value)));
if (! result.second && seq <=
result.first->second.first)
return false;
result.first->second.first = seq;
result.first->second.second = value;
return true;
}
// Update our position
// Return `true` if we changed our position
template <class UNL>
bool
update (UNL const& unl,
clock_type::time_point const& now)
{
if (consensus_)
return false;
++count_;
std::array<std::size_t, 2> v;
v.fill(0);
for(auto const& p : pos_)
if (p.first == id_ ||
unl.count(p.first) > 0)
++v[p.second.second];
using namespace std::chrono;
auto const iter = pos_.find(id_);
auto const super =
((unl.size() * nPercent) + 50) / 100;
if (v[0] >= super || v[1] >= super)
consensus_ = true;
// agree to disagree
v[0] += duration_cast<milliseconds>(
now - t0_).count() / 250;
if (v[0] >= v[1])
{
if (iter->second.second != false)
{
iter->second.second = false;
return true;
}
}
else
{
if (iter->second.second != true)
{
iter->second.second = true;
return true;
}
}
return false;
}
};
//--------------------------------------------------------------------------
class Peer
{
private:
struct PosMsg
{
int id;
std::size_t seq;
bool value; // position
};
public:
int id_;
std::set<int> unl_;
Config const& config_;
boost::optional<Round> round_;
std::chrono::milliseconds delay_;
Network& net_;
Peer (int id, Config const& config,
Network& net)
: id_(id)
, config_ (config)
, delay_(std::chrono::milliseconds(
net.rand(5, 50)))
, net_(net)
{
auto const size = net_.rand(
nUNLMin, nUNLMax + 1);
while(unl_.size() < size)
{
unl_.insert(net_.rand(nPeer));
unl_.erase(id_);
}
}
// Called to begin the round
void
start()
{
round_.emplace(id_,
!(id_%3), net_.now());
++round_->count_;
PosMsg m;
m.id = id_;
std::tie(m.seq, m.value) =
round_->pos();
broadcast(m);
using namespace std::chrono;
net_.timer(milliseconds(
700 + net_.rand(700)),
[=]() { timer(); });
}
void
receive (Peer& from, PosMsg const& m)
{
if (round_->receive(m.id,
m.seq, m.value))
relay(from, m);
else
++net_.dup;
}
void
timer()
{
if (round_->update(unl_, net_.now()))
{
PosMsg m;
m.id = id_;
std::tie(m.seq, m.value) =
round_->pos();
broadcast(m);
}
if (round_->consensus_)
return;
using namespace std::chrono;
net_.timer(milliseconds(700),
[=]() { timer(); });
}
//----------------------------------------------------------------------
// Send a message to this peer
template <class Message>
void
send (Peer& from, Message&& m)
{
++net_.sent;
using namespace std::chrono;
net_.send (from, *this,
[&, m]() { receive(from, m); });
}
// Broadcast a message to all links
template <class Message>
void
broadcast (Message const& m)
{
for(auto& link : net_.links(*this))
link.to.send(*this, m);
}
// Relay a message to all links
template <class Message>
void
relay (Peer& from, Message const& m)
{
for(auto& link : net_.links(*this))
if (&link.to != &from)
link.to.send(*this, m);
}
};
//--------------------------------------------------------------------------
struct Network : BasicNetwork<Peer>
{
std::size_t dup = 0; // total dup
std::size_t sent = 0; // total sent
std::vector<Peer> pv;
Network (std::size_t seed,
Config const& config)
{
this->rng().seed(seed);
using namespace std;
using namespace std::chrono;
pv.reserve(nPeer);
for(std::size_t id = 0; id < nPeer; ++id)
pv.emplace_back(id, config, *this);
for(auto& peer : pv)
for(int i = 0; i < nDegree; ++i)
connect_one(peer);
}
// Add one random connection
void
connect_one(Peer& from)
{
using namespace std::chrono;
auto const delay = from.delay_ +
milliseconds(rand(5, 200));
for(;;)
if (connect(from,
pv[rand(pv.size())], delay))
break;
}
template <class Log>
void
report (std::chrono::milliseconds ms, Log& log)
{
std::array<std::size_t, 2> n;
std::vector<std::size_t> count;
n.fill(0);
std::size_t consensus = 0;
for(auto const& p : pv)
{
++n[p.round_->value()];
++nth(count, p.round_->count_);
if (p.round_->consensus_)
++consensus;
}
log <<
n[1] << "/" << n[0] << ", " <<
"consensus: " << consensus << " in " <<
ms.count() << "ms, " <<
"sent: " << sent << ", " <<
"dup: " << dup << ", " <<
"count: " << seq_string(count);
}
// Execute a round of consensus
template <class Log>
void
round (Log& log)
{
using namespace std::chrono;
for(int i = 0; i < nPeer; ++i)
pv[i].start();
auto const t0 = now();
#if 0
do
{
report(duration_cast<
milliseconds>(now() - t0), log);
}
while (step_for(milliseconds(50)));
#else
step();
#endif
report(duration_cast<
milliseconds>(now() - t0), log);
}
};
template <class Log>
static
void
run (Log& log)
{
log << "Sim1" << ":";
Config config;
for(auto i = 1; i <= nTrial; ++i)
{
Network net(i, config);
for(auto j = 1; j <= nRound; ++j)
net.round(log);
}
}
};
} // test
} // ripple
#endif

View File

@@ -1,434 +0,0 @@
//------------------------------------------------------------------------------
/*
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_UNL_SIM2_H_INCLUDED
#define RIPPLE_UNL_SIM2_H_INCLUDED
#include <ripple/unl/tests/BasicNetwork.h>
#include <beast/container/aged_unordered_map.h>
#include <boost/container/flat_set.hpp>
#include <boost/optional.hpp>
#include <algorithm>
#include <memory>
#include <sstream>
#include <unordered_map>
#include <vector>
namespace ripple {
namespace test {
template <class Log>
struct Sim2
{
struct Config
{
};
static int const nPeer = 100; // # of peers
static int const nDegree = 10; // outdegree
static int const nTrial = 1000000; // number of trials
static int const nRound = 1; // number of rounds
static int const nUNLMin = 20;
static int const nUNLMax = 30;
static int const nPos = 10;
using NodeKey = int; // identifies a consensus participant
using ItemKey = int; // identifies a ballot item
using ItemSet = boost::container::flat_set<ItemKey>;
using clock_type = std::chrono::system_clock;
struct Network;
struct PosMsg
{
NodeKey id;
std::size_t seq;
ItemSet items;
bool last;
};
// A round of consensus.
// Each round consists of a series of votes,
// terminating when a supermajority is reached.
class Round
{
private:
int thresh_ = 50;
public:
struct Pos
{
ItemSet items;
bool last = false;
std::size_t seq = 0;
};
int id_;
bool failed_ = false;
bool consensus_ = false;
std::unordered_map<NodeKey, Pos> pos_;
std::size_t count_ = 0;
clock_type::time_point t0_;
Log& log_;
public:
// Create a new round with initial position
Round (NodeKey id, ItemSet&& pos,
clock_type::time_point now, Log& log)
: id_ (id)
, t0_ (now)
, log_ (log)
{
using namespace std;
pos_[id].items = std::move(pos);
}
std::shared_ptr<PosMsg const>
posMsg()
{
auto const iter = pos_.find(id_);
auto m = std::make_shared<PosMsg>();
m->id = id_;
m->seq = ++iter->second.seq;
m->items = iter->second.items;
m->last = consensus_;
return m;
}
ItemSet const&
items() const
{
return pos_.find(id_)->second.items;
}
// Update a peer's position
// Return `true` if we should relay
bool
receive (PosMsg const& m)
{
if (m.id == id_)
return false;
using namespace std;
auto& pos = pos_[m.id];
if (m.seq <= pos.seq)
return false;
pos.seq = m.seq;
pos.last = m.last;
pos.items = m.items;
return true;
}
// Update our position
// Returns `true` if we changed our position
template <class UNL>
bool
update (UNL const& unl,
clock_type::time_point const& now)
{
if (consensus_)
return false;
// count votes per item from unl
boost::container::flat_map<
ItemKey, std::size_t> votes;
for(auto const& pos : pos_)
{
if (! unl.count(pos.first))
continue;
for(auto const& item : pos.second.items)
{
auto const result =
votes.emplace(item, 1);
if (! result.second)
++result.first->second;
}
}
// calculate our new position
ItemSet items;
{
auto const needed =
(thresh_ * unl.size() + 50) / 100;
for(auto const& v : votes)
if (v.second >= needed)
items.insert(v.first);
thresh_ += 5;
}
// see if we reached a consensus
std::size_t most = 0;
std::size_t agree = 0;
for(auto const& pos : pos_)
{
if (! unl.count(pos.first))
continue;
if (pos.second.items == items)
++agree;
else if (! pos.second.last)
++most;
}
//{
auto const needed =
(80 * unl.size() + 50) / 100;
if (agree >= needed)
{
consensus_ = true;
}
else if (agree + most < needed)
{
failed_ = true;
consensus_ = true;
}
//}
if (now.time_since_epoch() >=
std::chrono::seconds(7))
{
log_ <<
"agree = " << agree <<
", most = " << most <<
", needed = " << needed <<
", thresh_ = " << thresh_ <<
", items.size() = " << items.size();
}
auto const iter = pos_.find(id_);
if (! consensus_ &&
iter->second.items == items)
return false;
iter->second.items = items;
return true;
}
};
//--------------------------------------------------------------------------
class Peer
{
private:
//beast::aged_unordered_map<
public:
NodeKey id_;
std::set<NodeKey> unl_;
Config const& config_;
boost::optional<Round> round_;
std::chrono::milliseconds delay_;
Network& net_;
Peer (int id, Config const& config,
Network& net)
: id_ (id)
, config_ (config)
, delay_ (std::chrono::milliseconds(
net.rand(5, 50)))
, net_ (net)
{
auto const size = 1 + net_.rand(
nUNLMin, nUNLMax + 1);
unl_.insert(id_); // self
while(unl_.size() < size)
unl_.insert(net_.rand(nPeer));
}
// Called to begin the round
void
start()
{
{
ItemSet pos;
for(int i = 0; i < nPos; ++i)
if (net_.rand(2))
pos.insert(i);
round_.emplace(id_, std::move(pos),
net_.now(), net_.log);
}
broadcast(round_->posMsg());
using namespace std::chrono;
net_.timer(milliseconds(
700 + net_.rand(700)),
[=]() { timer(); });
}
void
receive (Peer& from,
std::shared_ptr<PosMsg const> const& m)
{
if (round_->receive(*m))
relay(from, m);
else
++net_.dup;
}
void
timer()
{
if (round_->update(unl_, net_.now()))
broadcast(round_->posMsg());
if (round_->consensus_)
return;
using namespace std::chrono;
net_.timer(milliseconds(700),
[=]() { timer(); });
}
//----------------------------------------------------------------------
// Send a message to this peer
template <class Message>
void
send (Peer& from,
std::shared_ptr<Message const> const& m)
{
++net_.sent;
net_.send (from, *this,
[&, m]() { receive(from, m); });
}
// Broadcast a message to all links
template <class Message>
void
broadcast (std::shared_ptr<
Message const> const& m)
{
for(auto& link : net_.links(*this))
link.to.send(*this, m);
}
// Relay a message to all links
template <class Message>
void
relay (Peer& from,
std::shared_ptr<Message const> const& m)
{
for(auto& link : net_.links(*this))
if (&link.to != &from)
link.to.send(*this, m);
}
};
//--------------------------------------------------------------------------
struct Network : BasicNetwork<Peer>
{
std::size_t dup = 0; // total dup
std::size_t sent = 0; // total sent
std::vector<Peer> pv;
Log& log;
Network (std::size_t seed,
Config const& config, Log& log_)
: log (log_)
{
this->rng.seed(seed);
using namespace std;
using namespace std::chrono;
pv.reserve(nPeer);
for(std::size_t id = 0; id < nPeer; ++id)
pv.emplace_back(id, config, *this);
for(auto& peer : pv)
for(int i = 0; i < nDegree; ++i)
connect_one(peer);
}
// Add one random connection
void
connect_one(Peer& from)
{
using namespace std::chrono;
auto const delay = from.delay_ +
milliseconds(this->rand(5, 200));
for(;;)
if (connect(from,
pv[this->rand(pv.size())], delay))
break;
}
void
report (std::size_t n,
std::chrono::milliseconds ms, Log& log)
{
std::size_t failed = 0;
std::size_t consensus = 0;
std::vector<std::size_t> hist;
hist.resize(nPos);
for(auto const& p : pv)
{
hist_accum(hist, p.round_->items());
if (p.round_->consensus_)
++consensus;
if (p.round_->failed_)
++failed;
}
log <<
((n > 0) ? "#" + std::to_string(n) + " " : "") <<
seq_string(hist, 3) << " " <<
"consensus: " << consensus - failed << " in " <<
ms.count() << "ms, " <<
"sent: " << sent << ", " <<
"dup: " << dup;
}
// Execute a round of consensus
void
round (std::size_t n)
{
using namespace std::chrono;
for(int i = 0; i < nPeer; ++i)
pv[i].start();
auto const t0 = this->now();
#if 0
do
{
report(0, duration_cast<
milliseconds>(now() - t0), log);
}
while (this->step_for(milliseconds(50)));
#else
this->step();
#endif
report(n, duration_cast<
milliseconds>(this->now() - t0), log);
}
};
static
void
run (Log& log)
{
log << "Sim2" << ":";
Config config;
for(auto i = 1; i <= nTrial; ++i)
{
//log << "Trial " << i;
Network net(i, config, log);
for(auto j = 1; j <= nRound; ++j)
net.round(i);
//log << "\n";
}
}
};
} // test
} // ripple
#endif
/*
Try limiting threshold to 80
Try slower increase of threshold
Increase UNL sizes
*/

View File

@@ -1,512 +0,0 @@
//------------------------------------------------------------------------------
/*
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_UNL_SIM3_H_INCLUDED
#define RIPPLE_UNL_SIM3_H_INCLUDED
#include <ripple/unl/tests/BasicNetwork.h>
#include <beast/container/aged_unordered_map.h>
#include <boost/container/flat_set.hpp>
#include <boost/optional.hpp>
#include <algorithm>
#include <memory>
#include <sstream>
#include <unordered_map>
#include <vector>
namespace ripple {
namespace test {
template <class Log>
struct Sim3
{
struct Config
{
int unl;
int peers = 100;
int trial = 100;
};
static int const nDegree = 10; // outdegree
static int const nItem = 10; // number of items
static int const nUpdateMS = 700;
using NodeKey = int; // identifies a consensus participant
using ItemKey = int; // identifies a ballot item
using ItemSet = boost::container::flat_set<ItemKey>;
using clock_type = std::chrono::system_clock;
using millis = std::chrono::milliseconds;
class Network;
struct PosMsg
{
NodeKey id;
std::size_t seq;
ItemSet items;
bool last;
};
// A round of consensus.
// Each round consists of a series of votes,
// terminating when a supermajority is reached.
class Round
{
private:
int thresh_ = 50;
public:
struct Pos
{
ItemSet items;
bool last = false;
std::size_t seq = 0;
};
NodeKey id_;
bool failed_ = false;
bool consensus_ = false;
std::unordered_map<NodeKey, Pos> pos_;
std::size_t count_ = 0;
clock_type::time_point t0_;
Log& log_;
public:
// Create a new round with initial position
Round (NodeKey id, ItemSet&& pos,
clock_type::time_point now, Log& log)
: id_ (id)
, t0_ (now)
, log_ (log)
{
using namespace std;
pos_[id].items = std::move(pos);
}
std::shared_ptr<PosMsg const>
posMsg()
{
auto const iter = pos_.find(id_);
auto m = std::make_shared<PosMsg>();
m->id = id_;
m->seq = ++iter->second.seq;
m->items = iter->second.items;
m->last = consensus_;
return m;
}
ItemSet const&
items() const
{
return pos_.find(id_)->second.items;
}
// Update a peer's position
// Return `true` if we should relay
bool
receive (PosMsg const& m)
{
if (m.id == id_)
return false;
auto& pos = pos_[m.id];
if (m.seq <= pos.seq)
return false;
pos.seq = m.seq;
pos.last = m.last;
pos.items = m.items;
return true;
}
// Update our position
// Returns `true` if we changed our position
template <class UNL>
bool
update (UNL const& unl,
clock_type::time_point const& now)
{
if (consensus_)
return false;
// count votes per item from unl
boost::container::flat_map<
ItemKey, std::size_t> votes;
for(auto const& pos : pos_)
{
if (! unl.count(pos.first))
continue;
for(auto const& item : pos.second.items)
{
auto const result =
votes.emplace(item, 1);
if (! result.second)
++result.first->second;
}
}
// calculate our new position
ItemSet items;
{
auto const needed =
(thresh_ * unl.size() + 50) / 100;
for(auto const& v : votes)
if (v.second >= needed)
items.insert(v.first);
#if 1
thresh_ += 5;
#endif
#if 0
// This causes occasional byzantine
// failure in a large number of nodes
if (thresh_ > 80)
thresh_ = 80;
#endif
}
// see if we reached a consensus
std::size_t most = 0;
std::size_t agree = 0;
for(auto const& pos : pos_)
{
if (! unl.count(pos.first))
continue;
if (pos.first == id_ ||
pos.second.items == items)
++agree;
else if (! pos.second.last)
++most;
}
{
auto const needed =
(80 * unl.size() + 50) / 100;
if (agree >= needed)
{
consensus_ = true;
}
else if (agree + most < needed)
{
failed_ = true;
consensus_ = true;
}
}
auto const iter = pos_.find(id_);
if (! consensus_ &&
iter->second.items == items)
return false;
iter->second.items = items;
return true;
}
};
//--------------------------------------------------------------------------
class Peer
{
private:
//beast::aged_unordered_map<
public:
NodeKey id_;
std::set<NodeKey> unl_;
Config const& config_;
boost::optional<Round> round_;
millis delay_;
Network& net_;
Peer (int id, Config const& config,
Network& net)
: id_ (id)
, config_ (config)
, delay_ (millis(
net.rand(5, 50)))
, net_ (net)
{
unl_.insert(id_); // self
while(unl_.size() <= config_.unl)
unl_.insert(net_.rand(config_.peers));
}
// Called to begin the round
void
start()
{
{
ItemSet pos;
for(int i = 0; i < nItem; ++i)
if (net_.rand(2))
pos.insert(i);
round_.emplace(id_, std::move(pos),
net_.now(), net_.log);
}
broadcast(round_->posMsg());
using namespace std::chrono;
net_.timer(milliseconds(
nUpdateMS + net_.rand(nUpdateMS)),
[=]() { timer(); });
}
void
receive (Peer& from,
std::shared_ptr<PosMsg const> const& m)
{
if (round_->receive(*m))
relay(from, m);
else
++net_.dup;
}
void
timer()
{
if (round_->update(unl_, net_.now()))
broadcast(round_->posMsg());
if (round_->consensus_)
return;
using namespace std::chrono;
net_.timer(milliseconds(nUpdateMS),
[=]() { timer(); });
}
//----------------------------------------------------------------------
// Send a message to this peer
template <class Message>
void
send (Peer& from,
std::shared_ptr<Message const> const& m)
{
++net_.sent;
net_.send (from, *this,
[&, m]() { receive(from, m); });
}
// Broadcast a message to all links
template <class Message>
void
broadcast (std::shared_ptr<
Message const> const& m)
{
for(auto& link : net_.links(*this))
link.to.send(*this, m);
}
// Relay a message to all links
template <class Message>
void
relay (Peer& from,
std::shared_ptr<Message const> const& m)
{
for(auto& link : net_.links(*this))
if (&link.to != &from)
link.to.send(*this, m);
}
};
//--------------------------------------------------------------------------
// The result of one round
struct Result
{
std::size_t elapsed;
std::size_t failure = 0;
std::size_t consensus = 0;
std::set<ItemSet> sets;
};
// The results of several rounds
struct Results
{
std::size_t rounds = 0;
std::size_t perfect = 0;
std::vector<std::size_t> elapsed;
std::vector<std::size_t> failure;
std::vector<std::size_t> consensus;
void
aggregate (Result const& result)
{
++rounds;
perfect += result.sets.size() == 1;
elapsed.push_back(result.elapsed);
failure.push_back(result.failure);
consensus.push_back(result.consensus);
}
};
struct Report
{
std::size_t perfect;
std::size_t elapsed_min;
std::size_t elapsed_max;
Report (Results& results, Config const& config)
{
perfect = results.perfect;
#if 0
std::sort(
results.elapsed.begin(), results.elapsed.end());
std::sort(
results.consensus.begin(), results.consensus.end(),
std::greater<std::size_t>{});
#endif
elapsed_min = results.elapsed.front();
elapsed_max = results.elapsed.back();
}
};
class Network : public BasicNetwork<Peer>
{
private:
Config const& config_;
public:
std::size_t dup = 0; // total dup
std::size_t sent = 0; // total sent
std::vector<Peer> pv;
Log& log;
Network (std::size_t seed,
Config const& config, Log& log_)
: config_ (config)
, log (log_)
{
this->rng.seed(seed);
using namespace std;
using namespace std::chrono;
pv.reserve(config.peers);
for(std::size_t id = 0; id < config_.peers; ++id)
pv.emplace_back(id, config, *this);
for(auto& peer : pv)
for(int i = 0; i < nDegree; ++i)
connect_one(peer);
}
// Add one random connection
void
connect_one(Peer& from)
{
using namespace std::chrono;
auto const delay = from.delay_ +
milliseconds(this->rand(5, 200));
for(;;)
if (connect(from,
pv[this->rand(pv.size())], delay))
break;
}
// Execute one round of consensus
Result
run()
{
Result result;
using namespace std::chrono;
for(int i = 0; i < config_.peers; ++i)
pv[i].start();
auto const t0 = this->now();
this->step();
result.elapsed = duration_cast<
millis>(this->now() - t0).count();
for(auto const& p : pv)
{
if (p.round_->failed_)
++result.failure;
if (p.round_->consensus_)
{
++result.consensus;
result.sets.insert(p.round_->items());
}
}
return result;
}
};
static
void
report (Log& log, Result const& result,
Config const& config)
{
log <<
result.elapsed << "\t" <<
result.failure << "\t" <<
result.consensus << "\t" <<
result.sets.size();
;
}
static
void
report (Log& log, Report const& report,
Config const& config)
{
log <<
report.perfect << "\t" <<
report.elapsed_min << "\t" <<
report.elapsed_max << "\t" <<
config.peers << "\t" <<
config.unl << "\t" <<
config.trial
;
}
static
void
run (Log& log)
{
log << "Sim3" << ":";
#if 1
log <<
"perfect\t" <<
"elapsed_min\t" <<
"elapsed_max\t" <<
"peers\t" <<
"unl\t" <<
"trial\t"
;
#else
log <<
"elapsed\t" <<
"failure\t" <<
"consensus\t" <<
"positions\t"
;
#endif
for (int unl = 40; unl > 5; --unl)
{
Results results;
Config config;
config.unl = unl;
for(auto i = 1; i <= config.trial; ++i)
{
Network net(i, config, log);
//report(log, net.run(), config);
results.aggregate(net.run());
}
report(log, Report(results, config), config);
}
}
};
} // test
} // ripple
#endif
/*
Try limiting threshold to 80
Try slower increase of threshold
Increase UNL sizes
*/

View File

@@ -1,607 +0,0 @@
//------------------------------------------------------------------------------
/*
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_UNL_SIM4_H_INCLUDED
#define RIPPLE_UNL_SIM4_H_INCLUDED
#include <ripple/unl/tests/BasicNetwork.h>
#include <beast/container/aged_unordered_map.h>
#include <boost/container/flat_set.hpp>
#include <boost/optional.hpp>
#include <algorithm>
#include <memory>
#include <sstream>
#include <unordered_set>
#include <unordered_map>
#include <vector>
namespace ripple {
namespace test {
template <class Log>
struct Sim4
{
struct Config
{
int unl = 20;
int peers = 100;
int trials = 100;
int rounds = 1;
};
static int const nDegree = 10; // outdegree
static int const nItem = 10; // number of items
enum
{
nUpdateMS = 700
};
using NodeKey = int; // identifies a consensus participant
using ItemKey = int; // identifies a ballot item
using ItemSet = boost::container::flat_set<ItemKey>;
using clock_type = std::chrono::system_clock;
using millis = std::chrono::milliseconds;
struct Network;
struct TxMsg
{
ItemKey id;
};
struct PosMsg
{
NodeKey id;
std::size_t ord;
std::size_t seq;
ItemSet items;
bool last;
};
// A pool of items
// This is the equivalent of the "open ledger"
class Pool
{
private:
ItemSet items_;
public:
// Insert an item into the pool
void
insert (ItemKey id)
{
items_.insert(id);
}
// Returns the items in the pool
ItemSet const&
items() const
{
return items_;
}
};
// A round of consensus.
// Each round consists of a series of votes,
// terminating when a supermajority is reached.
struct Round
{
struct Pos
{
ItemSet items;
bool last = false;
std::size_t seq = 0;
};
int id_;
Log& log_;
std::size_t ord_;
clock_type::time_point t0_;
int thresh_ = 50;
bool failed_ = false;
bool consensus_ = false;
std::size_t count_ = 0;
std::unordered_map<NodeKey, Pos> pos_;
// Create a new round with initial position
Round (NodeKey id, std::size_t ord,
ItemSet const& items,
clock_type::time_point now,
Log& log)
: id_ (id)
, log_ (log)
, ord_ (ord)
, t0_ (now)
{
using namespace std;
pos_[id].items = items;
}
std::shared_ptr<PosMsg const>
posMsg()
{
auto const iter = pos_.find(id_);
auto m = std::make_shared<PosMsg>();
m->id = id_;
m->seq = ++iter->second.seq;
m->items = iter->second.items;
m->last = consensus_;
return m;
}
ItemSet const&
items() const
{
return pos_.find(id_)->second.items;
}
// Update a peer's position
// Return `true` if we should relay
bool
receive (PosMsg const& m)
{
if (m.id == id_)
return false;
using namespace std;
auto& pos = pos_[m.id];
if (m.seq <= pos.seq)
return false;
pos.seq = m.seq;
pos.last = m.last;
pos.items = m.items;
return true;
}
// Update our position
// Returns `true` if we changed our position
template <class UNL>
bool
update (UNL const& unl,
clock_type::time_point const& now)
{
if (consensus_)
return false;
// count votes per item from unl
boost::container::flat_map<
ItemKey, std::size_t> votes;
for(auto const& pos : pos_)
{
if (! unl.count(pos.first))
continue;
for(auto const& item : pos.second.items)
{
auto const result =
votes.emplace(item, 1);
if (! result.second)
++result.first->second;
}
}
// calculate our new position
ItemSet items;
{
auto const needed =
(thresh_ * unl.size() + 50) / 100;
for(auto const& v : votes)
if (v.second >= needed)
items.insert(v.first);
thresh_ += 5;
}
// see if we reached a consensus
std::size_t most = 0;
std::size_t agree = 0;
for(auto const& pos : pos_)
{
if (! unl.count(pos.first))
continue;
if (pos.first == id_ ||
pos.second.items == items)
++agree;
else if (! pos.second.last)
++most;
}
{
auto const needed =
(80 * unl.size() + 50) / 100;
if (agree >= needed)
{
consensus_ = true;
}
else if (agree + most < needed)
{
failed_ = true;
consensus_ = true;
}
}
auto const iter = pos_.find(id_);
if (! consensus_ &&
iter->second.items == items)
return false;
iter->second.items = items;
return true;
}
};
//--------------------------------------------------------------------------
struct Peer
{
//beast::aged_unordered_map<
NodeKey id_;
std::size_t ord_ = 0;
std::set<NodeKey> unl_;
Config const& config_;
boost::optional<Round> round_;
millis delay_;
Network& net_;
Pool pool_;
std::unordered_map<ItemKey,
boost::container::flat_set<Peer*>> item_tab_;
Peer (int id, Config const& config,
Network& net)
: id_ (id)
, config_ (config)
, delay_ (millis(
net.rand(5, 50)))
, net_ (net)
{
unl_.insert(id_); // self
while(unl_.size() <= config_.unl)
unl_.insert(net_.rand(config_.peers));
}
void
init()
{
net_.timer(millis(2000),
[&]() { on_close(); });
}
// Broadcast a new item
void
inject (ItemKey id)
{
item_tab_[id].insert(this);
TxMsg m;
m.id = id;
broadcast(m);
}
// Closes the pool and starts the round
void
on_close()
{
round_.emplace(id_, ++ord_, pool_.items(),
net_.now(), net_.log);
broadcast(round_->posMsg());
net_.timer(millis(
nUpdateMS + net_.rand(nUpdateMS)),
[=]() { on_update(); });
}
// Updates our position during the round
void
on_update()
{
if (round_->update(unl_, net_.now()))
broadcast(round_->posMsg());
if (round_->consensus_)
return;
using namespace std::chrono;
net_.timer(millis(nUpdateMS),
[=]() { on_update(); });
}
// Called when a transaction is received
void
receive (Peer& from, TxMsg const& m)
{
auto& seen = item_tab_[m.id];
if(! seen.empty())
{
++net_.dup;
return;
}
seen.insert(&from);
net_.timer(net_.now() +
millis(net_.rand(200, 600)),
[&, m]()
{
pool_.insert(m.id);
for(auto& link : net_.links(*this))
if (seen.count(&link.to) == 0)
link.to.send(*this, m);
});
}
// Called when a position is received
void
receive (Peer& from,
std::shared_ptr<PosMsg const> const& m)
{
if (round_->receive(*m))
relay(from, m);
else
++net_.dup;
}
//----------------------------------------------------------------------
// Send a message to this peer
template <class Message>
void
send (Peer& from, Message const& m)
{
++net_.sent;
net_.send (from, *this,
[&, m]() { receive(from, m); });
}
// Send a message to this peer
template <class Message>
void
send (Peer& from,
std::shared_ptr<Message const> const& m)
{
++net_.sent;
net_.send (from, *this,
[&, m]() { receive(from, m); });
}
// Broadcast a message to all links
template <class Message>
void
broadcast (std::shared_ptr<
Message const> const& m)
{
for(auto& link : net_.links(*this))
link.to.send(*this, m);
}
// Broadcast a message to all links
template <class Message>
void
broadcast (Message const& m)
{
for(auto& link : net_.links(*this))
link.to.send(*this, m);
}
// Relay a message to all links
template <class Message>
void
relay (Peer& from,
std::shared_ptr<Message const> const& m)
{
for(auto& link : net_.links(*this))
if (&link.to != &from)
link.to.send(*this, m);
}
// Relay a message to all links
template <class Message>
void
relay (Peer& from, Message const& m)
{
for(auto& link : net_.links(*this))
if (&link.to != &from)
link.to.send(*this, m);
}
};
//--------------------------------------------------------------------------
// The result of one round
struct Result
{
std::size_t elapsed;
std::size_t failure = 0;
std::size_t consensus = 0;
std::set<ItemSet> sets;
};
// The results of several rounds
struct Results
{
std::size_t rounds = 0;
std::size_t perfect = 0;
std::vector<std::size_t> elapsed;
std::vector<std::size_t> failure;
std::vector<std::size_t> consensus;
void
aggregate (Result const& result)
{
++rounds;
perfect += result.sets.size() == 1;
elapsed.push_back(result.elapsed);
failure.push_back(result.failure);
consensus.push_back(result.consensus);
}
};
struct Report
{
std::size_t perfect;
std::size_t elapsed_min;
std::size_t elapsed_max;
Report (Results& results, Config const& config)
{
perfect = results.perfect;
#if 0
std::sort(
results.elapsed.begin(), results.elapsed.end());
std::sort(
results.consensus.begin(), results.consensus.end(),
std::greater<std::size_t>{});
#endif
elapsed_min = results.elapsed.front();
elapsed_max = results.elapsed.back();
}
};
class Network : public BasicNetwork<Peer>
{
private:
Config const& config_;
ItemKey seq_ = 0;
public:
std::size_t dup = 0; // total dup
std::size_t sent = 0; // total sent
std::vector<Peer> pv;
Log& log;
Network (std::size_t seed,
Config const& config, Log& log_)
: config_ (config)
, log (log_)
{
this->rng().seed(seed);
using namespace std;
using namespace std::chrono;
pv.reserve(config.peers);
for(std::size_t id = 0; id < config_.peers; ++id)
pv.emplace_back(id, config, *this);
for(auto& peer : pv)
for(int i = 0; i < nDegree; ++i)
connect_one(peer);
}
// Add one random connection
void
connect_one(Peer& from)
{
using namespace std::chrono;
auto const delay = from.delay_ +
milliseconds(this->rand(5, 200));
for(;;)
if (this->connect(from,
pv[this->rand(pv.size())], delay))
break;
}
void
report (std::size_t n,
millis ms, Log& log)
{
std::size_t failed = 0;
std::size_t consensus = 0;
std::set<ItemSet> unique;
for(auto const& p : pv)
{
if (! p.round_)
continue;
unique.insert(p.round_->items());
if (p.round_->consensus_)
++consensus;
if (p.round_->failed_)
++failed;
}
log <<
n << "\t" <<
unique.size() << "\t" <<
consensus << "\t" <<
failed << "\t" <<
ms.count() << "ms\t" <<
sent << "\t" <<
dup;
}
// Inject a random item
void
inject()
{
pv[this->rand(pv.size())].inject(++seq_);
}
void
on_timer()
{
inject();
if(this->now().time_since_epoch() <=
std::chrono::seconds(4))
this->timer(millis(250),
[&]() { on_timer(); });
}
// Execute a round of consensus
void
run (std::size_t n)
{
using namespace std::chrono;
for(int i = 0; i < config_.peers; ++i)
pv[i].init();
inject();
this->timer(millis(250),
[&]() { on_timer(); });
auto const t0 = this->now();
#if 0
do
{
report(n, duration_cast<
milliseconds>(now() - t0), log);
}
while (this->step_for(milliseconds(50)));
#else
this->step();
#endif
report(n, duration_cast<
milliseconds>(this->now() - t0), log);
}
};
static
void
run (Log& log)
{
log << "Sim4" << ":";
log <<
"n\t" <<
"unique\t" <<
"consensus\t" <<
"failed\t" <<
"time\t" <<
"sent\t" <<
"dup";
Config config;
for(auto i = 1; i <= config.trials; ++i)
{
Network net(i, config, log);
for(auto j = 1; j <= config.rounds; ++j)
net.run(i);
}
}
};
} // test
} // ripple
#endif
/*
Try limiting threshold to 80
Try slower increase of threshold
Increase UNL sizes
*/

View File

@@ -1,382 +0,0 @@
//------------------------------------------------------------------------------
/*
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 <ripple/unl/tests/metrics.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
class Network;
class Peer;
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 Links>
Slot*
get (int id, Peer& from,
Links const& links)
{
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& link : links)
if (&link.to != &from)
slot.down.insert(&link.to);
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)
{
}
};
//--------------------------------------------------------------------------
class Peer
{
private:
Network& net_;
public:
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_) { }
};
int id = 0; // validator id or 0
int seq = 0;
Policy policy;
std::map<int, int> seen;
std::chrono::milliseconds delay;
Peer (int id_, Network& net)
: net_ (net)
, id (id_)
, delay (std::chrono::milliseconds(
net.rand(5, 50)))
{
}
// Called when a peer disconnects
void
disconnect (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& link : net_.links(*this))
link.to.send(*this,
UnsquelchMsg{ id });
}
// Broadcast a validation
void
broadcast()
{
broadcast(ValMsg{ id, ++seq });
}
// Receive a validation
void
receive (Peer& from, ValMsg const& m)
{
if (m.id == id)
{
++nth(net_.dup, m.id - 1);
return from.send(*this,
SquelchMsg(m.id));
}
auto slot = policy.get(
m.id, from, net_.links(*this));
if (! slot || ! policy.uplink(
m.id, from, *slot))
return from.send(*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(*this, m);
}
// Receive a squelch message
void
receive (Peer& from, SquelchMsg const& m)
{
policy.squelch (m.id, from);
}
// Receive an unsquelch message
void
receive (Peer& from, UnsquelchMsg const& m)
{
policy.unsquelch (m.id, from);
}
//----------------------------------------------------------------------
// Send a message to this peer
template <class Message>
void
send (Peer& from, Message&& m)
{
++net_.sent;
using namespace std::chrono;
net_.send (from, *this,
[&, m]() { receive(from, m); });
}
// Broadcast a message to all links
template <class Message>
void
broadcast (Message const& m)
{
for(auto& link : net_.links(*this))
link.to.send(*this, m);
}
};
//--------------------------------------------------------------------------
class Network : public BasicNetwork<Peer>
{
public:
std::size_t sent = 0;
std::vector<Peer> pv;
std::vector<std::size_t> heard;
std::vector<std::size_t> dup;
Network()
{
using namespace std;
using namespace std::chrono;
pv.reserve(nPeer);
for(std::size_t id = 1; id <=nPeer; ++id)
pv.emplace_back(
id <= nValidator ? id : 0, *this);
for (auto& peer : pv)
for (auto i = 0; i < nDegree; ++i)
connect_one(peer);
}
// Add one random connection
void
connect_one(Peer& from)
{
using namespace std::chrono;
auto const delay = from.delay +
milliseconds(rand(5, 200));
for(;;)
if (connect(from,
pv[rand(pv.size())], delay))
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(peer);
peer.disconnect(link.to);
// 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
run (Log& log)
{
for (int i = nStep; i--;)
{
churn();
for(int j = 0; j < nValidator; ++j)
pv[j].broadcast();
step();
}
}
};
};
//------------------------------------------------------------------------------
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.run(log);
std::size_t reach = 0;
std::vector<int> dist;
std::vector<int> degree;
net.bfs(net.pv[0],
[&](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);
}
}

View File

@@ -1,100 +0,0 @@
//------------------------------------------------------------------------------
/*
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_METRICS_H_INCLUDED
#define RIPPLE_SIM_METRICS_H_INCLUDED
#include <iomanip>
#include <sstream>
#include <string>
namespace ripple {
namespace test {
template <class FwdRange>
std::string
seq_string (FwdRange const& r, int width = 0)
{
std::stringstream ss;
auto iter = std::begin(r);
if (iter == std::end(r))
return ss.str();
ss << std::setw(width) << *iter++;
while(iter != std::end(r))
ss << ", " <<
std::setw(width) << *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];
}
template <class Hist, class FwdRange>
void
hist_accum (Hist& h, FwdRange const& r)
{
for(auto const& v : r)
++nth(h, v);
}
//------------------------------------------------------------------------------
template <class = void>
inline
std::string
pad (std::string s, std::size_t n)
{
if (s.size() < n)
s.insert(0, n - s.size(), ' ');
return s;
}
} // test
} // ripple
#endif