Redesign CSF framework (RIPD-1361):

- Separate `Scheduler` from `BasicNetwork`.
- Add an event/collector framework for monitoring invariants and calculating statistics.
- Allow distinct network and trust connections between Peers.
- Add a simple routing strategy to support broadcasting arbitrary messages.
- Add a common directed graph (`Digraph`) class for representing network and trust topologies.
- Add a `PeerGroup` class for simpler specification of the trust and network topologies.
- Add a `LedgerOracle` class to ensure distinct ledger histories and simplify branch checking.
- Add a `Submitter` to send transactions in at fixed or random intervals to fixed or random peers.

Co-authored-by: Joseph McGee
This commit is contained in:
Brad Chase
2017-06-14 11:59:06 -04:00
committed by seelabs
parent b9fc9f6334
commit 2c13d9eb57
51 changed files with 6642 additions and 2473 deletions

View File

@@ -4489,14 +4489,26 @@
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\src\test\consensus\ByzantineFailureSim_test.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\src\test\consensus\Consensus_test.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\src\test\consensus\DistributedValidatorsSim_test.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\src\test\consensus\LedgerTiming_test.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\src\test\consensus\ScaleFreeSim_test.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\src\test\consensus\Validations_test.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
@@ -4545,19 +4557,61 @@
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\src\test\csf\impl\UNL.cpp">
<ClInclude Include="..\..\src\test\csf\CollectorRef.h">
</ClInclude>
<ClInclude Include="..\..\src\test\csf\collectors.h">
</ClInclude>
<ClInclude Include="..\..\src\test\csf\Digraph.h">
</ClInclude>
<ClCompile Include="..\..\src\test\csf\Digraph_test.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
</ClCompile>
<ClInclude Include="..\..\src\test\csf\Ledger.h">
<ClInclude Include="..\..\src\test\csf\events.h">
</ClInclude>
<ClInclude Include="..\..\src\test\csf\Histogram.h">
</ClInclude>
<ClCompile Include="..\..\src\test\csf\Histogram_test.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\src\test\csf\impl\ledgers.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\src\test\csf\impl\Sim.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
</ClCompile>
<ClInclude Include="..\..\src\test\csf\ledgers.h">
</ClInclude>
<ClInclude Include="..\..\src\test\csf\Peer.h">
</ClInclude>
<ClInclude Include="..\..\src\test\csf\PeerGroup.h">
</ClInclude>
<ClInclude Include="..\..\src\test\csf\Proposal.h">
</ClInclude>
<ClInclude Include="..\..\src\test\csf\random.h">
</ClInclude>
<ClInclude Include="..\..\src\test\csf\Scheduler.h">
</ClInclude>
<ClCompile Include="..\..\src\test\csf\Scheduler_test.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
</ClCompile>
<ClInclude Include="..\..\src\test\csf\Sim.h">
</ClInclude>
<ClInclude Include="..\..\src\test\csf\SimTime.h">
</ClInclude>
<ClInclude Include="..\..\src\test\csf\submitters.h">
</ClInclude>
<ClInclude Include="..\..\src\test\csf\timers.h">
</ClInclude>
<ClInclude Include="..\..\src\test\csf\TrustGraph.h">
</ClInclude>
<ClInclude Include="..\..\src\test\csf\Tx.h">
</ClInclude>
<ClInclude Include="..\..\src\test\csf\UNL.h">
<ClInclude Include="..\..\src\test\csf\Validation.h">
</ClInclude>
<ClCompile Include="..\..\src\test\json\json_value_test.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>

View File

@@ -5250,12 +5250,21 @@
<ClCompile Include="..\..\src\test\conditions\PreimageSha256_test.cpp">
<Filter>test\conditions</Filter>
</ClCompile>
<ClCompile Include="..\..\src\test\consensus\ByzantineFailureSim_test.cpp">
<Filter>test\consensus</Filter>
</ClCompile>
<ClCompile Include="..\..\src\test\consensus\Consensus_test.cpp">
<Filter>test\consensus</Filter>
</ClCompile>
<ClCompile Include="..\..\src\test\consensus\DistributedValidatorsSim_test.cpp">
<Filter>test\consensus</Filter>
</ClCompile>
<ClCompile Include="..\..\src\test\consensus\LedgerTiming_test.cpp">
<Filter>test\consensus</Filter>
</ClCompile>
<ClCompile Include="..\..\src\test\consensus\ScaleFreeSim_test.cpp">
<Filter>test\consensus</Filter>
</ClCompile>
<ClCompile Include="..\..\src\test\consensus\Validations_test.cpp">
<Filter>test\consensus</Filter>
</ClCompile>
@@ -5295,22 +5304,73 @@
<ClCompile Include="..\..\src\test\csf\BasicNetwork_test.cpp">
<Filter>test\csf</Filter>
</ClCompile>
<ClCompile Include="..\..\src\test\csf\impl\UNL.cpp">
<ClInclude Include="..\..\src\test\csf\CollectorRef.h">
<Filter>test\csf</Filter>
</ClInclude>
<ClInclude Include="..\..\src\test\csf\collectors.h">
<Filter>test\csf</Filter>
</ClInclude>
<ClInclude Include="..\..\src\test\csf\Digraph.h">
<Filter>test\csf</Filter>
</ClInclude>
<ClCompile Include="..\..\src\test\csf\Digraph_test.cpp">
<Filter>test\csf</Filter>
</ClCompile>
<ClInclude Include="..\..\src\test\csf\events.h">
<Filter>test\csf</Filter>
</ClInclude>
<ClInclude Include="..\..\src\test\csf\Histogram.h">
<Filter>test\csf</Filter>
</ClInclude>
<ClCompile Include="..\..\src\test\csf\Histogram_test.cpp">
<Filter>test\csf</Filter>
</ClCompile>
<ClCompile Include="..\..\src\test\csf\impl\ledgers.cpp">
<Filter>test\csf\impl</Filter>
</ClCompile>
<ClInclude Include="..\..\src\test\csf\Ledger.h">
<ClCompile Include="..\..\src\test\csf\impl\Sim.cpp">
<Filter>test\csf\impl</Filter>
</ClCompile>
<ClInclude Include="..\..\src\test\csf\ledgers.h">
<Filter>test\csf</Filter>
</ClInclude>
<ClInclude Include="..\..\src\test\csf\Peer.h">
<Filter>test\csf</Filter>
</ClInclude>
<ClInclude Include="..\..\src\test\csf\PeerGroup.h">
<Filter>test\csf</Filter>
</ClInclude>
<ClInclude Include="..\..\src\test\csf\Proposal.h">
<Filter>test\csf</Filter>
</ClInclude>
<ClInclude Include="..\..\src\test\csf\random.h">
<Filter>test\csf</Filter>
</ClInclude>
<ClInclude Include="..\..\src\test\csf\Scheduler.h">
<Filter>test\csf</Filter>
</ClInclude>
<ClCompile Include="..\..\src\test\csf\Scheduler_test.cpp">
<Filter>test\csf</Filter>
</ClCompile>
<ClInclude Include="..\..\src\test\csf\Sim.h">
<Filter>test\csf</Filter>
</ClInclude>
<ClInclude Include="..\..\src\test\csf\SimTime.h">
<Filter>test\csf</Filter>
</ClInclude>
<ClInclude Include="..\..\src\test\csf\submitters.h">
<Filter>test\csf</Filter>
</ClInclude>
<ClInclude Include="..\..\src\test\csf\timers.h">
<Filter>test\csf</Filter>
</ClInclude>
<ClInclude Include="..\..\src\test\csf\TrustGraph.h">
<Filter>test\csf</Filter>
</ClInclude>
<ClInclude Include="..\..\src\test\csf\Tx.h">
<Filter>test\csf</Filter>
</ClInclude>
<ClInclude Include="..\..\src\test\csf\UNL.h">
<ClInclude Include="..\..\src\test\csf\Validation.h">
<Filter>test\csf</Filter>
</ClInclude>
<ClCompile Include="..\..\src\test\json\json_value_test.cpp">

View File

@@ -458,10 +458,12 @@ struct Ledger
{
using ID = ...;
using Seq = //std::uint32_t?...;
ID const & id() const;
// Sequence number that is 1 more than the parent ledger's seq()
std::size_t seq() const;
Seq seq() const;
// Whether the ledger's close time was a non-trivial consensus result
bool closeAgree() const;
@@ -633,14 +635,14 @@ struct Adaptor
// Propose the position to peers.
void propose(ConsensusProposal<...> const & pos);
// Relay a received peer proposal on to other peer's.
void relay(PeerPosition_t const & pos);
// Share a received peer proposal with other peers.
void share(PeerPosition_t const & pos);
// Relay a disputed transaction to peers
void relay(TxSet::Tx const & tx);
// Share a disputed transaction with peers
void share(TxSet::Tx const & tx);
// Realy given transaction set with peers
void relay(TxSet const &s);
// Share given transaction set with peers
void share(TxSet const &s);
//... implementation specific
};
@@ -649,7 +651,7 @@ struct Adaptor
The implementing class hides many details of the peer communication
model from the generic code.
* The =relay= member functions are responsible for sharing the given type with a
* The =share= member functions are responsible for sharing the given type with a
node's peers, but are agnostic to the mechanism. Ideally, messages are delivered
faster than =LEDGER_GRANULARITY=.
* The generic code does not specify how transactions are submitted by clients,

View File

