Refactor consensus for simulation (RIPD-1011):

This is a substantial refactor of the consensus code and also introduces
a basic consensus simulation and testing framework.  The new generic/templated
version is in src/ripple/consensus and documents the current type requirements.
The version adapted for the RCL is in src/ripple/app/consensus.  The testing
framework is in src/test/csf.

Minor behavioral changes/fixes include:
* Adjust close time offset even when not validating.
* Remove spurious proposing_ = false call at end of handleLCL.
* Remove unused functionality provided by checkLastValidation.
* Separate open and converge time
* Don't send a bow out if we're not proposing
* Prevent consensus stopping if NetworkOPs switches to disconnect mode while
  consensus accepts a ledger
* Prevent a corner case in which Consensus::gotTxSet or Consensus::peerProposal
  has the potential to update internal state while an dispatched accept job is
  running.
* Distinguish external and internal calls to startNewRound.  Only external
  calls can reset the proposing_ state of consensus
This commit is contained in:
Brad Chase
2016-11-02 15:16:02 -07:00
parent fc0d64f5ee
commit bc5a74057d
56 changed files with 6492 additions and 3785 deletions

View File

@@ -20,7 +20,6 @@
#include <ripple/app/main/Application.h>
#include <ripple/app/misc/LoadFeeTrack.h>
#include <ripple/app/misc/TxQ.h>
#include <ripple/app/ledger/LedgerConsensus.h>
#include <ripple/app/tx/apply.h>
#include <ripple/basics/Log.h>
#include <ripple/basics/mulDiv.h>

View File

