mirror of
https://github.com/XRPLF/rippled.git
synced 2025-12-06 17:27:55 +00:00
Refactor consensus for simulation (RIPD-1011):
This is a substantial refactor of the consensus code and also introduces a basic consensus simulation and testing framework. The new generic/templated version is in src/ripple/consensus and documents the current type requirements. The version adapted for the RCL is in src/ripple/app/consensus. The testing framework is in src/test/csf. Minor behavioral changes/fixes include: * Adjust close time offset even when not validating. * Remove spurious proposing_ = false call at end of handleLCL. * Remove unused functionality provided by checkLastValidation. * Separate open and converge time * Don't send a bow out if we're not proposing * Prevent consensus stopping if NetworkOPs switches to disconnect mode while consensus accepts a ledger * Prevent a corner case in which Consensus::gotTxSet or Consensus::peerProposal has the potential to update internal state while an dispatched accept job is running. * Distinguish external and internal calls to startNewRound. Only external calls can reset the proposing_ state of consensus
This commit is contained in:
917
src/test/csf/BasicNetwork.h
Normal file
917
src/test/csf/BasicNetwork.h
Normal file
@@ -0,0 +1,917 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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_TEST_CSF_BASICNETWORK_H_INCLUDED
|
||||
#define RIPPLE_TEST_CSF_BASICNETWORK_H_INCLUDED
|
||||
|
||||
#include <ripple/basics/qalloc.h>
|
||||
#include <ripple/beast/clock/manual_clock.h>
|
||||
#include <ripple/beast/hash/hash_append.h>
|
||||
#include <ripple/beast/hash/uhash.h>
|
||||
#include <boost/container/flat_map.hpp>
|
||||
#include <boost/intrusive/list.hpp>
|
||||
#include <boost/intrusive/set.hpp>
|
||||
#include <boost/range/adaptor/transformed.hpp>
|
||||
#include <boost/range/iterator_range.hpp>
|
||||
#include <boost/tuple/tuple.hpp>
|
||||
#include <deque>
|
||||
#include <memory>
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <iomanip>
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
namespace csf {
|
||||
/** Peer to peer network simulator.
|
||||
|
||||
The network is formed from a set of Peer objects representing
|
||||
vertices and configurable connections representing edges.
|
||||
The caller is responsible for creating the Peer objects ahead
|
||||
of time.
|
||||
|
||||
Peer objects cannot be destroyed once the BasicNetwork is
|
||||
constructed. To handle peers going online and offline,
|
||||
callers can simply disconnect all links and reconnect them
|
||||
later. Connections are directed, one end is the inbound
|
||||
Peer and the other is the outbound Peer.
|
||||
|
||||
Peers may send messages along their connections. To simulate
|
||||
the effects of latency, these messages can be delayed by a
|
||||
configurable duration set when the link is established.
|
||||
Messages always arrive in the order they were sent on a
|
||||
particular connection.
|
||||
|
||||
A message is modeled using a lambda function. The caller
|
||||
provides the code to execute upon delivery of the message.
|
||||
If a Peer is disconnected, all messages pending delivery
|
||||
at either end of the associated connection are discarded.
|
||||
|
||||
A timer may be set for a Peer. When the timer expires,
|
||||
a caller provided lambda is invoked. Timers may be canceled
|
||||
using a token returned when the timer is created.
|
||||
|
||||
After creating the Peer set, constructing the network,
|
||||
and establishing connections, the caller uses one or more
|
||||
of the step, step_one, step_for, step_until and step_while
|
||||
functions to iterate the network,
|
||||
|
||||
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
|
||||
---------- ---- ------------
|
||||
P Peer
|
||||
u, v Values of type P
|
||||
P u(v) CopyConstructible
|
||||
u.~P() Destructible
|
||||
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
|
||||
{
|
||||
public:
|
||||
using peer_type = Peer;
|
||||
|
||||
using clock_type =
|
||||
beast::manual_clock<
|
||||
std::chrono::steady_clock>;
|
||||
|
||||
using duration =
|
||||
typename clock_type::duration;
|
||||
|
||||
using time_point =
|
||||
typename clock_type::time_point;
|
||||
|
||||
private:
|
||||
struct by_to_tag {};
|
||||
struct by_from_tag {};
|
||||
struct by_when_tag {};
|
||||
|
||||
using by_to_hook =
|
||||
boost::intrusive::list_base_hook<
|
||||
boost::intrusive::link_mode<
|
||||
boost::intrusive::normal_link>,
|
||||
boost::intrusive::tag<by_to_tag>>;
|
||||
|
||||
using by_from_hook =
|
||||
boost::intrusive::list_base_hook<
|
||||
boost::intrusive::link_mode<
|
||||
boost::intrusive::normal_link>,
|
||||
boost::intrusive::tag<by_from_tag>>;
|
||||
|
||||
using by_when_hook =
|
||||
boost::intrusive::set_base_hook<
|
||||
boost::intrusive::link_mode<
|
||||
boost::intrusive::normal_link>>;
|
||||
|
||||
struct msg
|
||||
: by_to_hook, by_from_hook, by_when_hook
|
||||
{
|
||||
Peer to;
|
||||
Peer from;
|
||||
time_point when;
|
||||
|
||||
msg (msg const&) = delete;
|
||||
msg& operator= (msg const&) = delete;
|
||||
virtual ~msg() = default;
|
||||
virtual void operator()() const = 0;
|
||||
|
||||
msg (Peer const& from_, Peer const& to_,
|
||||
time_point when_)
|
||||
: to(to_), from(from_), when(when_)
|
||||
{
|
||||
}
|
||||
|
||||
bool
|
||||
operator< (msg const& other) const
|
||||
{
|
||||
return when < other.when;
|
||||
}
|
||||
};
|
||||
|
||||
template <class Handler>
|
||||
class msg_impl : public msg
|
||||
{
|
||||
private:
|
||||
Handler const h_;
|
||||
|
||||
public:
|
||||
msg_impl (msg_impl const&) = delete;
|
||||
msg_impl& operator= (msg_impl const&) = delete;
|
||||
|
||||
msg_impl (Peer const& from_, Peer const& to_,
|
||||
time_point when_, Handler&& h)
|
||||
: msg (from_, to_, when_)
|
||||
, h_ (std::move(h))
|
||||
{
|
||||
}
|
||||
|
||||
msg_impl (Peer const& from_, Peer const& to_,
|
||||
time_point when_, Handler const& h)
|
||||
: msg (from_, to_, when_)
|
||||
, h_ (h)
|
||||
{
|
||||
}
|
||||
|
||||
void operator()() const override
|
||||
{
|
||||
h_();
|
||||
}
|
||||
};
|
||||
|
||||
class queue_type
|
||||
{
|
||||
private:
|
||||
using by_to_list = typename
|
||||
boost::intrusive::make_list<msg,
|
||||
boost::intrusive::base_hook<by_to_hook>,
|
||||
boost::intrusive::constant_time_size<false>>::type;
|
||||
|
||||
using by_from_list = typename
|
||||
boost::intrusive::make_list<msg,
|
||||
boost::intrusive::base_hook<by_from_hook>,
|
||||
boost::intrusive::constant_time_size<false>>::type;
|
||||
|
||||
using by_when_set = typename
|
||||
boost::intrusive::make_multiset<msg,
|
||||
boost::intrusive::constant_time_size<false>>::type;
|
||||
|
||||
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 =
|
||||
typename by_when_set::iterator;
|
||||
|
||||
queue_type (queue_type const&) = delete;
|
||||
queue_type& operator= (queue_type const&) = delete;
|
||||
|
||||
explicit
|
||||
queue_type (qalloc const& alloc);
|
||||
|
||||
~queue_type();
|
||||
|
||||
bool
|
||||
empty() const;
|
||||
|
||||
iterator
|
||||
begin();
|
||||
|
||||
iterator
|
||||
end();
|
||||
|
||||
template <class Handler>
|
||||
typename by_when_set::iterator
|
||||
emplace (Peer const& from, Peer const& to,
|
||||
time_point when, Handler&& h);
|
||||
|
||||
void
|
||||
erase (iterator iter);
|
||||
|
||||
void
|
||||
remove (Peer const& from, Peer const& to);
|
||||
};
|
||||
|
||||
struct link_type
|
||||
{
|
||||
bool inbound;
|
||||
duration delay;
|
||||
|
||||
link_type (bool inbound_, duration delay_)
|
||||
: inbound (inbound_)
|
||||
, delay (delay_)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
using links_type =
|
||||
boost::container::flat_map<Peer, link_type>;
|
||||
|
||||
class link_transform;
|
||||
|
||||
qalloc alloc_;
|
||||
queue_type queue_;
|
||||
// VFALCO This is an ugly wart, aged containers
|
||||
// want a non-const reference to a clock.
|
||||
clock_type mutable clock_;
|
||||
std::unordered_map<Peer, links_type> links_;
|
||||
|
||||
public:
|
||||
BasicNetwork (BasicNetwork const&) = delete;
|
||||
BasicNetwork& operator= (BasicNetwork const&) = delete;
|
||||
|
||||
BasicNetwork();
|
||||
|
||||
/** Return the allocator. */
|
||||
qalloc const&
|
||||
alloc() const;
|
||||
|
||||
/** Return the clock. */
|
||||
clock_type&
|
||||
clock() const;
|
||||
|
||||
/** Return the current network time.
|
||||
|
||||
@note The epoch is unspecified
|
||||
*/
|
||||
time_point
|
||||
now() const;
|
||||
|
||||
/** 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 const& from, Peer const& 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 const& peer1, Peer const& peer2);
|
||||
|
||||
/** Return the range of active links.
|
||||
|
||||
@return A random access range.
|
||||
*/
|
||||
boost::transformed_range<
|
||||
link_transform, links_type>
|
||||
links (Peer const& 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,
|
||||
the function will be invoked with
|
||||
no arguments.
|
||||
|
||||
@note Its the caller's responsibility to
|
||||
ensure that the body of the function performs
|
||||
activity consistent with `from`'s receipt of
|
||||
a message from `to`.
|
||||
*/
|
||||
template <class Function>
|
||||
void
|
||||
send (Peer const& from, Peer const& to,
|
||||
Function&& f);
|
||||
|
||||
// Used to cancel timers
|
||||
struct cancel_token;
|
||||
|
||||
/** Deliver a timer notification.
|
||||
|
||||
Effects:
|
||||
|
||||
When the network time is reached,
|
||||
the function will be called with
|
||||
no arguments.
|
||||
*/
|
||||
template <class Function>
|
||||
cancel_token
|
||||
timer (time_point const& when,
|
||||
Function&& f);
|
||||
|
||||
/** Deliver a timer notification.
|
||||
|
||||
Effects:
|
||||
|
||||
When the specified time has elapsed,
|
||||
the function will be called with
|
||||
no arguments.
|
||||
*/
|
||||
template <class Function>
|
||||
cancel_token
|
||||
timer (duration const& delay,
|
||||
Function&& f);
|
||||
|
||||
/** Cancel a timer.
|
||||
|
||||
Preconditions:
|
||||
|
||||
`token` was the return value of a call
|
||||
timer() which has not yet been invoked.
|
||||
*/
|
||||
void
|
||||
cancel (cancel_token const& token);
|
||||
|
||||
/** Perform breadth-first search.
|
||||
|
||||
Function will be called with this signature:
|
||||
|
||||
void(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 const& 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
|
||||
step_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
|
||||
step();
|
||||
|
||||
/** Run the network while a condition is true.
|
||||
|
||||
Function takes no arguments and will be called
|
||||
repeatedly after each message is processed to
|
||||
decide whether to continue.
|
||||
|
||||
Effects:
|
||||
|
||||
The clock is advanced to the time
|
||||
of the last delivered message.
|
||||
|
||||
@return `true` if any message was processed.
|
||||
*/
|
||||
template <class Function>
|
||||
bool
|
||||
step_while(Function && func);
|
||||
|
||||
/** Run the network until the specified time.
|
||||
|
||||
Effects:
|
||||
|
||||
The clock is advanced to the
|
||||
specified time.
|
||||
|
||||
@return `true` if any messages remain.
|
||||
*/
|
||||
bool
|
||||
step_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 messages remain.
|
||||
*/
|
||||
template <class Period, class Rep>
|
||||
bool
|
||||
step_for (std::chrono::duration<
|
||||
Period, Rep> const& amount);
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
template <class Peer>
|
||||
BasicNetwork<Peer>::queue_type::queue_type(
|
||||
qalloc const& alloc)
|
||||
: alloc_ (alloc)
|
||||
{
|
||||
}
|
||||
|
||||
template <class Peer>
|
||||
BasicNetwork<Peer>::queue_type::~queue_type()
|
||||
{
|
||||
for(auto iter = by_when_.begin();
|
||||
iter != by_when_.end();)
|
||||
{
|
||||
auto m = &*iter;
|
||||
++iter;
|
||||
m->~msg();
|
||||
alloc_.dealloc(m, 1);
|
||||
}
|
||||
}
|
||||
|
||||
template <class Peer>
|
||||
inline
|
||||
bool
|
||||
BasicNetwork<Peer>::queue_type::empty() const
|
||||
{
|
||||
return by_when_.empty();
|
||||
}
|
||||
|
||||
template <class Peer>
|
||||
inline
|
||||
auto
|
||||
BasicNetwork<Peer>::queue_type::begin() ->
|
||||
iterator
|
||||
{
|
||||
return by_when_.begin();
|
||||
}
|
||||
|
||||
template <class Peer>
|
||||
inline
|
||||
auto
|
||||
BasicNetwork<Peer>::queue_type::end() ->
|
||||
iterator
|
||||
{
|
||||
return by_when_.end();
|
||||
}
|
||||
|
||||
template <class Peer>
|
||||
template <class Handler>
|
||||
auto
|
||||
BasicNetwork<Peer>::queue_type::emplace(
|
||||
Peer const& from, Peer const& to, time_point when,
|
||||
Handler&& h) ->
|
||||
typename by_when_set::iterator
|
||||
{
|
||||
using msg_type = msg_impl<
|
||||
std::decay_t<Handler>>;
|
||||
auto const p = alloc_.alloc<msg_type>(1);
|
||||
auto& m = *new(p) msg_type(from, to,
|
||||
when, std::forward<Handler>(h));
|
||||
if (to)
|
||||
by_to_[to].push_back(m);
|
||||
if (from)
|
||||
by_from_[from].push_back(m);
|
||||
return by_when_.insert(m);
|
||||
}
|
||||
|
||||
template <class Peer>
|
||||
void
|
||||
BasicNetwork<Peer>::queue_type::erase(
|
||||
iterator iter)
|
||||
{
|
||||
auto& m = *iter;
|
||||
if (iter->to)
|
||||
{
|
||||
auto& list = by_to_[iter->to];
|
||||
list.erase(list.iterator_to(m));
|
||||
}
|
||||
if (iter->from)
|
||||
{
|
||||
auto& list = by_from_[iter->from];
|
||||
list.erase(list.iterator_to(m));
|
||||
}
|
||||
by_when_.erase(iter);
|
||||
m.~msg();
|
||||
alloc_.dealloc(&m, 1);
|
||||
}
|
||||
|
||||
template <class Peer>
|
||||
void
|
||||
BasicNetwork<Peer>::queue_type::remove(
|
||||
Peer const& from, Peer const& to)
|
||||
{
|
||||
{
|
||||
auto& list = by_to_[to];
|
||||
for(auto iter = list.begin();
|
||||
iter != list.end();)
|
||||
{
|
||||
auto& m = *iter++;
|
||||
if (m.from == from)
|
||||
erase(by_when_.iterator_to(m));
|
||||
}
|
||||
}
|
||||
{
|
||||
auto& list = by_to_[from];
|
||||
for(auto iter = list.begin();
|
||||
iter != list.end();)
|
||||
{
|
||||
auto& m = *iter++;
|
||||
if (m.from == to)
|
||||
erase(by_when_.iterator_to(m));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
template <class Peer>
|
||||
class BasicNetwork<Peer>::link_transform
|
||||
{
|
||||
private:
|
||||
BasicNetwork& net_;
|
||||
Peer from_;
|
||||
|
||||
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 const& from, Peer const& to_,
|
||||
bool inbound_)
|
||||
: to(to_)
|
||||
, inbound(inbound_)
|
||||
, net_(net)
|
||||
, from_(from)
|
||||
{
|
||||
}
|
||||
|
||||
/** Disconnect this link.
|
||||
|
||||
Effects:
|
||||
|
||||
The connection is removed at both ends.
|
||||
*/
|
||||
bool
|
||||
disconnect() const
|
||||
{
|
||||
return net_.disconnect(from_, to);
|
||||
}
|
||||
|
||||
private:
|
||||
BasicNetwork& net_;
|
||||
Peer from_;
|
||||
};
|
||||
|
||||
link_transform (BasicNetwork& net,
|
||||
Peer const& from)
|
||||
: net_(net)
|
||||
, from_(from)
|
||||
{
|
||||
}
|
||||
|
||||
result_type const
|
||||
operator()(argument_type const& v) const
|
||||
{
|
||||
return result_type(net_, from_,
|
||||
v.first, v.second.inbound);
|
||||
}
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
template <class Peer>
|
||||
struct BasicNetwork<Peer>::cancel_token
|
||||
{
|
||||
private:
|
||||
typename queue_type::iterator iter_;
|
||||
|
||||
public:
|
||||
cancel_token() = delete;
|
||||
cancel_token (cancel_token const&) = default;
|
||||
cancel_token& operator= (cancel_token const&) = default;
|
||||
|
||||
private:
|
||||
friend class BasicNetwork;
|
||||
cancel_token(typename
|
||||
queue_type::iterator iter)
|
||||
: iter_ (iter)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
template <class Peer>
|
||||
BasicNetwork<Peer>::BasicNetwork()
|
||||
: queue_ (alloc_)
|
||||
{
|
||||
}
|
||||
|
||||
template <class Peer>
|
||||
inline
|
||||
qalloc const&
|
||||
BasicNetwork<Peer>::alloc() const
|
||||
{
|
||||
return alloc_;
|
||||
}
|
||||
|
||||
template <class Peer>
|
||||
inline
|
||||
auto
|
||||
BasicNetwork<Peer>::clock() const ->
|
||||
clock_type&
|
||||
{
|
||||
return clock_;
|
||||
}
|
||||
|
||||
template <class Peer>
|
||||
inline
|
||||
auto
|
||||
BasicNetwork<Peer>::now() const ->
|
||||
time_point
|
||||
{
|
||||
return clock_.now();
|
||||
}
|
||||
|
||||
template <class Peer>
|
||||
bool
|
||||
BasicNetwork<Peer>::connect(
|
||||
Peer const& from, Peer const& 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>
|
||||
bool
|
||||
BasicNetwork<Peer>::disconnect(
|
||||
Peer const& peer1, Peer const& peer2)
|
||||
{
|
||||
if (links_[peer1].erase(peer2) == 0)
|
||||
return false;
|
||||
auto const n =
|
||||
links_[peer2].erase(peer1);
|
||||
(void)n;
|
||||
assert(n);
|
||||
queue_.remove(peer1, peer2);
|
||||
return true;
|
||||
}
|
||||
|
||||
template <class Peer>
|
||||
inline
|
||||
auto
|
||||
BasicNetwork<Peer>::links(Peer const& from) ->
|
||||
boost::transformed_range<
|
||||
link_transform, links_type>
|
||||
{
|
||||
return boost::adaptors::transform(
|
||||
links_[from],
|
||||
link_transform{ *this, from });
|
||||
}
|
||||
|
||||
template <class Peer>
|
||||
template <class Function>
|
||||
inline
|
||||
void
|
||||
BasicNetwork<Peer>::send(
|
||||
Peer const& from, Peer const& to,
|
||||
Function&& f)
|
||||
{
|
||||
using namespace std;
|
||||
auto const iter =
|
||||
links_[from].find(to);
|
||||
queue_.emplace(from, to,
|
||||
clock_.now() + iter->second.delay,
|
||||
forward<Function>(f));
|
||||
}
|
||||
|
||||
template <class Peer>
|
||||
template <class Function>
|
||||
inline
|
||||
auto
|
||||
BasicNetwork<Peer>::timer(
|
||||
time_point const& when, Function&& f) ->
|
||||
cancel_token
|
||||
{
|
||||
using namespace std;
|
||||
return queue_.emplace(
|
||||
nullptr, nullptr, when,
|
||||
forward<Function>(f));
|
||||
}
|
||||
|
||||
template <class Peer>
|
||||
template <class Function>
|
||||
inline
|
||||
auto
|
||||
BasicNetwork<Peer>::timer(
|
||||
duration const& delay, Function&& f) ->
|
||||
cancel_token
|
||||
{
|
||||
return timer(clock_.now() + delay,
|
||||
std::forward<Function>(f));
|
||||
}
|
||||
|
||||
template <class Peer>
|
||||
inline
|
||||
void
|
||||
BasicNetwork<Peer>::cancel(
|
||||
cancel_token const& token)
|
||||
{
|
||||
queue_.erase(token.iter_);
|
||||
}
|
||||
|
||||
template <class Peer>
|
||||
bool
|
||||
BasicNetwork<Peer>::step_one()
|
||||
{
|
||||
if (queue_.empty())
|
||||
return false;
|
||||
auto const iter = queue_.begin();
|
||||
clock_.set(iter->when);
|
||||
(*iter)();
|
||||
queue_.erase(iter);
|
||||
return true;
|
||||
}
|
||||
|
||||
template <class Peer>
|
||||
bool
|
||||
BasicNetwork<Peer>::step()
|
||||
{
|
||||
if (! step_one())
|
||||
return false;
|
||||
for(;;)
|
||||
if (! step_one())
|
||||
break;
|
||||
return true;
|
||||
}
|
||||
|
||||
template <class Peer>
|
||||
template <class Function>
|
||||
bool
|
||||
BasicNetwork<Peer>::step_while(Function && f)
|
||||
{
|
||||
bool ran = false;
|
||||
while (f() && step_one())
|
||||
ran = true;
|
||||
return ran;
|
||||
}
|
||||
|
||||
template <class Peer>
|
||||
bool
|
||||
BasicNetwork<Peer>::step_until(
|
||||
time_point const& until)
|
||||
{
|
||||
// VFALCO This routine needs optimizing
|
||||
if(queue_.empty())
|
||||
{
|
||||
clock_.set(until);
|
||||
return false;
|
||||
}
|
||||
auto iter = queue_.begin();
|
||||
if(iter->when > until)
|
||||
{
|
||||
clock_.set(until);
|
||||
return true;
|
||||
}
|
||||
do
|
||||
{
|
||||
step_one();
|
||||
iter = queue_.begin();
|
||||
}
|
||||
while(iter != queue_.end() &&
|
||||
iter->when <= until);
|
||||
clock_.set(until);
|
||||
return iter != queue_.end();
|
||||
}
|
||||
|
||||
template <class Peer>
|
||||
template <class Period, class Rep>
|
||||
inline
|
||||
bool
|
||||
BasicNetwork<Peer>::step_for(
|
||||
std::chrono::duration<Period, Rep> const& amount)
|
||||
{
|
||||
return step_until(now() + amount);
|
||||
}
|
||||
|
||||
template <class Peer>
|
||||
template <class Function>
|
||||
void
|
||||
BasicNetwork<Peer>::bfs(
|
||||
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);
|
||||
while(! q.empty())
|
||||
{
|
||||
auto v = q.front();
|
||||
q.pop_front();
|
||||
f(v.second, v.first);
|
||||
for(auto const& link : links_[v.first])
|
||||
{
|
||||
auto const& w = link.first;
|
||||
if (seen.count(w) == 0)
|
||||
{
|
||||
q.emplace_back(w, v.second + 1);
|
||||
seen.insert(w);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // csf
|
||||
} // test
|
||||
} // ripple
|
||||
|
||||
#endif
|
||||
133
src/test/csf/BasicNetwork_test.cpp
Normal file
133
src/test/csf/BasicNetwork_test.cpp
Normal 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 <test/csf/BasicNetwork.h>
|
||||
#include <ripple/beast/unit_test.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);
|
||||
csf::BasicNetwork<Peer*> net;
|
||||
BEAST_EXPECT(! net.connect(&pv[0], &pv[0]));
|
||||
BEAST_EXPECT(net.connect(&pv[0], &pv[1], 1s));
|
||||
BEAST_EXPECT(net.connect(&pv[1], &pv[2], 1s));
|
||||
BEAST_EXPECT(! net.connect(&pv[0], &pv[1]));
|
||||
std::size_t diameter = 0;
|
||||
net.bfs(&pv[0],
|
||||
[&](auto d, Peer*)
|
||||
{ diameter = std::max(d, diameter); });
|
||||
BEAST_EXPECT(diameter == 2);
|
||||
for(auto& peer : pv)
|
||||
peer.start(net);
|
||||
BEAST_EXPECT(net.step_for(0s));
|
||||
BEAST_EXPECT(net.step_for(1s));
|
||||
BEAST_EXPECT(net.step());
|
||||
BEAST_EXPECT(! net.step());
|
||||
BEAST_EXPECT(! net.step_for(1s));
|
||||
net.send(&pv[0], &pv[1], []{});
|
||||
net.send(&pv[1], &pv[0], []{});
|
||||
BEAST_EXPECT(net.disconnect(&pv[0], &pv[1]));
|
||||
BEAST_EXPECT(! net.disconnect(&pv[0], &pv[1]));
|
||||
for(;;)
|
||||
{
|
||||
auto const links = net.links(&pv[1]);
|
||||
if(links.empty())
|
||||
break;
|
||||
BEAST_EXPECT(links[0].disconnect());
|
||||
}
|
||||
BEAST_EXPECT(pv[0].set ==
|
||||
std::set<int>({0, 2, 4}));
|
||||
BEAST_EXPECT(pv[1].set ==
|
||||
std::set<int>({1, 3}));
|
||||
BEAST_EXPECT(pv[2].set ==
|
||||
std::set<int>({2, 4}));
|
||||
net.timer(0s, []{});
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(BasicNetwork, test, ripple);
|
||||
|
||||
} // test
|
||||
} // ripple
|
||||
|
||||
197
src/test/csf/Ledger.h
Normal file
197
src/test/csf/Ledger.h
Normal file
@@ -0,0 +1,197 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2012-2017 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_TEST_CSF_LEDGER_H_INCLUDED
|
||||
#define RIPPLE_TEST_CSF_LEDGER_H_INCLUDED
|
||||
|
||||
#include <ripple/basics/chrono.h>
|
||||
#include <test/csf/Tx.h>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
namespace csf {
|
||||
|
||||
/** A ledger is a set of observed transactions and a sequence number
|
||||
identifying the ledger.
|
||||
|
||||
Peers in the consensus process are trying to agree on a set of transactions
|
||||
to include in a ledger. For unit testing, each transaction is a
|
||||
single integer and the ledger is a set of observed integers. This means
|
||||
future ledgers have prior ledgers as subsets, e.g.
|
||||
|
||||
Ledger 0 : {}
|
||||
Ledger 1 : {1,4,5}
|
||||
Ledger 2 : {1,2,4,5,10}
|
||||
....
|
||||
|
||||
Tx - Integer
|
||||
TxSet - Set of Tx
|
||||
Ledger - Set of Tx and sequence number
|
||||
*/
|
||||
|
||||
class Ledger
|
||||
{
|
||||
|
||||
public:
|
||||
|
||||
struct ID
|
||||
{
|
||||
std::uint32_t seq = 0;
|
||||
TxSetType txs = TxSetType{};
|
||||
|
||||
bool operator==(ID const & o) const
|
||||
{
|
||||
return seq == o.seq && txs == o.txs;
|
||||
}
|
||||
|
||||
bool operator!=(ID const & o) const
|
||||
{
|
||||
return !(*this == o);
|
||||
}
|
||||
|
||||
bool operator<(ID const & o) const
|
||||
{
|
||||
return std::tie(seq, txs) < std::tie(o.seq, o.txs);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
auto const &
|
||||
id() const
|
||||
{
|
||||
return id_;
|
||||
}
|
||||
|
||||
auto
|
||||
seq() const
|
||||
{
|
||||
return id_.seq;
|
||||
}
|
||||
|
||||
auto
|
||||
closeTimeResolution() const
|
||||
{
|
||||
return closeTimeResolution_;
|
||||
}
|
||||
|
||||
auto
|
||||
closeAgree() const
|
||||
{
|
||||
return closeTimeAgree_;
|
||||
}
|
||||
|
||||
auto
|
||||
closeTime() const
|
||||
{
|
||||
return closeTime_;
|
||||
}
|
||||
|
||||
auto
|
||||
actualCloseTime() const
|
||||
{
|
||||
return actualCloseTime_;
|
||||
}
|
||||
|
||||
auto
|
||||
parentCloseTime() const
|
||||
{
|
||||
return parentCloseTime_;
|
||||
}
|
||||
|
||||
auto const &
|
||||
parentID() const
|
||||
{
|
||||
return parentID_;
|
||||
}
|
||||
|
||||
Json::Value
|
||||
getJson() const
|
||||
{
|
||||
Json::Value res(Json::objectValue);
|
||||
res["seq"] = seq();
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
//! Apply the given transactions to this ledger
|
||||
Ledger
|
||||
close(TxSetType const & txs,
|
||||
NetClock::duration closeTimeResolution,
|
||||
NetClock::time_point const & consensusCloseTime,
|
||||
bool closeTimeAgree) const
|
||||
{
|
||||
Ledger res{ *this };
|
||||
res.id_.txs.insert(txs.begin(), txs.end());
|
||||
res.id_ .seq= seq() + 1;
|
||||
res.closeTimeResolution_ = closeTimeResolution;
|
||||
res.actualCloseTime_ = consensusCloseTime;
|
||||
res.closeTime_ = effectiveCloseTime(consensusCloseTime,
|
||||
closeTimeResolution, parentCloseTime_);
|
||||
res.closeTimeAgree_ = closeTimeAgree;
|
||||
res.parentCloseTime_ = closeTime();
|
||||
res.parentID_ = id();
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
|
||||
//! Unique identifier of ledger is combination of sequence number and id
|
||||
ID id_;
|
||||
|
||||
//! Bucket resolution used to determine close time
|
||||
NetClock::duration closeTimeResolution_ = ledgerDefaultTimeResolution;
|
||||
|
||||
//! When the ledger closed
|
||||
NetClock::time_point closeTime_;
|
||||
|
||||
//! Whether consenssus agreed on the close time
|
||||
bool closeTimeAgree_ = true;
|
||||
|
||||
//! Parent ledger id
|
||||
ID parentID_;
|
||||
|
||||
//! Parent ledger close time
|
||||
NetClock::time_point parentCloseTime_;
|
||||
|
||||
//! Close time unadjusted by closeTimeResolution
|
||||
NetClock::time_point actualCloseTime_;
|
||||
|
||||
};
|
||||
|
||||
inline
|
||||
std::ostream &
|
||||
operator<<(std::ostream & o, Ledger::ID const & id)
|
||||
{
|
||||
return o << id.seq << "," << id.txs;
|
||||
}
|
||||
|
||||
inline
|
||||
std::string
|
||||
to_string(Ledger::ID const & id)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << id;
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
} // csf
|
||||
} // test
|
||||
} // ripple
|
||||
|
||||
#endif
|
||||
492
src/test/csf/Peer.h
Normal file
492
src/test/csf/Peer.h
Normal file
@@ -0,0 +1,492 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2012-2017 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_TEST_CSF_PEER_H_INCLUDED
|
||||
#define RIPPLE_TEST_CSF_PEER_H_INCLUDED
|
||||
|
||||
#include <boost/container/flat_map.hpp>
|
||||
#include <boost/container/flat_set.hpp>
|
||||
|
||||
#include <test/csf/Tx.h>
|
||||
#include <test/csf/Ledger.h>
|
||||
#include <test/csf/UNL.h>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
namespace csf {
|
||||
|
||||
|
||||
/** Store validations reached by peers */
|
||||
struct Validation
|
||||
{
|
||||
PeerID id;
|
||||
Ledger::ID ledger;
|
||||
Ledger::ID prevLedger;
|
||||
};
|
||||
|
||||
namespace bc = boost::container;
|
||||
|
||||
class Validations
|
||||
{
|
||||
//< Ledgers seen by peers, saved in order received (which should be order
|
||||
//< created)
|
||||
bc::flat_map<Ledger::ID, bc::flat_set<PeerID>> nodesFromLedger;
|
||||
bc::flat_map<Ledger::ID, bc::flat_set<PeerID>> nodesFromPrevLedger;
|
||||
bc::flat_map<Ledger::ID, bc::flat_map<Ledger::ID, std::size_t>> childLedgers;
|
||||
public:
|
||||
void
|
||||
update(Validation const & v)
|
||||
{
|
||||
nodesFromLedger[v.ledger].insert(v.id);
|
||||
if(v.ledger.seq > 0)
|
||||
{
|
||||
nodesFromPrevLedger[v.prevLedger].insert(v.id);
|
||||
childLedgers[v.prevLedger][v.ledger]++;
|
||||
}
|
||||
}
|
||||
|
||||
//< The number of peers who have validated this ledger
|
||||
std::size_t
|
||||
proposersValidated(Ledger::ID const & prevLedger) const
|
||||
{
|
||||
auto it = nodesFromLedger.find(prevLedger);
|
||||
if(it != nodesFromLedger.end())
|
||||
return it->second.size();
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** The number of peers that are past this ledger, i.e.
|
||||
they have a newer most recent ledger, but have this ledger
|
||||
as an ancestor.
|
||||
*/
|
||||
std::size_t
|
||||
proposersFinished(Ledger::ID const & prevLedger) const
|
||||
{
|
||||
auto it = nodesFromPrevLedger.find(prevLedger);
|
||||
if(it != nodesFromPrevLedger.end())
|
||||
return it->second.size();
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** Returns the ledger starting from prevLedger with the most validations.
|
||||
*/
|
||||
Ledger::ID
|
||||
getBestLCL(Ledger::ID const & currLedger,
|
||||
Ledger::ID const & prevLedger) const
|
||||
{
|
||||
auto it = childLedgers.find(prevLedger);
|
||||
if (it != childLedgers.end() &&
|
||||
! it->second.empty())
|
||||
{
|
||||
std::size_t bestCount = 0;
|
||||
Ledger::ID bestLedger;
|
||||
|
||||
for (auto const & b : it->second)
|
||||
{
|
||||
auto currCount = b.second;
|
||||
if(currLedger == b.first)
|
||||
currCount++;
|
||||
if(currCount > bestCount)
|
||||
bestLedger = b.first;
|
||||
if(currCount == bestCount && currLedger == b.first)
|
||||
bestLedger = b.first;
|
||||
}
|
||||
return bestLedger;
|
||||
}
|
||||
return currLedger;
|
||||
}
|
||||
};
|
||||
|
||||
/** Proposal is a position taken in the consensus process and is represented
|
||||
directly from the generic types.
|
||||
*/
|
||||
using Proposal = ConsensusProposal<PeerID, Ledger::ID, TxSetType>;
|
||||
|
||||
struct Traits
|
||||
{
|
||||
using Ledger_t = Ledger;
|
||||
using NodeID_t = PeerID;
|
||||
using TxSet_t = TxSet;
|
||||
using MissingTxException_t = MissingTx;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/** Represents a single node participating in the consensus process.
|
||||
It implements the Callbacks required by Consensus.
|
||||
*/
|
||||
struct Peer : public Consensus<Peer, Traits>
|
||||
{
|
||||
using Base = Consensus<Peer, Traits>;
|
||||
|
||||
//! Our unique ID
|
||||
PeerID id;
|
||||
|
||||
//! openTxs that haven't been closed in a ledger yet
|
||||
TxSetType openTxs;
|
||||
|
||||
//! last ledger this peer closed
|
||||
Ledger lastClosedLedger;
|
||||
|
||||
//! Handle to network for sending messages
|
||||
BasicNetwork<Peer*> & net;
|
||||
|
||||
//! UNL of trusted peers
|
||||
UNL unl;
|
||||
|
||||
//! Most recent ledger completed by peers
|
||||
Validations peerValidations;
|
||||
|
||||
// The ledgers, proposals, TxSets and Txs this peer has seen
|
||||
bc::flat_map<Ledger::ID, Ledger> ledgers;
|
||||
|
||||
//! Map from Ledger::ID to vector of Positions with that ledger
|
||||
//! as the prior ledger
|
||||
bc::flat_map<Ledger::ID, std::vector<Proposal>> peerPositions_;
|
||||
bc::flat_map<TxSet::ID, TxSet> txSets;
|
||||
|
||||
int completedLedgers = 0;
|
||||
int targetLedgers = std::numeric_limits<int>::max();
|
||||
|
||||
//! Skew samples from the network clock; to be refactored into a
|
||||
//! clock time once it is provided separately from the network.
|
||||
std::chrono::seconds clockSkew{0};
|
||||
|
||||
//! Delay in processing validations from remote peers
|
||||
std::chrono::milliseconds validationDelay{0};
|
||||
|
||||
//! Delay in acquiring missing ledger from the network
|
||||
std::chrono::milliseconds missingLedgerDelay{0};
|
||||
|
||||
bool validating = true;
|
||||
bool proposing = true;
|
||||
|
||||
//! All peers start from the default constructed ledger
|
||||
Peer(PeerID i, BasicNetwork<Peer*> & n, UNL const & u)
|
||||
: Consensus<Peer, Traits>( n.clock(), beast::Journal{})
|
||||
, id{i}
|
||||
, net{n}
|
||||
, unl(u)
|
||||
{
|
||||
ledgers[lastClosedLedger.id()] = lastClosedLedger;
|
||||
}
|
||||
|
||||
|
||||
// @return whether we are proposing,validating
|
||||
// TODO: Bit akward that this is in callbacks, would be nice to extract
|
||||
std::pair<bool, bool>
|
||||
getMode()
|
||||
{
|
||||
// in RCL this hits NetworkOps to decide whether we are proposing
|
||||
// validating
|
||||
return{ proposing, validating };
|
||||
}
|
||||
|
||||
Ledger const *
|
||||
acquireLedger(Ledger::ID const & ledgerHash)
|
||||
{
|
||||
auto it = ledgers.find(ledgerHash);
|
||||
if (it != ledgers.end())
|
||||
return &(it->second);
|
||||
|
||||
// TODO Get from network/oracle properly!
|
||||
|
||||
for (auto const& link : net.links(this))
|
||||
{
|
||||
auto const & p = *link.to;
|
||||
auto it = p.ledgers.find(ledgerHash);
|
||||
if (it != p.ledgers.end())
|
||||
{
|
||||
schedule(missingLedgerDelay,
|
||||
[this, ledgerHash, ledger = it->second]()
|
||||
{
|
||||
ledgers.emplace(ledgerHash, ledger);
|
||||
});
|
||||
if(missingLedgerDelay == 0ms)
|
||||
return &ledgers[ledgerHash];
|
||||
break;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto const &
|
||||
proposals(Ledger::ID const & ledgerHash)
|
||||
{
|
||||
return peerPositions_[ledgerHash];
|
||||
}
|
||||
|
||||
TxSet const *
|
||||
acquireTxSet(TxSet::ID const & setId)
|
||||
{
|
||||
auto it = txSets.find(setId);
|
||||
if(it != txSets.end())
|
||||
return &(it->second);
|
||||
// TODO Get from network/oracle instead!
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
hasOpenTransactions() const
|
||||
{
|
||||
return !openTxs.empty();
|
||||
}
|
||||
|
||||
std::size_t
|
||||
proposersValidated(Ledger::ID const & prevLedger)
|
||||
{
|
||||
return peerValidations.proposersValidated(prevLedger);
|
||||
}
|
||||
|
||||
std::size_t
|
||||
proposersFinished(Ledger::ID const & prevLedger)
|
||||
{
|
||||
return peerValidations.proposersFinished(prevLedger);
|
||||
}
|
||||
|
||||
void
|
||||
onStartRound(Ledger const &) {}
|
||||
|
||||
void
|
||||
onClose(Ledger const &, bool ) {}
|
||||
|
||||
// don't really offload
|
||||
void
|
||||
dispatchAccept(TxSet const & f)
|
||||
{
|
||||
Base::accept(f);
|
||||
}
|
||||
|
||||
void
|
||||
share(TxSet const &s)
|
||||
{
|
||||
relay(s);
|
||||
}
|
||||
|
||||
Ledger::ID
|
||||
getLCL(Ledger::ID const & currLedger,
|
||||
Ledger::ID const & priorLedger,
|
||||
bool haveCorrectLCL)
|
||||
{
|
||||
// TODO: Use generic validation code
|
||||
if(currLedger.seq > 0 && priorLedger.seq > 0)
|
||||
return peerValidations.getBestLCL(currLedger, priorLedger);
|
||||
return currLedger;
|
||||
}
|
||||
|
||||
void
|
||||
propose(Proposal const & pos)
|
||||
{
|
||||
if(proposing)
|
||||
relay(pos);
|
||||
}
|
||||
|
||||
void
|
||||
relay(DisputedTx<Tx, PeerID> const & dispute)
|
||||
{
|
||||
relay(dispute.tx());
|
||||
}
|
||||
|
||||
std::pair <TxSet, Proposal>
|
||||
makeInitialPosition(
|
||||
Ledger const & prevLedger,
|
||||
bool isProposing,
|
||||
bool isCorrectLCL,
|
||||
NetClock::time_point closeTime,
|
||||
NetClock::time_point now)
|
||||
{
|
||||
TxSet res{ openTxs };
|
||||
|
||||
return { res,
|
||||
Proposal{prevLedger.id(), Proposal::seqJoin, res.id(), closeTime, now, id} };
|
||||
}
|
||||
|
||||
// Process the accepted transaction set, generating the newly closed ledger
|
||||
// and clearing out the openTxs that were included.
|
||||
// TODO: Kinda nasty it takes so many arguments . . . sign of bad coupling
|
||||
bool
|
||||
accept(TxSet const& set,
|
||||
NetClock::time_point consensusCloseTime,
|
||||
bool proposing_,
|
||||
bool validating_,
|
||||
bool haveCorrectLCL_,
|
||||
bool consensusFail_,
|
||||
Ledger::ID const & prevLedgerHash_,
|
||||
Ledger const & previousLedger_,
|
||||
NetClock::duration closeResolution_,
|
||||
NetClock::time_point const & now,
|
||||
std::chrono::milliseconds const & roundTime_,
|
||||
hash_map<Tx::ID, DisputedTx <Tx, PeerID>> const & disputes_,
|
||||
std::map <NetClock::time_point, int> closeTimes_,
|
||||
NetClock::time_point const & closeTime)
|
||||
{
|
||||
auto newLedger = previousLedger_.close(set.txs_, closeResolution_,
|
||||
closeTime, consensusCloseTime != NetClock::time_point{});
|
||||
ledgers[newLedger.id()] = newLedger;
|
||||
|
||||
lastClosedLedger = newLedger;
|
||||
|
||||
auto it = std::remove_if(openTxs.begin(), openTxs.end(),
|
||||
[&](Tx const & tx)
|
||||
{
|
||||
return set.exists(tx.id());
|
||||
});
|
||||
openTxs.erase(it, openTxs.end());
|
||||
|
||||
if(validating)
|
||||
relay(Validation{id, newLedger.id(), newLedger.parentID()});
|
||||
return validating_;
|
||||
}
|
||||
|
||||
void
|
||||
endConsensus(bool correct)
|
||||
{
|
||||
// kick off the next round...
|
||||
// in the actual implementation, this passes back through
|
||||
// network ops
|
||||
++completedLedgers;
|
||||
// startRound sets the LCL state, so we need to call it once after
|
||||
// the last requested round completes
|
||||
// TODO: reconsider this and instead just save LCL generated here?
|
||||
if(completedLedgers <= targetLedgers)
|
||||
{
|
||||
startRound(now(), lastClosedLedger.id(),
|
||||
lastClosedLedger);
|
||||
}
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
// non-callback helpers
|
||||
void
|
||||
receive(Proposal const & p)
|
||||
{
|
||||
if(unl.find(p.nodeID()) == unl.end())
|
||||
return;
|
||||
|
||||
// TODO: Be sure this is a new proposal!!!!!
|
||||
auto & dest = peerPositions_[p.prevLedger()];
|
||||
if(std::find(dest.begin(), dest.end(), p) != dest.end())
|
||||
return;
|
||||
dest.push_back(p);
|
||||
peerProposal(now(), p);
|
||||
|
||||
}
|
||||
|
||||
void
|
||||
receive(TxSet const & txs)
|
||||
{
|
||||
// save and map complete?
|
||||
auto it = txSets.insert(std::make_pair(txs.id(), txs));
|
||||
if(it.second)
|
||||
gotTxSet(now(), txs);
|
||||
}
|
||||
|
||||
void
|
||||
receive(Tx const & tx)
|
||||
{
|
||||
if (openTxs.find(tx.id()) == openTxs.end())
|
||||
{
|
||||
openTxs.insert(tx);
|
||||
// relay to peers???
|
||||
relay(tx);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
receive(Validation const & v)
|
||||
{
|
||||
if(unl.find(v.id) != unl.end())
|
||||
{
|
||||
schedule(validationDelay,
|
||||
[&, v]()
|
||||
{
|
||||
peerValidations.update(v);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void
|
||||
relay(T const & t)
|
||||
{
|
||||
for(auto const& link : net.links(this))
|
||||
net.send(this, link.to,
|
||||
[msg = t, to = link.to]
|
||||
{
|
||||
to->receive(msg);
|
||||
});
|
||||
}
|
||||
|
||||
// Receive and relay locally submitted transaction
|
||||
void
|
||||
submit(Tx const & tx)
|
||||
{
|
||||
receive(tx);
|
||||
relay(tx);
|
||||
}
|
||||
|
||||
void
|
||||
timerEntry()
|
||||
{
|
||||
Base::timerEntry(now());
|
||||
// only reschedule if not completed
|
||||
if(completedLedgers < targetLedgers)
|
||||
net.timer(LEDGER_GRANULARITY, [&]() { timerEntry(); });
|
||||
}
|
||||
void
|
||||
start()
|
||||
{
|
||||
net.timer(LEDGER_GRANULARITY, [&]() { timerEntry(); });
|
||||
// The ID is the one we have seen the most validations for
|
||||
// In practice, we might not actually have that ledger itself yet,
|
||||
// so there is no gaurantee that bestLCL == lastClosedLedger.id()
|
||||
auto bestLCL = peerValidations.getBestLCL(lastClosedLedger.id(),
|
||||
lastClosedLedger.parentID());
|
||||
startRound(now(), bestLCL,
|
||||
lastClosedLedger);
|
||||
}
|
||||
|
||||
NetClock::time_point
|
||||
now() const
|
||||
{
|
||||
// We don't care about the actual epochs, but do want the
|
||||
// generated NetClock time to be well past its epoch to ensure
|
||||
// any subtractions of two NetClock::time_point in the consensu
|
||||
// code are positive. (e.g. PROPOSE_FRESHNESS)
|
||||
using namespace std::chrono;
|
||||
return NetClock::time_point(duration_cast<NetClock::duration>
|
||||
(net.now().time_since_epoch()+ 86400s + clockSkew));
|
||||
}
|
||||
|
||||
// Schedule the provided callback in `when` duration, but if
|
||||
// `when` is 0, call immediately
|
||||
template <class T>
|
||||
void schedule(std::chrono::nanoseconds when, T && what)
|
||||
{
|
||||
if(when == 0ns)
|
||||
what();
|
||||
else
|
||||
net.timer(when, std::forward<T>(what));
|
||||
}
|
||||
};
|
||||
|
||||
} // csf
|
||||
} // test
|
||||
} // ripple
|
||||
#endif
|
||||
104
src/test/csf/Sim.h
Normal file
104
src/test/csf/Sim.h
Normal file
@@ -0,0 +1,104 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2012-2017 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_TEST_CSF_SIM_H_INCLUDED
|
||||
#define RIPPLE_TEST_CSF_SIM_H_INCLUDED
|
||||
|
||||
#include <test/csf/UNL.h>
|
||||
#include <test/csf/BasicNetwork.h>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
namespace csf {
|
||||
|
||||
class Sim
|
||||
{
|
||||
public:
|
||||
/** Create a simulator for the given trust graph and network topology.
|
||||
|
||||
Create a simulator for consensus over the given trust graph and connect
|
||||
the network links between nodes based on the provided topology.
|
||||
|
||||
Topology is is a functor with signature
|
||||
|
||||
boost::optional<std::chrono::duration> (NodeId i, NodeId j)
|
||||
|
||||
that returns the delay sending messages from node i to node j.
|
||||
|
||||
In general, this network graph is distinct from the trust graph, but
|
||||
users can use adaptors to present a TrustGraph as a Topology by
|
||||
specifying the delay between nodes.
|
||||
|
||||
@param g The trust graph between peers.
|
||||
@param top The network topology between peers.
|
||||
|
||||
*/
|
||||
template <class Topology>
|
||||
Sim(TrustGraph const & g, Topology const & top)
|
||||
{
|
||||
peers.reserve(g.numPeers());
|
||||
for(int i = 0; i < g.numPeers(); ++i)
|
||||
peers.emplace_back(i, net, g.unl(i));
|
||||
|
||||
for(int i = 0; i < peers.size(); ++i)
|
||||
{
|
||||
for(int j = 0; j < peers.size(); ++j)
|
||||
{
|
||||
if( i != j)
|
||||
{
|
||||
auto d = top(i,j);
|
||||
if (d)
|
||||
{
|
||||
net.connect(&peers[i], &peers[j], *d);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Run consensus protocol to generate the provided number of ledgers.
|
||||
|
||||
Has each peer run consensus until it creates `ledgers` more ledgers.
|
||||
|
||||
@param ledgers The number of additional ledgers to create
|
||||
*/
|
||||
void
|
||||
run(int ledgers)
|
||||
{
|
||||
for (auto & p : peers)
|
||||
{
|
||||
if(p.completedLedgers == 0)
|
||||
p.relay(Validation{p.id, p.LCL(), p.LCL()});
|
||||
p.targetLedgers = p.completedLedgers + ledgers;
|
||||
p.start();
|
||||
}
|
||||
net.step();
|
||||
}
|
||||
|
||||
std::vector<Peer> peers;
|
||||
BasicNetwork<Peer*> net;
|
||||
|
||||
};
|
||||
|
||||
|
||||
} // csf
|
||||
} // test
|
||||
} // ripple
|
||||
|
||||
#endif
|
||||
221
src/test/csf/Tx.h
Normal file
221
src/test/csf/Tx.h
Normal file
@@ -0,0 +1,221 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2012-2017 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_TEST_CSF_TX_H_INCLUDED
|
||||
#define RIPPLE_TEST_CSF_TX_H_INCLUDED
|
||||
|
||||
#include <ripple/beast/hash/hash_append.h>
|
||||
#include <boost/container/flat_set.hpp>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
#include <map>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
namespace csf {
|
||||
|
||||
//! A single transaction
|
||||
class Tx
|
||||
{
|
||||
public:
|
||||
using ID = std::uint32_t;
|
||||
|
||||
Tx(ID i) : id_{ i } {}
|
||||
|
||||
ID
|
||||
id() const
|
||||
{
|
||||
return id_;
|
||||
}
|
||||
|
||||
bool
|
||||
operator<(Tx const & o) const
|
||||
{
|
||||
return id_ < o.id_;
|
||||
}
|
||||
|
||||
bool
|
||||
operator==(Tx const & o) const
|
||||
{
|
||||
return id_ == o.id_;
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
ID id_;
|
||||
|
||||
};
|
||||
|
||||
//!-------------------------------------------------------------------------
|
||||
//! All sets of Tx are represented as a flat_set.
|
||||
using TxSetType = boost::container::flat_set<Tx>;
|
||||
|
||||
//! TxSet is a set of transactions to consider including in the ledger
|
||||
class TxSet
|
||||
{
|
||||
public:
|
||||
using ID = TxSetType;
|
||||
using Tx = csf::Tx;
|
||||
using MutableTxSet = TxSet;
|
||||
|
||||
TxSet() = default;
|
||||
TxSet(TxSetType const & s) : txs_{ s } {}
|
||||
|
||||
bool
|
||||
insert(Tx const & t)
|
||||
{
|
||||
return txs_.insert(t).second;
|
||||
}
|
||||
|
||||
bool
|
||||
erase(Tx::ID const & txId)
|
||||
{
|
||||
return txs_.erase(Tx{ txId }) > 0;
|
||||
}
|
||||
|
||||
bool
|
||||
exists(Tx::ID const txId) const
|
||||
{
|
||||
auto it = txs_.find(Tx{ txId });
|
||||
return it != txs_.end();
|
||||
}
|
||||
|
||||
Tx const *
|
||||
find(Tx::ID const& txId) const
|
||||
{
|
||||
auto it = txs_.find(Tx{ txId });
|
||||
if (it != txs_.end())
|
||||
return &(*it);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto const &
|
||||
id() const
|
||||
{
|
||||
return txs_;
|
||||
}
|
||||
|
||||
/** @return Map of Tx::ID that are missing. True means
|
||||
it was in this set and not other. False means
|
||||
it was in the other set and not this
|
||||
*/
|
||||
std::map<Tx::ID, bool>
|
||||
compare(TxSet const& other) const
|
||||
{
|
||||
std::map<Tx::ID, bool> res;
|
||||
|
||||
auto populate_diffs = [&res](auto const & a, auto const & b, bool s)
|
||||
{
|
||||
auto populator = [&](auto const & tx)
|
||||
{
|
||||
res[tx.id()] = s;
|
||||
};
|
||||
std::set_difference(
|
||||
a.begin(), a.end(),
|
||||
b.begin(), b.end(),
|
||||
boost::make_function_output_iterator(
|
||||
std::ref(populator)
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
populate_diffs(txs_, other.txs_, true);
|
||||
populate_diffs(other.txs_, txs_, false);
|
||||
return res;
|
||||
}
|
||||
|
||||
//! The set contains the actual transactions
|
||||
TxSetType txs_;
|
||||
};
|
||||
|
||||
|
||||
/** The RCL consensus process catches missing node SHAMap error
|
||||
in several points. This exception is meant to represent a similar
|
||||
case for the unit test.
|
||||
*/
|
||||
class MissingTx : public std::runtime_error
|
||||
{
|
||||
public:
|
||||
MissingTx()
|
||||
: std::runtime_error("MissingTx")
|
||||
{}
|
||||
};
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Helper functions for debug printing
|
||||
|
||||
inline
|
||||
std::ostream&
|
||||
operator<<(std::ostream & o, const Tx & t)
|
||||
{
|
||||
return o << t.id();
|
||||
}
|
||||
|
||||
template <class T>
|
||||
inline
|
||||
std::ostream&
|
||||
operator<<(std::ostream & o, boost::container::flat_set<T> const & ts)
|
||||
{
|
||||
o << "{ ";
|
||||
bool do_comma = false;
|
||||
for (auto const & t : ts)
|
||||
{
|
||||
if (do_comma)
|
||||
o << ", ";
|
||||
else
|
||||
do_comma = true;
|
||||
o << t;
|
||||
|
||||
|
||||
}
|
||||
o << " }";
|
||||
return o;
|
||||
|
||||
}
|
||||
|
||||
inline
|
||||
std::string
|
||||
to_string(TxSetType const & txs)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << txs;
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
template <class Hasher>
|
||||
inline
|
||||
void
|
||||
hash_append(Hasher& h, Tx const & tx)
|
||||
{
|
||||
using beast::hash_append;
|
||||
hash_append(h, tx.id());
|
||||
}
|
||||
|
||||
std::ostream&
|
||||
operator<<(std::ostream & o, MissingTx const &m)
|
||||
{
|
||||
return o << m.what();
|
||||
}
|
||||
|
||||
|
||||
} // csf
|
||||
} // test
|
||||
} // ripple
|
||||
|
||||
#endif
|
||||
277
src/test/csf/UNL.h
Normal file
277
src/test/csf/UNL.h
Normal file
@@ -0,0 +1,277 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2012-2017 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_TEST_CSF_UNL_H_INCLUDED
|
||||
#define RIPPLE_TEST_CSF_UNL_H_INCLUDED
|
||||
|
||||
#include <boost/container/flat_set.hpp>
|
||||
#include <boost/optional.hpp>
|
||||
#include <vector>
|
||||
#include <random>
|
||||
#include <numeric>
|
||||
#include <chrono>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
namespace csf {
|
||||
|
||||
/** Return a randomly shuffled copy of vector based on weights w.
|
||||
|
||||
@param v The set of values
|
||||
@param w The set of weights of each value
|
||||
@param g A pseudo-random number generator
|
||||
@return A vector with entries randomly sampled without replacement
|
||||
from the original vector based on the provided weights.
|
||||
I.e. res[0] comes from sample v[i] with weight w[i]/sum_k w[k]
|
||||
*/
|
||||
template <class T, class G>
|
||||
std::vector<T>
|
||||
random_weighted_shuffle(std::vector<T> v, std::vector<double> w, G & g)
|
||||
{
|
||||
using std::swap;
|
||||
|
||||
for (int i = 0; i < v.size() - 1; ++i)
|
||||
{
|
||||
// pick a random item weighted by w
|
||||
std::discrete_distribution<> dd(w.begin() + i, w.end());
|
||||
auto idx = dd(g);
|
||||
std::swap(v[i], v[idx]);
|
||||
std::swap(w[i], w[idx]);
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
|
||||
/** Power-law distribution with PDF
|
||||
|
||||
P(x) = (x/xmin)^-a
|
||||
|
||||
for a >= 1 and xmin >= 1
|
||||
*/
|
||||
class PowerLawDistribution
|
||||
{
|
||||
double xmin_;
|
||||
double a_;
|
||||
double inv_;
|
||||
std::uniform_real_distribution<double> uf_{0,1};
|
||||
|
||||
public:
|
||||
PowerLawDistribution(double xmin, double a)
|
||||
: xmin_{xmin}, a_{a}
|
||||
{
|
||||
inv_ = 1.0/(1.0 - a_);
|
||||
}
|
||||
|
||||
template <class Generator>
|
||||
inline
|
||||
double
|
||||
operator()(Generator & g)
|
||||
{
|
||||
// use inverse transform of CDF to sample
|
||||
// CDF is P(X <= x): 1 - (x/xmin)^(1-a)
|
||||
return xmin_ * std::pow(1 - uf_(g), inv_);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
//< Unique identifier for each node in the network
|
||||
using PeerID = std::uint32_t;
|
||||
|
||||
//< A unique node list defines a set of trusted peers used in consensus
|
||||
using UNL = boost::container::flat_set<PeerID>;
|
||||
|
||||
/** Trust graph defining the consensus simulation
|
||||
|
||||
Trust is a directed relationship from a node i to node j.
|
||||
If node i trusts node j, then node i has node j in its UNL.
|
||||
|
||||
Note that each node implicitly trusts itself but that need not be
|
||||
explicitly modeled, e.g. UNLS[assignment
|
||||
*/
|
||||
class TrustGraph
|
||||
{
|
||||
//< Unique UNLs for the network
|
||||
std::vector<UNL> UNLs_;
|
||||
|
||||
std::vector<int> assignment_;
|
||||
public:
|
||||
|
||||
//< Constructor
|
||||
TrustGraph(std::vector<UNL> UNLs, std::vector<int> assignment)
|
||||
: UNLs_{UNLs}
|
||||
, assignment_{assignment}
|
||||
{}
|
||||
|
||||
//< Whether node `i` trusts node `j`
|
||||
inline
|
||||
bool
|
||||
trusts(PeerID i, PeerID j) const
|
||||
{
|
||||
return unl(i).find(j) != unl(i).end();
|
||||
}
|
||||
|
||||
//< Get the UNL for node `i`
|
||||
inline
|
||||
UNL const &
|
||||
unl(PeerID i) const
|
||||
{
|
||||
return UNLs_[assignment_[i]];
|
||||
}
|
||||
|
||||
//< Check whether this trust graph satisfies the no forking condition
|
||||
bool
|
||||
canFork(double quorum) const;
|
||||
|
||||
|
||||
auto
|
||||
numPeers() const
|
||||
{
|
||||
return assignment_.size();
|
||||
}
|
||||
|
||||
//< Save grapviz dot file reprentation of the trust graph
|
||||
void
|
||||
save_dot(std::string const & fileName);
|
||||
|
||||
/** Generate a random trust graph based on random ranking of peers
|
||||
|
||||
Generate a random trust graph by
|
||||
|
||||
1. Randomly ranking the peers acording to RankPDF
|
||||
2. Generating `numUNL` random UNLs by sampling without replacement
|
||||
from the ranked nodes.
|
||||
3. Restricting the size of the random UNLs according to SizePDF
|
||||
|
||||
@param size The number of nodes in the trust graph
|
||||
@param numUNLs The number of UNLs to create
|
||||
@param rankPDF Generates random positive real numbers to use as ranks
|
||||
@param unlSizePDF Generates random integeres between (0,size-1) to
|
||||
restrict the size of generated PDF
|
||||
@param Generator The uniform random bit generator to use
|
||||
|
||||
@note RankPDF/SizePDF can model the full RandomDistribution concept
|
||||
defined in the STL, but for the purposes of this function need
|
||||
only provide:
|
||||
|
||||
auto operator()(Generator & g)
|
||||
|
||||
which should return the random sample.
|
||||
|
||||
|
||||
*/
|
||||
template <class RankPDF, class SizePDF, class Generator>
|
||||
static
|
||||
TrustGraph
|
||||
makeRandomRanked(int size,
|
||||
int numUNLs,
|
||||
RankPDF rankPDF,
|
||||
SizePDF unlSizePDF,
|
||||
Generator & g)
|
||||
{
|
||||
|
||||
|
||||
// 1. Generate ranks
|
||||
std::vector<double> weights(size);
|
||||
std::generate(weights.begin(), weights.end(), [&]()
|
||||
{
|
||||
return rankPDF(g);
|
||||
});
|
||||
|
||||
// 2. Generate UNLs based on sampling without replacement according
|
||||
// to weights
|
||||
std::vector<UNL> unls(numUNLs);
|
||||
std::generate(unls.begin(), unls.end(), [&]()
|
||||
{
|
||||
std::vector<PeerID> ids(size);
|
||||
std::iota(ids.begin(), ids.end(), 0);
|
||||
auto res = random_weighted_shuffle(ids, weights, g);
|
||||
return UNL(res.begin(), res.begin() + unlSizePDF(g));
|
||||
});
|
||||
|
||||
// 3. Assign membership
|
||||
std::vector<int> assignment(size);
|
||||
std::uniform_int_distribution<int> u(0, numUNLs-1);
|
||||
std::generate(assignment.begin(), assignment.end(),
|
||||
[&]()
|
||||
{
|
||||
return u(g);
|
||||
});
|
||||
|
||||
return TrustGraph(unls, assignment);
|
||||
}
|
||||
|
||||
/** Generate a 2 UNL trust graph with some overlap.
|
||||
|
||||
Generates a trust graph for `size` peers formed from
|
||||
two cliques with the given overlap. Nodes in the overlap
|
||||
trust both all other nodes, while nodes outside the overlap
|
||||
only trust nodes in their clique.
|
||||
|
||||
@param size The number of nodes in the trust graph
|
||||
@param overlap The number of nodes trusting both cliques
|
||||
*/
|
||||
static
|
||||
TrustGraph
|
||||
makeClique(int size, int overlap);
|
||||
|
||||
/** Generate a complete (fully-connect) trust graph
|
||||
|
||||
Generatest a trust graph in which all peers trust all
|
||||
other peers.
|
||||
|
||||
@param size The number of nodes in the trust graph
|
||||
*/
|
||||
static
|
||||
TrustGraph
|
||||
makeComplete(int size);
|
||||
};
|
||||
|
||||
|
||||
|
||||
//< Make the TrustGraph into a topology with delays given by DelayModel
|
||||
template <class DelayModel>
|
||||
auto
|
||||
topology(TrustGraph const & tg, DelayModel const & d)
|
||||
{
|
||||
return [&](PeerID i, PeerID j)
|
||||
{
|
||||
return tg.trusts(i,j) ? boost::make_optional(d(i,j)) : boost::none;
|
||||
};
|
||||
}
|
||||
|
||||
class fixed
|
||||
{
|
||||
std::chrono::nanoseconds d_;
|
||||
|
||||
public:
|
||||
fixed(std::chrono::nanoseconds const & d) : d_{d} {}
|
||||
|
||||
inline
|
||||
std::chrono::nanoseconds
|
||||
operator()(PeerID const & i, PeerID const & j) const
|
||||
{
|
||||
return d_;
|
||||
}
|
||||
};
|
||||
|
||||
} // csf
|
||||
} // test
|
||||
} // ripple
|
||||
|
||||
#endif
|
||||
135
src/test/csf/impl/UNL.cpp
Normal file
135
src/test/csf/impl/UNL.cpp
Normal file
@@ -0,0 +1,135 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2012-2017 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 <test/csf/UNL.h>
|
||||
#include <boost/iterator/counting_iterator.hpp>
|
||||
#include <fstream>
|
||||
#include <algorithm>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
namespace csf {
|
||||
|
||||
bool
|
||||
TrustGraph::canFork(double quorum) const
|
||||
{
|
||||
// Check the forking condition by looking at intersection
|
||||
// between all pairs of UNLs.
|
||||
|
||||
// First check if some nodes uses a UNL they are not members of, since
|
||||
// this creates an implicit UNL with that ndoe.
|
||||
|
||||
auto uniqueUNLs = UNLs_;
|
||||
|
||||
for (int i = 0; i < assignment_.size(); ++i)
|
||||
{
|
||||
auto const & myUNL = UNLs_[assignment_[i]];
|
||||
if(myUNL.find(i) == myUNL.end())
|
||||
{
|
||||
auto myUNLcopy = myUNL;
|
||||
myUNLcopy.insert(i);
|
||||
uniqueUNLs.push_back(std::move(myUNLcopy));
|
||||
}
|
||||
}
|
||||
|
||||
// Loop over all pairs of uniqueUNLs
|
||||
for (int i = 0; i < uniqueUNLs.size(); ++i)
|
||||
{
|
||||
for (int j = (i+1); j < uniqueUNLs.size(); ++j)
|
||||
{
|
||||
auto const & unlA = uniqueUNLs[i];
|
||||
auto const & unlB = uniqueUNLs[j];
|
||||
|
||||
double rhs = 2.0*(1.-quorum) *
|
||||
std::max(unlA.size(), unlB.size() );
|
||||
|
||||
int intersectionSize = std::count_if(unlA.begin(), unlA.end(),
|
||||
[&](PeerID id)
|
||||
{
|
||||
return unlB.find(id) != unlB.end();
|
||||
});
|
||||
|
||||
if(intersectionSize < rhs)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
TrustGraph
|
||||
TrustGraph::makeClique(int size, int overlap)
|
||||
{
|
||||
using bci = boost::counting_iterator<PeerID>;
|
||||
|
||||
// Split network into two cliques with the given overlap
|
||||
// Clique A has nodes [0,endA) and Clique B has [startB,numPeers)
|
||||
// Note: Clique B will have an extra peer when numPeers - overlap
|
||||
// is odd
|
||||
int endA = (size + overlap)/2;
|
||||
int startB = (size - overlap)/2;
|
||||
|
||||
std::vector<UNL> unls;
|
||||
unls.emplace_back(bci(0), bci(endA));
|
||||
unls.emplace_back(bci(startB), bci(size));
|
||||
unls.emplace_back(bci(0), bci(size));
|
||||
|
||||
std::vector<int> assignment(size,0);
|
||||
|
||||
for (int i = 0; i < size; ++i)
|
||||
{
|
||||
if(i < startB)
|
||||
assignment[i] = 0;
|
||||
else if(i > endA)
|
||||
assignment[i] = 1;
|
||||
else
|
||||
assignment[i] = 2;
|
||||
}
|
||||
|
||||
|
||||
return TrustGraph(unls, assignment);
|
||||
}
|
||||
|
||||
TrustGraph
|
||||
TrustGraph::makeComplete(int size)
|
||||
{
|
||||
UNL all{ boost::counting_iterator<PeerID>( 0 ),
|
||||
boost::counting_iterator<PeerID>( size ) };
|
||||
|
||||
return TrustGraph(std::vector<UNL>(1,all),
|
||||
std::vector<int>(size, 0));
|
||||
}
|
||||
|
||||
inline void TrustGraph::save_dot(std::string const & fileName)
|
||||
{
|
||||
std::ofstream out(fileName);
|
||||
out << "digraph {\n";
|
||||
for (int i = 0; i < assignment_.size(); ++i)
|
||||
{
|
||||
for (auto & j : UNLs_[assignment_[i]])
|
||||
{
|
||||
out << i << " -> " << j << ";\n";
|
||||
}
|
||||
|
||||
}
|
||||
out << "}\n";
|
||||
|
||||
}
|
||||
|
||||
} // csf
|
||||
} // test
|
||||
} // ripple
|
||||
Reference in New Issue
Block a user