@@ -126,7 +126,7 @@ RCLConsensus::Adaptor::acquireLedger(LedgerHash const& ledger)
void
RCLConsensus::Adaptor::relay(RCLCxPeerPos const& peerPos)
RCLConsensus::Adaptor::share(RCLCxPeerPos const& peerPos)
{
protocol::TMProposeSet prop;
@@ -150,7 +150,7 @@ RCLConsensus::Adaptor::relay(RCLCxPeerPos const& peerPos)
}
void
RCLConsensus::Adaptor::relay(RCLCxTx const& tx)
RCLConsensus::Adaptor::share(RCLCxTx const& tx)
{
// If we didn't relay this transaction recently, relay it to all peers
if (app_.getHashRouter().shouldRelay(tx.id()))
@@ -204,7 +204,7 @@ RCLConsensus::Adaptor::propose(RCLCxPeerPos::Proposal const& proposal)
}
void
RCLConsensus::Adaptor::relay(RCLTxSet const& set)
RCLConsensus::Adaptor::share(RCLTxSet const& set)
{
inboundTransactions_.giveSet(set.id(), set.map_, false);
}
@@ -254,19 +254,7 @@ RCLConsensus::Adaptor::getPrevLedger(
app_.getValidations().currentTrustedDistribution(
ledgerID, parentID, ledgerMaster_.getValidLedgerIndex());
uint256 netLgr = ledgerID;
int netLgrCount = 0;
for (auto const & it : ledgerCounts)
{
// Switch to ledger supported by more peers
// Or stick with ours on a tie
if ((it.second > netLgrCount) ||
((it.second == netLgrCount) && (it.first == ledgerID)))
{
netLgr = it.first;
netLgrCount = it.second;
}
}
uint256 netLgr = getPreferredLedger(ledgerID, ledgerCounts);
if (netLgr != ledgerID)
{

View File

@@ -158,21 +158,21 @@ class RCLConsensus
boost::optional<RCLCxLedger>
acquireLedger(LedgerHash const& ledger);
/** Relay the given proposal to all peers
/** Share the given proposal with all peers
@param peerPos The peer position to relay.
@param peerPos The peer position to share.
*/
void
relay(RCLCxPeerPos const& peerPos);
share(RCLCxPeerPos const& peerPos);
/** Relay disputed transacction to peers.
/** Share disputed transaction to peers.
Only relay if the provided transaction hasn't been shared recently.
Only share if the provided transaction hasn't been shared recently.
@param tx The disputed transaction to relay.
@param tx The disputed transaction to share.
*/
void
relay(RCLCxTx const& tx);
share(RCLCxTx const& tx);
/** Acquire the transaction set associated with a proposal.
@@ -215,12 +215,12 @@ class RCLConsensus
void
propose(RCLCxPeerPos::Proposal const& proposal);
/** Relay the given tx set to peers.
/** Share the given tx set to peers.
@param set The TxSet to share.
*/
void
relay(RCLTxSet const& set);
share(RCLTxSet const& set);
/** Get the ID of the previous ledger/last closed ledger(LCL) on the
network

View File

@@ -37,6 +37,8 @@ class RCLCxLedger
public:
//! Unique identifier of a ledger
using ID = LedgerHash;
//! Sequence number of a ledger
using Seq = LedgerIndex;
/** Default constructor
@@ -55,28 +57,28 @@ public:
}
//! Sequence number of the ledger.
auto const&
Seq const&
seq() const
{
return ledger_->info().seq;
}
//! Unique identifier (hash) of this ledger.
auto const&
ID const&
id() const
{
return ledger_->info().hash;
}
//! Unique identifier (hash) of this ledger's parent.
auto const&
ID const&
parentID() const
{
return ledger_->info().parentHash;
}
//! Resolution used when calculating this ledger's close time.
auto
NetClock::duration
closeTimeResolution() const
{
return ledger_->info().closeTimeResolution;
@@ -90,14 +92,14 @@ public:
}
//! The close time of this ledger
auto
NetClock::time_point
closeTime() const
{
return ledger_->info().closeTime;
}
//! The close time of this ledger's parent.
auto
NetClock::time_point
parentCloseTime() const
{
return ledger_->info().parentCloseTime;

View File

@@ -41,9 +41,18 @@ namespace ripple {
allowed arithmetic operations.
*/
template <class Int, class Tag>
class tagged_integer : boost::operators<tagged_integer<Int, Tag>>
, boost::shiftable<tagged_integer<Int, Tag>>
class tagged_integer
: boost::totally_ordered<
tagged_integer<Int, Tag>,
boost::integer_arithmetic<
tagged_integer<Int, Tag>,
boost::bitwise<
tagged_integer<Int, Tag>,
boost::unit_steppable<
tagged_integer<Int, Tag>,
boost::shiftable<tagged_integer<Int, Tag>>>>>>
{
private:
Int m_value;
@@ -63,6 +72,9 @@ public:
tagged_integer(OtherInt value) noexcept
: m_value(value)
{
static_assert(
sizeof(tagged_integer) == sizeof(Int),
"tagged_integer is adding padding");
}
bool

View File

@@ -176,10 +176,11 @@ checkConsensus(
struct Ledger
{
using ID = ...;
using Seq = ...;
// Unique identifier of ledgerr
ID const id() const;
auto seq() const;
Seq seq() const;
auto closeTimeResolution() const;
auto closeAgree() const;
auto closeTime() const;
@@ -257,14 +258,14 @@ checkConsensus(
// Propose the position to peers.
void propose(ConsensusProposal<...> const & pos);
// Relay a received peer proposal on to other peer's.
void relay(PeerPosition_t const & prop);
// Share a received peer proposal with other peer's.
void share(PeerPosition_t const & prop);
// Relay a disputed transaction to peers
void relay(Txn const & tx);
// Share a disputed transaction with peers
void share(Txn const & tx);
// Share given transaction set with peers
void relay(TxSet const &s);
void share(TxSet const &s);
// Consensus timing parameters and constants
ConsensusParms const &
@@ -642,7 +643,7 @@ Consensus<Adaptor>::startRoundInternal(
closeResolution_ = getNextLedgerTimeResolution(
previousLedger_.closeTimeResolution(),
previousLedger_.closeAgree(),
previousLedger_.seq() + 1);
previousLedger_.seq() + typename Ledger_t::Seq{1});
playbackProposals();
if (currPeerPositions_.size() > (prevProposers_ / 2))
@@ -878,7 +879,7 @@ Consensus<Adaptor>::getJson(bool full) const
if (mode_.get() != ConsensusMode::wrongLedger)
{
ret["synched"] = true;
ret["ledger_seq"] = previousLedger_.seq() + 1;
ret["ledger_seq"] = static_cast<std::uint32_t>(previousLedger_.seq())+ 1;
ret["close_granularity"] = static_cast<Int>(closeResolution_.count());
}
else
@@ -1038,7 +1039,7 @@ Consensus<Adaptor>::playbackProposals()
if (pos.proposal().prevLedger() == prevLedgerID_)
{
if (peerProposalInternal(now_, pos))
adaptor_.relay(pos);
adaptor_.share(pos);
}
}
}
@@ -1158,7 +1159,7 @@ Consensus<Adaptor>::closeLedger()
// Share the newly created transaction set if we haven't already
// received it from a peer
if (acquired_.emplace(result_->set.id(), result_->set).second)
adaptor_.relay(result_->set);
adaptor_.share(result_->set);
if (mode_.get() == ConsensusMode::proposing)
adaptor_.propose(result_->position);
@@ -1264,7 +1265,7 @@ Consensus<Adaptor>::updateOurPositions()
}
if (mutableSet)
ourNewSet.emplace(*mutableSet);
ourNewSet.emplace(std::move(*mutableSet));
}
NetClock::time_point consensusCloseTime = {};
@@ -1310,7 +1311,8 @@ Consensus<Adaptor>::updateOurPositions()
for (auto const& it : closeTimeVotes)
{
JLOG(j_.debug())
<< "CCTime: seq " << previousLedger_.seq() + 1 << ": "
<< "CCTime: seq "
<< static_cast<std::uint32_t>(previousLedger_.seq()) + 1 << ": "
<< it.first.time_since_epoch().count() << " has " << it.second
<< ", " << threshVote << " required";
@@ -1361,7 +1363,7 @@ Consensus<Adaptor>::updateOurPositions()
if (acquired_.emplace(newID, result_->set).second)
{
if (!result_->position.isBowOut())
adaptor_.relay(result_->set);
adaptor_.share(result_->set);
for (auto const& it : currPeerPositions_)
{
@@ -1493,7 +1495,8 @@ Consensus<Adaptor>::createDisputes(TxSet_t const& o)
JLOG(j_.debug()) << "Transaction " << txID << " is disputed";
typename Result::Dispute_t dtx{tx, result_->set.exists(txID), j_};
typename Result::Dispute_t dtx{tx, result_->set.exists(txID),
std::max(prevProposers_, currPeerPositions_.size()), j_};
// Update all of the available peer's votes on the disputed transaction
for (auto const& pit : currPeerPositions_)
@@ -1503,7 +1506,7 @@ Consensus<Adaptor>::createDisputes(TxSet_t const& o)
if (cit != acquired_.end())
dtx.setVote(pit.first, cit->second.exists(txID));
}
adaptor_.relay(dtx.tx());
adaptor_.share(dtx.tx());
result_->disputes.emplace(txID, std::move(dtx));
}

View File

@@ -20,6 +20,7 @@
#ifndef RIPPLE_APP_CONSENSUS_IMPL_DISPUTEDTX_H_INCLUDED
#define RIPPLE_APP_CONSENSUS_IMPL_DISPUTEDTX_H_INCLUDED
#include <boost/container/flat_map.hpp>
#include <ripple/basics/Log.h>
#include <ripple/basics/base_uint.h>
#include <ripple/beast/utility/Journal.h>
@@ -48,17 +49,19 @@ template <class Tx_t, class NodeID_t>
class DisputedTx
{
using TxID_t = typename Tx_t::ID;
using Map_t = boost::container::flat_map<NodeID_t, bool>;
public:
/** Constructor
@param tx The transaction under dispute
@param ourVote Our vote on whether tx should be included
@param numPeers Anticipated number of peer votes
@param j Journal for debugging
*/
DisputedTx(Tx_t const& tx, bool ourVote, beast::Journal j)
DisputedTx(Tx_t const& tx, bool ourVote, std::size_t numPeers, beast::Journal j)
: yays_(0), nays_(0), ourVote_(ourVote), tx_(tx), j_(j)
{
votes_.reserve(numPeers);
}
//! The unique id/hash of the disputed transaction.
@@ -127,9 +130,8 @@ private:
int nays_; //< Number of no votes
bool ourVote_; //< Our vote (true is yes)
Tx_t tx_; //< Transaction under dispute
hash_map<NodeID_t, bool> votes_; //< Votes of our peers
beast::Journal j_; //< Debug journal
Map_t votes_; //< Map from NodeID to vote
beast::Journal j_;
};
// Track a peer's yes/no vote on a particular disputed tx_

View File

@@ -46,7 +46,6 @@ auto constexpr increaseLedgerTimeResolutionEvery = 8;
//! How often we decrease the close time resolution (in numbers of ledgers)
auto constexpr decreaseLedgerTimeResolutionEvery = 1;
/** Calculates the close time resolution for the specified ledger.
The Ripple protocol uses binning to represent time intervals using only one
@@ -62,15 +61,22 @@ auto constexpr decreaseLedgerTimeResolutionEvery = 1;
@pre previousResolution must be a valid bin
from @ref ledgerPossibleTimeResolutions
@tparam Rep Type representing number of ticks in std::chrono::duration
@tparam Period An std::ratio representing tick period in
std::chrono::duration
@tparam Seq Unsigned integer-like type corresponding to the ledger sequence
number. It should be comparable to 0 and support modular
division. Built-in and tagged_integers are supported.
*/
template <class duration>
duration
template <class Rep, class Period, class Seq>
std::chrono::duration<Rep, Period>
getNextLedgerTimeResolution(
duration previousResolution,
std::chrono::duration<Rep, Period> previousResolution,
bool previousAgree,
std::uint32_t ledgerSeq)
Seq ledgerSeq)
{
assert(ledgerSeq);
assert(ledgerSeq != Seq{0});
using namespace std::chrono;
// Find the current resolution:
@@ -86,7 +92,8 @@ getNextLedgerTimeResolution(
// If we did not previously agree, we try to decrease the resolution to
// improve the chance that we will agree now.
if (!previousAgree && ledgerSeq % decreaseLedgerTimeResolutionEvery == 0)
if (!previousAgree &&
(ledgerSeq % Seq{decreaseLedgerTimeResolutionEvery} == Seq{0}))
{
if (++iter != std::end(ledgerPossibleTimeResolutions))
return *iter;
@@ -94,7 +101,8 @@ getNextLedgerTimeResolution(
// If we previously agreed, we try to increase the resolution to determine
// if we can continue to agree.
if (previousAgree && ledgerSeq % increaseLedgerTimeResolutionEvery == 0)
if (previousAgree &&
(ledgerSeq % Seq{increaseLedgerTimeResolutionEvery} == Seq{0}))
{
if (iter-- != std::begin(ledgerPossibleTimeResolutions))
return *iter;
@@ -110,12 +118,13 @@ getNextLedgerTimeResolution(
@return @b closeTime rounded to the nearest multiple of @b closeResolution.
Rounds up if @b closeTime is midway between multiples of @b closeResolution.
*/
template <class time_point>
time_point
template <class Clock, class Duration, class Rep, class Period>
std::chrono::time_point<Clock, Duration>
roundCloseTime(
time_point closeTime,
typename time_point::duration closeResolution)
std::chrono::time_point<Clock, Duration> closeTime,
std::chrono::duration<Rep, Period> closeResolution)
{
using time_point = decltype(closeTime);
if (closeTime == time_point{})
return closeTime;
@@ -132,14 +141,15 @@ roundCloseTime(
@param resolution The current close time resolution
@param priorCloseTime The close time of the prior ledger
*/
template <class time_point>
time_point
template <class Clock, class Duration, class Rep, class Period>
std::chrono::time_point<Clock, Duration>
effCloseTime(
time_point closeTime,
typename time_point::duration const resolution,
time_point priorCloseTime)
std::chrono::time_point<Clock, Duration> closeTime,
std::chrono::duration<Rep, Period> resolution,
std::chrono::time_point<Clock, Duration> priorCloseTime)
{
using namespace std::chrono_literals;
using time_point = decltype(closeTime);
if (closeTime == time_point{})
return closeTime;

View File

@@ -26,7 +26,6 @@
#include <ripple/beast/container/aged_container_utility.h>
#include <ripple/beast/container/aged_unordered_map.h>
#include <ripple/beast/utility/Journal.h>
#include <ripple/beast/utility/Zero.h>
#include <boost/optional.hpp>
#include <mutex>
#include <utility>
@@ -74,6 +73,7 @@ struct ValidationParms
std::chrono::seconds validationSET_EXPIRES = std::chrono::minutes{10};
};
/** Whether a validation is still current
Determines whether a validation can still be considered the current
@@ -103,6 +103,37 @@ isCurrent(
(seenTime < (now + p.validationCURRENT_LOCAL)));
}
/** Determine the preferred ledger based on its support
@param current The current ledger the node follows
@param dist Ledger IDs and corresponding counts of support
@return The ID of the ledger with most support, preferring to stick with
current ledger in the case of equal support
*/
template <class LedgerID>
inline LedgerID
getPreferredLedger(
LedgerID const& current,
hash_map<LedgerID, std::uint32_t> const& dist)
{
LedgerID netLgr = current;
int netLgrCount = 0;
for (auto const& it : dist)
{
// Switch to ledger supported by more peers
// On a tie, prefer the current ledger, or the one with higher ID
if ((it.second > netLgrCount) ||
((it.second == netLgrCount) &&
((it.first == current) ||
(it.first > netLgr && netLgr != current))))
{
netLgr = it.first;
netLgrCount = it.second;
}
}
return netLgr;
}
/** Maintains current and recent ledger validations.
Manages storage and queries related to validations received on the network.
@@ -192,6 +223,7 @@ class Validations
decay_result_t<decltype (&Validation::ledgerID)(Validation)>;
using NodeKey = decay_result_t<decltype (&Validation::key)(Validation)>;
using NodeID = decay_result_t<decltype (&Validation::nodeID)(Validation)>;
using SeqType = decay_result_t<decltype (&Validation::seq)(Validation)>;
using ScopedLock = std::lock_guard<MutexType>;
@@ -397,11 +429,12 @@ public:
Validation& oldVal = ins.first->second.val;
LedgerID const previousLedgerID = ins.first->second.prevLedgerID;
std::uint32_t const oldSeq{oldVal.seq()};
std::uint32_t const newSeq{val.seq()};
SeqType const oldSeq{oldVal.seq()};
SeqType const newSeq{val.seq()};
// Sequence of 0 indicates a missing sequence number
if (oldSeq && newSeq && oldSeq == newSeq)
if ((oldSeq != SeqType{0}) && (newSeq != SeqType{0}) &&
oldSeq == newSeq)
{
result = AddOutcome::sameSeq;
@@ -491,7 +524,9 @@ public:
ledgers one away from the current ledger to count as the current.
@param currentLedger The identifier of the ledger we believe is current
(0 if unknown)
@param priorLedger The identifier of our previous current ledger
(0 if unknown)
@param cutoffBefore Ignore ledgers with sequence number before this
@return Map representing the distribution of ledgerID by count
@@ -500,10 +535,10 @@ public:
currentTrustedDistribution(
LedgerID const& currentLedger,
LedgerID const& priorLedger,
std::uint32_t cutoffBefore)
SeqType cutoffBefore)
{
bool const valCurrentLedger = currentLedger != beast::zero;
bool const valPriorLedger = priorLedger != beast::zero;
bool const valCurrentLedger = currentLedger != LedgerID{0};
bool const valPriorLedger = priorLedger != LedgerID{0};
hash_map<LedgerID, std::uint32_t> ret;
@@ -524,8 +559,8 @@ public:
if (!v.trusted())
return;
std::uint32_t const seq = v.seq();
if ((seq == 0) || (seq >= cutoffBefore))
SeqType const seq = v.seq();
if ((seq == SeqType{0}) || (seq >= cutoffBefore))
{
// contains a live record
bool countPreferred =

View 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.
*/
//==============================================================================
#include <BeastConfig.h>
#include <ripple/beast/clock/manual_clock.h>
#include <ripple/beast/unit_test.h>
#include <test/csf.h>
#include <utility>
namespace ripple {
namespace test {
class ByzantineFailureSim_test : public beast::unit_test::suite
{
void
run() override
{
using namespace csf;
using namespace std::chrono;
// This test simulates a specific topology with nodes generating
// different ledgers due to a simulated byzantine failure (injecting
// an extra non-consensus transaction).
Sim sim;
ConsensusParms const parms{};
SimDuration const delay =
round<milliseconds>(0.2 * parms.ledgerGRANULARITY);
PeerGroup a = sim.createGroup(1);
PeerGroup b = sim.createGroup(1);
PeerGroup c = sim.createGroup(1);
PeerGroup d = sim.createGroup(1);
PeerGroup e = sim.createGroup(1);
PeerGroup f = sim.createGroup(1);
PeerGroup g = sim.createGroup(1);
a.trustAndConnect(a + b + c + g, delay);
b.trustAndConnect(b + a + c + d + e, delay);
c.trustAndConnect(c + a + b + d + e, delay);
d.trustAndConnect(d + b + c + e + f, delay);
e.trustAndConnect(e + b + c + d + f, delay);
f.trustAndConnect(f + d + e + g, delay);
g.trustAndConnect(g + a + f, delay);
PeerGroup network = a + b + c + d + e + f + g;
StreamCollector sc{std::cout};
sim.collectors.add(sc);
for (TrustGraph<Peer*>::ForkInfo const& fi :
sim.trustGraph.forkablePairs(0.8))
{
std::cout << "Can fork " << PeerGroup{fi.unlA} << " "
<< " " << PeerGroup{fi.unlB}
<< " overlap " << fi.overlap << " required "
<< fi.required << "\n";
};
// set prior state
sim.run(1);
PeerGroup byzantineNodes = a + b + c + g;
// All peers see some TX 0
for (Peer * peer : network)
{
peer->submit(Tx(0));
// Peers 0,1,2,6 will close the next ledger differently by injecting
// a non-consensus approved transaciton
if (byzantineNodes.contains(peer))
{
peer->txInjections.emplace(
peer->lastClosedLedger.seq(), Tx{42});
}
}
sim.run(4);
std::cout << "Branches: " << sim.branches() << "\n";
std::cout << "Fully synchronized: " << std::boolalpha
<< sim.synchronized() << "\n";
// Not tessting anything currently.
pass();
}
};
BEAST_DEFINE_TESTSUITE_MANUAL(ByzantineFailureSim, consensus, ripple);
} // namespace test
} // namespace ripple

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,273 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012-2016 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/beast/clock/manual_clock.h>
#include <ripple/beast/unit_test.h>
#include <test/csf.h>
#include <utility>
#include <boost/algorithm/string/split.hpp>
#include <boost/algorithm/string/classification.hpp>
#include <algorithm>
#include <sstream>
#include <fstream>
#include <string>
namespace ripple {
namespace test {
/** In progress simulations for diversifying and distributing validators
*/
class DistributedValidators_test : public beast::unit_test::suite
{
void
completeTrustCompleteConnectFixedDelay(
std::size_t numPeers,
std::chrono::milliseconds delay = std::chrono::milliseconds(200),
bool printHeaders = false)
{
using namespace csf;
using namespace std::chrono;
// Initialize persistent collector logs specific to this method
std::string const prefix =
"DistributedValidators_"
"completeTrustCompleteConnectFixedDelay";
std::fstream
txLog(prefix + "_tx.csv", std::ofstream::app),
ledgerLog(prefix + "_ledger.csv", std::ofstream::app);
// title
log << prefix << "(" << numPeers << "," << delay.count() << ")"
<< std::endl;
// number of peers, UNLs, connections
BEAST_EXPECT(numPeers >= 1);
Sim sim;
PeerGroup peers = sim.createGroup(numPeers);
// complete trust graph
peers.trust(peers);
// complete connect graph with fixed delay
peers.connect(peers, delay);
// Initialize collectors to track statistics to report
TxCollector txCollector;
LedgerCollector ledgerCollector;
auto colls = makeCollectors(txCollector, ledgerCollector);
sim.collectors.add(colls);
// Initial round to set prior state
sim.run(1);
// Run for 10 minues, submitting 100 tx/second
std::chrono::nanoseconds const simDuration = 10min;
std::chrono::nanoseconds const quiet = 10s;
Rate const rate{100, 1000ms};
// Initialize timers
HeartbeatTimer heart(sim.scheduler);
// txs, start/stop/step, target
auto peerSelector = makeSelector(peers.begin(),
peers.end(),
std::vector<double>(numPeers, 1.),
sim.rng);
auto txSubmitter = makeSubmitter(ConstantDistribution{rate.inv()},
sim.scheduler.now() + quiet,
sim.scheduler.now() + simDuration - quiet,
peerSelector,
sim.scheduler,
sim.rng);
// run simulation for given duration
heart.start();
sim.run(simDuration);
//BEAST_EXPECT(sim.branches() == 1);
//BEAST_EXPECT(sim.synchronized());
log << std::right;
log << "| Peers: "<< std::setw(2) << peers.size();
log << " | Duration: " << std::setw(6)
<< duration_cast<milliseconds>(simDuration).count() << " ms";
log << " | Branches: " << std::setw(1) << sim.branches();
log << " | Synchronized: " << std::setw(1)
<< (sim.synchronized() ? "Y" : "N");
log << " |" << std::endl;
txCollector.report(simDuration, log, true);
ledgerCollector.report(simDuration, log, false);
std::string const tag = std::to_string(numPeers);
txCollector.csv(simDuration, txLog, tag, printHeaders);
ledgerCollector.csv(simDuration, ledgerLog, tag, printHeaders);
log << std::endl;
}
void
completeTrustScaleFreeConnectFixedDelay(
std::size_t numPeers,
std::chrono::milliseconds delay = std::chrono::milliseconds(200),
bool printHeaders = false)
{
using namespace csf;
using namespace std::chrono;
// Initialize persistent collector logs specific to this method
std::string const prefix =
"DistributedValidators__"
"completeTrustScaleFreeConnectFixedDelay";
std::fstream
txLog(prefix + "_tx.csv", std::ofstream::app),
ledgerLog(prefix + "_ledger.csv", std::ofstream::app);
// title
log << prefix << "(" << numPeers << "," << delay.count() << ")"
<< std::endl;
// number of peers, UNLs, connections
int const numCNLs = std::max(int(1.00 * numPeers), 1);
int const minCNLSize = std::max(int(0.25 * numCNLs), 1);
int const maxCNLSize = std::max(int(0.50 * numCNLs), 1);
BEAST_EXPECT(numPeers >= 1);
BEAST_EXPECT(numCNLs >= 1);
BEAST_EXPECT(1 <= minCNLSize
&& minCNLSize <= maxCNLSize
&& maxCNLSize <= numPeers);
Sim sim;
PeerGroup peers = sim.createGroup(numPeers);
// complete trust graph
peers.trust(peers);
// scale-free connect graph with fixed delay
std::vector<double> const ranks =
sample(peers.size(), PowerLawDistribution{1, 3}, sim.rng);
randomRankedConnect(peers, ranks, numCNLs,
std::uniform_int_distribution<>{minCNLSize, maxCNLSize},
sim.rng, delay);
// Initialize collectors to track statistics to report
TxCollector txCollector;
LedgerCollector ledgerCollector;
auto colls = makeCollectors(txCollector, ledgerCollector);
sim.collectors.add(colls);
// Initial round to set prior state
sim.run(1);
// Run for 10 minues, submitting 100 tx/second
std::chrono::nanoseconds simDuration = 10min;
std::chrono::nanoseconds quiet = 10s;
Rate rate{100, 1000ms};
// Initialize timers
HeartbeatTimer heart(sim.scheduler);
// txs, start/stop/step, target
auto peerSelector = makeSelector(peers.begin(),
peers.end(),
std::vector<double>(numPeers, 1.),
sim.rng);
auto txSubmitter = makeSubmitter(ConstantDistribution{rate.inv()},
sim.scheduler.now() + quiet,
sim.scheduler.now() + simDuration - quiet,
peerSelector,
sim.scheduler,
sim.rng);
// run simulation for given duration
heart.start();
sim.run(simDuration);
//BEAST_EXPECT(sim.branches() == 1);
//BEAST_EXPECT(sim.synchronized());
log << std::right;
log << "| Peers: "<< std::setw(2) << peers.size();
log << " | Duration: " << std::setw(6)
<< duration_cast<milliseconds>(simDuration).count() << " ms";
log << " | Branches: " << std::setw(1) << sim.branches();
log << " | Synchronized: " << std::setw(1)
<< (sim.synchronized() ? "Y" : "N");
log << " |" << std::endl;
txCollector.report(simDuration, log, true);
ledgerCollector.report(simDuration, log, false);
std::string const tag = std::to_string(numPeers);
txCollector.csv(simDuration, txLog, tag, printHeaders);
ledgerCollector.csv(simDuration, ledgerLog, tag, printHeaders);
log << std::endl;
}
void
run() override
{
std::string const defaultArgs = "5 200";
std::string const args = arg().empty() ? defaultArgs : arg();
std::stringstream argStream(args);
int maxNumValidators = 0;
int delayCount(200);
argStream >> maxNumValidators;
argStream >> delayCount;
std::chrono::milliseconds const delay(delayCount);
log << "DistributedValidators: 1 to " << maxNumValidators << " Peers"
<< std::endl;
/**
* Simulate with N = 1 to N
* - complete trust graph is complete
* - complete network connectivity
* - fixed delay for network links
*/
completeTrustCompleteConnectFixedDelay(1, delay, true);
for(int i = 2; i <= maxNumValidators; i++)
{
completeTrustCompleteConnectFixedDelay(i, delay);
}
/**
* Simulate with N = 1 to N
* - complete trust graph is complete
* - scale-free network connectivity
* - fixed delay for network links
*/
completeTrustScaleFreeConnectFixedDelay(1, delay, true);
for(int i = 2; i <= maxNumValidators; i++)
{
completeTrustScaleFreeConnectFixedDelay(i, delay);
}
}
};
BEAST_DEFINE_TESTSUITE_MANUAL(DistributedValidators, consensus, ripple);
} // namespace test
} // namespace ripple

View File

@@ -95,11 +95,32 @@ class LedgerTiming_test : public beast::unit_test::suite
}
void testEffCloseTime()
{
using namespace std::chrono_literals;
using tp = NetClock::time_point;
tp close = effCloseTime(tp{10s}, 30s, tp{0s});
BEAST_EXPECT(close == tp{1s});
close = effCloseTime(tp{16s}, 30s, tp{0s});
BEAST_EXPECT(close == tp{30s});
close = effCloseTime(tp{16s}, 30s, tp{30s});
BEAST_EXPECT(close == tp{31s});
close = effCloseTime(tp{16s}, 30s, tp{60s});
BEAST_EXPECT(close == tp{61s});
close = effCloseTime(tp{31s}, 30s, tp{0s});
BEAST_EXPECT(close == tp{30s});
}
void
run() override
{
testGetNextLedgerTimeResolution();
testRoundCloseTime();
testEffCloseTime();
}
};

View File

@@ -0,0 +1,123 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012-2016 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/beast/clock/manual_clock.h>
#include <ripple/beast/unit_test.h>
#include <test/csf.h>
#include <test/csf/random.h>
#include <utility>
namespace ripple {
namespace test {
class ScaleFreeSim_test : public beast::unit_test::suite
{
void
run() override
{
using namespace std::chrono;
using namespace csf;
// Generate a quasi-random scale free network and simulate consensus
// as we vary transaction submission rates
int const N = 100; // Peers
int const numUNLs = 15; // UNL lists
int const minUNLSize = N / 4, maxUNLSize = N / 2;
ConsensusParms const parms{};
Sim sim;
PeerGroup network = sim.createGroup(N);
// generate trust ranks
std::vector<double> const ranks =
sample(network.size(), PowerLawDistribution{1, 3}, sim.rng);
// generate scale-free trust graph
randomRankedTrust(network, ranks, numUNLs,
std::uniform_int_distribution<>{minUNLSize, maxUNLSize},
sim.rng);
// nodes with a trust line in either direction are network-connected
network.connectFromTrust(
round<milliseconds>(0.2 * parms.ledgerGRANULARITY));
// Initialize collectors to track statistics to report
TxCollector txCollector;
LedgerCollector ledgerCollector;
auto colls = makeCollectors(txCollector, ledgerCollector);
sim.collectors.add(colls);
// Initial round to set prior state
sim.run(1);
// Initialize timers
HeartbeatTimer heart(sim.scheduler, seconds(10s));
// Run for 10 minues, submitting 100 tx/second
std::chrono::nanoseconds const simDuration = 10min;
std::chrono::nanoseconds const quiet = 10s;
Rate const rate{100, 1000ms};
// txs, start/stop/step, target
auto peerSelector = makeSelector(network.begin(),
network.end(),
ranks,
sim.rng);
auto txSubmitter = makeSubmitter(ConstantDistribution{rate.inv()},
sim.scheduler.now() + quiet,
sim.scheduler.now() + (simDuration - quiet),
peerSelector,
sim.scheduler,
sim.rng);
// run simulation for given duration
heart.start();
sim.run(simDuration);
BEAST_EXPECT(sim.branches() == 1);
BEAST_EXPECT(sim.synchronized());
// TODO: Clean up this formatting mess!!
log << "Peers: " << network.size() << std::endl;
log << "Simulated Duration: "
<< duration_cast<milliseconds>(simDuration).count()
<< " ms" << std::endl;
log << "Branches: " << sim.branches() << std::endl;
log << "Synchronized: " << (sim.synchronized() ? "Y" : "N")
<< std::endl;
log << std::endl;
txCollector.report(simDuration, log);
ledgerCollector.report(simDuration, log);
// Print summary?
// # forks? # of LCLs?
// # peers
// # tx submitted
// # ledgers/sec etc.?
}
};
BEAST_DEFINE_TESTSUITE_MANUAL(ScaleFreeSim, consensus, ripple);
} // namespace test
} // namespace ripple

View File

@@ -17,9 +17,11 @@
*/
//==============================================================================
#include <BeastConfig.h>
#include <ripple/basics/tagged_integer.h>
#include <ripple/beast/clock/manual_clock.h>
#include <ripple/beast/unit_test.h>
#include <ripple/consensus/Validations.h>
#include <test/csf/Validation.h>
#include <tuple>
#include <type_traits>
@@ -28,166 +30,10 @@
namespace ripple {
namespace test {
namespace csf {
class Validations_test : public beast::unit_test::suite
{
using clock_type = beast::abstract_clock<std::chrono::steady_clock> const;
//--------------------------------------------------------------------------
// Basic type wrappers for validation types
// Represents a ledger sequence number
struct Seq
{
explicit Seq(std::uint32_t sIn) : s{sIn}
{
}
Seq() : s{0}
{
}
operator std::uint32_t() const
{
return s;
}
std::uint32_t s;
};
// Represents a unique ledger identifier
struct ID
{
explicit ID(std::uint32_t idIn) : id{idIn}
{
}
ID() : id{0}
{
}
int
signum() const
{
return id == 0 ? 0 : 1;
}
operator std::size_t() const
{
return id;
}
template <class Hasher>
friend void
hash_append(Hasher& h, ID const& id)
{
using beast::hash_append;
hash_append(h, id.id);
}
std::uint32_t id;
};
class Node;
// Basic implementation of the requirements of Validation in the generic
// Validations class
class Validation
{
friend class Node;
ID ledgerID_ = ID{0};
Seq seq_ = Seq{0};
NetClock::time_point signTime_;
NetClock::time_point seenTime_;
std::string key_;
std::size_t nodeID_ = 0;
bool trusted_ = true;
boost::optional<std::uint32_t> loadFee_;
public:
Validation()
{
}
ID
ledgerID() const
{
return ledgerID_;
}
Seq
seq() const
{
return seq_;
}
NetClock::time_point
signTime() const
{
return signTime_;
}
NetClock::time_point
seenTime() const
{
return seenTime_;
}
std::string
key() const
{
return key_;
}
std::uint32_t
nodeID() const
{
return nodeID_;
}
bool
trusted() const
{
return trusted_;
}
boost::optional<std::uint32_t>
loadFee() const
{
return loadFee_;
}
Validation const&
unwrap() const
{
return *this;
}
auto
asTie() const
{
return std::tie(
ledgerID_,
seq_,
signTime_,
seenTime_,
key_,
nodeID_,
trusted_,
loadFee_);
}
bool
operator==(Validation const& o) const
{
return asTie() == o.asTie();
}
bool
operator<(Validation const& o) const
{
return asTie() < o.asTie();
}
};
// Helper to convert steady_clock to a reasonable NetClock
// This allows a single manual clock in the unit tests
@@ -206,13 +52,13 @@ class Validations_test : public beast::unit_test::suite
class Node
{
clock_type const& c_;
std::size_t nodeID_;
PeerID nodeID_;
bool trusted_ = true;
std::size_t signIdx_ = 0;
std::size_t signIdx_ = 1;
boost::optional<std::uint32_t> loadFee_;
public:
Node(std::uint32_t nodeID, clock_type const& c) : c_(c), nodeID_(nodeID)
Node(PeerID nodeID, clock_type const& c) : c_(c), nodeID_(nodeID)
{
}
@@ -234,7 +80,7 @@ class Validations_test : public beast::unit_test::suite
loadFee_ = fee;
}
std::size_t
PeerID
nodeID() const
{
return nodeID_;
@@ -246,18 +92,17 @@ class Validations_test : public beast::unit_test::suite
signIdx_++;
}
std::string
masterKey() const
{
return std::to_string(nodeID_);
}
std::string
PeerKey
currKey() const
{
return masterKey() + "_" + std::to_string(signIdx_);
return std::make_pair(nodeID_, signIdx_);
}
PeerKey
masterKey() const
{
return std::make_pair(nodeID_, 0);
}
NetClock::time_point
now() const
{
@@ -267,54 +112,30 @@ class Validations_test : public beast::unit_test::suite
// Issue a new validation with given sequence number and id and
// with signing and seen times offset from the common clock
Validation
validation(
Seq seq,
ID i,
validation(Ledger::Seq seq,
Ledger::ID i,
NetClock::duration signOffset,
NetClock::duration seenOffset) const
{
Validation v;
v.seq_ = seq;
v.ledgerID_ = i;
v.signTime_ = now() + signOffset;
v.seenTime_ = now() + seenOffset;
v.nodeID_ = nodeID_;
v.key_ = currKey();
v.trusted_ = trusted_;
v.loadFee_ = loadFee_;
return v;
return Validation{i, seq, now() + signOffset, now() + seenOffset,
currKey(), nodeID_, trusted_, loadFee_};
}
// Issue a new validation with the given sequence number and id
Validation
validation(Seq seq, ID i) const
validation(Ledger::Seq seq, Ledger::ID i) const
{
return validation(
seq, i, NetClock::duration{0}, NetClock::duration{0});
}
};
// Non-locking mutex to avoid the need for testing generic Validations
struct DummyMutex
{
void
lock()
{
}
void
unlock()
{
}
};
// Saved StaleData for inspection in test
struct StaleData
{
std::vector<Validation> stale;
hash_map<std::string, Validation> flushed;
hash_map<PeerKey, Validation> flushed;
};
// Generic Validations policy that saves stale/flushed data into
@@ -325,8 +146,7 @@ class Validations_test : public beast::unit_test::suite
clock_type& c_;
public:
StalePolicy(StaleData& sd, clock_type& c)
: staleData_{sd}, c_{c}
StalePolicy(StaleData& sd, clock_type& c) : staleData_{sd}, c_{c}
{
}
@@ -343,15 +163,28 @@ class Validations_test : public beast::unit_test::suite
}
void
flush(hash_map<std::string, Validation>&& remaining)
flush(hash_map<PeerKey, Validation>&& remaining)
{
staleData_.flushed = std::move(remaining);
}
};
// Non-locking mutex to avoid locks in generic Validations
struct NotAMutex
{
void
lock()
{
}
void
unlock()
{
}
};
// Specialize generic Validations using the above types
using TestValidations =
Validations<StalePolicy, Validation, DummyMutex>;
using TestValidations = Validations<StalePolicy, Validation, NotAMutex>;
// Hoist enum for writing simpler tests
using AddOutcome = TestValidations::AddOutcome;
@@ -365,7 +198,7 @@ class Validations_test : public beast::unit_test::suite
beast::manual_clock<std::chrono::steady_clock> clock_;
beast::Journal j_;
TestValidations tv_;
int nextNodeId_ = 0;
PeerID nextNodeId_{0};
public:
TestHarness() : tv_(p_, clock_, j_, staleData_, clock_)
@@ -417,7 +250,7 @@ class Validations_test : public beast::unit_test::suite
return staleData_.stale;
}
hash_map<std::string, Validation> const&
hash_map<PeerKey, Validation> const&
flushed() const
{
return staleData_.flushed;
@@ -434,7 +267,7 @@ class Validations_test : public beast::unit_test::suite
Node a = harness.makeNode();
{
{
auto const v = a.validation(Seq{1}, ID{1});
auto const v = a.validation(Ledger::Seq{1}, Ledger::ID{1});
// Add a current validation
BEAST_EXPECT(AddOutcome::current == harness.add(a, v));
@@ -448,42 +281,44 @@ class Validations_test : public beast::unit_test::suite
// Replace with a new validation and ensure the old one is stale
BEAST_EXPECT(harness.stale().empty());
BEAST_EXPECT(
AddOutcome::current == harness.add(a, Seq{2}, ID{2}));
BEAST_EXPECT(AddOutcome::current ==
harness.add(a, Ledger::Seq{2}, Ledger::ID{2}));
BEAST_EXPECT(harness.stale().size() == 1);
BEAST_EXPECT(harness.stale()[0].ledgerID() == 1);
BEAST_EXPECT(harness.stale()[0].ledgerID() == Ledger::ID{1});
}
{
// Test the node changing signing key, then reissuing a ledger
// Confirm old ledger on hand, but not new ledger
BEAST_EXPECT(harness.vals().numTrustedForLedger(ID{2}) == 1);
BEAST_EXPECT(harness.vals().numTrustedForLedger(ID{20}) == 0);
BEAST_EXPECT(
harness.vals().numTrustedForLedger(Ledger::ID{2}) == 1);
BEAST_EXPECT(
harness.vals().numTrustedForLedger(Ledger::ID{20}) == 0);
// Issue a new signing key and re-issue the validation with a
// new ID but the same sequence number
a.advanceKey();
// No validations following ID{2}
BEAST_EXPECT(harness.vals().getNodesAfter(ID{2}) == 0);
BEAST_EXPECT(harness.vals().getNodesAfter(Ledger::ID{2}) == 0);
BEAST_EXPECT(
AddOutcome::sameSeq == harness.add(a, Seq{2}, ID{20}));
AddOutcome::sameSeq == harness.add(a, Ledger::Seq{2}, Ledger::ID{20}));
// Old ID should be gone ...
BEAST_EXPECT(harness.vals().numTrustedForLedger(ID{2}) == 0);
BEAST_EXPECT(harness.vals().numTrustedForLedger(ID{20}) == 1);
BEAST_EXPECT(harness.vals().numTrustedForLedger(Ledger::ID{2}) == 0);
BEAST_EXPECT(harness.vals().numTrustedForLedger(Ledger::ID{20}) == 1);
{
// Should be the only trusted for ID{20}
auto trustedVals =
harness.vals().getTrustedForLedger(ID{20});
harness.vals().getTrustedForLedger(Ledger::ID{20});
BEAST_EXPECT(trustedVals.size() == 1);
BEAST_EXPECT(trustedVals[0].key() == a.currKey());
// ... and should be the only node after ID{2}
BEAST_EXPECT(harness.vals().getNodesAfter(ID{2}) == 1);
BEAST_EXPECT(harness.vals().getNodesAfter(Ledger::ID{2}) == 1);
}
@@ -492,35 +327,35 @@ class Validations_test : public beast::unit_test::suite
a.advanceKey();
BEAST_EXPECT(
AddOutcome::sameSeq == harness.add(a, Seq{2}, ID{20}));
AddOutcome::sameSeq == harness.add(a, Ledger::Seq{2}, Ledger::ID{20}));
{
// Still the only trusted validation for ID{20}
auto trustedVals =
harness.vals().getTrustedForLedger(ID{20});
harness.vals().getTrustedForLedger(Ledger::ID{20});
BEAST_EXPECT(trustedVals.size() == 1);
BEAST_EXPECT(trustedVals[0].key() == a.currKey());
// and still follows ID{2} since it was a re-issue
BEAST_EXPECT(harness.vals().getNodesAfter(ID{2}) == 1);
BEAST_EXPECT(harness.vals().getNodesAfter(Ledger::ID{2}) == 1);
}
}
{
// Processing validations out of order should ignore the older
harness.clock().advance(2s);
auto const val3 = a.validation(Seq{3}, ID{3});
auto const val3 = a.validation(Ledger::Seq{3}, Ledger::ID{3});
harness.clock().advance(4s);
auto const val4 = a.validation(Seq{4}, ID{4});
auto const val4 = a.validation(Ledger::Seq{4}, Ledger::ID{4});
BEAST_EXPECT(AddOutcome::current == harness.add(a, val4));
BEAST_EXPECT(AddOutcome::stale == harness.add(a, val3));
// re-issued should not be added
auto const val4reissue = a.validation(Seq{4}, ID{44});
auto const val4reissue =
a.validation(Ledger::Seq{4}, Ledger::ID{44});
BEAST_EXPECT(AddOutcome::stale == harness.add(a, val4reissue));
}
{
// Process validations out of order with shifted times
@@ -529,48 +364,32 @@ class Validations_test : public beast::unit_test::suite
harness.clock().advance(1h);
// Establish a new current validation
BEAST_EXPECT(
AddOutcome::current == harness.add(a, Seq{8}, ID{8}));
BEAST_EXPECT(AddOutcome::current ==
harness.add(a, Ledger::Seq{8}, Ledger::ID{8}));
// Process a validation that has "later" seq but early sign time
BEAST_EXPECT(
AddOutcome::stale ==
harness.add(a, Seq{9}, ID{9}, -1s, -1s));
BEAST_EXPECT(AddOutcome::stale ==
harness.add(a, Ledger::Seq{9}, Ledger::ID{9}, -1s, -1s));
// Process a validation that has an "earlier" seq but later sign time
BEAST_EXPECT(
AddOutcome::current ==
harness.add(a, Seq{7}, ID{7}, 1s, 1s));
// Process a validation that has an "earlier" seq but later sign
// time
BEAST_EXPECT(AddOutcome::current ==
harness.add(a, Ledger::Seq{7}, Ledger::ID{7}, 1s, 1s));
}
{
// Test stale on arrival validations
harness.clock().advance(1h);
BEAST_EXPECT(
AddOutcome::stale ==
harness.add(
a,
Seq{15},
ID{15},
-harness.parms().validationCURRENT_EARLY,
0s));
BEAST_EXPECT(AddOutcome::stale ==
harness.add(a, Ledger::Seq{15}, Ledger::ID{15},
-harness.parms().validationCURRENT_EARLY, 0s));
BEAST_EXPECT(
AddOutcome::stale ==
harness.add(
a,
Seq{15},
ID{15},
harness.parms().validationCURRENT_WALL,
0s));
BEAST_EXPECT(AddOutcome::stale ==
harness.add(a, Ledger::Seq{15}, Ledger::ID{15},
harness.parms().validationCURRENT_WALL, 0s));
BEAST_EXPECT(
AddOutcome::stale ==
harness.add(
a,
Seq{15},
ID{15},
0s,
BEAST_EXPECT(AddOutcome::stale ==
harness.add(a, Ledger::Seq{15}, Ledger::ID{15}, 0s,
harness.parms().validationCURRENT_LOCAL));
}
}
@@ -583,7 +402,8 @@ class Validations_test : public beast::unit_test::suite
TestHarness harness;
Node a = harness.makeNode();
BEAST_EXPECT(AddOutcome::current == harness.add(a, Seq{1}, ID{1}));
BEAST_EXPECT(AddOutcome::current ==
harness.add(a, Ledger::Seq{1}, Ledger::ID{1}));
harness.vals().currentTrusted();
BEAST_EXPECT(harness.stale().empty());
harness.clock().advance(harness.parms().validationCURRENT_LOCAL);
@@ -592,7 +412,7 @@ class Validations_test : public beast::unit_test::suite
harness.vals().currentTrusted();
BEAST_EXPECT(harness.stale().size() == 1);
BEAST_EXPECT(harness.stale()[0].ledgerID() == 1);
BEAST_EXPECT(harness.stale()[0].ledgerID() == Ledger::ID{1});
}
void
@@ -610,24 +430,29 @@ class Validations_test : public beast::unit_test::suite
// first round a,b,c agree, d has differing id
for (auto const& node : {a, b, c})
BEAST_EXPECT(
AddOutcome::current == harness.add(node, Seq{1}, ID{1}));
BEAST_EXPECT(AddOutcome::current == harness.add(d, Seq{1}, ID{10}));
BEAST_EXPECT(AddOutcome::current ==
harness.add(node, Ledger::Seq{1}, Ledger::ID{1}));
BEAST_EXPECT(AddOutcome::current ==
harness.add(d, Ledger::Seq{1}, Ledger::ID{10}));
// Nothing past ledger 1 yet
BEAST_EXPECT(harness.vals().getNodesAfter(ID{1}) == 0);
BEAST_EXPECT(harness.vals().getNodesAfter(Ledger::ID{1}) == 0);
harness.clock().advance(5s);
// a and b have the same prior id, but b has a different current id
// c is untrusted but on the same prior id
// d has a different prior id
BEAST_EXPECT(AddOutcome::current == harness.add(a, Seq{2}, ID{2}));
BEAST_EXPECT(AddOutcome::current == harness.add(b, Seq{2}, ID{20}));
BEAST_EXPECT(AddOutcome::current == harness.add(c, Seq{2}, ID{2}));
BEAST_EXPECT(AddOutcome::current == harness.add(d, Seq{2}, ID{2}));
BEAST_EXPECT(AddOutcome::current ==
harness.add(a, Ledger::Seq{2}, Ledger::ID{2}));
BEAST_EXPECT(AddOutcome::current ==
harness.add(b, Ledger::Seq{2}, Ledger::ID{20}));
BEAST_EXPECT(AddOutcome::current ==
harness.add(c, Ledger::Seq{2}, Ledger::ID{2}));
BEAST_EXPECT(AddOutcome::current ==
harness.add(d, Ledger::Seq{2}, Ledger::ID{2}));
BEAST_EXPECT(harness.vals().getNodesAfter(ID{1}) == 2);
BEAST_EXPECT(harness.vals().getNodesAfter(Ledger::ID{1}) == 2);
}
void
@@ -640,24 +465,30 @@ class Validations_test : public beast::unit_test::suite
Node a = harness.makeNode(), b = harness.makeNode();
b.untrust();
BEAST_EXPECT(AddOutcome::current == harness.add(a, Seq{1}, ID{1}));
BEAST_EXPECT(AddOutcome::current == harness.add(b, Seq{1}, ID{3}));
BEAST_EXPECT(AddOutcome::current ==
harness.add(a, Ledger::Seq{1}, Ledger::ID{1}));
BEAST_EXPECT(AddOutcome::current ==
harness.add(b, Ledger::Seq{1}, Ledger::ID{3}));
// Only a is trusted
BEAST_EXPECT(harness.vals().currentTrusted().size() == 1);
BEAST_EXPECT(harness.vals().currentTrusted()[0].ledgerID() == ID{1});
BEAST_EXPECT(harness.vals().currentTrusted()[0].seq() == Seq{1});
BEAST_EXPECT(
harness.vals().currentTrusted()[0].ledgerID() == Ledger::ID{1});
BEAST_EXPECT(
harness.vals().currentTrusted()[0].seq() == Ledger::Seq{1});
harness.clock().advance(3s);
for (auto const& node : {a, b})
BEAST_EXPECT(
AddOutcome::current == harness.add(node, Seq{2}, ID{2}));
BEAST_EXPECT(AddOutcome::current ==
harness.add(node, Ledger::Seq{2}, Ledger::ID{2}));
// New validation for a
BEAST_EXPECT(harness.vals().currentTrusted().size() == 1);
BEAST_EXPECT(harness.vals().currentTrusted()[0].ledgerID() == ID{2});
BEAST_EXPECT(harness.vals().currentTrusted()[0].seq() == Seq{2});
BEAST_EXPECT(
harness.vals().currentTrusted()[0].ledgerID() == Ledger::ID{2});
BEAST_EXPECT(
harness.vals().currentTrusted()[0].seq() == Ledger::Seq{2});
// Pass enough time for it to go stale
harness.clock().advance(harness.parms().validationCURRENT_LOCAL);
@@ -675,14 +506,13 @@ class Validations_test : public beast::unit_test::suite
b.untrust();
for (auto const& node : {a, b})
BEAST_EXPECT(
AddOutcome::current == harness.add(node, Seq{1}, ID{1}));
BEAST_EXPECT(AddOutcome::current ==
harness.add(node, Ledger::Seq{1}, Ledger::ID{1}));
{
hash_set<std::string> const expectedKeys = {a.masterKey(),
b.masterKey()};
BEAST_EXPECT(
harness.vals().getCurrentPublicKeys() == expectedKeys);
hash_set<PeerKey> const expectedKeys = {
a.masterKey(), b.masterKey()};
BEAST_EXPECT(harness.vals().getCurrentPublicKeys() == expectedKeys);
}
harness.clock().advance(3s);
@@ -692,14 +522,13 @@ class Validations_test : public beast::unit_test::suite
b.advanceKey();
for (auto const& node : {a, b})
BEAST_EXPECT(
AddOutcome::current == harness.add(node, Seq{2}, ID{2}));
BEAST_EXPECT(AddOutcome::current ==
harness.add(node, Ledger::Seq{2}, Ledger::ID{2}));
{
hash_set<std::string> const expectedKeys = {a.masterKey(),
b.masterKey()};
BEAST_EXPECT(
harness.vals().getCurrentPublicKeys() == expectedKeys);
hash_set<PeerKey> const expectedKeys = {
a.masterKey(), b.masterKey()};
BEAST_EXPECT(harness.vals().getCurrentPublicKeys() == expectedKeys);
}
// Pass enough time for them to go stale
@@ -727,61 +556,70 @@ class Validations_test : public beast::unit_test::suite
// goldilocks on seq 2, but is not trusted
for (auto const& node : {baby, papa, mama, goldilocks})
BEAST_EXPECT(
AddOutcome::current == harness.add(node, Seq{1}, ID{1}));
BEAST_EXPECT(AddOutcome::current ==
harness.add(node, Ledger::Seq{1}, Ledger::ID{1}));
harness.clock().advance(1s);
for (auto const& node : {baby, mama, goldilocks})
BEAST_EXPECT(
AddOutcome::current == harness.add(node, Seq{2}, ID{2}));
BEAST_EXPECT(AddOutcome::current ==
harness.add(node, Ledger::Seq{2}, Ledger::ID{2}));
harness.clock().advance(1s);
BEAST_EXPECT(AddOutcome::current == harness.add(mama, Seq{3}, ID{3}));
BEAST_EXPECT(AddOutcome::current ==
harness.add(mama, Ledger::Seq{3}, Ledger::ID{3}));
{
// Allow slippage that treats all trusted as the current ledger
auto res = harness.vals().currentTrustedDistribution(
ID{2}, // Current ledger
ID{1}, // Prior ledger
Seq{0}); // No cutoff
Ledger::ID{2}, // Current ledger
Ledger::ID{1}, // Prior ledger
Ledger::Seq{0}); // No cutoff
BEAST_EXPECT(res.size() == 1);
BEAST_EXPECT(res[ID{2}] == 3);
BEAST_EXPECT(res[Ledger::ID{2}] == 3);
BEAST_EXPECT(
getPreferredLedger(Ledger::ID{2}, res) == Ledger::ID{2});
}
{
// Don't allow slippage back for prior ledger
auto res = harness.vals().currentTrustedDistribution(
ID{2}, // Current ledger
ID{0}, // No prior ledger
Seq{0}); // No cutoff
Ledger::ID{2}, // Current ledger
Ledger::ID{0}, // No prior ledger
Ledger::Seq{0}); // No cutoff
BEAST_EXPECT(res.size() == 2);
BEAST_EXPECT(res[ID{2}] == 2);
BEAST_EXPECT(res[ID{1}] == 1);
BEAST_EXPECT(res[Ledger::ID{2}] == 2);
BEAST_EXPECT(res[Ledger::ID{1}] == 1);
BEAST_EXPECT(
getPreferredLedger(Ledger::ID{2}, res) == Ledger::ID{2});
}
{
// Don't allow any slips
auto res = harness.vals().currentTrustedDistribution(
ID{0}, // No current ledger
ID{0}, // No prior ledger
Seq{0}); // No cutoff
Ledger::ID{0}, // No current ledger
Ledger::ID{0}, // No prior ledger
Ledger::Seq{0}); // No cutoff
BEAST_EXPECT(res.size() == 3);
BEAST_EXPECT(res[ID{1}] == 1);
BEAST_EXPECT(res[ID{2}] == 1);
BEAST_EXPECT(res[ID{3}] == 1);
BEAST_EXPECT(res[Ledger::ID{1}] == 1);
BEAST_EXPECT(res[Ledger::ID{2}] == 1);
BEAST_EXPECT(res[Ledger::ID{3}] == 1);
BEAST_EXPECT(
getPreferredLedger(Ledger::ID{0}, res) == Ledger::ID{3});
}
{
// Cutoff old sequence numbers
auto res = harness.vals().currentTrustedDistribution(
ID{2}, // current ledger
ID{1}, // prior ledger
Seq{2}); // Only sequence 2 or later
Ledger::ID{2}, // current ledger
Ledger::ID{1}, // prior ledger
Ledger::Seq{2}); // Only sequence 2 or later
BEAST_EXPECT(res.size() == 1);
BEAST_EXPECT(res[ID{2}] == 2);
BEAST_EXPECT(res[Ledger::ID{2}] == 2);
BEAST_EXPECT(
getPreferredLedger(Ledger::ID{2}, res) == Ledger::ID{2});
}
}
@@ -806,7 +644,7 @@ class Validations_test : public beast::unit_test::suite
b.setLoadFee(1);
c.setLoadFee(12);
hash_map<ID, std::vector<Validation>> trustedValidations;
hash_map<Ledger::ID, std::vector<Validation>> trustedValidations;
//----------------------------------------------------------------------
// checkers
@@ -820,11 +658,9 @@ class Validations_test : public beast::unit_test::suite
auto const& id = it.first;
auto const& expectedValidations = it.second;
BEAST_EXPECT(
harness.vals().numTrustedForLedger(id) ==
BEAST_EXPECT(harness.vals().numTrustedForLedger(id) ==
expectedValidations.size());
BEAST_EXPECT(
sorted(harness.vals().getTrustedForLedger(id)) ==
BEAST_EXPECT(sorted(harness.vals().getTrustedForLedger(id)) ==
sorted(expectedValidations));
std::vector<NetClock::time_point> expectedTimes;
@@ -836,30 +672,28 @@ class Validations_test : public beast::unit_test::suite
expectedFees.push_back(val.loadFee().value_or(baseFee));
}
BEAST_EXPECT(
sorted(harness.vals().fees(id, baseFee)) ==
BEAST_EXPECT(sorted(harness.vals().fees(id, baseFee)) ==
sorted(expectedFees));
BEAST_EXPECT(
sorted(harness.vals().getTrustedValidationTimes(id)) ==
sorted(expectedTimes));
BEAST_EXPECT(sorted(harness.vals().getTrustedValidationTimes(
id)) == sorted(expectedTimes));
}
};
//----------------------------------------------------------------------
// Add a dummy ID to cover unknown ledger identifiers
trustedValidations[ID{100}] = {};
trustedValidations[Ledger::ID{100}] = {};
// first round a,b,c agree, d differs
for (auto const& node : {a, b, c})
{
auto const val = node.validation(Seq{1}, ID{1});
auto const val = node.validation(Ledger::Seq{1}, Ledger::ID{1});
BEAST_EXPECT(AddOutcome::current == harness.add(node, val));
if (val.trusted())
trustedValidations[val.ledgerID()].emplace_back(val);
}
{
auto const val = d.validation(Seq{1}, ID{11});
auto const val = d.validation(Ledger::Seq{1}, Ledger::ID{11});
BEAST_EXPECT(AddOutcome::current == harness.add(d, val));
trustedValidations[val.ledgerID()].emplace_back(val);
}
@@ -868,13 +702,13 @@ class Validations_test : public beast::unit_test::suite
// second round, a,b,c move to ledger 2, d now thinks ledger 1
for (auto const& node : {a, b, c})
{
auto const val = node.validation(Seq{2}, ID{2});
auto const val = node.validation(Ledger::Seq{2}, Ledger::ID{2});
BEAST_EXPECT(AddOutcome::current == harness.add(node, val));
if (val.trusted())
trustedValidations[val.ledgerID()].emplace_back(val);
}
{
auto const val = d.validation(Seq{2}, ID{1});
auto const val = d.validation(Ledger::Seq{2}, Ledger::ID{1});
BEAST_EXPECT(AddOutcome::current == harness.add(d, val));
trustedValidations[val.ledgerID()].emplace_back(val);
}
@@ -890,11 +724,12 @@ class Validations_test : public beast::unit_test::suite
TestHarness harness;
Node a = harness.makeNode();
BEAST_EXPECT(AddOutcome::current == harness.add(a, Seq{1}, ID{1}));
BEAST_EXPECT(harness.vals().numTrustedForLedger(ID{1}));
BEAST_EXPECT(AddOutcome::current ==
harness.add(a, Ledger::Seq{1}, Ledger::ID{1}));
BEAST_EXPECT(harness.vals().numTrustedForLedger(Ledger::ID{1}));
harness.clock().advance(harness.parms().validationSET_EXPIRES);
harness.vals().expire();
BEAST_EXPECT(!harness.vals().numTrustedForLedger(ID{1}));
BEAST_EXPECT(!harness.vals().numTrustedForLedger(Ledger::ID{1}));
}
void
@@ -909,26 +744,22 @@ class Validations_test : public beast::unit_test::suite
c = harness.makeNode();
c.untrust();
hash_map<std::string, Validation> expected;
Validation staleA;
hash_map<PeerKey, Validation> expected;
for (auto const& node : {a, b, c})
{
auto const val = node.validation(Seq{1}, ID{1});
auto const val = node.validation(Ledger::Seq{1}, Ledger::ID{1});
BEAST_EXPECT(AddOutcome::current == harness.add(node, val));
if (node.nodeID() == a.nodeID())
{
staleA = val;
}
else
expected[node.masterKey()] = val;
expected.emplace(node.masterKey(), val);
}
Validation staleA = expected.find(a.masterKey())->second;
// Send in a new validation for a, saving the new one into the expected
// map after setting the proper prior ledger ID it replaced
harness.clock().advance(1s);
auto newVal = a.validation(Seq{2}, ID{2});
auto newVal = a.validation(Ledger::Seq{2}, Ledger::ID{2});
BEAST_EXPECT(AddOutcome::current == harness.add(a, newVal));
expected[a.masterKey()] = newVal;
expected.find(a.masterKey())->second = newVal;
// Now flush
harness.vals().flush();
@@ -943,6 +774,57 @@ class Validations_test : public beast::unit_test::suite
BEAST_EXPECT(flushed == expected);
}
void
testGetPreferredLedger()
{
using Distribution = hash_map<Ledger::ID, std::uint32_t>;
{
Ledger::ID const current{1};
Distribution dist;
BEAST_EXPECT(getPreferredLedger(current, dist) == current);
}
{
Ledger::ID const current{1};
Distribution dist;
dist[Ledger::ID{2}] = 2;
BEAST_EXPECT(getPreferredLedger(current, dist) == Ledger::ID{2});
}
{
Ledger::ID const current{1};
Distribution dist;
dist[Ledger::ID{1}] = 1;
dist[Ledger::ID{2}] = 2;
BEAST_EXPECT(getPreferredLedger(current, dist) == Ledger::ID{2});
}
{
Ledger::ID const current{1};
Distribution dist;
dist[Ledger::ID{1}] = 2;
dist[Ledger::ID{2}] = 2;
BEAST_EXPECT(getPreferredLedger(current, dist) == current);
}
{
Ledger::ID const current{2};
Distribution dist;
dist[Ledger::ID{1}] = 2;
dist[Ledger::ID{2}] = 2;
BEAST_EXPECT(getPreferredLedger(current, dist) == current);
}
{
Ledger::ID const current{1};
Distribution dist;
dist[Ledger::ID{2}] = 2;
dist[Ledger::ID{3}] = 2;
BEAST_EXPECT(getPreferredLedger(current, dist) == Ledger::ID{3});
}
}
void
run() override
{
@@ -955,9 +837,11 @@ class Validations_test : public beast::unit_test::suite
testTrustedByLedgerFunctions();
testExpire();
testFlush();
testGetPreferredLedger();
}
};
BEAST_DEFINE_TESTSUITE(Validations, consensus, ripple);
} // namespace test
} // namespace ripple
} // csf
} // test
} // ripple