@@ -0,0 +1,474 @@
//------------------------------------------------------------------------------
/*
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/unit_test.h>
#include <ripple/consensus/Consensus.h>
#include <ripple/consensus/ConsensusProposal.h>
#include <ripple/beast/clock/manual_clock.h>
#include <boost/function_output_iterator.hpp>
#include <test/csf.h>
#include <utility>
namespace ripple {
namespace test {
class Consensus_test : public beast::unit_test::suite
{
public:
void
testStandalone()
{
using namespace csf;
auto tg = TrustGraph::makeComplete(1);
Sim s(tg, topology(tg, fixed{LEDGER_GRANULARITY}));
auto & p = s.peers[0];
p.targetLedgers = 1;
p.start();
p.submit(Tx{ 1 });
s.net.step();
// Inspect that the proper ledger was created
BEAST_EXPECT(p.LCL().seq == 1);
BEAST_EXPECT(p.LCL() == p.lastClosedLedger.id());
BEAST_EXPECT(p.lastClosedLedger.id().txs.size() == 1);
BEAST_EXPECT(p.lastClosedLedger.id().txs.find(Tx{ 1 })
!= p.lastClosedLedger.id().txs.end());
BEAST_EXPECT(p.getLastCloseProposers() == 0);
}
void
testPeersAgree()
{
using namespace csf;
using namespace std::chrono;
auto tg = TrustGraph::makeComplete(5);
Sim sim(tg,
topology(tg, fixed{round<milliseconds>(0.2 * LEDGER_GRANULARITY)}));
// everyone submits their own ID as a TX and relay it to peers
for (auto & p : sim.peers)
p.submit(Tx(p.id));
// Verify all peers have the same LCL and it has all the Txs
sim.run(1);
for (auto & p : sim.peers)
{
auto const &lgrID = p.LCL();
BEAST_EXPECT(lgrID.seq == 1);
BEAST_EXPECT(p.getLastCloseProposers() == sim.peers.size() - 1);
for(std::uint32_t i = 0; i < sim.peers.size(); ++i)
BEAST_EXPECT(lgrID.txs.find(Tx{ i }) != lgrID.txs.end());
// Matches peer 0 ledger
BEAST_EXPECT(lgrID.txs == sim.peers[0].LCL().txs);
}
}
void
testSlowPeer()
{
using namespace csf;
using namespace std::chrono;
// Run two tests
// 1. The slow peer is participating inconsensus
// 2. The slow peer is just observing
for(auto isParticipant : {true, false})
{
auto tg = TrustGraph::makeComplete(5);
Sim sim(tg, topology(tg,[](PeerID i, PeerID j)
{
auto delayFactor = (i == 0 || j == 0) ? 1.1 : 0.2;
return round<milliseconds>(delayFactor* LEDGER_GRANULARITY);
}));
sim.peers[0].proposing = sim.peers[0].validating = isParticipant;
// All peers submit their own ID as a transaction and relay it to peers
for (auto & p : sim.peers)
{
p.submit(Tx{ p.id });
}
sim.run(1);
// Verify all peers have same LCL but are missing transaction 0 which
// was not received by all peers before the ledger closed
for (auto & p : sim.peers)
{
auto const &lgrID = p.LCL();
BEAST_EXPECT(lgrID.seq == 1);
// If peer 0 is participating
if(isParticipant)
{
BEAST_EXPECT(p.getLastCloseProposers()
== sim.peers.size() - 1);
// Peer 0 closes first because it sees a quorum of agreeing positions
// from all other peers in one hop (1->0, 2->0, ..)
// The other peers take an extra timer period before they find that
// Peer 0 agrees with them ( 1->0->1, 2->0->2, ...)
if(p.id != 0)
BEAST_EXPECT(p.getLastConvergeDuration()
> sim.peers[0].getLastConvergeDuration());
}
else // peer 0 is not participating
{
auto const proposers = p.getLastCloseProposers();
if(p.id == 0)
BEAST_EXPECT(proposers == sim.peers.size() - 1);
else
BEAST_EXPECT(proposers == sim.peers.size() - 2);
// so all peers should have closed together
BEAST_EXPECT(p.getLastConvergeDuration()
== sim.peers[0].getLastConvergeDuration());
}
BEAST_EXPECT(lgrID.txs.find(Tx{ 0 }) == lgrID.txs.end());
for(std::uint32_t i = 1; i < sim.peers.size(); ++i)
BEAST_EXPECT(lgrID.txs.find(Tx{ i }) != lgrID.txs.end());
// Matches peer 0 ledger
BEAST_EXPECT(lgrID.txs == sim.peers[0].LCL().txs);
}
BEAST_EXPECT(sim.peers[0].openTxs.find(Tx{ 0 })
!= sim.peers[0].openTxs.end());
}
}
void
testCloseTimeDisagree()
{
using namespace csf;
using namespace std::chrono;
// This is a very specialized test to get ledgers to disagree on
// the close time. It unfortunately assumes knowledge about current
// timing constants. This is a necessary evil to get coverage up
// pending more extensive refactorings of timing constants.
// In order to agree-to-disagree on the close time, there must be no
// clear majority of nodes agreeing on a close time. This test
// sets a relative offset to the peers internal clocks so that they
// send proposals with differing times.
// However, they have to agree on the effective close time, not the
// exact close time. The minimum closeTimeResolution is given by
// ledgerPossibleTimeResolutions[0], which is currently 10s. This means
// the skews need to be at least 10 seconds.
// Complicating this matter is that nodes will ignore proposals
// with times more than PROPOSE_FRESHNESS =20s in the past. So at
// the minimum granularity, we have at most 3 types of skews (0s,10s,20s).
// This test therefore has 6 nodes, with 2 nodes having each type of
// skew. Then no majority (1/3 < 1/2) of nodes will agree on an
// actual close time.
auto tg = TrustGraph::makeComplete(6);
Sim sim(tg,
topology(tg, fixed{round<milliseconds>(0.2 * LEDGER_GRANULARITY)}));
// Run consensus without skew until we have a short close time resolution
while(sim.peers.front().lastClosedLedger.closeTimeResolution() >=
PROPOSE_FRESHNESS)
sim.run(1);
// Introduce a shift on the time of half the peers
sim.peers[0].clockSkew = PROPOSE_FRESHNESS/2;
sim.peers[1].clockSkew = PROPOSE_FRESHNESS/2;
sim.peers[2].clockSkew = PROPOSE_FRESHNESS;
sim.peers[3].clockSkew = PROPOSE_FRESHNESS;
// Verify all peers have the same LCL and it has all the Txs
sim.run(1);
for (auto & p : sim.peers)
{
BEAST_EXPECT(! p.lastClosedLedger.closeAgree());
}
}
void
testWrongLCL()
{
using namespace csf;
using namespace std::chrono;
// Specialized test to exercise a temporary fork in which some peers
// are working on an incorrect prior ledger.
// Vary the time it takes to process validations to exercise detecting
// the wrong LCL at different phases of consensus
for(auto validationDelay : {0s, LEDGER_MIN_CLOSE})
{
// Consider 10 peers:
// 0 1 2 3 4 5 6 7 8 9
//
// Nodes 0-1 trust nodes 0-4
// Nodes 2-9 trust nodes 2-9
//
// By submitting tx 0 to nodes 0-4 and tx 1 to nodes 5-9,
// nodes 0-1 will generate the wrong LCL (with tx 0). The remaining
// nodes will instead accept the ledger with tx 1.
// Nodes 0-1 will detect this mismatch during a subsequent round
// since nodes 2-4 will validate a different ledger.
// Nodes 0-1 will acquire the proper ledger from the network and
// resume consensus and eventually generate the dominant network ledger
std::vector<UNL> unls;
unls.push_back({2,3,4,5,6,7,8,9});
unls.push_back({0,1,2,3,4});
std::vector<int> membership(10,0);
membership[0] = 1;
membership[1] = 1;
TrustGraph tg{unls, membership};
// This topology can fork, which is why we are using it for this test.
BEAST_EXPECT(tg.canFork(minimumConsensusPercentage/100.));
auto netDelay = round<milliseconds>(0.2 * LEDGER_GRANULARITY);
Sim sim(tg, topology(tg, fixed{netDelay}));
// initial round to set prior state
sim.run(1);
// Nodes in smaller UNL have seen tx 0, nodes in other unl have seen tx 1
for (auto & p : sim.peers)
{
p.validationDelay = validationDelay;
p.missingLedgerDelay = netDelay;
if (unls[1].find(p.id) != unls[1].end())
p.openTxs.insert(Tx{0});
else
p.openTxs.insert(Tx{1});
}
// Run for 2 additional rounds
// - One round to generate different ledgers
// - One round to detect different prior ledgers (but still generate
// wrong ones) and recover
sim.run(2);
bc::flat_map<int, bc::flat_set<Ledger::ID>> ledgers;
for (auto & p : sim.peers)
{
for (auto const & l : p.ledgers)
{
ledgers[l.first.seq].insert(l.first);
}
}
BEAST_EXPECT(ledgers[0].size() == 1);
BEAST_EXPECT(ledgers[1].size() == 1);
BEAST_EXPECT(ledgers[2].size() == 2);
BEAST_EXPECT(ledgers[3].size() == 1);
}
}
void
testFork()
{
using namespace csf;
using namespace std::chrono;
int numPeers = 10;
for(int overlap = 0; overlap <= numPeers; ++overlap)
{
auto tg = TrustGraph::makeClique(numPeers, overlap);
Sim sim(tg, topology(tg,
fixed{round<milliseconds>(0.2 * LEDGER_GRANULARITY)}));
// Initial round to set prior state
sim.run(1);
for (auto & p : sim.peers)
{
// Nodes have only seen transactions from their neighbors
p.openTxs.insert(Tx{p.id});
for(auto const link : sim.net.links(&p))
p.openTxs.insert(Tx{link.to->id});
}
sim.run(1);
// See if the network forked
bc::flat_set<Ledger::ID> ledgers;
for (auto & p : sim.peers)
{
ledgers.insert(p.LCL());
}
// Fork should not happen for 40% or greater overlap
// Since the overlapped nodes have a UNL that is the union of the
// two cliques, the maximum sized UNL list is the number of peers
if(overlap > 0.4 * numPeers)
BEAST_EXPECT(ledgers.size() == 1);
else // Even if we do fork, there shouldn't be more than 3 ledgers
// One for cliqueA, one for cliqueB and one for nodes in both
BEAST_EXPECT(ledgers.size() <= 3);
}
}
void
simClockSkew()
{
using namespace csf;
// Attempting to test what happens if peers enter consensus well
// separated in time. Initial round (in which peers are not staggered)
// is used to get the network going, then transactions are submitted
// together and consensus continues.
// For all the times below, the same ledger is built but the close times
// disgree. BUT THE LEDGER DOES NOT SHOW disagreeing close times.
// It is probably because peer proposals are stale, so they get ignored
// but with no peer proposals, we always assume close time consensus is
// true.
// Disabled while continuing to understand testt.
for(auto stagger : {800ms, 1600ms, 3200ms, 30000ms, 45000ms, 300000ms})
{
auto tg = TrustGraph::makeComplete(5);
Sim sim(tg, topology(tg, [](PeerID i, PeerID)
{
return 200ms * (i + 1);
}));
// all transactions submitted before starting
// Initial round to set prior state
sim.run(1);
for (auto & p : sim.peers)
{
p.openTxs.insert(Tx{ 0 });
p.targetLedgers = p.completedLedgers + 1;
}
// stagger start of consensus
for (auto & p : sim.peers)
{
p.start();
sim.net.step_for(stagger);
}
// run until all peers have accepted all transactions
sim.net.step_while([&]()
{
for(auto & p : sim.peers)
{
if(p.LCL().txs.size() != 1)
{
return true;
}
}
return false;
});
}
}
void
simScaleFree()
{
using namespace std::chrono;
using namespace csf;
// Generate a quasi-random scale free network and simulate consensus
// for a single transaction
int N = 100; // Peers
int numUNLs = 15; // UNL lists
int minUNLSize = N/4, maxUNLSize = N / 2;
double transProb = 0.5;
std::mt19937_64 rng;
auto tg = TrustGraph::makeRandomRanked(N, numUNLs,
PowerLawDistribution{1,3},
std::uniform_int_distribution<>{minUNLSize, maxUNLSize},
rng);
Sim sim{tg, topology(tg, fixed{round<milliseconds>(0.2 * LEDGER_GRANULARITY)})};
// Initial round to set prior state
sim.run(1);
std::uniform_real_distribution<> u{};
for (auto & p : sim.peers)
{
// 50-50 chance to have seen a transaction
if(u(rng) >= transProb)
p.openTxs.insert(Tx{0});
}
sim.run(1);
// See if the network forked
bc::flat_set<Ledger::ID> ledgers;
for (auto & p : sim.peers)
{
ledgers.insert(p.LCL());
}
BEAST_EXPECT(ledgers.size() == 1);
}
void
run() override
{
testStandalone();
testPeersAgree();
testSlowPeer();
testCloseTimeDisagree();
testWrongLCL();
testFork();
simClockSkew();
simScaleFree();
}
};
BEAST_DEFINE_TESTSUITE(Consensus, consensus, ripple);
} // test
} // ripple

View File

@@ -0,0 +1,164 @@
//------------------------------------------------------------------------------
/*
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/unit_test.h>
#include <ripple/consensus/LedgerTiming.h>
namespace ripple {
namespace test {
class LedgerTiming_test : public beast::unit_test::suite
{
beast::Journal j;
void testGetNextLedgerTimeResolution()
{
// helper to iteratively call into getNextLedgerTimeResolution
struct test_res
{
std::uint32_t decrease = 0;
std::uint32_t equal = 0;
std::uint32_t increase = 0;
static test_res run(bool previousAgree, std::uint32_t rounds)
{
test_res res;
auto closeResolution = ledgerDefaultTimeResolution;
auto nextCloseResolution = closeResolution;
std::uint32_t round = 0;
do
{
nextCloseResolution = getNextLedgerTimeResolution(
closeResolution, previousAgree, ++round);
if (nextCloseResolution < closeResolution)
++res.decrease;
else if (nextCloseResolution > closeResolution)
++res.increase;
else
++res.equal;
std::swap(nextCloseResolution, closeResolution);
} while (round < rounds);
return res;
}
};
// If we never agree on close time, only can increase resolution
// until hit the max
auto decreases = test_res::run(false, 10);
BEAST_EXPECT(decreases.increase == 3);
BEAST_EXPECT(decreases.decrease == 0);
BEAST_EXPECT(decreases.equal == 7);
// If we always agree on close time, only can decrease resolution
// until hit the min
auto increases = test_res::run(false, 100);
BEAST_EXPECT(increases.increase == 3);
BEAST_EXPECT(increases.decrease == 0);
BEAST_EXPECT(increases.equal == 97);
}
void testRoundCloseTime()
{
// A closeTime equal to the epoch is not modified
using tp = NetClock::time_point;
tp def;
BEAST_EXPECT(def == roundCloseTime(def, 30s));
// Otherwise, the closeTime is rounded to the nearest
// rounding up on ties
BEAST_EXPECT(tp{ 0s } == roundCloseTime(tp{ 29s }, 60s));
BEAST_EXPECT(tp{ 30s } == roundCloseTime(tp{ 30s }, 1s));
BEAST_EXPECT(tp{ 60s } == roundCloseTime(tp{ 31s }, 60s));
BEAST_EXPECT(tp{ 60s } == roundCloseTime(tp{ 30s }, 60s));
BEAST_EXPECT(tp{ 60s } == roundCloseTime(tp{ 59s }, 60s));
BEAST_EXPECT(tp{ 60s } == roundCloseTime(tp{ 60s }, 60s));
BEAST_EXPECT(tp{ 60s } == roundCloseTime(tp{ 61s }, 60s));
}
void testShouldCloseLedger()
{
// Bizarre times forcibly close
BEAST_EXPECT(shouldCloseLedger(true, 10, 10, 10, -10s, 10s, 1s, 1s, j));
BEAST_EXPECT(shouldCloseLedger(true, 10, 10, 10, 100h, 10s, 1s, 1s, j));
BEAST_EXPECT(shouldCloseLedger(true, 10, 10, 10, 10s, 100h, 1s, 1s, j));
// Rest of network has closed
BEAST_EXPECT(shouldCloseLedger(true, 10, 3, 5, 10s, 10s, 10s, 10s, j));
// No transactions means wait until end of internval
BEAST_EXPECT(!shouldCloseLedger(false, 10, 0, 0, 1s, 1s, 1s, 10s, j));
BEAST_EXPECT(shouldCloseLedger(false, 10, 0, 0, 1s, 10s, 1s, 10s, j));
// Enforce minimum ledger open time
BEAST_EXPECT(!shouldCloseLedger(true, 10, 0, 0, 10s, 10s, 1s, 10s, j));
// Don't go too much faster than last time
BEAST_EXPECT(!shouldCloseLedger(true, 10, 0, 0, 10s, 10s, 3s, 10s, j));
BEAST_EXPECT(shouldCloseLedger(true, 10, 0, 0, 10s, 10s, 10s, 10s, j));
}
void testCheckConsensus()
{
// Not enough time has elapsed
BEAST_EXPECT( ConsensusState::No
== checkConsensus(10, 2, 2, 0, 3s, 2s, true, j));
// If not enough peers have propsed, ensure
// more time for proposals
BEAST_EXPECT( ConsensusState::No
== checkConsensus(10, 2, 2, 0, 3s, 4s, true, j));
// Enough time has elapsed and we all agree
BEAST_EXPECT( ConsensusState::Yes
== checkConsensus(10, 2, 2, 0, 3s, 10s, true, j));
// Enough time has elapsed and we don't yet agree
BEAST_EXPECT( ConsensusState::No
== checkConsensus(10, 2, 1, 0, 3s, 10s, true, j));
// Our peers have moved on
// Enough time has elapsed and we all agree
BEAST_EXPECT( ConsensusState::MovedOn
== checkConsensus(10, 2, 1, 8, 3s, 10s, true, j));
// No peers makes it easy to agree
BEAST_EXPECT( ConsensusState::Yes
== checkConsensus(0, 0, 0, 0, 3s, 10s, true, j));
}
void
run() override
{
testGetNextLedgerTimeResolution();
testRoundCloseTime();
testShouldCloseLedger();
testCheckConsensus();
}
};
BEAST_DEFINE_TESTSUITE(LedgerTiming, consensus, ripple);
} // test
} // ripple

26
src/test/csf.h Normal file
View File

@@ -0,0 +1,26 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012-2017 Ripple Labs Inc
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include <test/csf/Tx.h>
#include <test/csf/Ledger.h>
#include <test/csf/BasicNetwork.h>
#include <test/csf/Peer.h>
#include <test/csf/UNL.h>
#include <test/csf/Sim.h>
#include <test/csf/Peer.h>

View File

@@ -17,8 +17,8 @@
*/
//==============================================================================
#ifndef RIPPLE_TEST_BASICNETWORK_H_INCLUDED
#define RIPPLE_TEST_BASICNETWORK_H_INCLUDED
#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>
@@ -35,14 +35,13 @@
#include <cassert>
#include <cstdint>
#include <iomanip>
#include <random>
#include <type_traits>
#include <unordered_map>
#include <unordered_set>
namespace ripple {
namespace test {
namespace csf {
/** Peer to peer network simulator.
The network is formed from a set of Peer objects representing
@@ -73,8 +72,8 @@ namespace test {
After creating the Peer set, constructing the network,
and establishing connections, the caller uses one or more
of the step, step_one, step_for, and step_until functions
to iterate the network,
of the step, step_one, step_for, step_until and step_while
functions to iterate the network,
Peer Requirements:
@@ -263,7 +262,6 @@ private:
// VFALCO This is an ugly wart, aged containers
// want a non-const reference to a clock.
clock_type mutable clock_;
std::mt19937_64 rng_;
std::unordered_map<Peer, links_type> links_;
public:
@@ -272,10 +270,6 @@ public:
BasicNetwork();
/** A source of pseudo-random numbers. */
std::mt19937_64&
rng();
/** Return the allocator. */
qalloc const&
alloc() const;
@@ -291,14 +285,6 @@ public:
time_point
now() const;
/** Return a random integer in range [0, n) */
std::size_t
rand (std::size_t n);
/** Return a random integer in range [first, last) */
std::size_t
rand (std::size_t first, std::size_t last);
/** Connect two peers.
The link is directed, with `from` establishing
@@ -447,6 +433,23 @@ public:
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:
@@ -683,14 +686,6 @@ BasicNetwork<Peer>::BasicNetwork()
{
}
template <class Peer>
inline
std::mt19937_64&
BasicNetwork<Peer>::rng()
{
return rng_;
}
template <class Peer>
inline
qalloc const&
@@ -717,24 +712,6 @@ BasicNetwork<Peer>::now() const ->
return clock_.now();
}
template <class Peer>
inline
std::size_t
BasicNetwork<Peer>::rand(std::size_t n)
{
return std::uniform_int_distribution<
std::size_t>(0, n - 1)(rng_);
}
template <class Peer>
inline
std::size_t
BasicNetwork<Peer>::rand(
std::size_t first, std::size_t last)
{
return first + rand(last - first);
}
template <class Peer>
bool
BasicNetwork<Peer>::connect(
@@ -857,6 +834,17 @@ BasicNetwork<Peer>::step()
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(
@@ -922,6 +910,7 @@ BasicNetwork<Peer>::bfs(
}
}
} // csf
} // test
} // ripple

View File

@@ -18,7 +18,7 @@
//==============================================================================
#include <BeastConfig.h>
#include <test/jtx/BasicNetwork.h>
#include <test/csf/BasicNetwork.h>
#include <ripple/beast/unit_test.h>
#include <set>
#include <vector>
@@ -26,6 +26,7 @@
namespace ripple {
namespace test {
class BasicNetwork_test : public beast::unit_test::suite
{
public:
@@ -87,8 +88,7 @@ public:
pv.emplace_back(0);
pv.emplace_back(1);
pv.emplace_back(2);
BasicNetwork<Peer*> net;
BEAST_EXPECT(net.rand(0, 1) == 0);
csf::BasicNetwork<Peer*> net;
BEAST_EXPECT(! net.connect(&pv[0], &pv[0]));
BEAST_EXPECT(net.connect(&pv[0], &pv[1], 1s));
BEAST_EXPECT(net.connect(&pv[1], &pv[2], 1s));

197
src/test/csf/Ledger.h Normal file
View File

@@ -0,0 +1,197 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012-2017 Ripple Labs Inc
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#ifndef RIPPLE_TEST_CSF_LEDGER_H_INCLUDED
#define RIPPLE_TEST_CSF_LEDGER_H_INCLUDED
#include <ripple/basics/chrono.h>
#include <test/csf/Tx.h>
namespace ripple {
namespace test {
namespace csf {
/** A ledger is a set of observed transactions and a sequence number
identifying the ledger.
Peers in the consensus process are trying to agree on a set of transactions
to include in a ledger. For unit testing, each transaction is a
single integer and the ledger is a set of observed integers. This means
future ledgers have prior ledgers as subsets, e.g.
Ledger 0 : {}
Ledger 1 : {1,4,5}
Ledger 2 : {1,2,4,5,10}
....
Tx - Integer
TxSet - Set of Tx
Ledger - Set of Tx and sequence number
*/
class Ledger
{
public:
struct ID
{
std::uint32_t seq = 0;
TxSetType txs = TxSetType{};
bool operator==(ID const & o) const
{
return seq == o.seq && txs == o.txs;
}
bool operator!=(ID const & o) const
{
return !(*this == o);
}
bool operator<(ID const & o) const
{
return std::tie(seq, txs) < std::tie(o.seq, o.txs);
}
};
auto const &
id() const
{
return id_;
}
auto
seq() const
{
return id_.seq;
}
auto
closeTimeResolution() const
{
return closeTimeResolution_;
}
auto
closeAgree() const
{
return closeTimeAgree_;
}
auto
closeTime() const
{
return closeTime_;
}
auto
actualCloseTime() const
{
return actualCloseTime_;
}
auto
parentCloseTime() const
{
return parentCloseTime_;
}
auto const &
parentID() const
{
return parentID_;
}
Json::Value
getJson() const
{
Json::Value res(Json::objectValue);
res["seq"] = seq();
return res;
}
//! Apply the given transactions to this ledger
Ledger
close(TxSetType const & txs,
NetClock::duration closeTimeResolution,
NetClock::time_point const & consensusCloseTime,
bool closeTimeAgree) const
{
Ledger res{ *this };
res.id_.txs.insert(txs.begin(), txs.end());
res.id_ .seq= seq() + 1;
res.closeTimeResolution_ = closeTimeResolution;
res.actualCloseTime_ = consensusCloseTime;
res.closeTime_ = effectiveCloseTime(consensusCloseTime,
closeTimeResolution, parentCloseTime_);
res.closeTimeAgree_ = closeTimeAgree;
res.parentCloseTime_ = closeTime();
res.parentID_ = id();
return res;
}
private:
//! Unique identifier of ledger is combination of sequence number and id
ID id_;
//! Bucket resolution used to determine close time
NetClock::duration closeTimeResolution_ = ledgerDefaultTimeResolution;
//! When the ledger closed
NetClock::time_point closeTime_;
//! Whether consenssus agreed on the close time
bool closeTimeAgree_ = true;
//! Parent ledger id
ID parentID_;
//! Parent ledger close time
NetClock::time_point parentCloseTime_;
//! Close time unadjusted by closeTimeResolution
NetClock::time_point actualCloseTime_;
};
inline
std::ostream &
operator<<(std::ostream & o, Ledger::ID const & id)
{
return o << id.seq << "," << id.txs;
}
inline
std::string
to_string(Ledger::ID const & id)
{
std::stringstream ss;
ss << id;
return ss.str();
}
} // csf
} // test
} // ripple
#endif

