mirror of
https://github.com/XRPLF/rippled.git
synced 2025-11-27 14:35:52 +00:00
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:
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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_
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 =
|
||||
|
||||
104
src/test/consensus/ByzantineFailureSim_test.cpp
Normal file
104
src/test/consensus/ByzantineFailureSim_test.cpp
Normal file
@@ -0,0 +1,104 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2012-2017 Ripple Labs Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
#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
273
src/test/consensus/DistributedValidatorsSim_test.cpp
Normal file
273
src/test/consensus/DistributedValidatorsSim_test.cpp
Normal 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
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
123
src/test/consensus/ScaleFreeSim_test.cpp
Normal file
123
src/test/consensus/ScaleFreeSim_test.cpp
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
346
src/test/csf/CollectorRef.h
Normal 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
253
src/test/csf/Digraph.h
Normal 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
|
||||
100
src/test/csf/Digraph_test.cpp
Normal file
100
src/test/csf/Digraph_test.cpp
Normal 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
131
src/test/csf/Histogram.h
Normal 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
|
||||
89
src/test/csf/Histogram_test.cpp
Normal file
89
src/test/csf/Histogram_test.cpp
Normal 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
|
||||
@@ -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
370
src/test/csf/PeerGroup.h
Normal 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
39
src/test/csf/Proposal.h
Normal 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
191
src/test/csf/README.md
Normal 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.
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
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
465
src/test/csf/Scheduler.h
Normal 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
|
||||
89
src/test/csf/Scheduler_test.cpp
Normal file
89
src/test/csf/Scheduler_test.cpp
Normal 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
|
||||
@@ -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
42
src/test/csf/SimTime.h
Normal 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
176
src/test/csf/TrustGraph.h
Normal 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
|
||||
@@ -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_;
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
@@ -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
167
src/test/csf/Validation.h
Normal 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
740
src/test/csf/collectors.h
Normal 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
BIN
src/test/csf/csf_graph.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8.8 KiB |
BIN
src/test/csf/csf_overview.png
Normal file
BIN
src/test/csf/csf_overview.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 78 KiB |
155
src/test/csf/events.h
Normal file
155
src/test/csf/events.h
Normal 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
76
src/test/csf/impl/Sim.cpp
Normal 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
|
||||
@@ -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
|
||||
140
src/test/csf/impl/ledgers.cpp
Normal file
140
src/test/csf/impl/ledgers.cpp
Normal 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
263
src/test/csf/ledgers.h
Normal 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
178
src/test/csf/random.h
Normal 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
130
src/test/csf/submitters.h
Normal 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
86
src/test/csf/timers.h
Normal 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
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user