View File

@@ -17,10 +17,20 @@
*/
//==============================================================================
#include <test/csf/Tx.h>
#include <test/csf/Ledger.h>
#include <test/csf/BasicNetwork.h>
#include <test/csf/Histogram.h>
#include <test/csf/Peer.h>
#include <test/csf/UNL.h>
#include <test/csf/PeerGroup.h>
#include <test/csf/Proposal.h>
#include <test/csf/Scheduler.h>
#include <test/csf/Sim.h>
#include <test/csf/Peer.h>
#include <test/csf/SimTime.h>
#include <test/csf/TrustGraph.h>
#include <test/csf/Tx.h>
#include <test/csf/collectors.h>
#include <test/csf/Digraph.h>
#include <test/csf/events.h>
#include <test/csf/ledgers.h>
#include <test/csf/random.h>
#include <test/csf/submitters.h>
#include <test/csf/timers.h>

View File

@@ -20,24 +20,8 @@
#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 <cassert>
#include <cstdint>
#include <deque>
#include <iomanip>
#include <memory>
#include <type_traits>
#include <unordered_map>
#include <unordered_set>
#include <test/csf/Scheduler.h>
#include <test/csf/Digraph.h>
namespace ripple {
namespace test {
@@ -64,16 +48,18 @@ namespace csf {
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.
at either end of the connection will not be delivered.
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.
When creating the Peer set, the caller needs to provide a
Scheduler object for managing the the timing and delivery
of messages. After constructing the network, and establishing
connections, the caller uses the scheduler's step_* functions
to drive messages through the network.
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,
The graph of peers and connections is internally represented
using Digraph<Peer,BasicNetwork::link_type>. Clients have
const access to that graph to perform additional operations not
directly provided by BasicNetwork.
Peer Requirements:
@@ -91,198 +77,40 @@ namespace csf {
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 clock_type = Scheduler::clock_type;
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_)
bool inbound = false;
duration delay{};
time_point established{};
link_type() = default;
link_type(bool inbound_, duration delay_, time_point established_)
: inbound(inbound_), delay(delay_), established(established_)
{
}
};
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_;
Scheduler& scheduler;
Digraph<Peer, link_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;
BasicNetwork(Scheduler& s);
/** Connect two peers.
@@ -327,13 +155,6 @@ public:
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:
@@ -356,335 +177,33 @@ public:
void
send(Peer const& from, Peer const& to, Function&& f);
// Used to cancel timers
struct cancel_token;
/** Deliver a timer notification.
/** Return the range of active links.
Effects:
When the network time is reached,
the function will be called with
no arguments.
@return A random access range over Digraph::Edge instances
*/
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
links(Peer const& from)
{
auto m = &*iter;
++iter;
m->~msg();
alloc_.dealloc(m, 1);
return links_.outEdges(from);
}
/** Return the underlying digraph
*/
Digraph<Peer, link_type> const &
graph() const
{
return links_;
}
};
//------------------------------------------------------------------------------
template <class Peer>
BasicNetwork<Peer>::BasicNetwork(Scheduler& s) : scheduler(s)
{
}
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,
@@ -692,169 +211,49 @@ BasicNetwork<Peer>::connect(
{
if (to == from)
return false;
using namespace std;
if (!links_[from].emplace(to, link_type{false, delay}).second)
time_point const now = scheduler.now();
if(!links_.connect(from, to, link_type{false, delay, now}))
return false;
auto const result = links_[to].emplace(from, link_type{true, delay});
auto const result = links_.connect(to, from, link_type{true, delay, now});
(void)result;
assert(result.second);
assert(result);
return true;
}
template <class Peer>
bool
inline bool
BasicNetwork<Peer>::disconnect(Peer const& peer1, Peer const& peer2)
{
if (links_[peer1].erase(peer2) == 0)
if (! links_.disconnect(peer1, peer2))
return false;
auto const n = links_[peer2].erase(peer1);
(void)n;
assert(n);
queue_.remove(peer1, peer2);
bool r = links_.disconnect(peer2, peer1);
(void)r;
assert(r);
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));
auto link = links_.edge(from,to);
if(!link)
return;
time_point const sent = scheduler.now();
scheduler.in(
link->delay,
[ from, to, sent, f = std::forward<Function>(f), this ] {
// only process if still connected and connection was
// not broken since the message was sent
auto link = links_.edge(from, to);
if (link && link->established <= sent)
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
} // namespace csf
} // namespace test
} // namespace ripple
#endif