492
src/test/csf/Peer.h Normal file
View File

@@ -0,0 +1,492 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012-2017 Ripple Labs Inc
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#ifndef RIPPLE_TEST_CSF_PEER_H_INCLUDED
#define RIPPLE_TEST_CSF_PEER_H_INCLUDED
#include <boost/container/flat_map.hpp>
#include <boost/container/flat_set.hpp>
#include <test/csf/Tx.h>
#include <test/csf/Ledger.h>
#include <test/csf/UNL.h>
namespace ripple {
namespace test {
namespace csf {
/** Store validations reached by peers */
struct Validation
{
PeerID id;
Ledger::ID ledger;
Ledger::ID prevLedger;
};
namespace bc = boost::container;
class Validations
{
//< Ledgers seen by peers, saved in order received (which should be order
//< created)
bc::flat_map<Ledger::ID, bc::flat_set<PeerID>> nodesFromLedger;
bc::flat_map<Ledger::ID, bc::flat_set<PeerID>> nodesFromPrevLedger;
bc::flat_map<Ledger::ID, bc::flat_map<Ledger::ID, std::size_t>> childLedgers;
public:
void
update(Validation const & v)
{
nodesFromLedger[v.ledger].insert(v.id);
if(v.ledger.seq > 0)
{
nodesFromPrevLedger[v.prevLedger].insert(v.id);
childLedgers[v.prevLedger][v.ledger]++;
}
}
//< The number of peers who have validated this ledger
std::size_t
proposersValidated(Ledger::ID const & prevLedger) const
{
auto it = nodesFromLedger.find(prevLedger);
if(it != nodesFromLedger.end())
return it->second.size();
return 0;
}
/** The number of peers that are past this ledger, i.e.
they have a newer most recent ledger, but have this ledger
as an ancestor.
*/
std::size_t
proposersFinished(Ledger::ID const & prevLedger) const
{
auto it = nodesFromPrevLedger.find(prevLedger);
if(it != nodesFromPrevLedger.end())
return it->second.size();
return 0;
}
/** Returns the ledger starting from prevLedger with the most validations.
*/
Ledger::ID
getBestLCL(Ledger::ID const & currLedger,
Ledger::ID const & prevLedger) const
{
auto it = childLedgers.find(prevLedger);
if (it != childLedgers.end() &&
! it->second.empty())
{
std::size_t bestCount = 0;
Ledger::ID bestLedger;
for (auto const & b : it->second)
{
auto currCount = b.second;
if(currLedger == b.first)
currCount++;
if(currCount > bestCount)
bestLedger = b.first;
if(currCount == bestCount && currLedger == b.first)
bestLedger = b.first;
}
return bestLedger;
}
return currLedger;
}
};
/** Proposal is a position taken in the consensus process and is represented
directly from the generic types.
*/
using Proposal = ConsensusProposal<PeerID, Ledger::ID, TxSetType>;
struct Traits
{
using Ledger_t = Ledger;
using NodeID_t = PeerID;
using TxSet_t = TxSet;
using MissingTxException_t = MissingTx;
};
/** Represents a single node participating in the consensus process.
It implements the Callbacks required by Consensus.
*/
struct Peer : public Consensus<Peer, Traits>
{
using Base = Consensus<Peer, Traits>;
//! Our unique ID
PeerID id;
//! openTxs that haven't been closed in a ledger yet
TxSetType openTxs;
//! last ledger this peer closed
Ledger lastClosedLedger;
//! Handle to network for sending messages
BasicNetwork<Peer*> & net;
//! UNL of trusted peers
UNL unl;
//! Most recent ledger completed by peers
Validations peerValidations;
// The ledgers, proposals, TxSets and Txs this peer has seen
bc::flat_map<Ledger::ID, Ledger> ledgers;
//! Map from Ledger::ID to vector of Positions with that ledger
//! as the prior ledger
bc::flat_map<Ledger::ID, std::vector<Proposal>> peerPositions_;
bc::flat_map<TxSet::ID, TxSet> txSets;
int completedLedgers = 0;
int targetLedgers = std::numeric_limits<int>::max();
//! Skew samples from the network clock; to be refactored into a
//! clock time once it is provided separately from the network.
std::chrono::seconds clockSkew{0};
//! Delay in processing validations from remote peers
std::chrono::milliseconds validationDelay{0};
//! Delay in acquiring missing ledger from the network
std::chrono::milliseconds missingLedgerDelay{0};
bool validating = true;
bool proposing = true;
//! All peers start from the default constructed ledger
Peer(PeerID i, BasicNetwork<Peer*> & n, UNL const & u)
: Consensus<Peer, Traits>( n.clock(), beast::Journal{})
, id{i}
, net{n}
, unl(u)
{
ledgers[lastClosedLedger.id()] = lastClosedLedger;
}
// @return whether we are proposing,validating
// TODO: Bit akward that this is in callbacks, would be nice to extract
std::pair<bool, bool>
getMode()
{
// in RCL this hits NetworkOps to decide whether we are proposing
// validating
return{ proposing, validating };
}
Ledger const *
acquireLedger(Ledger::ID const & ledgerHash)
{
auto it = ledgers.find(ledgerHash);
if (it != ledgers.end())
return &(it->second);
// TODO Get from network/oracle properly!
for (auto const& link : net.links(this))
{
auto const & p = *link.to;
auto it = p.ledgers.find(ledgerHash);
if (it != p.ledgers.end())
{
schedule(missingLedgerDelay,
[this, ledgerHash, ledger = it->second]()
{
ledgers.emplace(ledgerHash, ledger);
});
if(missingLedgerDelay == 0ms)
return &ledgers[ledgerHash];
break;
}
}
return nullptr;
}
auto const &
proposals(Ledger::ID const & ledgerHash)
{
return peerPositions_[ledgerHash];
}
TxSet const *
acquireTxSet(TxSet::ID const & setId)
{
auto it = txSets.find(setId);
if(it != txSets.end())
return &(it->second);
// TODO Get from network/oracle instead!
return nullptr;
}
bool
hasOpenTransactions() const
{
return !openTxs.empty();
}
std::size_t
proposersValidated(Ledger::ID const & prevLedger)
{
return peerValidations.proposersValidated(prevLedger);
}
std::size_t
proposersFinished(Ledger::ID const & prevLedger)
{
return peerValidations.proposersFinished(prevLedger);
}
void
onStartRound(Ledger const &) {}
void
onClose(Ledger const &, bool ) {}
// don't really offload
void
dispatchAccept(TxSet const & f)
{
Base::accept(f);
}
void
share(TxSet const &s)
{
relay(s);
}
Ledger::ID
getLCL(Ledger::ID const & currLedger,
Ledger::ID const & priorLedger,
bool haveCorrectLCL)
{
// TODO: Use generic validation code
if(currLedger.seq > 0 && priorLedger.seq > 0)
return peerValidations.getBestLCL(currLedger, priorLedger);
return currLedger;
}
void
propose(Proposal const & pos)
{
if(proposing)
relay(pos);
}
void
relay(DisputedTx<Tx, PeerID> const & dispute)
{
relay(dispute.tx());
}
std::pair <TxSet, Proposal>
makeInitialPosition(
Ledger const & prevLedger,
bool isProposing,
bool isCorrectLCL,
NetClock::time_point closeTime,
NetClock::time_point now)
{
TxSet res{ openTxs };
return { res,
Proposal{prevLedger.id(), Proposal::seqJoin, res.id(), closeTime, now, id} };
}
// Process the accepted transaction set, generating the newly closed ledger
// and clearing out the openTxs that were included.
// TODO: Kinda nasty it takes so many arguments . . . sign of bad coupling
bool
accept(TxSet const& set,
NetClock::time_point consensusCloseTime,
bool proposing_,
bool validating_,
bool haveCorrectLCL_,
bool consensusFail_,
Ledger::ID const & prevLedgerHash_,
Ledger const & previousLedger_,
NetClock::duration closeResolution_,
NetClock::time_point const & now,
std::chrono::milliseconds const & roundTime_,
hash_map<Tx::ID, DisputedTx <Tx, PeerID>> const & disputes_,
std::map <NetClock::time_point, int> closeTimes_,
NetClock::time_point const & closeTime)
{
auto newLedger = previousLedger_.close(set.txs_, closeResolution_,
closeTime, consensusCloseTime != NetClock::time_point{});
ledgers[newLedger.id()] = newLedger;
lastClosedLedger = newLedger;
auto it = std::remove_if(openTxs.begin(), openTxs.end(),
[&](Tx const & tx)
{
return set.exists(tx.id());
});
openTxs.erase(it, openTxs.end());
if(validating)
relay(Validation{id, newLedger.id(), newLedger.parentID()});
return validating_;
}
void
endConsensus(bool correct)
{
// kick off the next round...
// in the actual implementation, this passes back through
// network ops
++completedLedgers;
// startRound sets the LCL state, so we need to call it once after
// the last requested round completes
// TODO: reconsider this and instead just save LCL generated here?
if(completedLedgers <= targetLedgers)
{
startRound(now(), lastClosedLedger.id(),
lastClosedLedger);
}
}
//-------------------------------------------------------------------------
// non-callback helpers
void
receive(Proposal const & p)
{
if(unl.find(p.nodeID()) == unl.end())
return;
// TODO: Be sure this is a new proposal!!!!!
auto & dest = peerPositions_[p.prevLedger()];
if(std::find(dest.begin(), dest.end(), p) != dest.end())
return;
dest.push_back(p);
peerProposal(now(), p);
}
void
receive(TxSet const & txs)
{
// save and map complete?
auto it = txSets.insert(std::make_pair(txs.id(), txs));
if(it.second)
gotTxSet(now(), txs);
}
void
receive(Tx const & tx)
{
if (openTxs.find(tx.id()) == openTxs.end())
{
openTxs.insert(tx);
// relay to peers???
relay(tx);
}
}
void
receive(Validation const & v)
{
if(unl.find(v.id) != unl.end())
{
schedule(validationDelay,
[&, v]()
{
peerValidations.update(v);
});
}
}
template <class T>
void
relay(T const & t)
{
for(auto const& link : net.links(this))
net.send(this, link.to,
[msg = t, to = link.to]
{
to->receive(msg);
});
}
// Receive and relay locally submitted transaction
void
submit(Tx const & tx)
{
receive(tx);
relay(tx);
}
void
timerEntry()
{
Base::timerEntry(now());
// only reschedule if not completed
if(completedLedgers < targetLedgers)
net.timer(LEDGER_GRANULARITY, [&]() { timerEntry(); });
}
void
start()
{
net.timer(LEDGER_GRANULARITY, [&]() { timerEntry(); });
// The ID is the one we have seen the most validations for
// In practice, we might not actually have that ledger itself yet,
// so there is no gaurantee that bestLCL == lastClosedLedger.id()
auto bestLCL = peerValidations.getBestLCL(lastClosedLedger.id(),
lastClosedLedger.parentID());
startRound(now(), bestLCL,
lastClosedLedger);
}
NetClock::time_point
now() const
{
// We don't care about the actual epochs, but do want the
// generated NetClock time to be well past its epoch to ensure
// any subtractions of two NetClock::time_point in the consensu
// code are positive. (e.g. PROPOSE_FRESHNESS)
using namespace std::chrono;
return NetClock::time_point(duration_cast<NetClock::duration>
(net.now().time_since_epoch()+ 86400s + clockSkew));
}
// Schedule the provided callback in `when` duration, but if
// `when` is 0, call immediately
template <class T>
void schedule(std::chrono::nanoseconds when, T && what)
{
if(when == 0ns)
what();
else
net.timer(when, std::forward<T>(what));
}
};
} // csf
} // test
} // ripple
#endif

104
src/test/csf/Sim.h Normal file
View File

@@ -0,0 +1,104 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012-2017 Ripple Labs Inc
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#ifndef RIPPLE_TEST_CSF_SIM_H_INCLUDED
#define RIPPLE_TEST_CSF_SIM_H_INCLUDED
#include <test/csf/UNL.h>
#include <test/csf/BasicNetwork.h>
namespace ripple {
namespace test {
namespace csf {
class Sim
{
public:
/** Create a simulator for the given trust graph and network topology.
Create a simulator for consensus over the given trust graph and connect
the network links between nodes based on the provided topology.
Topology is is a functor with signature
boost::optional<std::chrono::duration> (NodeId i, NodeId j)
that returns the delay sending messages from node i to node j.
In general, this network graph is distinct from the trust graph, but
users can use adaptors to present a TrustGraph as a Topology by
specifying the delay between nodes.
@param g The trust graph between peers.
@param top The network topology between peers.
*/
template <class Topology>
Sim(TrustGraph const & g, Topology const & top)
{
peers.reserve(g.numPeers());
for(int i = 0; i < g.numPeers(); ++i)
peers.emplace_back(i, net, g.unl(i));
for(int i = 0; i < peers.size(); ++i)
{
for(int j = 0; j < peers.size(); ++j)
{
if( i != j)
{
auto d = top(i,j);
if (d)
{
net.connect(&peers[i], &peers[j], *d);
}
}
}
}
}
/** Run consensus protocol to generate the provided number of ledgers.
Has each peer run consensus until it creates `ledgers` more ledgers.
@param ledgers The number of additional ledgers to create
*/
void
run(int ledgers)
{
for (auto & p : peers)
{
if(p.completedLedgers == 0)
p.relay(Validation{p.id, p.LCL(), p.LCL()});
p.targetLedgers = p.completedLedgers + ledgers;
p.start();
}
net.step();
}
std::vector<Peer> peers;
BasicNetwork<Peer*> net;
};
} // csf
} // test
} // ripple
#endif