View File

@@ -21,6 +21,7 @@
#include <ripple/beast/unit_test.h>
#include <set>
#include <test/csf/BasicNetwork.h>
#include <test/csf/Scheduler.h>
#include <vector>
namespace ripple {
@@ -43,20 +44,20 @@ public:
template <class Net>
void
start(Net& net)
start(csf::Scheduler& scheduler, Net& net)
{
using namespace std::chrono_literals;
auto t = net.timer(1s, [&] { set.insert(0); });
auto t = scheduler.in(1s, [&] { set.insert(0); });
if (id == 0)
{
for (auto const& link : net.links(this))
net.send(this, link.to, [&, to = link.to ] {
net.send(this, link.target, [&, to = link.target ] {
to->receive(net, this, 1);
});
}
else
{
net.cancel(t);
scheduler.cancel(t);
}
}
@@ -69,7 +70,7 @@ public:
if (m < 5)
{
for (auto const& link : net.links(this))
net.send(this, link.to, [&, mm = m, to = link.to ] {
net.send(this, link.target, [&, mm = m, to = link.target ] {
to->receive(net, this, mm);
});
}
@@ -77,29 +78,26 @@ public:
};
void
run() override
testNetwork()
{
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;
csf::Scheduler scheduler;
csf::BasicNetwork<Peer*> net(scheduler);
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));
peer.start(scheduler, net);
BEAST_EXPECT(scheduler.step_for(0s));
BEAST_EXPECT(scheduler.step_for(1s));
BEAST_EXPECT(scheduler.step());
BEAST_EXPECT(!scheduler.step());
BEAST_EXPECT(!scheduler.step_for(1s));
net.send(&pv[0], &pv[1], [] {});
net.send(&pv[1], &pv[0], [] {});
BEAST_EXPECT(net.disconnect(&pv[0], &pv[1]));
@@ -109,16 +107,46 @@ public:
auto const links = net.links(&pv[1]);
if (links.empty())
break;
BEAST_EXPECT(links[0].disconnect());
BEAST_EXPECT(net.disconnect(&pv[1], links[0].target));
}
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, [] {});
}
void
testDisconnect()
{
using namespace std::chrono_literals;
csf::Scheduler scheduler;
csf::BasicNetwork<int> net(scheduler);
BEAST_EXPECT(net.connect(0, 1, 1s));
BEAST_EXPECT(net.connect(0, 2, 2s));
std::set<int> delivered;
net.send(0, 1, [&]() { delivered.insert(1); });
net.send(0, 2, [&]() { delivered.insert(2); });
scheduler.in(1000ms, [&]() { BEAST_EXPECT(net.disconnect(0, 2)); });
scheduler.in(1100ms, [&]() { BEAST_EXPECT(net.connect(0, 2)); });
scheduler.step();
// only the first message is delivered because the disconnect at 1 s
// purges all pending messages from 0 to 2
BEAST_EXPECT(delivered == std::set<int>({1}));
}
void
run() override
{
testNetwork();
testDisconnect();
}
};
BEAST_DEFINE_TESTSUITE(BasicNetwork, test, ripple);
} // test
} // ripple
} // namespace test
} // namespace ripple

346
src/test/csf/CollectorRef.h Normal file
View File

@@ -0,0 +1,346 @@
//------------------------------------------------------------------------------
/*
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_COLLECTOREF_H_INCLUDED
#define RIPPLE_TEST_CSF_COLLECTOREF_H_INCLUDED
#include <test/csf/events.h>
#include <test/csf/SimTime.h>
namespace ripple {
namespace test {
namespace csf {
/** Holds a type-erased reference to an arbitray collector.
A collector is any class that implements
on(NodeID, SimTime, Event)
for all events emitted by a Peer.
This class is used to type-erase the actual collector used by each peer in
the simulation. The idea is to compose complicated and typed collectors using
the helpers in collectors.h, then only type erase at the higher-most level
when adding to the simulation.
The example code below demonstrates the reason for storing the collector
as a reference. The collector's lifetime will generally be be longer than
the simulation; perhaps several simulations are run for a single collector
instance. The collector potentially stores lots of data as well, so the
simulation needs to point to the single instance, rather than requiring
collectors to manage copying that data efficiently in their design.
@code
// Initialize a specific collector that might write to a file.
SomeFancyCollector collector{"out.file"};
// Setup your simulation
Sim sim(trustgraph, topology, collector);
// Run the simulation
sim.run(100);
// do any reported related to the collector
collector.report();
@endcode
@note If a new event type is added, it needs to be added to the interfaces
below.
*/
class CollectorRef
{
using tp = SimTime;
// Interface for type-erased collector instance
struct ICollector
{
virtual ~ICollector() = default;
virtual void
on(PeerID node, tp when, Share<Tx> const&) = 0;
virtual void
on(PeerID node, tp when, Share<TxSet> const&) = 0;
virtual void
on(PeerID node, tp when, Share<Validation> const&) = 0;
virtual void
on(PeerID node, tp when, Share<Ledger> const&) = 0;
virtual void
on(PeerID node, tp when, Share<Proposal> const&) = 0;
virtual void
on(PeerID node, tp when, Receive<Tx> const&) = 0;
virtual void
on(PeerID node, tp when, Receive<TxSet> const&) = 0;
virtual void
on(PeerID node, tp when, Receive<Validation> const&) = 0;
virtual void
on(PeerID node, tp when, Receive<Ledger> const&) = 0;
virtual void
on(PeerID node, tp when, Receive<Proposal> const&) = 0;
virtual void
on(PeerID node, tp when, Relay<Tx> const&) = 0;
virtual void
on(PeerID node, tp when, Relay<TxSet> const&) = 0;
virtual void
on(PeerID node, tp when, Relay<Validation> const&) = 0;
virtual void
on(PeerID node, tp when, Relay<Ledger> const&) = 0;
virtual void
on(PeerID node, tp when, Relay<Proposal> const&) = 0;
virtual void
on(PeerID node, tp when, SubmitTx const&) = 0;
virtual void
on(PeerID node, tp when, StartRound const&) = 0;
virtual void
on(PeerID node, tp when, CloseLedger const&) = 0;
virtual void
on(PeerID node, tp when, AcceptLedger const&) = 0;
virtual void
on(PeerID node, tp when, WrongPrevLedger const&) = 0;
virtual void
on(PeerID node, tp when, FullyValidateLedger const&) = 0;
};
// Bridge between type-ful collector T and type erased instance
template <class T>
class Any final : public ICollector
{
T & t_;
public:
Any(T & t) : t_{t}
{
}
// Can't copy
Any(Any const & ) = delete;
Any& operator=(Any const & ) = delete;
Any(Any && ) = default;
Any& operator=(Any && ) = default;
virtual void
on(PeerID node, tp when, Share<Tx> const& e) override
{
t_.on(node, when, e);
}
virtual void
on(PeerID node, tp when, Share<TxSet> const& e) override
{
t_.on(node, when, e);
}
virtual void
on(PeerID node, tp when, Share<Validation> const& e) override
{
t_.on(node, when, e);
}
virtual void
on(PeerID node, tp when, Share<Ledger> const& e) override
{
t_.on(node, when, e);
}
virtual void
on(PeerID node, tp when, Share<Proposal> const& e) override
{
t_.on(node, when, e);
}
void
on(PeerID node, tp when, Receive<Tx> const& e) override
{
t_.on(node, when, e);
}
virtual void
on(PeerID node, tp when, Receive<TxSet> const& e) override
{
t_.on(node, when, e);
}
virtual void
on(PeerID node, tp when, Receive<Validation> const& e) override
{
t_.on(node, when, e);
}
virtual void
on(PeerID node, tp when, Receive<Ledger> const& e) override
{
t_.on(node, when, e);
}
virtual void
on(PeerID node, tp when, Receive<Proposal> const& e) override
{
t_.on(node, when, e);
}
void
on(PeerID node, tp when, Relay<Tx> const& e) override
{
t_.on(node, when, e);
}
virtual void
on(PeerID node, tp when, Relay<TxSet> const& e) override
{
t_.on(node, when, e);
}
virtual void
on(PeerID node, tp when, Relay<Validation> const& e) override
{
t_.on(node, when, e);
}
virtual void
on(PeerID node, tp when, Relay<Ledger> const& e) override
{
t_.on(node, when, e);
}
virtual void
on(PeerID node, tp when, Relay<Proposal> const& e) override
{
t_.on(node, when, e);
}
virtual void
on(PeerID node, tp when, SubmitTx const& e) override
{
t_.on(node, when, e);
}
virtual void
on(PeerID node, tp when, StartRound const& e) override
{
t_.on(node, when, e);
}
virtual void
on(PeerID node, tp when, CloseLedger const& e) override
{
t_.on(node, when, e);
}
virtual void
on(PeerID node, tp when, AcceptLedger const& e) override
{
t_.on(node, when, e);
}
virtual void
on(PeerID node, tp when, WrongPrevLedger const& e) override
{
t_.on(node, when, e);
}
virtual void
on(PeerID node, tp when, FullyValidateLedger const& e) override
{
t_.on(node, when, e);
}
};
std::unique_ptr<ICollector> impl_;
public:
template <class T>
CollectorRef(T& t) : impl_{new Any<T>(t)}
{
}
// Non-copyable
CollectorRef(CollectorRef const& c) = delete;
CollectorRef& operator=(CollectorRef& c) = delete;
CollectorRef(CollectorRef&&) = default;
CollectorRef& operator=(CollectorRef&&) = default;
template <class E>
void
on(PeerID node, tp when, E const& e)
{
impl_->on(node, when, e);
}
};
/** A container of CollectorRefs
A set of CollectorRef instances that process the same events. An event is
processed by collectors in the order the collectors were added.
This class type-erases the collector instances. By contract, the
Collectors/collectors class/helper in collectors.h are not type erased and
offer an opportunity for type transformations and combinations with
improved compiler optimizations.
*/
class CollectorRefs
{
std::vector<CollectorRef> collectors_;
public:
template <class Collector>
void add(Collector & collector)
{
collectors_.emplace_back(collector);
}
template <class E>
void
on(PeerID node, SimTime when, E const& e)
{
for (auto & c : collectors_)
{
c.on(node, when, e);
}
}
};
} // namespace csf
} // namespace test
} // namespace ripple
#endif

253
src/test/csf/Digraph.h Normal file
View File

@@ -0,0 +1,253 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012-2017 Ripple Labs Inc
Permission target 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_DIGRAPH_H_INCLUDED
#define RIPPLE_TEST_CSF_DIGRAPH_H_INCLUDED
#include <boost/container/flat_map.hpp>
#include <boost/optional.hpp>
#include <boost/range/adaptor/transformed.hpp>
#include <boost/range/iterator_range.hpp>
#include <fstream>
#include <unordered_map>
#include <type_traits>
namespace ripple {
namespace test {
namespace csf {
namespace detail {
// Dummy class when no edge data needed for graph
struct NoEdgeData
{
};
} // namespace detail
/** Directed graph
Basic directed graph that uses an adjacency list to represent out edges.
Instances of Vertex uniquely identify vertices in the graph. Instances of
EdgeData is any data to store in the edge connecting two vertices.
Both Vertex and EdgeData should be lightweight and cheap to copy.
*/
template <class Vertex, class EdgeData = detail::NoEdgeData>
class Digraph
{
using Links = boost::container::flat_map<Vertex, EdgeData>;
using Graph = boost::container::flat_map<Vertex, Links>;
Graph graph_;
// Allows returning empty iterables for unknown vertices
Links empty;
public:
/** Connect two vertices
@param source The source vertex
@param target The target vertex
@param e The edge data
@return true if the edge was created
*/
bool
connect(Vertex source, Vertex target, EdgeData e)
{
return graph_[source].emplace(target, e).second;
}
/** Connect two vertices using default constructed edge data
@param source The source vertex
@param target The target vertex
@return true if the edge was created
*/
bool
connect(Vertex source, Vertex target)
{
return connect(source, target, EdgeData{});
}
/** Disconnect two vertices
@param source The source vertex
@param target The target vertex
@return true if an edge was removed
If source is not connected to target, this function does nothing.
*/
bool
disconnect(Vertex source, Vertex target)
{
auto it = graph_.find(source);
if (it != graph_.end())
{
return it->second.erase(target) > 0;
}
return false;
}
/** Return edge data between two vertices
@param source The source vertex
@param target The target vertex
@return optional<Edge> which is boost::none if no edge exists
*/
boost::optional<EdgeData>
edge(Vertex source, Vertex target) const
{
auto it = graph_.find(source);
if (it != graph_.end())
{
auto edgeIt = it->second.find(target);
if (edgeIt != it->second.end())
return edgeIt->second;
}
return boost::none;
}
/** Check if two vertices are connected
@param source The source vertex
@param target The target vertex
@return true if the source has an out edge to target
*/
bool
connected(Vertex source, Vertex target) const
{
return edge(source, target) != boost::none;
}
/** Range over vertices in the graph
@return A boost transformed range over the vertices with out edges in the
graph
*/
auto
outVertices() const
{
return boost::adaptors::transform(
graph_,
[](typename Graph::value_type const& v) { return v.first; });
}
/** Range over target vertices
@param source The source vertex
@return A boost transformed range over the target vertices of source.
*/
auto
outVertices(Vertex source) const
{
auto transform = [](typename Links::value_type const& link) {
return link.first;
};
auto it = graph_.find(source);
if (it != graph_.end())
return boost::adaptors::transform(it->second, transform);
return boost::adaptors::transform(empty, transform);
}
/** Vertices and data associated with an Edge
*/
struct Edge
{
Vertex source;
Vertex target;
EdgeData data;
};
/** Range of out edges
@param source The source vertex
@return A boost transformed range of Edge type for all out edges of
source.
*/
auto
outEdges(Vertex source) const
{
auto transform = [source](typename Links::value_type const& link) {
return Edge{source, link.first, link.second};
};
auto it = graph_.find(source);
if (it != graph_.end())
return boost::adaptors::transform(it->second, transform);
return boost::adaptors::transform(empty, transform);
}
/** Vertex out-degree
@param source The source vertex
@return The number of outgoing edges from source
*/
std::size_t
outDegree(Vertex source) const
{
auto it = graph_.find(source);
if (it != graph_.end())
return it->second.size();
return 0;
}
/** Save GraphViz dot file
Save a GraphViz dot description of the graph
@param fileName The output file (creates)
@param vertexName A invokable T vertexName(Vertex const &) that
returns the name target use for the vertex in the file
T must be be ostream-able
*/
template <class VertexName>
void
saveDot(std::ostream & out, VertexName&& vertexName) const
{
out << "digraph {\n";
for (auto const& vData : graph_)
{
auto const fromName = vertexName(vData.first);
for (auto const& eData : vData.second)
{
auto const toName = vertexName(eData.first);
out << fromName << " -> " << toName << ";\n";
}
}
out << "}\n";
}
template <class VertexName>
void
saveDot(std::string const& fileName, VertexName&& vertexName) const
{
std::ofstream out(fileName);
saveDot(out, std::forward<VertexName>(vertexName));
}
};
} // namespace csf
} // namespace test
} // namespace ripple
#endif

View File

@@ -0,0 +1,100 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2017 Ripple Labs Inc.
Permission target 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/beast/unit_test.h>
#include <test/csf/Digraph.h>
#include <vector>
#include <string>
namespace ripple {
namespace test {
class Digraph_test : public beast::unit_test::suite
{
public:
void
run() override
{
using namespace csf;
using Graph = Digraph<char,std::string>;
Graph graph;
BEAST_EXPECT(!graph.connected('a', 'b'));
BEAST_EXPECT(!graph.edge('a', 'b'));
BEAST_EXPECT(!graph.disconnect('a', 'b'));
BEAST_EXPECT(graph.connect('a', 'b', "foobar"));
BEAST_EXPECT(graph.connected('a', 'b'));
BEAST_EXPECT(*graph.edge('a', 'b') == "foobar");
BEAST_EXPECT(!graph.connect('a', 'b', "repeat"));
BEAST_EXPECT(graph.disconnect('a', 'b'));
BEAST_EXPECT(graph.connect('a', 'b', "repeat"));
BEAST_EXPECT(graph.connected('a', 'b'));
BEAST_EXPECT(*graph.edge('a', 'b') == "repeat");
BEAST_EXPECT(graph.connect('a', 'c', "tree"));
{
std::vector<std::tuple<char, char, std::string>> edges;
for (auto const & edge : graph.outEdges('a'))
{
edges.emplace_back(edge.source, edge.target, edge.data);
}
std::vector<std::tuple<char, char, std::string>> expected;
expected.emplace_back('a', 'b', "repeat");
expected.emplace_back('a', 'c', "tree");
BEAST_EXPECT(edges == expected);
BEAST_EXPECT(graph.outDegree('a') == expected.size());
}
BEAST_EXPECT(graph.outEdges('r').size() == 0);
BEAST_EXPECT(graph.outDegree('r') == 0);
BEAST_EXPECT(graph.outDegree('c') == 0);
// only 'a' has out edges
BEAST_EXPECT(graph.outVertices().size() == 1);
std::vector<char> expected = {'b','c'};
BEAST_EXPECT((graph.outVertices('a') == expected));
BEAST_EXPECT(graph.outVertices('b').size() == 0);
BEAST_EXPECT(graph.outVertices('c').size() == 0);
BEAST_EXPECT(graph.outVertices('r').size() == 0);
std::stringstream ss;
graph.saveDot(ss, [](char v) { return v;});
std::string expectedDot = "digraph {\n"
"a -> b;\n"
"a -> c;\n"
"}\n";
BEAST_EXPECT(ss.str() == expectedDot);
}
};
BEAST_DEFINE_TESTSUITE(Digraph, test, ripple);
} // namespace test
} // namespace ripple

131
src/test/csf/Histogram.h Normal file
View File

@@ -0,0 +1,131 @@
//------------------------------------------------------------------------------
/*
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_HISTOGRAM_H_INCLUDED
#define RIPPLE_TEST_CSF_HISTOGRAM_H_INCLUDED
#include <map>
#include <chrono>
#include <algorithm>
namespace ripple {
namespace test {
namespace csf {
/** Basic histogram.
Histogram for a type `T` that satisfies
- Default construction: T{}
- Comparison : T a, b; bool res = a < b
- Addition: T a, b; T c = a + b;
- Multiplication : T a, std::size_t b; T c = a * b;
- Divison: T a; std::size_t b; T c = a/b;
*/
template <class T, class Compare = std::less<T>>
class Histogram
{
// TODO: Consider logarithimic bins around expected median if this becomes
// unscaleable
std::map<T, std::size_t, Compare> counts_;
std::size_t samples = 0;
public:
/** Insert an sample */
void
insert(T const & s)
{
++counts_[s];
++samples;
}
/** The number of samples */
std::size_t
size() const
{
return samples;
}
/** The number of distinct samples (bins) */
std::size_t
numBins() const
{
return counts_.size();
}
/** Minimum observed value */
T
minValue() const
{
return counts_.empty() ? T{} : counts_.begin()->first;
}
/** Maximum observed value */
T
maxValue() const
{
return counts_.empty() ? T{} : counts_.rbegin()->first;
}
/** Histogram average */
T
avg() const
{
T tmp{};
if(samples == 0)
return tmp;
// Since counts are sorted, shouldn't need to worry much about numerical
// error
for (auto const& it : counts_)
{
tmp += it.first * it.second;
}
return tmp/samples;
}
/** Calculate the given percentile of the distribution.
@param p Percentile between 0 and 1, e.g. 0.50 is 50-th percentile
If the percentile falls between two bins, uses the nearest bin.
@return The given percentile of the distribution
*/
T
percentile(float p) const
{
assert(p >= 0 && p <=1);
std::size_t pos = std::round(p * samples);
if(counts_.empty())
return T{};
auto it = counts_.begin();
std::size_t cumsum = it->second;
while (it != counts_.end() && cumsum < pos)
{
++it;
cumsum += it->second;
}
return it->first;
}
};
} // namespace csf
} // namespace test
} // namespace ripple
#endif

View File

@@ -0,0 +1,89 @@
//------------------------------------------------------------------------------
/*
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 <BeastConfig.h>
#include <ripple/beast/unit_test.h>
#include <test/csf/Histogram.h>
namespace ripple {
namespace test {
class Histogram_test : public beast::unit_test::suite
{
public:
void
run() override
{
using namespace csf;
Histogram<int> hist;
BEAST_EXPECT(hist.size() == 0);
BEAST_EXPECT(hist.numBins() == 0);
BEAST_EXPECT(hist.minValue() == 0);
BEAST_EXPECT(hist.maxValue() == 0);
BEAST_EXPECT(hist.avg() == 0);
BEAST_EXPECT(hist.percentile(0.0f) == hist.minValue());
BEAST_EXPECT(hist.percentile(0.5f) == 0);
BEAST_EXPECT(hist.percentile(0.9f) == 0);
BEAST_EXPECT(hist.percentile(1.0f) == hist.maxValue());
hist.insert(1);
BEAST_EXPECT(hist.size() == 1);
BEAST_EXPECT(hist.numBins() == 1);
BEAST_EXPECT(hist.minValue() == 1);
BEAST_EXPECT(hist.maxValue() == 1);
BEAST_EXPECT(hist.avg() == 1);
BEAST_EXPECT(hist.percentile(0.0f) == hist.minValue());
BEAST_EXPECT(hist.percentile(0.5f) == 1);
BEAST_EXPECT(hist.percentile(0.9f) == 1);
BEAST_EXPECT(hist.percentile(1.0f) == hist.maxValue());
hist.insert(9);
BEAST_EXPECT(hist.size() == 2);
BEAST_EXPECT(hist.numBins() == 2);
BEAST_EXPECT(hist.minValue() == 1);
BEAST_EXPECT(hist.maxValue() == 9);
BEAST_EXPECT(hist.avg() == 5);
BEAST_EXPECT(hist.percentile(0.0f) == hist.minValue());
BEAST_EXPECT(hist.percentile(0.5f) == 1);
BEAST_EXPECT(hist.percentile(0.9f) == 9);
BEAST_EXPECT(hist.percentile(1.0f) == hist.maxValue());
hist.insert(1);
BEAST_EXPECT(hist.size() == 3);
BEAST_EXPECT(hist.numBins() == 2);
BEAST_EXPECT(hist.minValue() == 1);
BEAST_EXPECT(hist.maxValue() == 9);
BEAST_EXPECT(hist.avg() == 11/3);
BEAST_EXPECT(hist.percentile(0.0f) == hist.minValue());
BEAST_EXPECT(hist.percentile(0.5f) == 1);
BEAST_EXPECT(hist.percentile(0.9f) == 9);
BEAST_EXPECT(hist.percentile(1.0f) == hist.maxValue());
}
};
BEAST_DEFINE_TESTSUITE(Histogram, test, ripple);
} // test
} // ripple

View File

@@ -1,183 +0,0 @@
//------------------------------------------------------------------------------
/*
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 <ripple/consensus/LedgerTiming.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
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.closeTime_ = effCloseTime(
consensusCloseTime, closeTimeResolution, closeTime());
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_;
};
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

File diff suppressed because it is too large Load Diff

370
src/test/csf/PeerGroup.h Normal file
View File

@@ -0,0 +1,370 @@
//------------------------------------------------------------------------------
/*
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_PEERGROUP_H_INCLUDED
#define RIPPLE_TEST_CSF_PEERGROUP_H_INCLUDED
#include <algorithm>
#include <test/csf/Peer.h>
#include <test/csf/random.h>
#include <vector>
namespace ripple {
namespace test {
namespace csf {
/** A group of simulation Peers
A PeerGroup is a convenient handle for logically grouping peers together,
and then creating trust or network relations for the group at large. Peer
groups may also be combined to build out more complex structures.
The PeerGroup provides random access style iterators and operator[]
*/
class PeerGroup
{
using peers_type = std::vector<Peer*>;
peers_type peers_;
public:
using iterator = peers_type::iterator;
using const_iterator = peers_type::const_iterator;
using reference = peers_type::reference;
using const_reference = peers_type::const_reference;
PeerGroup() = default;
PeerGroup(PeerGroup const&) = default;
PeerGroup(Peer* peer) : peers_{1, peer}
{
}
PeerGroup(std::vector<Peer*>&& peers) : peers_{std::move(peers)}
{
std::sort(peers_.begin(), peers_.end());
}
PeerGroup(std::vector<Peer*> const& peers) : peers_{peers}
{
std::sort(peers_.begin(), peers_.end());
}
PeerGroup(std::set<Peer*> const& peers) : peers_{peers.begin(), peers.end()}
{
}
iterator
begin()
{
return peers_.begin();
}
iterator
end()
{
return peers_.end();
}
const_iterator
begin() const
{
return peers_.begin();
}
const_iterator
end() const
{
return peers_.end();
}
const_reference
operator[](std::size_t i) const
{
return peers_[i];
}
bool
contains(Peer const * p)
{
return std::find(peers_.begin(), peers_.end(), p) != peers_.end();
}
std::size_t
size() const
{
return peers_.size();
}
/** Establish trust
Establish trust from all peers in this group to all peers in o
@param o The group of peers to trust
*/
void
trust(PeerGroup const & o)
{
for(Peer * p : peers_)
{
for (Peer * target : o.peers_)
{
p->trust(*target);
}
}
}
/** Revoke trust
Revoke trust from all peers in this group to all peers in o
@param o The group of peers to untrust
*/
void
untrust(PeerGroup const & o)
{
for(Peer * p : peers_)
{
for (Peer * target : o.peers_)
{
p->untrust(*target);
}
}
}
/** Establish network connection
Establish outbound connections from all peers in this group to all peers in
o. If a connection already exists, no new connection is established.
@param o The group of peers to connect to (will get inbound connections)
@param delay The fixed messaging delay for all established connections
*/
void
connect(PeerGroup const& o, SimDuration delay)
{
for(Peer * p : peers_)
{
for (Peer * target : o.peers_)
{
// cannot send messages to self over network
if(p != target)
p->connect(*target, delay);
}
}
}
/** Destroy network connection
Destroy connections from all peers in this group to all peers in o
@param o The group of peers to disconnect from
*/
void
disconnect(PeerGroup const &o)
{
for(Peer * p : peers_)
{
for (Peer * target : o.peers_)
{
p->disconnect(*target);
}
}
}
/** Establish trust and network connection
Establish trust and create a network connection with fixed delay
from all peers in this group to all peers in o
@param o The group of peers to trust and connect to
@param delay The fixed messaging delay for all established connections
*/
void
trustAndConnect(PeerGroup const & o, SimDuration delay)
{
trust(o);
connect(o, delay);
}
/** Establish network connections based on trust relations
For each peers in this group, create outbound network connection
to the set of peers it trusts. If a coonnection already exists, it is
not recreated.
@param delay The fixed messaging delay for all established connections
*/
void
connectFromTrust(SimDuration delay)
{
for (Peer * peer : peers_)
{
for (Peer * to : peer->trustGraph.trustedPeers(peer))
{
peer->connect(*to, delay);
}
}
}
// Union of PeerGroups
friend
PeerGroup
operator+(PeerGroup const & a, PeerGroup const & b)
{
PeerGroup res;
std::set_union(
a.peers_.begin(),
a.peers_.end(),
b.peers_.begin(),
b.peers_.end(),
std::back_inserter(res.peers_));
return res;
}
// Set difference of PeerGroups
friend
PeerGroup
operator-(PeerGroup const & a, PeerGroup const & b)
{
PeerGroup res;
std::set_difference(
a.peers_.begin(),
a.peers_.end(),
b.peers_.begin(),
b.peers_.end(),
std::back_inserter(res.peers_));
return res;
}
friend std::ostream&
operator<<(std::ostream& o, PeerGroup const& t)
{
o << "{";
bool first = true;
for (Peer const* p : t)
{
if(!first)
o << ", ";
first = false;
o << p->id;
}
o << "}";
return o;
}
};
/** Randomly generate peer groups according to ranks.
Generates random peer groups based on a provided ranking of peers. This
mimics a process of randomly generating UNLs, where more "important" peers
are more likely to appear in a UNL.
`numGroups` subgroups are generated by randomly sampling without without
replacement from peers according to the `ranks`.
@param peers The group of peers
@param ranks The relative importance of each peer, must match the size of
peers. Higher relative rank means more likely to be sampled.
@param numGroups The number of peer link groups to generate
@param sizeDist The distribution that determines the size of a link group
@param g The uniform random bit generator
*/
template <class RandomNumberDistribution, class Generator>
std::vector<PeerGroup>
randomRankedGroups(
PeerGroup & peers,
std::vector<double> const & ranks,
int numGroups,
RandomNumberDistribution sizeDist,
Generator& g)
{
assert(peers.size() == ranks.size());
std::vector<PeerGroup> groups;
groups.reserve(numGroups);
std::vector<Peer*> rawPeers(peers.begin(), peers.end());
std::generate_n(std::back_inserter(groups), numGroups, [&]() {
std::vector<Peer*> res = random_weighted_shuffle(rawPeers, ranks, g);
res.resize(sizeDist(g));
return PeerGroup(std::move(res));
});
return groups;
}
/** Generate random trust groups based on peer rankings.
@see randomRankedGroups for descriptions of the arguments
*/
template <class RandomNumberDistribution, class Generator>
void
randomRankedTrust(
PeerGroup & peers,
std::vector<double> const & ranks,
int numGroups,
RandomNumberDistribution sizeDist,
Generator& g)
{
std::vector<PeerGroup> const groups =
randomRankedGroups(peers, ranks, numGroups, sizeDist, g);
std::uniform_int_distribution<int> u(0, groups.size() - 1);
for(auto & peer : peers)
{
for(auto & target : groups[u(g)])
peer->trust(*target);
}
}
/** Generate random network groups based on peer rankings.
@see randomRankedGroups for descriptions of the arguments
*/
template <class RandomNumberDistribution, class Generator>
void
randomRankedConnect(
PeerGroup & peers,
std::vector<double> const & ranks,
int numGroups,
RandomNumberDistribution sizeDist,
Generator& g,
SimDuration delay)
{
std::vector<PeerGroup> const groups =
randomRankedGroups(peers, ranks, numGroups, sizeDist, g);
std::uniform_int_distribution<int> u(0, groups.size() - 1);
for(auto & peer : peers)
{
for(auto & target : groups[u(g)])
peer->connect(*target, delay);
}
}
} // namespace csf
} // namespace test
} // namespace ripple
#endif

39
src/test/csf/Proposal.h Normal file
View File

@@ -0,0 +1,39 @@
//------------------------------------------------------------------------------
/*
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_PROPOSAL_H_INCLUDED
#define RIPPLE_TEST_CSF_PROPOSAL_H_INCLUDED
#include <ripple/consensus/ConsensusProposal.h>
#include <test/csf/ledgers.h>
#include <test/csf/Tx.h>
#include <test/csf/Validation.h>
namespace ripple {
namespace test {
namespace csf {
/** Proposal is a position taken in the consensus process and is represented
directly from the generic types.
*/
using Proposal = ConsensusProposal<PeerID, Ledger::ID, TxSet::ID>;
} // namespace csf
} // namespace test
} // namespace ripple
#endif

191
src/test/csf/README.md Normal file
View File