221
src/test/csf/Tx.h Normal file
View File

@@ -0,0 +1,221 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012-2017 Ripple Labs Inc
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#ifndef RIPPLE_TEST_CSF_TX_H_INCLUDED
#define RIPPLE_TEST_CSF_TX_H_INCLUDED
#include <ripple/beast/hash/hash_append.h>
#include <boost/container/flat_set.hpp>
#include <ostream>
#include <string>
#include <map>
namespace ripple {
namespace test {
namespace csf {
//! A single transaction
class Tx
{
public:
using ID = std::uint32_t;
Tx(ID i) : id_{ i } {}
ID
id() const
{
return id_;
}
bool
operator<(Tx const & o) const
{
return id_ < o.id_;
}
bool
operator==(Tx const & o) const
{
return id_ == o.id_;
}
private:
ID id_;
};
//!-------------------------------------------------------------------------
//! All sets of Tx are represented as a flat_set.
using TxSetType = boost::container::flat_set<Tx>;
//! TxSet is a set of transactions to consider including in the ledger
class TxSet
{
public:
using ID = TxSetType;
using Tx = csf::Tx;
using MutableTxSet = TxSet;
TxSet() = default;
TxSet(TxSetType const & s) : txs_{ s } {}
bool
insert(Tx const & t)
{
return txs_.insert(t).second;
}
bool
erase(Tx::ID const & txId)
{
return txs_.erase(Tx{ txId }) > 0;
}
bool
exists(Tx::ID const txId) const
{
auto it = txs_.find(Tx{ txId });
return it != txs_.end();
}
Tx const *
find(Tx::ID const& txId) const
{
auto it = txs_.find(Tx{ txId });
if (it != txs_.end())
return &(*it);
return nullptr;
}
auto const &
id() const
{
return txs_;
}
/** @return Map of Tx::ID that are missing. True means
it was in this set and not other. False means
it was in the other set and not this
*/
std::map<Tx::ID, bool>
compare(TxSet const& other) const
{
std::map<Tx::ID, bool> res;
auto populate_diffs = [&res](auto const & a, auto const & b, bool s)
{
auto populator = [&](auto const & tx)
{
res[tx.id()] = s;
};
std::set_difference(
a.begin(), a.end(),
b.begin(), b.end(),
boost::make_function_output_iterator(
std::ref(populator)
)
);
};
populate_diffs(txs_, other.txs_, true);
populate_diffs(other.txs_, txs_, false);
return res;
}
//! The set contains the actual transactions
TxSetType txs_;
};
/** The RCL consensus process catches missing node SHAMap error
in several points. This exception is meant to represent a similar
case for the unit test.
*/
class MissingTx : public std::runtime_error
{
public:
MissingTx()
: std::runtime_error("MissingTx")
{}
};
//------------------------------------------------------------------------------
// Helper functions for debug printing
inline
std::ostream&
operator<<(std::ostream & o, const Tx & t)
{
return o << t.id();
}
template <class T>
inline
std::ostream&
operator<<(std::ostream & o, boost::container::flat_set<T> const & ts)
{
o << "{ ";
bool do_comma = false;
for (auto const & t : ts)
{
if (do_comma)
o << ", ";
else
do_comma = true;
o << t;
}
o << " }";
return o;
}
inline
std::string
to_string(TxSetType const & txs)
{
std::stringstream ss;
ss << txs;
return ss.str();
}
template <class Hasher>
inline
void
hash_append(Hasher& h, Tx const & tx)
{
using beast::hash_append;
hash_append(h, tx.id());
}
std::ostream&
operator<<(std::ostream & o, MissingTx const &m)
{
return o << m.what();
}
} // csf
} // test
} // ripple
#endif