@@ -0,0 +1,191 @@
# Consensus Simulation Framework
The Consensus Simulation Framework is a set of software components for
describing, running and analyzing simulations of the consensus algorithm in a
controlled manner. It is also used to unit test the generic Ripple consensus
algorithm implementation. The framework is in its early stages, so the design
and supported features are subject to change.
## Overview
The simulation framework focuses on simulating the core consensus and validation
algorithms as a [discrete event
simulation](https://en.wikipedia.org/wiki/Discrete_event_simulation). It is
completely abstracted from the details of the XRP ledger and transactions. In
the simulation, a ledger is simply a set of observed integers and transactions
are single integers. The consensus process works to agree on the set of integers
to include in the next ledger.
![CSF Overview](./csf_overview.png "CSF Overview")
The diagram above gives a stylized overview of the components provided by the
framework. These are combined by the simulation author into the simulation
specification, which defines the configuration of the system and the data to
collect when running the simulation. The specification includes:
- A collection of [`Peer`s](./Peer.h) that represent the participants in the
network, with each independently running the consensus algorithm.
- The `Peer` trust relationships as a `TrustGraph`. This is a directed graph
whose edges define what other `Peer`s a given `Peer` trusts. In other words,
the set of out edges for a `Peer` in the graph correspond to the UNL of that
`Peer`.
- The network communication layer as a `BasicNetwork`. This models the overlay
network topology in which messages are routed between `Peer`s. This graph
topology can be configured independently from the `TrustGraph`.
- Transaction `Submitter`s that model the submission of client transactions to
the network.
- `Collector`s that aggregate, filter and analyze data from the simulation.
Typically, this is used to monitor invariants or generate reports.
Once specified, the simulation runs using a single `Scheduler` that manages the
global clock and sequencing of activity. During the course of simulation,
`Peer`s generate `Ledger`s and `Validation`s as a result of consensus,
eventually fully validating the consensus history of accepted transactions. Each
`Peer` also issues various `Event`s during the simulation, which are analyzed by
the registered `Collector`s.
## Example Simulation
Below is a basic simulation we can walk through to get an understanding of the
framework. This simulation is for a set of 5 validators that aren't directly
connected but rely on a single hub node for communication.
![Example Sim](./csf_graph.png "Example Sim")
Each Peer has a unique transaction submitted, then runs one round of the
consensus algorithm.
```c++
Sim sim;
PeerGroup validators = sim.createGroup(5);
PeerGroup center = sim.createGroup(1);
PeerGroup network = validators + center;
center[0]->runAsValidator = false;
validators.trust(validators);
center.trust(validators);
using namespace std::chrono;
SimDuration delay = 200ms;
validators.connect(center, delay);
SimDurationCollector simDur;
sim.collectors.add(simDur);
// prep round to set initial state.
sim.run(1);
// everyone submits their own ID as a TX and relay it to peers
for (Peer * p : validators)
p->submit(Tx(static_cast<std::uint32_t>(p->id)));
sim.run(1);
std::cout << (simDur.stop - simDur.start).count() << std::endl;
assert(sim.synchronized());
```
### `Sim` and `PeerGroup`
```c++
Sim sim;
PeerGroup validators = sim.createGroup(5);
PeerGroup center = sim.createGroup(1);
PeerGroup network = validators + center;
center[0]->runAsValidator = false;
```
The simulation code starts by creating a single instance of the [`Sim`
class](./Sim.h). This class is used to manage the overall simulation and
internally owns most other components, including the `Peer`s, `Scheduler`,
`BasicNetwork` and `TrustGraph`. The next two lines create two differ
`PeerGroup`s of size 5 and 1 . A [`PeerGroup`](./PeerGroup.h) is a convenient
way for configuring a set of related peers together and internally has a vector
of pointers to the `Peer`s which are owned by the `Sim`. `PeerGroup`s can be
combined using `+/-` operators to configure more complex relationships of nodes
as shown by `PeerGroup network`. Note that each call to `createGroup` adds that
many new `Peer`s to the simulation, but does not specify any trust or network
relationships for the new `Peer`s.
Lastly, the single `Peer` in the size 1 `center` group is switched from running
as a validator (the default) to running as a tracking peer. The [`Peer`
class](./Peer.h) has a variety of configurable parameters that control how it
behaves during the simulation.
## `trust` and `connect`
```c++
validators.trust(validators);
center.trust(validators);
using namespace std::chrono;
SimDuration delay = 200ms;
validators.connect(center, delay);
```
Although the `sim` object has accessible instances of
[TrustGraph](./TrustGraph.h) and [BasicNetwork](./BasicNetwork.h), it is more
convenient to manage the graphs via the `PeerGroup`s. The first two lines
create a trust topology in which all `Peer`s trust the 5 validating `Peer`s. Or
in the UNL perspective, all `Peer`s are configured with the same UNL listing the
5 validating `Peer`s. The two lines could've been rewritten as
`network.trust(validators)`.
The next lines create the network communication topology. Each of the validating
`Peer`s connects to the central hub `Peer` with a fixed delay of 200ms. Note
that the network connections are really undirected, but are represented
internally in a directed graph using edge pairs of inbound and outbound connections.
## Collectors
```c++
SimDurationCollector simDur;
sim.collectors.add(simDur);
```
The next lines add a single collector to the simulation. The
`SimDurationCollector` is a a simple example collector which tracks the total
duration of the simulation. More generally, a collector is any class that
implements `void on(NodeID, SimTime, Event)` for all [Events](./events.h)
emitted by a Peer. Events are arbitrary types used to indicate some action or
change of state of a `Peer`. Other [existing collectors](./collectors.h) measure
latencies of transaction submission to validation or the rate of ledger closing
and monitor any jumps in ledger history.
Note that the collector lifetime is independent of the simulation and is added
to the simulation by reference. This is intentional, since collectors might be
used across several simulations to collect more complex combinations of data. At
the end of the simulation, we print out the total duration by subtracting
`simDur` members.
```c++
std::cout << (simDur.stop - simDur.start).count() << std::endl;
```
## Transaction submission
```c++
// everyone submits their own ID as a TX and relay it to peers
for (Peer * p : validators)
p->submit(Tx(static_cast<std::uint32_t>(p->id)));
```
In this basic example, we explicitly submit a single transaction to each
validator. For larger simulations, clients can use a [Submitter](./submitters.h)
to send transactions in at fixed or random intervals to fixed or random `Peer`s.
## Run
The example has two calls to `sim.run(1)`. This call runs the simulation until
each `Peer` has closed one additional ledger. After closing the additional
ledger, the `Peer` stops participating in consensus. The first call is used to
ensure a more useful prior state of all `Peer`s. After the transaction
submission, the second call to `run` results in one additional ledger that
accepts those transactions.
Alternatively, you can specify a duration to run the simulation, e.g.
`sim.run(10s)` which would have `Peer`s continuously run consensus until the
scheduler has elapsed 10 additional seconds. The `sim.scheduler.in` or
`sim.scheduler.at` methods can schedule arbitrary code to execute at a later
time in the simulation, for example removing a network connection or modifying
the trust graph.

465
src/test/csf/Scheduler.h Normal file
View File

@@ -0,0 +1,465 @@
//------------------------------------------------------------------------------
/*
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_SCHEDULER_H_INCLUDED
#define RIPPLE_TEST_CSF_SCHEDULER_H_INCLUDED
#include <ripple/basics/qalloc.h>
#include <ripple/beast/clock/manual_clock.h>
#include <boost/intrusive/set.hpp>
#include <type_traits>
#include <utility>
namespace ripple {
namespace test {
namespace csf {
/** Simulated discrete-event scheduler.
Simulates the behavior of events using a single common clock.
An event is modeled using a lambda function and is scheduled to occur at a
specific time. Events may be canceled using a token returned when the
event is scheduled.
The caller uses one or more of the step, step_one, step_for, step_until and
step_while functions to process scheduled events.
*/
class Scheduler
{
public:
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:
using by_when_hook = boost::intrusive::set_base_hook<
boost::intrusive::link_mode<boost::intrusive::normal_link>>;
struct event : by_when_hook
{
time_point when;
event(event const&) = delete;
event&
operator=(event const&) = delete;
virtual ~event() = default;
// Called to perform the event
virtual void
operator()() const = 0;
event(time_point when_) : when(when_)
{
}
bool
operator<(event const& other) const
{
return when < other.when;
}
};
template <class Handler>
class event_impl : public event
{
Handler const h_;
public:
event_impl(event_impl const&) = delete;
event_impl&
operator=(event_impl const&) = delete;
template <class DeducedHandler>
event_impl(time_point when_, DeducedHandler&& h)
: event(when_)
, h_(std::forward<DeducedHandler>(h))
{
}
void
operator()() const override
{
h_();
}
};
class queue_type
{
private:
using by_when_set = typename boost::intrusive::make_multiset<
event,
boost::intrusive::constant_time_size<false>>::type;
qalloc alloc_;
by_when_set by_when_;
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(time_point when, Handler&& h);
iterator
erase(iterator iter);
};
qalloc alloc_;
queue_type queue_;
// Aged containers that rely on this clock take a non-const reference =(
mutable clock_type clock_;
public:
Scheduler(Scheduler const&) = delete;
Scheduler&
operator=(Scheduler const&) = delete;
Scheduler();
/** Return the allocator. */
qalloc const&
alloc() const;
/** Return the clock. (aged_containers want a non-const ref =( */
clock_type &
clock() const;
/** Return the current network time.
@note The epoch is unspecified
*/
time_point
now() const;
// Used to cancel timers
struct cancel_token;
/** Schedule an event at a specific time
Effects:
When the network time is reached,
the function will be called with
no arguments.
*/
template <class Function>
cancel_token
at(time_point const& when, Function&& f);
/** Schedule an event after a specified duration passes
Effects:
When the specified time has elapsed,
the function will be called with
no arguments.
*/
template <class Function>
cancel_token
in(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);
/** Run the scheduler for up to one event.
Effects:
The clock is advanced to the time
of the last delivered event.
@return `true` if an event was processed.
*/
bool
step_one();
/** Run the scheduler until no events remain.
Effects:
The clock is advanced to the time
of the last event.
@return `true` if an event was processed.
*/
bool
step();
/** Run the scheduler while a condition is true.
Function takes no arguments and will be called
repeatedly after each event is processed to
decide whether to continue.
Effects:
The clock is advanced to the time
of the last delivered event.
@return `true` if any event was processed.
*/
template <class Function>
bool
step_while(Function&& func);
/** Run the scheduler until the specified time.
Effects:
The clock is advanced to the
specified time.
@return `true` if any event remain.
*/
bool
step_until(time_point const& until);
/** Run the scheduler until time has elapsed.
Effects:
The clock is advanced by the
specified duration.
@return `true` if any event remain.
*/
template <class Period, class Rep>
bool
step_for(std::chrono::duration<Period, Rep> const& amount);
};
//------------------------------------------------------------------------------
inline Scheduler::queue_type::queue_type(qalloc const& alloc) : alloc_(alloc)
{
}
inline Scheduler::queue_type::~queue_type()
{
for (auto iter = by_when_.begin(); iter != by_when_.end();)
{
auto e = &*iter;
++iter;
e->~event();
alloc_.dealloc(e, 1);
}
}
inline bool
Scheduler::queue_type::empty() const
{
return by_when_.empty();
}
inline auto
Scheduler::queue_type::begin() -> iterator
{
return by_when_.begin();
}
inline auto
Scheduler::queue_type::end() -> iterator
{
return by_when_.end();
}
template <class Handler>
inline auto
Scheduler::queue_type::emplace(time_point when, Handler&& h) ->
typename by_when_set::iterator
{
using event_type = event_impl<std::decay_t<Handler>>;
auto const p = alloc_.alloc<event_type>(1);
auto& e = *new (p) event_type(
when, std::forward<Handler>(h));
return by_when_.insert(e);
}
inline auto
Scheduler::queue_type::erase(iterator iter) -> typename by_when_set::iterator
{
auto& e = *iter;
auto next = by_when_.erase(iter);
e.~event();
alloc_.dealloc(&e, 1);
return next;
}
//-----------------------------------------------------------------------------
struct Scheduler::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 Scheduler;
cancel_token(typename queue_type::iterator iter) : iter_(iter)
{
}
};
//------------------------------------------------------------------------------
inline Scheduler::Scheduler() : queue_(alloc_)
{
}
inline qalloc const&
Scheduler::alloc() const
{
return alloc_;
}
inline auto
Scheduler::clock() const -> clock_type &
{
return clock_;
}
inline auto
Scheduler::now() const -> time_point
{
return clock_.now();
}
template <class Function>
inline auto
Scheduler::at(time_point const& when, Function&& f) -> cancel_token
{
return queue_.emplace(when, std::forward<Function>(f));
}
template <class Function>
inline auto
Scheduler::in(duration const& delay, Function&& f) -> cancel_token
{
return at(clock_.now() + delay, std::forward<Function>(f));
}
inline void
Scheduler::cancel(cancel_token const& token)
{
queue_.erase(token.iter_);
}
inline bool
Scheduler::step_one()
{
if (queue_.empty())
return false;
auto const iter = queue_.begin();
clock_.set(iter->when);
(*iter)();
queue_.erase(iter);
return true;
}
inline bool
Scheduler::step()
{
if (!step_one())
return false;
for (;;)
if (!step_one())
break;
return true;
}
template <class Function>
inline bool
Scheduler::step_while(Function&& f)
{
bool ran = false;
while (f() && step_one())
ran = true;
return ran;
}
inline bool
Scheduler::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 Period, class Rep>
inline bool
Scheduler::step_for(std::chrono::duration<Period, Rep> const& amount)
{
return step_until(now() + amount);
}
} // namespace csf
} // namespace test
} // namespace ripple
#endif

View File

@@ -0,0 +1,89 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 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 <BeastConfig.h>
#include <ripple/beast/unit_test.h>
#include <test/csf/Scheduler.h>
#include <set>
namespace ripple {
namespace test {
class Scheduler_test : public beast::unit_test::suite
{
public:
void
run() override
{
using namespace std::chrono_literals;
csf::Scheduler scheduler;
std::set<int> seen;
scheduler.in(1s, [&]{ seen.insert(1);});
scheduler.in(2s, [&]{ seen.insert(2);});
auto token = scheduler.in(3s, [&]{ seen.insert(3);});
scheduler.at(scheduler.now() + 4s, [&]{ seen.insert(4);});
scheduler.at(scheduler.now() + 8s, [&]{ seen.insert(8);});
auto start = scheduler.now();
// Process first event
BEAST_EXPECT(seen.empty());
BEAST_EXPECT(scheduler.step_one());
BEAST_EXPECT(seen == std::set<int>({1}));
BEAST_EXPECT(scheduler.now() == (start + 1s));
// No processing if stepping until current time
BEAST_EXPECT(scheduler.step_until(scheduler.now()));
BEAST_EXPECT(seen == std::set<int>({1}));
BEAST_EXPECT(scheduler.now() == (start + 1s));
// Process next event
BEAST_EXPECT(scheduler.step_for(1s));
BEAST_EXPECT(seen == std::set<int>({1,2}));
BEAST_EXPECT(scheduler.now() == (start + 2s));
// Don't process cancelled event, but advance clock
scheduler.cancel(token);
BEAST_EXPECT(scheduler.step_for(1s));
BEAST_EXPECT(seen == std::set<int>({1,2}));
BEAST_EXPECT(scheduler.now() == (start + 3s));
// Process until 3 seen ints
BEAST_EXPECT(scheduler.step_while([&]() { return seen.size() < 3; }));
BEAST_EXPECT(seen == std::set<int>({1,2,4}));
BEAST_EXPECT(scheduler.now() == (start + 4s));
// Process the rest
BEAST_EXPECT(scheduler.step());
BEAST_EXPECT(seen == std::set<int>({1,2,4,8}));
BEAST_EXPECT(scheduler.now() == (start + 8s));
// Process the rest again doesn't advance
BEAST_EXPECT(!scheduler.step());
BEAST_EXPECT(seen == std::set<int>({1,2,4,8}));
BEAST_EXPECT(scheduler.now() == (start + 8s));
}
};
BEAST_DEFINE_TESTSUITE(Scheduler, test, ripple);
} // namespace test
} // namespace ripple

View File

@@ -20,84 +20,142 @@
#ifndef RIPPLE_TEST_CSF_SIM_H_INCLUDED
#define RIPPLE_TEST_CSF_SIM_H_INCLUDED
#include <test/csf/Digraph.h>
#include <test/csf/SimTime.h>
#include <test/csf/BasicNetwork.h>
#include <test/csf/UNL.h>
#include <test/csf/Scheduler.h>
#include <test/csf/Peer.h>
#include <test/csf/PeerGroup.h>
#include <test/csf/TrustGraph.h>
#include <test/csf/CollectorRef.h>
#include <iostream>
#include <deque>
#include <random>
namespace ripple {
namespace test {
namespace csf {
/** Sink that prepends simulation time to messages */
class BasicSink : public beast::Journal::Sink
{
Scheduler::clock_type const & clock_;
public:
BasicSink (Scheduler::clock_type const & clock)
: Sink (beast::severities::kDisabled, false)
, clock_{clock}
{
}
void
write (beast::severities::Severity level,
std::string const& text) override
{
if (level < threshold())
return;
std::cout << clock_.now().time_since_epoch().count() << " " << text
<< std::endl;
}
};
class Sim
{
// Use a deque to have stable pointers even when dynamically adding peers
// - Alternatively consider using unique_ptrs allocated from arena
std::deque<Peer> peers;
public:
/** Create a simulator for the given trust graph and network topology.
std::mt19937_64 rng;
Scheduler scheduler;
BasicSink sink;
beast::Journal j;
LedgerOracle oracle;
BasicNetwork<Peer*> net;
TrustGraph<Peer*> trustGraph;
CollectorRefs collectors;
Create a simulator for consensus over the given trust graph and connect
the network links between nodes based on the provided topology.
/** Create a simulation
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.
@param parms Consensus parameters to use in the simulation
Creates a new simulation. The simulation has no peers, no trust links
and no network connections.
*/
template <class Topology>
Sim(ConsensusParms parms, TrustGraph const& g, Topology const& top)
Sim() : sink{scheduler.clock()}, j{sink}, net{scheduler}
{
peers.reserve(g.numPeers());
for (int i = 0; i < g.numPeers(); ++i)
peers.emplace_back(i, net, g.unl(i), parms);
}
for (int i = 0; i < peers.size(); ++i)
/** Create a new group of peers.
Creates a new group of peers. The peers do not have any trust relations
or network connections by default. Those must be configured by the client.
@param numPeers The number of peers in the group
@return PeerGroup representing these new peers
@note This increases the number of peers in the simulation by numPeers.
*/
PeerGroup
createGroup(std::size_t numPeers)
{
for (int j = 0; j < peers.size(); ++j)
std::vector<Peer*> newPeers;
newPeers.reserve(numPeers);
for (std::size_t i = 0; i < numPeers; ++i)
{
if (i != j)
peers.emplace_back(
PeerID{static_cast<std::uint32_t>(peers.size())},
scheduler,
oracle,
net,
trustGraph,
collectors,
j);
newPeers.emplace_back(&peers.back());
}
return PeerGroup{newPeers};
}
//! The number of peers in the simulation
std::size_t
size() const
{
auto d = top(i, j);
if (d)
{
net.connect(&peers[i], &peers[j], *d);
}
}
}
}
return peers.size();
}
/** Run consensus protocol to generate the provided number of ledgers.
Has each peer run consensus until it creates `ledgers` more ledgers.
Has each peer run consensus until it closes `ledgers` more ledgers.
@param ledgers The number of additional ledgers to create
@param ledgers The number of additional ledgers to close
*/
void
run(int ledgers)
{
for (auto& p : peers)
{
if (p.completedLedgers == 0)
p.relay(Validation{p.id, p.prevLedgerID(), p.prevLedgerID()});
p.targetLedgers = p.completedLedgers + ledgers;
p.start();
}
net.step();
}
run(int ledgers);
/** Run consensus for the given duration */
void
run(SimDuration const& dur);
/** Check whether all peers in the network are synchronized.
Nodes in the network are synchronized if they share the same last
fully validated and last generated ledger.
*/
bool
synchronized() const;
/** Calculate the number of branches in the network.
A branch occurs if two peers have fullyValidatedLedgers that are not on
the same chain of ledgers.
*/
std::size_t
branches() const;
std::vector<Peer> peers;
BasicNetwork<Peer*> net;
};
} // csf
} // test
} // ripple
} // namespace csf
} // namespace test
} // namespace ripple
#endif

42
src/test/csf/SimTime.h Normal file
View File

@@ -0,0 +1,42 @@
//------------------------------------------------------------------------------
/*
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_SIMTIME_H_INCLUDED
#define RIPPLE_TEST_CSF_SIMTIME_H_INCLUDED
#include <ripple/beast/clock/manual_clock.h>
#include <chrono>
namespace ripple {
namespace test {
namespace csf {
using RealClock = std::chrono::system_clock;
using RealDuration = RealClock::duration;
using RealTime = RealClock::time_point;
using SimClock = beast::manual_clock<std::chrono::steady_clock>;
using SimDuration = typename SimClock::duration;
using SimTime = typename SimClock::time_point;
} // namespace csf
} // namespace test
} // namespace ripple
#endif

176
src/test/csf/TrustGraph.h Normal file
View File

@@ -0,0 +1,176 @@
//------------------------------------------------------------------------------
/*
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 <test/csf/random.h>
#include <boost/container/flat_set.hpp>
#include <boost/optional.hpp>
#include <chrono>
#include <numeric>
#include <random>
#include <vector>
namespace ripple {
namespace test {
namespace csf {
/** Trust graph
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.
This class wraps a digraph representing the trust relationships for all peers
in the simulation.
*/
template <class Peer>
class TrustGraph
{
using Graph = Digraph<Peer>;
Graph graph_;
public:
/** Create an empty trust graph
*/
TrustGraph() = default;
Graph const&
graph()
{
return graph_;
}
/** Create trust
Establish trust between Peer `from` and Peer `to`; as if `from` put `to`
in its UNL.
@param from The peer granting trust
@param to The peer receiving trust
*/
void
trust(Peer const& from, Peer const& to)
{
graph_.connect(from, to);
}
/** Remove trust
Revoke trust from Peer `from` to Peer `to`; as if `from` removed `to`
from its UNL.
@param from The peer revoking trust
@param to The peer being revoked
*/
void
untrust(Peer const& from, Peer const& to)
{
graph_.disconnect(from, to);
}
//< Whether from trusts to
bool
trusts(Peer const& from, Peer const& to) const
{
return graph_.connected(from, to);
}
/** Range over trusted peers
@param a The node granting trust
@return boost transformed range over nodes `a` trusts, i.e. the nodes
in its UNL
*/
auto
trustedPeers(Peer const & a) const
{
return graph_.outVertices(a);
}
/** An example of nodes that fail the whitepaper no-forking condition
*/
struct ForkInfo
{
std::set<Peer> unlA;
std::set<Peer> unlB;
int overlap;
double required;
};
//< Return nodes that fail the white-paper no-forking condition
std::vector<ForkInfo>
forkablePairs(double quorum) const
{
// Check the forking condition by looking at intersection
// of UNL between all pairs of nodes.
// TODO: Use the improved bound instead of the whitepaper bound.
using UNL = std::set<Peer>;
std::set<UNL> unique;
for (Peer const & peer : graph_.outVertices())
{
unique.emplace(
std::begin(trustedPeers(peer)), std::end(trustedPeers(peer)));
}
std::vector<UNL> uniqueUNLs(unique.begin(), unique.end());
std::vector<ForkInfo> res;
// 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(), [&](Peer p) {
return unlB.find(p) != unlB.end();
});
if (intersectionSize < rhs)
{
res.emplace_back(ForkInfo{unlA, unlB, intersectionSize, rhs});
}
}
}
return res;
}
/** Check whether this trust graph satisfies the whitepaper no-forking
condition
*/
bool
canFork(double quorum) const
{
return !forkablePairs(quorum).empty();
}
};
} // csf
} // test
} // ripple
#endif

View File

@@ -18,8 +18,9 @@
//==============================================================================
#ifndef RIPPLE_TEST_CSF_TX_H_INCLUDED
#define RIPPLE_TEST_CSF_TX_H_INCLUDED
#include <ripple/beast/hash/uhash.h>
#include <ripple/beast/hash/hash_append.h>
#include <boost/function_output_iterator.hpp>
#include <boost/container/flat_set.hpp>
#include <map>
#include <ostream>
@@ -62,19 +63,29 @@ private:
};
//!-------------------------------------------------------------------------
//! All sets of Tx are represented as a flat_set.
//! All sets of Tx are represented as a flat_set for performance.
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 ID = beast::uhash<>::result_type;
using Tx = csf::Tx;
using MutableTxSet = TxSet;
TxSet() = default;
TxSet(TxSetType const& s) : txs_{s}
static ID calcID(TxSetType const & txs)
{
return beast::uhash<>{}(txs);
}
class MutableTxSet
{
friend class TxSet;
TxSetType txs_;
public:
MutableTxSet(TxSet const& s) : txs_{s.txs_}
{
}
@@ -89,6 +100,17 @@ public:
{
return txs_.erase(Tx{txId}) > 0;
}
};
TxSet() = default;
TxSet(TxSetType const& s) : txs_{s}, id_{calcID(txs_)}
{
}
TxSet(MutableTxSet && m)
: txs_{std::move(m.txs_)}, id_{calcID(txs_)}
{
}
bool
exists(Tx::ID const txId) const
@@ -106,12 +128,18 @@ public:
return nullptr;
}
auto const&
id() const
TxSetType const &
txs() const
{
return txs_;
}
ID
id() const
{
return id_;
}
/** @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
@@ -136,8 +164,12 @@ public:
return res;
}
private:
//! The set contains the actual transactions
TxSetType txs_;
//! The unique ID of this tx set
ID id_;
};
//------------------------------------------------------------------------------

View File

@@ -1,258 +0,0 @@
//------------------------------------------------------------------------------
/*
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 <chrono>
#include <numeric>
#include <random>
#include <vector>
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

167
src/test/csf/Validation.h Normal file
View File

@@ -0,0 +1,167 @@
//------------------------------------------------------------------------------
/*
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_VALIDATION_H_INCLUDED
#define RIPPLE_TEST_CSF_VALIDATION_H_INCLUDED
#include <boost/optional.hpp>
#include <memory>
#include <ripple/basics/tagged_integer.h>
#include <test/csf/ledgers.h>
#include <utility>
namespace ripple {
namespace test {
namespace csf {
struct PeerIDTag;
//< Uniquely identifies a peer
using PeerID = tagged_integer<std::uint32_t, PeerIDTag>;
/** The current key of a peer
Eventually, the second entry in the pair can be used to model ephemeral
keys. Right now, the convention is to have the second entry 0 as the
master key.
*/
using PeerKey = std::pair<PeerID, std::uint32_t>;
/** Validation of a specific ledger by a specific Peer.
*/
class Validation
{
Ledger::ID ledgerID_{0};
Ledger::Seq seq_{0};
NetClock::time_point signTime_;
NetClock::time_point seenTime_;
PeerKey key_;
PeerID nodeID_{0};
bool trusted_ = true;
boost::optional<std::uint32_t> loadFee_;
public:
Validation(Ledger::ID id,
Ledger::Seq seq,
NetClock::time_point sign,
NetClock::time_point seen,
PeerKey key,
PeerID nodeID,
bool trusted,
boost::optional<std::uint32_t> loadFee = boost::none)
: ledgerID_{id}
, seq_{seq}
, signTime_{sign}
, seenTime_{seen}
, key_{key}
, nodeID_{nodeID}
, trusted_{trusted}
, loadFee_{loadFee}
{
}
Ledger::ID
ledgerID() const
{
return ledgerID_;
}
Ledger::Seq
seq() const
{
return seq_;
}
NetClock::time_point
signTime() const
{
return signTime_;
}
NetClock::time_point
seenTime() const
{
return seenTime_;
}
PeerKey
key() const
{
return key_;
}
PeerID
nodeID() const
{
return nodeID_;
}
bool
trusted() const
{
return trusted_;
}
boost::optional<std::uint32_t>
loadFee() const
{
return loadFee_;
}
Validation const&
unwrap() const
{
return *this;
}
auto
asTie() const
{
return std::tie(ledgerID_, seq_, signTime_, seenTime_, key_, nodeID_,
trusted_, loadFee_);
}
bool
operator==(Validation const& o) const
{
return asTie() == o.asTie();
}
bool
operator<(Validation const& o) const
{
return asTie() < o.asTie();
}
void
setTrusted()
{
trusted_ = true;
}
void
setSeen(NetClock::time_point seen)
{
seenTime_ = seen;
}
};
} // ripple
} // test
} // csf
#endif

740
src/test/csf/collectors.h Normal file
View File

@@ -0,0 +1,740 @@
//------------------------------------------------------------------------------
/*
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_COLLECTORS_H_INCLUDED
#define RIPPLE_TEST_CSF_COLLECTORS_H_INCLUDED
#include <ripple/basics/UnorderedContainers.h>
#include <boost/optional.hpp>
#include <chrono>
#include <ostream>
#include <test/csf/Histogram.h>
#include <test/csf/SimTime.h>
#include <test/csf/events.h>
#include <tuple>
namespace ripple {
namespace test {
namespace csf {
// A collector is any class that implements
//
// on(NodeID, SimTime, Event)
//
// for all events emitted by a Peer.
//
// This file contains helper functions for composing different collectors
// and also defines several standard collectors available for simulations.
/** Group of collectors.
Presents a group of collectors as a single collector which process an event
by calling each collector sequentially. This is analagous to CollectorRefs
in CollectorRef.h, but does *not* erase the type information of the combined
collectors.
*/
template <class... Cs>
class Collectors
{
std::tuple<Cs&...> cs;
template <class C, class E>
static void
apply(C& c, PeerID who, SimTime when, E e)
{
c.on(who, when, e);
}
template <std::size_t... Is, class E>
static void
apply(
std::tuple<Cs&...>& cs,
PeerID who,
SimTime when,
E e,
std::index_sequence<Is...>)
{
// Sean Parent for_each_argument trick (C++ fold expressions would be
// nice here)
(void)std::array<int, sizeof...(Cs)>{
{((apply(std::get<Is>(cs), who, when, e)), 0)...}};
}
public:
/** Constructor
@param cs References to the collectors to call together
*/
Collectors(Cs&... cs) : cs(std::tie(cs...))
{
}
template <class E>
void
on(PeerID who, SimTime when, E e)
{
apply(cs, who, when, e, std::index_sequence_for<Cs...>{});
}
};
/** Create an instance of Collectors<Cs...> */
template <class... Cs>
Collectors<Cs...>
makeCollectors(Cs&... cs)
{
return Collectors<Cs...>(cs...);
}
/** Maintain an instance of a Collector per peer
For each peer that emits events, this class maintains a corresponding
instance of CollectorType, only forwarding events emitted by the peer to
the related instance.
CollectorType should be default constructible.
*/
template <class CollectorType>
struct CollectByNode
{
std::map<PeerID, CollectorType> byNode;
CollectorType&
operator[](PeerID who)
{
return byNode[who];
}
CollectorType const&
operator[](PeerID who) const
{
return byNode[who];
}
template <class E>
void
on(PeerID who, SimTime when, E const& e)
{
byNode[who].on(who, when, e);
}
};
/** Collector which ignores all events */
struct NullCollector
{
template <class E>
void
on(PeerID, SimTime, E const& e)
{
}
};
/** Tracks the overall duration of a simulation */
struct SimDurationCollector
{
bool init = false;
SimTime start;
SimTime stop;
template <class E>
void
on(PeerID, SimTime when, E const& e)
{
if (!init)
{
start = when;
init = true;
}
else
stop = when;
}
};
/** Tracks the submission -> accepted -> validated evolution of transactions.
This collector tracks transactions through the network by monitoring the
*first* time the transaction is seen by any node in the network, or
seen by any node's accepted or fully validated ledger.
If transactions submitted to the network do not have unique IDs, this
collector will not track subsequent submissions.
*/
struct TxCollector
{
// Counts
std::size_t submitted{0};
std::size_t accepted{0};
std::size_t validated{0};
struct Tracker
{
Tx tx;
SimTime submitted;
boost::optional<SimTime> accepted;
boost::optional<SimTime> validated;
Tracker(Tx tx_, SimTime submitted_) : tx{tx_}, submitted{submitted_}
{
}
};
hash_map<Tx::ID, Tracker> txs;
using Hist = Histogram<SimTime::duration>;
Hist submitToAccept;
Hist submitToValidate;
// Ignore most events by default
template <class E>
void
on(PeerID, SimTime when, E const& e)
{
}
void
on(PeerID who, SimTime when, SubmitTx const& e)
{
// save first time it was seen
if (txs.emplace(e.tx.id(), Tracker{e.tx, when}).second)
{
submitted++;
}
}
void
on(PeerID who, SimTime when, AcceptLedger const& e)
{
for (auto const& tx : e.ledger.txs())
{
auto it = txs.find(tx.id());
if (it != txs.end() && !it->second.accepted)
{
Tracker& tracker = it->second;
tracker.accepted = when;
accepted++;
submitToAccept.insert(*tracker.accepted - tracker.submitted);
}
}
}
void
on(PeerID who, SimTime when, FullyValidateLedger const& e)
{
for (auto const& tx : e.ledger.txs())
{
auto it = txs.find(tx.id());
if (it != txs.end() && !it->second.validated)
{
Tracker& tracker = it->second;
// Should only validated a previously accepted Tx
assert(tracker.accepted);
tracker.validated = when;
validated++;
submitToValidate.insert(*tracker.validated - tracker.submitted);
}
}
}
// Returns the number of txs which were never accepted
std::size_t
orphaned() const
{
return std::count_if(txs.begin(), txs.end(), [](auto const& it) {
return !it.second.accepted;
});
}
// Returns the number of txs which were never validated
std::size_t
unvalidated() const
{
return std::count_if(txs.begin(), txs.end(), [](auto const& it) {
return !it.second.validated;
});
}
template <class T>
void
report(SimDuration simDuration, T& log, bool printBreakline = false)
{
using namespace std::chrono;
auto perSec = [&simDuration](std::size_t count)
{
return double(count)/duration_cast<seconds>(simDuration).count();
};
auto fmtS = [](SimDuration dur)
{
return duration_cast<duration<float>>(dur).count();
};
if (printBreakline)
{
log << std::setw(11) << std::setfill('-') << "-" << "-"
<< std::setw(7) << std::setfill('-') << "-" << "-"
<< std::setw(7) << std::setfill('-') << "-" << "-"
<< std::setw(36) << std::setfill('-') << "-"
<< std::endl;
log << std::setfill(' ');
}
log << std::left
<< std::setw(11) << "TxStats" << "|"
<< std::setw(7) << "Count" << "|"
<< std::setw(7) << "Per Sec" << "|"
<< std::setw(15) << "Latency (sec)"
<< std::right
<< std::setw(7) << "10-ile"
<< std::setw(7) << "50-ile"
<< std::setw(7) << "90-ile"
<< std::left
<< std::endl;
log << std::setw(11) << std::setfill('-') << "-" << "|"
<< std::setw(7) << std::setfill('-') << "-" << "|"
<< std::setw(7) << std::setfill('-') << "-" << "|"
<< std::setw(36) << std::setfill('-') << "-"
<< std::endl;
log << std::setfill(' ');
log << std::left <<
std::setw(11) << "Submit " << "|"
<< std::right
<< std::setw(7) << submitted << "|"
<< std::setw(7) << std::setprecision(2) << perSec(submitted) << "|"
<< std::setw(36) << "" << std::endl;
log << std::left
<< std::setw(11) << "Accept " << "|"
<< std::right
<< std::setw(7) << accepted << "|"
<< std::setw(7) << std::setprecision(2) << perSec(accepted) << "|"
<< std::setw(15) << std::left << "From Submit" << std::right
<< std::setw(7) << std::setprecision(2) << fmtS(submitToAccept.percentile(0.1f))
<< std::setw(7) << std::setprecision(2) << fmtS(submitToAccept.percentile(0.5f))
<< std::setw(7) << std::setprecision(2) << fmtS(submitToAccept.percentile(0.9f))
<< std::endl;
log << std::left
<< std::setw(11) << "Validate " << "|"
<< std::right
<< std::setw(7) << validated << "|"
<< std::setw(7) << std::setprecision(2) << perSec(validated) << "|"
<< std::setw(15) << std::left << "From Submit" << std::right
<< std::setw(7) << std::setprecision(2) << fmtS(submitToValidate.percentile(0.1f))
<< std::setw(7) << std::setprecision(2) << fmtS(submitToValidate.percentile(0.5f))
<< std::setw(7) << std::setprecision(2) << fmtS(submitToValidate.percentile(0.9f))
<< std::endl;
log << std::left
<< std::setw(11) << "Orphan" << "|"
<< std::right
<< std::setw(7) << orphaned() << "|"
<< std::setw(7) << "" << "|"
<< std::setw(36) << std::endl;
log << std::left
<< std::setw(11) << "Unvalidated" << "|"
<< std::right
<< std::setw(7) << unvalidated() << "|"
<< std::setw(7) << "" << "|"
<< std::setw(43) << std::endl;
log << std::setw(11) << std::setfill('-') << "-" << "-"
<< std::setw(7) << std::setfill('-') << "-" << "-"
<< std::setw(7) << std::setfill('-') << "-" << "-"
<< std::setw(36) << std::setfill('-') << "-"
<< std::endl;
log << std::setfill(' ');
}
template <class T, class Tag>
void
csv(SimDuration simDuration, T& log, Tag const& tag, bool printHeaders = false)
{
using namespace std::chrono;
auto perSec = [&simDuration](std::size_t count)
{
return double(count)/duration_cast<seconds>(simDuration).count();
};
auto fmtS = [](SimDuration dur)
{
return duration_cast<duration<float>>(dur).count();
};
if(printHeaders)
{
log << "tag" << ","
<< "txNumSubmitted" << ","
<< "txNumAccepted" << ","
<< "txNumValidated" << ","
<< "txNumOrphaned" << ","
<< "txUnvalidated" << ","
<< "txRateSumbitted" << ","
<< "txRateAccepted" << ","
<< "txRateValidated" << ","
<< "txLatencySubmitToAccept10Pctl" << ","
<< "txLatencySubmitToAccept50Pctl" << ","
<< "txLatencySubmitToAccept90Pctl" << ","
<< "txLatencySubmitToValidatet10Pctl" << ","
<< "txLatencySubmitToValidatet50Pctl" << ","
<< "txLatencySubmitToValidatet90Pctl"
<< std::endl;
}
log << tag << ","
// txNumSubmitted
<< submitted << ","
// txNumAccepted
<< accepted << ","
// txNumValidated
<< validated << ","
// txNumOrphaned
<< orphaned() << ","
// txNumUnvalidated
<< unvalidated() << ","
// txRateSubmitted
<< std::setprecision(2) << perSec(submitted) << ","
// txRateAccepted
<< std::setprecision(2) << perSec(accepted) << ","
// txRateValidated
<< std::setprecision(2) << perSec(validated) << ","
// txLatencySubmitToAccept10Pctl
<< std::setprecision(2) << fmtS(submitToAccept.percentile(0.1f)) << ","
// txLatencySubmitToAccept50Pctl
<< std::setprecision(2) << fmtS(submitToAccept.percentile(0.5f)) << ","
// txLatencySubmitToAccept90Pctl
<< std::setprecision(2) << fmtS(submitToAccept.percentile(0.9f)) << ","
// txLatencySubmitToValidate10Pctl
<< std::setprecision(2) << fmtS(submitToValidate.percentile(0.1f)) << ","
// txLatencySubmitToValidate50Pctl
<< std::setprecision(2) << fmtS(submitToValidate.percentile(0.5f)) << ","
// txLatencySubmitToValidate90Pctl
<< std::setprecision(2) << fmtS(submitToValidate.percentile(0.9f)) << ","
<< std::endl;
}
};
/** Tracks the accepted -> validated evolution of ledgers.
This collector tracks ledgers through the network by monitoring the
*first* time the ledger is accepted or fully validated by ANY node.
*/
struct LedgerCollector
{
std::size_t accepted{0};
std::size_t fullyValidated{0};
struct Tracker
{
SimTime accepted;
boost::optional<SimTime> fullyValidated;
Tracker(SimTime accepted_) : accepted{accepted_}
{
}
};
hash_map<Ledger::ID, Tracker> ledgers_;
using Hist = Histogram<SimTime::duration>;
Hist acceptToFullyValid;
Hist acceptToAccept;
Hist fullyValidToFullyValid;
// Ignore most events by default
template <class E>
void
on(PeerID, SimTime, E const& e)
{
}
void
on(PeerID who, SimTime when, AcceptLedger const& e)
{
// First time this ledger accepted
if (ledgers_.emplace(e.ledger.id(), Tracker{when}).second)
{
++accepted;
// ignore jumps?
if (e.prior.id() == e.ledger.parentID())
{
auto const it = ledgers_.find(e.ledger.parentID());
if (it != ledgers_.end())
{
acceptToAccept.insert(when - it->second.accepted);
}
}
}
}
void
on(PeerID who, SimTime when, FullyValidateLedger const& e)
{
// ignore jumps
if (e.prior.id() == e.ledger.parentID())
{
auto const it = ledgers_.find(e.ledger.id());
assert(it != ledgers_.end());
auto& tracker = it->second;
// first time fully validated
if (!tracker.fullyValidated)
{
++fullyValidated;
tracker.fullyValidated = when;
acceptToFullyValid.insert(when - tracker.accepted);
auto const parentIt = ledgers_.find(e.ledger.parentID());
if (parentIt != ledgers_.end())
{
auto& parentTracker = parentIt->second;
if (parentTracker.fullyValidated)
{
fullyValidToFullyValid.insert(
when - *parentTracker.fullyValidated);
}
}
}
}
}
std::size_t
unvalidated() const
{
return std::count_if(
ledgers_.begin(), ledgers_.end(), [](auto const& it) {
return !it.second.fullyValidated;
});
}
template <class T>
void
report(SimDuration simDuration, T& log, bool printBreakline = false)
{
using namespace std::chrono;
auto perSec = [&simDuration](std::size_t count)
{
return double(count)/duration_cast<seconds>(simDuration).count();
};
auto fmtS = [](SimDuration dur)
{
return duration_cast<duration<float>>(dur).count();
};
if (printBreakline)
{
log << std::setw(11) << std::setfill('-') << "-" << "-"
<< std::setw(7) << std::setfill('-') << "-" << "-"
<< std::setw(7) << std::setfill('-') << "-" << "-"
<< std::setw(36) << std::setfill('-') << "-"
<< std::endl;
log << std::setfill(' ');
}
log << std::left
<< std::setw(11) << "LedgerStats" << "|"
<< std::setw(7) << "Count" << "|"
<< std::setw(7) << "Per Sec" << "|"
<< std::setw(15) << "Latency (sec)"
<< std::right
<< std::setw(7) << "10-ile"
<< std::setw(7) << "50-ile"
<< std::setw(7) << "90-ile"
<< std::left
<< std::endl;
log << std::setw(11) << std::setfill('-') << "-" << "|"
<< std::setw(7) << std::setfill('-') << "-" << "|"
<< std::setw(7) << std::setfill('-') << "-" << "|"
<< std::setw(36) << std::setfill('-') << "-"
<< std::endl;
log << std::setfill(' ');
log << std::left
<< std::setw(11) << "Accept " << "|"
<< std::right
<< std::setw(7) << accepted << "|"
<< std::setw(7) << std::setprecision(2) << perSec(accepted) << "|"
<< std::setw(15) << std::left << "From Accept" << std::right
<< std::setw(7) << std::setprecision(2) << fmtS(acceptToAccept.percentile(0.1f))
<< std::setw(7) << std::setprecision(2) << fmtS(acceptToAccept.percentile(0.5f))
<< std::setw(7) << std::setprecision(2) << fmtS(acceptToAccept.percentile(0.9f))
<< std::endl;
log << std::left
<< std::setw(11) << "Validate " << "|"
<< std::right
<< std::setw(7) << fullyValidated << "|"
<< std::setw(7) << std::setprecision(2) << perSec(fullyValidated) << "|"
<< std::setw(15) << std::left << "From Validate " << std::right
<< std::setw(7) << std::setprecision(2) << fmtS(fullyValidToFullyValid.percentile(0.1f))
<< std::setw(7) << std::setprecision(2) << fmtS(fullyValidToFullyValid.percentile(0.5f))
<< std::setw(7) << std::setprecision(2) << fmtS(fullyValidToFullyValid.percentile(0.9f))
<< std::endl;
log << std::setw(11) << std::setfill('-') << "-" << "-"
<< std::setw(7) << std::setfill('-') << "-" << "-"
<< std::setw(7) << std::setfill('-') << "-" << "-"
<< std::setw(36) << std::setfill('-') << "-"
<< std::endl;
log << std::setfill(' ');
}
template <class T, class Tag>
void
csv(SimDuration simDuration, T& log, Tag const& tag, bool printHeaders = false)
{
using namespace std::chrono;
auto perSec = [&simDuration](std::size_t count)
{
return double(count)/duration_cast<seconds>(simDuration).count();
};
auto fmtS = [](SimDuration dur)
{
return duration_cast<duration<float>>(dur).count();
};
if(printHeaders)
{
log << "tag" << ","
<< "ledgerNumAccepted" << ","
<< "ledgerNumFullyValidated" << ","
<< "ledgerRateAccepted" << ","
<< "ledgerRateFullyValidated" << ","
<< "ledgerLatencyAcceptToAccept10Pctl" << ","
<< "ledgerLatencyAcceptToAccept50Pctl" << ","
<< "ledgerLatencyAcceptToAccept90Pctl" << ","
<< "ledgerLatencyFullyValidToFullyValid10Pctl" << ","
<< "ledgerLatencyFullyValidToFullyValid50Pctl" << ","
<< "ledgerLatencyFullyValidToFullyValid90Pctl"
<< std::endl;
}
log << tag << ","
// ledgerNumAccepted
<< accepted << ","
// ledgerNumFullyValidated
<< fullyValidated << ","
// ledgerRateAccepted
<< std::setprecision(2) << perSec(accepted) << ","
// ledgerRateFullyValidated
<< std::setprecision(2) << perSec(fullyValidated) << ","
// ledgerLatencyAcceptToAccept10Pctl
<< std::setprecision(2) << fmtS(acceptToAccept.percentile(0.1f)) << ","
// ledgerLatencyAcceptToAccept50Pctl
<< std::setprecision(2) << fmtS(acceptToAccept.percentile(0.5f)) << ","
// ledgerLatencyAcceptToAccept90Pctl
<< std::setprecision(2) << fmtS(acceptToAccept.percentile(0.9f)) << ","
// ledgerLatencyFullyValidToFullyValid10Pctl
<< std::setprecision(2) << fmtS(fullyValidToFullyValid.percentile(0.1f)) << ","
// ledgerLatencyFullyValidToFullyValid50Pctl
<< std::setprecision(2) << fmtS(fullyValidToFullyValid.percentile(0.5f)) << ","
// ledgerLatencyFullyValidToFullyValid90Pctl
<< std::setprecision(2) << fmtS(fullyValidToFullyValid.percentile(0.9f))
<< std::endl;
}
};
/** Write out stream of ledger activity
Writes information about every accepted and fully-validated ledger to a
provided std::ostream.
*/
struct StreamCollector
{
std::ostream& out;
// Ignore most events by default
template <class E>
void
on(PeerID, SimTime, E const& e)
{
}
void
on(PeerID who, SimTime when, AcceptLedger const& e)
{
out << when.time_since_epoch().count() << ": Node " << who << " accepted "
<< "L" << e.ledger.id() << " " << e.ledger.txs() << "\n";
}
void
on(PeerID who, SimTime when, FullyValidateLedger const& e)
{
out << when.time_since_epoch().count() << ": Node " << who
<< " fully-validated " << "L"<< e.ledger.id() << " " << e.ledger.txs()
<< "\n";
}
};
/** Saves information about Jumps for closed and fully validated ledgers. A
jump occurs when a node closes/fully validates a new ledger that is not the
immediate child of the prior closed/fully validated ledgers. This includes
jumps across branches and jumps ahead in the same branch of ledger history.
*/
struct JumpCollector
{
struct Jump
{
PeerID id;
SimTime when;
Ledger from;
Ledger to;
};
std::vector<Jump> closeJumps;
std::vector<Jump> fullyValidatedJumps;
// Ignore most events by default
template <class E>
void
on(PeerID, SimTime, E const& e)
{
}
void
on(PeerID who, SimTime when, AcceptLedger const& e)
{
// Not a direct child -> parent switch
if(e.ledger.parentID() != e.prior.id())
closeJumps.emplace_back(Jump{who, when, e.prior, e.ledger});
}
void
on(PeerID who, SimTime when, FullyValidateLedger const& e)
{
// Not a direct child -> parent switch
if (e.ledger.parentID() != e.prior.id())
fullyValidatedJumps.emplace_back(
Jump{who, when, e.prior, e.ledger});
}
};
} // namespace csf
} // namespace test
} // namespace ripple
#endif