277
src/test/csf/UNL.h Normal file
View File

@@ -0,0 +1,277 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012-2017 Ripple Labs Inc
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#ifndef RIPPLE_TEST_CSF_UNL_H_INCLUDED
#define RIPPLE_TEST_CSF_UNL_H_INCLUDED
#include <boost/container/flat_set.hpp>
#include <boost/optional.hpp>
#include <vector>
#include <random>
#include <numeric>
#include <chrono>
namespace ripple {
namespace test {
namespace csf {
/** Return a randomly shuffled copy of vector based on weights w.
@param v The set of values
@param w The set of weights of each value
@param g A pseudo-random number generator
@return A vector with entries randomly sampled without replacement
from the original vector based on the provided weights.
I.e. res[0] comes from sample v[i] with weight w[i]/sum_k w[k]
*/
template <class T, class G>
std::vector<T>
random_weighted_shuffle(std::vector<T> v, std::vector<double> w, G & g)
{
using std::swap;
for (int i = 0; i < v.size() - 1; ++i)
{
// pick a random item weighted by w
std::discrete_distribution<> dd(w.begin() + i, w.end());
auto idx = dd(g);
std::swap(v[i], v[idx]);
std::swap(w[i], w[idx]);
}
return v;
}
/** Power-law distribution with PDF
P(x) = (x/xmin)^-a
for a >= 1 and xmin >= 1
*/
class PowerLawDistribution
{
double xmin_;
double a_;
double inv_;
std::uniform_real_distribution<double> uf_{0,1};
public:
PowerLawDistribution(double xmin, double a)
: xmin_{xmin}, a_{a}
{
inv_ = 1.0/(1.0 - a_);
}
template <class Generator>
inline
double
operator()(Generator & g)
{
// use inverse transform of CDF to sample
// CDF is P(X <= x): 1 - (x/xmin)^(1-a)
return xmin_ * std::pow(1 - uf_(g), inv_);
}
};
//< Unique identifier for each node in the network
using PeerID = std::uint32_t;
//< A unique node list defines a set of trusted peers used in consensus
using UNL = boost::container::flat_set<PeerID>;
/** Trust graph defining the consensus simulation
Trust is a directed relationship from a node i to node j.
If node i trusts node j, then node i has node j in its UNL.
Note that each node implicitly trusts itself but that need not be
explicitly modeled, e.g. UNLS[assignment
*/
class TrustGraph
{
//< Unique UNLs for the network
std::vector<UNL> UNLs_;
std::vector<int> assignment_;
public:
//< Constructor
TrustGraph(std::vector<UNL> UNLs, std::vector<int> assignment)
: UNLs_{UNLs}
, assignment_{assignment}
{}
//< Whether node `i` trusts node `j`
inline
bool
trusts(PeerID i, PeerID j) const
{
return unl(i).find(j) != unl(i).end();
}
//< Get the UNL for node `i`
inline
UNL const &
unl(PeerID i) const
{
return UNLs_[assignment_[i]];
}
//< Check whether this trust graph satisfies the no forking condition
bool
canFork(double quorum) const;
auto
numPeers() const
{
return assignment_.size();
}
//< Save grapviz dot file reprentation of the trust graph
void
save_dot(std::string const & fileName);
/** Generate a random trust graph based on random ranking of peers
Generate a random trust graph by
1. Randomly ranking the peers acording to RankPDF
2. Generating `numUNL` random UNLs by sampling without replacement
from the ranked nodes.
3. Restricting the size of the random UNLs according to SizePDF
@param size The number of nodes in the trust graph
@param numUNLs The number of UNLs to create
@param rankPDF Generates random positive real numbers to use as ranks
@param unlSizePDF Generates random integeres between (0,size-1) to
restrict the size of generated PDF
@param Generator The uniform random bit generator to use
@note RankPDF/SizePDF can model the full RandomDistribution concept
defined in the STL, but for the purposes of this function need
only provide:
auto operator()(Generator & g)
which should return the random sample.
*/
template <class RankPDF, class SizePDF, class Generator>
static
TrustGraph
makeRandomRanked(int size,
int numUNLs,
RankPDF rankPDF,
SizePDF unlSizePDF,
Generator & g)
{
// 1. Generate ranks
std::vector<double> weights(size);
std::generate(weights.begin(), weights.end(), [&]()
{
return rankPDF(g);
});
// 2. Generate UNLs based on sampling without replacement according
// to weights
std::vector<UNL> unls(numUNLs);
std::generate(unls.begin(), unls.end(), [&]()
{
std::vector<PeerID> ids(size);
std::iota(ids.begin(), ids.end(), 0);
auto res = random_weighted_shuffle(ids, weights, g);
return UNL(res.begin(), res.begin() + unlSizePDF(g));
});
// 3. Assign membership
std::vector<int> assignment(size);
std::uniform_int_distribution<int> u(0, numUNLs-1);
std::generate(assignment.begin(), assignment.end(),
[&]()
{
return u(g);
});
return TrustGraph(unls, assignment);
}
/** Generate a 2 UNL trust graph with some overlap.
Generates a trust graph for `size` peers formed from
two cliques with the given overlap. Nodes in the overlap
trust both all other nodes, while nodes outside the overlap
only trust nodes in their clique.
@param size The number of nodes in the trust graph
@param overlap The number of nodes trusting both cliques
*/
static
TrustGraph
makeClique(int size, int overlap);
/** Generate a complete (fully-connect) trust graph
Generatest a trust graph in which all peers trust all
other peers.
@param size The number of nodes in the trust graph
*/
static
TrustGraph
makeComplete(int size);
};
//< Make the TrustGraph into a topology with delays given by DelayModel
template <class DelayModel>
auto
topology(TrustGraph const & tg, DelayModel const & d)
{
return [&](PeerID i, PeerID j)
{
return tg.trusts(i,j) ? boost::make_optional(d(i,j)) : boost::none;
};
}
class fixed
{
std::chrono::nanoseconds d_;
public:
fixed(std::chrono::nanoseconds const & d) : d_{d} {}
inline
std::chrono::nanoseconds
operator()(PeerID const & i, PeerID const & j) const
{
return d_;
}
};
} // csf
} // test
} // ripple
#endif

135
src/test/csf/impl/UNL.cpp Normal file
View File

@@ -0,0 +1,135 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012-2017 Ripple Labs Inc
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include <test/csf/UNL.h>
#include <boost/iterator/counting_iterator.hpp>
#include <fstream>
#include <algorithm>
namespace ripple {
namespace test {
namespace csf {
bool
TrustGraph::canFork(double quorum) const
{
// Check the forking condition by looking at intersection
// between all pairs of UNLs.
// First check if some nodes uses a UNL they are not members of, since
// this creates an implicit UNL with that ndoe.
auto uniqueUNLs = UNLs_;
for (int i = 0; i < assignment_.size(); ++i)
{
auto const & myUNL = UNLs_[assignment_[i]];
if(myUNL.find(i) == myUNL.end())
{
auto myUNLcopy = myUNL;
myUNLcopy.insert(i);
uniqueUNLs.push_back(std::move(myUNLcopy));
}
}
// Loop over all pairs of uniqueUNLs
for (int i = 0; i < uniqueUNLs.size(); ++i)
{
for (int j = (i+1); j < uniqueUNLs.size(); ++j)
{
auto const & unlA = uniqueUNLs[i];
auto const & unlB = uniqueUNLs[j];
double rhs = 2.0*(1.-quorum) *
std::max(unlA.size(), unlB.size() );
int intersectionSize = std::count_if(unlA.begin(), unlA.end(),
[&](PeerID id)
{
return unlB.find(id) != unlB.end();
});
if(intersectionSize < rhs)
return true;
}
}
return false;
}
TrustGraph
TrustGraph::makeClique(int size, int overlap)
{
using bci = boost::counting_iterator<PeerID>;
// Split network into two cliques with the given overlap
// Clique A has nodes [0,endA) and Clique B has [startB,numPeers)
// Note: Clique B will have an extra peer when numPeers - overlap
// is odd
int endA = (size + overlap)/2;
int startB = (size - overlap)/2;
std::vector<UNL> unls;
unls.emplace_back(bci(0), bci(endA));
unls.emplace_back(bci(startB), bci(size));
unls.emplace_back(bci(0), bci(size));
std::vector<int> assignment(size,0);
for (int i = 0; i < size; ++i)
{
if(i < startB)
assignment[i] = 0;
else if(i > endA)
assignment[i] = 1;
else
assignment[i] = 2;
}
return TrustGraph(unls, assignment);
}
TrustGraph
TrustGraph::makeComplete(int size)
{
UNL all{ boost::counting_iterator<PeerID>( 0 ),
boost::counting_iterator<PeerID>( size ) };
return TrustGraph(std::vector<UNL>(1,all),
std::vector<int>(size, 0));
}
inline void TrustGraph::save_dot(std::string const & fileName)
{
std::ofstream out(fileName);
out << "digraph {\n";
for (int i = 0; i < assignment_.size(); ++i)
{
for (auto & j : UNLs_[assignment_[i]])
{
out << i << " -> " << j << ";\n";
}
}
out << "}\n";
}
} // csf
} // test
} // ripple

View File

@@ -30,7 +30,7 @@
#include <test/jtx/utility.h>
#include <test/jtx/JSONRPCClient.h>
#include <ripple/app/ledger/LedgerMaster.h>
#include <ripple/app/ledger/LedgerTiming.h>
#include <ripple/consensus/LedgerTiming.h>
#include <ripple/app/misc/NetworkOPs.h>
#include <ripple/app/misc/TxQ.h>
#include <ripple/basics/contract.h>

View File

@@ -0,0 +1,21 @@
//------------------------------------------------------------------------------
/*
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 <test/consensus/Consensus_test.cpp>
#include <test/consensus/LedgerTiming_test.cpp>

View File

@@ -0,0 +1,23 @@
//------------------------------------------------------------------------------
/*
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/BasicNetwork_test.cpp>
#include <test/csf/impl/UNL.cpp>

View File

@@ -49,7 +49,5 @@
#include <test/jtx/impl/JSONRPCClient.cpp>
#include <test/jtx/impl/ManualTimeKeeper.cpp>
#include <test/jtx/impl/WSClient.cpp>
#include <test/jtx/BasicNetwork_test.cpp>
#include <test/jtx/Env_test.cpp>
#include <test/jtx/WSClient_test.cpp>