BIN
src/test/csf/csf_graph.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

155
src/test/csf/events.h Normal file
View File

@@ -0,0 +1,155 @@
//------------------------------------------------------------------------------
/*
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_EVENTS_H_INCLUDED
#define RIPPLE_TEST_CSF_EVENTS_H_INCLUDED
#include <test/csf/Tx.h>
#include <test/csf/Validation.h>
#include <test/csf/ledgers.h>
#include <test/csf/Proposal.h>
#include <chrono>
namespace ripple {
namespace test {
namespace csf {
// Events are emitted by peers at a variety of points during the simulation.
// Each event is emitted by a particlar peer at a particular time. Collectors
// process these events, perhaps calculating statistics or storing events to
// a log for post-processing.
//
// The Event types can be arbitrary, but should be copyable and lightweight.
//
// Example collectors can be found in collectors.h, but have the general
// interface:
//
// @code
// template <class T>
// struct Collector
// {
// template <class Event>
// void
// on(peerID who, SimTime when, Event e);
// };
// @endcode
//
// CollectorRef.f defines a type-erased holder for arbitrary Collectors. If
// any new events are added, the interface there needs to be updated.
/** A value to be flooded to all other peers starting from this peer.
*/
template <class V>
struct Share
{
//! Event that is shared
V val;
};
/** A value relayed to another peer as part of flooding
*/
template <class V>
struct Relay
{
//! Peer relaying to
PeerID to;
//! The value to relay
V val;
};
/** A value received from another peer as part of flooding
*/
template <class V>
struct Receive
{
//! Peer that sent the value
PeerID from;
//! The received value
V val;
};
/** A transaction submitted to a peer */
struct SubmitTx
{
//! The submitted transaction
Tx tx;
};
/** Peer starts a new consensus round
*/
struct StartRound
{
//! The preferred ledger for the start of consensus
Ledger::ID bestLedger;
//! The prior ledger on hand
Ledger prevLedger;
};
/** Peer closed the open ledger
*/
struct CloseLedger
{
// The ledger closed on
Ledger prevLedger;
// Initial txs for including in ledger
TxSetType txs;
};
//! Peer accepted consensus results
struct AcceptLedger
{
// The newly created ledger
Ledger ledger;
// The prior ledger (this is a jump if prior.id() != ledger.parentID())
Ledger prior;
};
//! Peer detected a wrong prior ledger during consensus
struct WrongPrevLedger
{
// ID of wrong ledger we had
Ledger::ID wrong;
// ID of what we think is the correct ledger
Ledger::ID right;
};
//! Peer fully validated a new ledger
struct FullyValidateLedger
{
//! The new fully validated ledger
Ledger ledger;
//! The prior fully validated ledger
//! This is a jump if prior.id() != ledger.parentID()
Ledger prior;
};
} // namespace csf
} // namespace test
} // namespace ripple
#endif

76
src/test/csf/impl/Sim.cpp Normal file
View File

@@ -0,0 +1,76 @@
//------------------------------------------------------------------------------
/*
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 <BeastConfig.h>
#include <test/csf/Sim.h>
namespace ripple {
namespace test {
namespace csf {
void
Sim::run(int ledgers)
{
for (auto& p : peers)
{
p.targetLedgers = p.completedLedgers + ledgers;
p.start();
}
scheduler.step();
}
void
Sim::run(SimDuration const & dur)
{
for (auto& p : peers)
{
p.targetLedgers = std::numeric_limits<decltype(p.targetLedgers)>::max();
p.start();
}
scheduler.step_for(dur);
}
bool
Sim::synchronized() const
{
if (peers.size() < 1)
return true;
Peer const& ref = peers.front();
return std::all_of(peers.begin(), peers.end(), [&ref](Peer const& p) {
return p.lastClosedLedger.id() ==
ref.lastClosedLedger.id() &&
p.fullyValidatedLedger.id() ==
ref.fullyValidatedLedger.id();
});
}
std::size_t
Sim::branches() const
{
if(peers.size() < 1)
return 0;
std::set<Ledger> ledgers;
for(auto const & peer : peers)
ledgers.insert(peer.fullyValidatedLedger);
return oracle.branches(ledgers);
}
} // namespace csf
} // namespace test
} // namespace ripple

View File

@@ -1,131 +0,0 @@
//------------------------------------------------------------------------------
/*
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 <boost/iterator/counting_iterator.hpp>
#include <algorithm>
#include <fstream>
#include <test/csf/UNL.h>
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

View File

@@ -0,0 +1,140 @@
//------------------------------------------------------------------------------
/*
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 <BeastConfig.h>
#include <test/csf/ledgers.h>
#include <sstream>
namespace ripple {
namespace test {
namespace csf {
Ledger::Instance const Ledger::genesis;
Json::Value
Ledger::getJson() const
{
Json::Value res(Json::objectValue);
res["id"] = static_cast<ID::value_type>(id());
res["seq"] = static_cast<Seq::value_type>(seq());
return res;
}
LedgerOracle::LedgerOracle()
{
instances_.insert(InstanceEntry{Ledger::genesis, nextID()});
}
Ledger::ID
LedgerOracle::nextID() const
{
return Ledger::ID{static_cast<Ledger::ID::value_type>(instances_.size())};
}
Ledger
LedgerOracle::accept(
Ledger const& parent,
TxSetType const& txs,
NetClock::duration closeTimeResolution,
NetClock::time_point const& consensusCloseTime)
{
Ledger::Instance next(*parent.instance_);
next.txs.insert(txs.begin(), txs.end());
next.seq = parent.seq() + Ledger::Seq{1};
next.closeTimeResolution = closeTimeResolution;
next.closeTimeAgree = consensusCloseTime != NetClock::time_point{};
if(next.closeTimeAgree)
next.closeTime = effCloseTime(
consensusCloseTime, closeTimeResolution, parent.closeTime());
else
next.closeTime = parent.closeTime() + 1s;
next.parentCloseTime = parent.closeTime();
next.parentID = parent.id();
auto it = instances_.left.find(next);
if (it == instances_.left.end())
{
using Entry = InstanceMap::left_value_type;
it = instances_.left.insert(Entry{next, nextID()}).first;
}
return Ledger(it->second, &(it->first));
}
boost::optional<Ledger>
LedgerOracle::lookup(Ledger::ID const & id) const
{
auto const it = instances_.right.find(id);
if(it != instances_.right.end())
{
return Ledger(it->first, &(it->second));
}
return boost::none;
}
bool
LedgerOracle::isAncestor(Ledger const & ancestor, Ledger const& descendant) const
{
// The ancestor must have an earlier sequence number than the descendent
if(ancestor.seq() >= descendant.seq())
return false;
boost::optional<Ledger> current{descendant};
while(current && current->seq() > ancestor.seq())
current = lookup(current->parentID());
return current && (current->id() == ancestor.id());
}
std::size_t
LedgerOracle::branches(std::set<Ledger> const & ledgers) const
{
// Tips always maintains the Ledgers with largest sequence number
// along all known chains.
std::vector<Ledger> tips;
tips.reserve(ledgers.size());
for (Ledger const & ledger : ledgers)
{
// Three options,
// 1. ledger is on a new branch
// 2. ledger is on a branch that we have seen tip for
// 3. ledger is the new tip for a branch
bool found = false;
for (auto idx = 0; idx < tips.size() && !found; ++idx)
{
bool const idxEarlier = tips[idx].seq() < ledger.seq();
Ledger const & earlier = idxEarlier ? tips[idx] : ledger;
Ledger const & later = idxEarlier ? ledger : tips[idx] ;
if (isAncestor(earlier, later))
{
tips[idx] = later;
found = true;
}
}
if(!found)
tips.push_back(ledger);
}
// The size of tips is the number of branches
return tips.size();
}
} // namespace csf
} // namespace test
} // namespace ripple

263
src/test/csf/ledgers.h Normal file
View File

@@ -0,0 +1,263 @@
//------------------------------------------------------------------------------
/*
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_LEDGERS_H_INCLUDED
#define RIPPLE_TEST_CSF_LEDGERS_H_INCLUDED
#include <ripple/basics/UnorderedContainers.h>
#include <ripple/basics/chrono.h>
#include <ripple/consensus/LedgerTiming.h>
#include <ripple/basics/tagged_integer.h>
#include <ripple/consensus/LedgerTiming.h>
#include <ripple/json/json_value.h>
#include <test/csf/Tx.h>
#include <boost/bimap/bimap.hpp>
#include <boost/optional.hpp>
#include <set>
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 simulation, each transaction is a single
integer and the ledger is the 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}
....
Ledgers are immutable value types. All ledgers with the same sequence
number, transactions, close time, etc. will have the same ledger ID. The
LedgerOracle class below manges ID assignments for a simulation and is the
only way to close and create a new ledger. Since the parent ledger ID is
part of type, this also means ledgers with distinct histories will have
distinct ids, even if they have the same set of transactions, sequence
number and close time.
*/
class Ledger
{
friend class LedgerOracle;
public:
struct SeqTag;
using Seq = tagged_integer<std::uint32_t, SeqTag>;
struct IdTag;
using ID = tagged_integer<std::uint32_t, IdTag>;
private:
// The instance is the common immutable data that will be assigned a unique
// ID by the oracle
struct Instance
{
Instance() {}
// Sequence number
Seq seq{0};
// Transactions added to generate this ledger
TxSetType txs;
// Resolution used to determine close time
NetClock::duration closeTimeResolution = ledgerDefaultTimeResolution;
//! When the ledger closed (up to closeTimeResolution)
NetClock::time_point closeTime;
//! Whether consensus agreed on the close time
bool closeTimeAgree = true;
//! Parent ledger id
ID parentID{0};
//! Parent ledger close time
NetClock::time_point parentCloseTime;
auto
asTie() const
{
return std::tie(seq, txs, closeTimeResolution, closeTime,
closeTimeAgree, parentID, parentCloseTime);
}
friend bool
operator==(Instance const& a, Instance const& b)
{
return a.asTie() == b.asTie();
}
friend bool
operator!=(Instance const& a, Instance const& b)
{
return a.asTie() != b.asTie();
}
friend bool
operator<(Instance const & a, Instance const & b)
{
return a.asTie() < b.asTie();
}
template <class Hasher>
friend void
hash_append(Hasher& h, Ledger::Instance const& instance)
{
using beast::hash_append;
hash_append(h, instance.asTie());
}
};
// Single common genesis instance
static const Instance genesis;
Ledger(ID id, Instance const* i) : id_{id}, instance_{i}
{
}
public:
Ledger() : id_{0}, instance_(&genesis)
{
}
ID
id() const
{
return id_;
}
Seq
seq() const
{
return instance_->seq;
}
NetClock::duration
closeTimeResolution() const
{
return instance_->closeTimeResolution;
}
bool
closeAgree() const
{
return instance_->closeTimeAgree;
}
NetClock::time_point
closeTime() const
{
return instance_->closeTime;
}
NetClock::time_point
parentCloseTime() const
{
return instance_->parentCloseTime;
}
ID
parentID() const
{
return instance_->parentID;
}
TxSetType const&
txs() const
{
return instance_->txs;
}
Json::Value getJson() const;
friend bool
operator<(Ledger const & a, Ledger const & b)
{
return a.id() < b.id();
}
private:
ID id_{0};
Instance const* instance_;
};
/** Oracle maintaining unique ledgers for a simulation.
*/
class LedgerOracle
{
using InstanceMap = boost::bimaps::bimap<Ledger::Instance, Ledger::ID>;
using InstanceEntry = InstanceMap::value_type;
// Set of all known ledgers; note this is never pruned
InstanceMap instances_;
// ID for the next unique ledger
Ledger::ID
nextID() const;
public:
LedgerOracle();
/** Find the ledger with the given ID */
boost::optional<Ledger>
lookup(Ledger::ID const & id) const;
/** Accept the given txs and generate a new ledger
@param curr The current ledger
@param txs The transactions to apply to the current ledger
@param closeTimeResolution Resolution used in determining close time
@param consensusCloseTime The consensus agreed close time, no valid time
if 0
*/
Ledger
accept(Ledger const & curr, TxSetType const& txs,
NetClock::duration closeTimeResolution,
NetClock::time_point const& consensusCloseTime);
/** Determine whether ancestor is really an ancestor of descendent */
bool
isAncestor(Ledger const & ancestor, Ledger const& descendant) const;
/** Determine the number of distinct branches for the set of ledgers.
Ledgers A and B are on different branches if A != B, A is not an ancestor
of B and B is not an ancestor of A, e.g.
/--> A
O
\--> B
*/
std::size_t
branches(std::set<Ledger> const & ledgers) const;
};
} // csf
} // test
} // ripple
#endif

178
src/test/csf/random.h Normal file
View File

@@ -0,0 +1,178 @@
//------------------------------------------------------------------------------
/*
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_RANDOM_H_INCLUDED
#define RIPPLE_TEST_CSF_RANDOM_H_INCLUDED
#include <random>
#include <vector>
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;
}
/** Generate a vector of random samples
@param size the size of the sample
@param dist the distribution to sample
@param g the pseudo-random number generator
@return vector of samples
*/
template <class RandomNumberDistribution, class Generator>
std::vector<typename RandomNumberDistribution::result_type>
sample( std::size_t size, RandomNumberDistribution dist, Generator& g)
{
std::vector<typename RandomNumberDistribution::result_type> res(size);
std::generate(res.begin(), res.end(), [&dist, &g]() { return dist(g); });
return res;
}
/** Invocable that returns random samples from a range according to a discrete
distribution
Given a pair of random access iterators begin and end, each call to the
instance of Selector returns a random entry in the range (begin,end)
according to the weights provided at construction.
*/
template <class RAIter, class Generator>
class Selector
{
RAIter first_, last_;
std::discrete_distribution<> dd_;
Generator g_;
public:
/** Constructor
@param first Random access iterator to the start of the range
@param last Random access iterator to the end of the range
@param w Vector of weights of size list-first
@param g the pseudo-random number generator
*/
Selector(RAIter first, RAIter last, std::vector<double> const& w,
Generator& g)
: first_{first}, last_{last}, dd_{w.begin(), w.end()}, g_{g}
{
using tag = typename std::iterator_traits<RAIter>::iterator_category;
static_assert(
std::is_same<tag, std::random_access_iterator_tag>::value,
"Selector only supports random access iterators.");
// TODO: Allow for forward iterators
}
typename std::iterator_traits<RAIter>::value_type
operator()()
{
auto idx = dd_(g_);
return *(first_ + idx);
}
};
template <typename Iter, typename Generator>
Selector<Iter,Generator>
makeSelector(Iter first, Iter last, std::vector<double> const& w, Generator& g)
{
return Selector<Iter, Generator>(first, last, w, g);
}
//------------------------------------------------------------------------------
// Additional distributions of interest not defined in in <random>
/** Constant "distribution" that always returns the same value
*/
class ConstantDistribution
{
double t_;
public:
ConstantDistribution(double const& t) : t_{t}
{
}
template <class Generator>
inline double
operator()(Generator& )
{
return t_;
}
};
/** 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:
using result_type = double;
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_);
}
};
} // csf
} // test
} // ripple
#endif

130
src/test/csf/submitters.h Normal file
View File

@@ -0,0 +1,130 @@
//------------------------------------------------------------------------------
/*
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_SUBMITTERS_H_INCLUDED
#define RIPPLE_TEST_CSF_SUBMITTERS_H_INCLUDED
#include <test/csf/SimTime.h>
#include <test/csf/Scheduler.h>
#include <test/csf/Peer.h>
#include <test/csf/Tx.h>
#include <type_traits>
namespace ripple {
namespace test {
namespace csf {
// Submitters are classes for simulating submission of transactions to the network
/** Represents rate as a count/duration */
struct Rate
{
std::size_t count;
SimDuration duration;
double
inv() const
{
return duration.count()/double(count);
}
};
/** Submits transactions to a specified peer
Submits successive transactions beginning at start, then spaced according
to succesive calls of distribution(), until stop.
@tparam Distribution is a `UniformRandomBitGenerator` from the STL that
is used by random distributions to generate random samples
@tparam Generator is an object with member
T operator()(Generator &g)
which generates the delay T in SimDuration units to the next
transaction. For the current definition of SimDuration, this is
currently the number of nanoseconds. Submitter internally casts
arithmetic T to SimDuration::rep units to allow using standard
library distributions as a Distribution.
*/
template <class Distribution, class Generator, class Selector>
class Submitter
{
Distribution dist_;
SimTime stop_;
std::uint32_t nextID_ = 0;
Selector selector_;
Scheduler & scheduler_;
Generator & g_;
// Convert generated durations to SimDuration
static SimDuration
asDuration(SimDuration d)
{
return d;
}
template <class T>
static
std::enable_if_t<std::is_arithmetic<T>::value, SimDuration>
asDuration(T t)
{
return SimDuration{static_cast<SimDuration::rep>(t)};
}
void
submit()
{
selector_()->submit(Tx{nextID_++});
if (scheduler_.now() < stop_)
{
scheduler_.in(asDuration(dist_(g_)), [&]() { submit(); });
}
}
public:
Submitter(
Distribution dist,
SimTime start,
SimTime end,
Selector & selector,
Scheduler & s,
Generator & g)
: dist_{dist}, stop_{end}, selector_{selector}, scheduler_{s}, g_{g}
{
scheduler_.at(start, [&]() { submit(); });
}
};
template <class Distribution, class Generator, class Selector>
Submitter<Distribution, Generator, Selector>
makeSubmitter(
Distribution dist,
SimTime start,
SimTime end,
Selector& sel,
Scheduler& s,
Generator& g)
{
return Submitter<Distribution, Generator, Selector>(
dist, start ,end, sel, s, g);
}
} // namespace csf
} // namespace test
} // namespace ripple
#endif

86
src/test/csf/timers.h Normal file
View File

@@ -0,0 +1,86 @@
//------------------------------------------------------------------------------
/*
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_TIMERS_H_INCLUDED
#define RIPPLE_TEST_CSF_TIMERS_H_INCLUDED
#include <chrono>
#include <ostream>
#include <test/csf/Scheduler.h>
#include <test/csf/SimTime.h>
namespace ripple {
namespace test {
namespace csf {
// Timers are classes that schedule repeated events and are mostly independent
// of simulation-specific details.
/** Gives heartbeat of simulation to signal simulation progression
*/
class HeartbeatTimer
{
Scheduler & scheduler_;
SimDuration interval_;
std::ostream & out_;
RealTime startRealTime_;
SimTime startSimTime_;
public:
HeartbeatTimer(
Scheduler& sched,
SimDuration interval = std::chrono::seconds(60s),
std::ostream& out = std::cerr)
: scheduler_{sched}, interval_{interval}, out_{out},
startRealTime_{RealClock::now()},
startSimTime_{sched.now()}
{
};
void
start()
{
scheduler_.in(interval_, [this](){beat(scheduler_.now());});
};
void
beat(SimTime when)
{
using namespace std::chrono;
RealTime realTime = RealClock::now();
SimTime simTime = when;
RealDuration realDuration = realTime - startRealTime_;
SimDuration simDuration = simTime - startSimTime_;
out_ << "Heartbeat. Time Elapsed: {sim: "
<< duration_cast<seconds>(simDuration).count()
<< "s | real: "
<< duration_cast<seconds>(realDuration).count()
<< "s}\n" << std::flush;
scheduler_.in(interval_, [this](){beat(scheduler_.now());});
}
};
} // namespace csf
} // namespace test
} // namespace ripple
#endif

View File

@@ -17,6 +17,9 @@
*/
//==============================================================================
#include <test/consensus/ByzantineFailureSim_test.cpp>
#include <test/consensus/Consensus_test.cpp>
#include <test/consensus/DistributedValidatorsSim_test.cpp>
#include <test/consensus/LedgerTiming_test.cpp>
#include <test/consensus/ScaleFreeSim_test.cpp>
#include <test/consensus/Validations_test.cpp>

View File

@@ -19,5 +19,9 @@
#include <BeastConfig.h>
#include <test/csf/Digraph_test.cpp>
#include <test/csf/Histogram_test.cpp>
#include <test/csf/BasicNetwork_test.cpp>
#include <test/csf/impl/UNL.cpp>
#include <test/csf/Scheduler_test.cpp>
#include <test/csf/impl/ledgers.cpp>
#include <test/csf/impl/Sim.cpp>