mirror of
https://github.com/XRPLF/rippled.git
synced 2025-12-06 17:27:55 +00:00
Refactor consensus for simulation (RIPD-1011):
This is a substantial refactor of the consensus code and also introduces a basic consensus simulation and testing framework. The new generic/templated version is in src/ripple/consensus and documents the current type requirements. The version adapted for the RCL is in src/ripple/app/consensus. The testing framework is in src/test/csf. Minor behavioral changes/fixes include: * Adjust close time offset even when not validating. * Remove spurious proposing_ = false call at end of handleLCL. * Remove unused functionality provided by checkLastValidation. * Separate open and converge time * Don't send a bow out if we're not proposing * Prevent consensus stopping if NetworkOPs switches to disconnect mode while consensus accepts a ledger * Prevent a corner case in which Consensus::gotTxSet or Consensus::peerProposal has the potential to update internal state while an dispatched accept job is running. * Distinguish external and internal calls to startNewRound. Only external calls can reset the proposing_ state of consensus
This commit is contained in:
@@ -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>
|
||||
|
||||
474
src/test/consensus/Consensus_test.cpp
Normal file
474
src/test/consensus/Consensus_test.cpp
Normal 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
|
||||
164
src/test/consensus/LedgerTiming_test.cpp
Normal file
164
src/test/consensus/LedgerTiming_test.cpp
Normal 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
26
src/test/csf.h
Normal 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>
|
||||
@@ -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
|
||||
|
||||
@@ -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
197
src/test/csf/Ledger.h
Normal file
@@ -0,0 +1,197 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2012-2017 Ripple Labs Inc
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
#ifndef RIPPLE_TEST_CSF_LEDGER_H_INCLUDED
|
||||
#define RIPPLE_TEST_CSF_LEDGER_H_INCLUDED
|
||||
|
||||
#include <ripple/basics/chrono.h>
|
||||
#include <test/csf/Tx.h>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
namespace csf {
|
||||
|
||||
/** A ledger is a set of observed transactions and a sequence number
|
||||
identifying the ledger.
|
||||
|
||||
Peers in the consensus process are trying to agree on a set of transactions
|
||||
to include in a ledger. For unit testing, each transaction is a
|
||||
single integer and the ledger is a set of observed integers. This means
|
||||
future ledgers have prior ledgers as subsets, e.g.
|
||||
|
||||
Ledger 0 : {}
|
||||
Ledger 1 : {1,4,5}
|
||||
Ledger 2 : {1,2,4,5,10}
|
||||
....
|
||||
|
||||
Tx - Integer
|
||||
TxSet - Set of Tx
|
||||
Ledger - Set of Tx and sequence number
|
||||
*/
|
||||
|
||||
class Ledger
|
||||
{
|
||||
|
||||
public:
|
||||
|
||||
struct ID
|
||||
{
|
||||
std::uint32_t seq = 0;
|
||||
TxSetType txs = TxSetType{};
|
||||
|
||||
bool operator==(ID const & o) const
|
||||
{
|
||||
return seq == o.seq && txs == o.txs;
|
||||
}
|
||||
|
||||
bool operator!=(ID const & o) const
|
||||
{
|
||||
return !(*this == o);
|
||||
}
|
||||
|
||||
bool operator<(ID const & o) const
|
||||
{
|
||||
return std::tie(seq, txs) < std::tie(o.seq, o.txs);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
auto const &
|
||||
id() const
|
||||
{
|
||||
return id_;
|
||||
}
|
||||
|
||||
auto
|
||||
seq() const
|
||||
{
|
||||
return id_.seq;
|
||||
}
|
||||
|
||||
auto
|
||||
closeTimeResolution() const
|
||||
{
|
||||
return closeTimeResolution_;
|
||||
}
|
||||
|
||||
auto
|
||||
closeAgree() const
|
||||
{
|
||||
return closeTimeAgree_;
|
||||
}
|
||||
|
||||
auto
|
||||
closeTime() const
|
||||
{
|
||||
return closeTime_;
|
||||
}
|
||||
|
||||
auto
|
||||
actualCloseTime() const
|
||||
{
|
||||
return actualCloseTime_;
|
||||
}
|
||||
|
||||
auto
|
||||
parentCloseTime() const
|
||||
{
|
||||
return parentCloseTime_;
|
||||
}
|
||||
|
||||
auto const &
|
||||
parentID() const
|
||||
{
|
||||
return parentID_;
|
||||
}
|
||||
|
||||
Json::Value
|
||||
getJson() const
|
||||
{
|
||||
Json::Value res(Json::objectValue);
|
||||
res["seq"] = seq();
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
//! Apply the given transactions to this ledger
|
||||
Ledger
|
||||
close(TxSetType const & txs,
|
||||
NetClock::duration closeTimeResolution,
|
||||
NetClock::time_point const & consensusCloseTime,
|
||||
bool closeTimeAgree) const
|
||||
{
|
||||
Ledger res{ *this };
|
||||
res.id_.txs.insert(txs.begin(), txs.end());
|
||||
res.id_ .seq= seq() + 1;
|
||||
res.closeTimeResolution_ = closeTimeResolution;
|
||||
res.actualCloseTime_ = consensusCloseTime;
|
||||
res.closeTime_ = effectiveCloseTime(consensusCloseTime,
|
||||
closeTimeResolution, parentCloseTime_);
|
||||
res.closeTimeAgree_ = closeTimeAgree;
|
||||
res.parentCloseTime_ = closeTime();
|
||||
res.parentID_ = id();
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
|
||||
//! Unique identifier of ledger is combination of sequence number and id
|
||||
ID id_;
|
||||
|
||||
//! Bucket resolution used to determine close time
|
||||
NetClock::duration closeTimeResolution_ = ledgerDefaultTimeResolution;
|
||||
|
||||
//! When the ledger closed
|
||||
NetClock::time_point closeTime_;
|
||||
|
||||
//! Whether consenssus agreed on the close time
|
||||
bool closeTimeAgree_ = true;
|
||||
|
||||
//! Parent ledger id
|
||||
ID parentID_;
|
||||
|
||||
//! Parent ledger close time
|
||||
NetClock::time_point parentCloseTime_;
|
||||
|
||||
//! Close time unadjusted by closeTimeResolution
|
||||
NetClock::time_point actualCloseTime_;
|
||||
|
||||
};
|
||||
|
||||
inline
|
||||
std::ostream &
|
||||
operator<<(std::ostream & o, Ledger::ID const & id)
|
||||
{
|
||||
return o << id.seq << "," << id.txs;
|
||||
}
|
||||
|
||||
inline
|
||||
std::string
|
||||
to_string(Ledger::ID const & id)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << id;
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
} // csf
|
||||
} // test
|
||||
} // ripple
|
||||
|
||||
#endif
|
||||
492
src/test/csf/Peer.h
Normal file
492
src/test/csf/Peer.h
Normal file
@@ -0,0 +1,492 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2012-2017 Ripple Labs Inc
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
#ifndef RIPPLE_TEST_CSF_PEER_H_INCLUDED
|
||||
#define RIPPLE_TEST_CSF_PEER_H_INCLUDED
|
||||
|
||||
#include <boost/container/flat_map.hpp>
|
||||
#include <boost/container/flat_set.hpp>
|
||||
|
||||
#include <test/csf/Tx.h>
|
||||
#include <test/csf/Ledger.h>
|
||||
#include <test/csf/UNL.h>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
namespace csf {
|
||||
|
||||
|
||||
/** Store validations reached by peers */
|
||||
struct Validation
|
||||
{
|
||||
PeerID id;
|
||||
Ledger::ID ledger;
|
||||
Ledger::ID prevLedger;
|
||||
};
|
||||
|
||||
namespace bc = boost::container;
|
||||
|
||||
class Validations
|
||||
{
|
||||
//< Ledgers seen by peers, saved in order received (which should be order
|
||||
//< created)
|
||||
bc::flat_map<Ledger::ID, bc::flat_set<PeerID>> nodesFromLedger;
|
||||
bc::flat_map<Ledger::ID, bc::flat_set<PeerID>> nodesFromPrevLedger;
|
||||
bc::flat_map<Ledger::ID, bc::flat_map<Ledger::ID, std::size_t>> childLedgers;
|
||||
public:
|
||||
void
|
||||
update(Validation const & v)
|
||||
{
|
||||
nodesFromLedger[v.ledger].insert(v.id);
|
||||
if(v.ledger.seq > 0)
|
||||
{
|
||||
nodesFromPrevLedger[v.prevLedger].insert(v.id);
|
||||
childLedgers[v.prevLedger][v.ledger]++;
|
||||
}
|
||||
}
|
||||
|
||||
//< The number of peers who have validated this ledger
|
||||
std::size_t
|
||||
proposersValidated(Ledger::ID const & prevLedger) const
|
||||
{
|
||||
auto it = nodesFromLedger.find(prevLedger);
|
||||
if(it != nodesFromLedger.end())
|
||||
return it->second.size();
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** The number of peers that are past this ledger, i.e.
|
||||
they have a newer most recent ledger, but have this ledger
|
||||
as an ancestor.
|
||||
*/
|
||||
std::size_t
|
||||
proposersFinished(Ledger::ID const & prevLedger) const
|
||||
{
|
||||
auto it = nodesFromPrevLedger.find(prevLedger);
|
||||
if(it != nodesFromPrevLedger.end())
|
||||
return it->second.size();
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** Returns the ledger starting from prevLedger with the most validations.
|
||||
*/
|
||||
Ledger::ID
|
||||
getBestLCL(Ledger::ID const & currLedger,
|
||||
Ledger::ID const & prevLedger) const
|
||||
{
|
||||
auto it = childLedgers.find(prevLedger);
|
||||
if (it != childLedgers.end() &&
|
||||
! it->second.empty())
|
||||
{
|
||||
std::size_t bestCount = 0;
|
||||
Ledger::ID bestLedger;
|
||||
|
||||
for (auto const & b : it->second)
|
||||
{
|
||||
auto currCount = b.second;
|
||||
if(currLedger == b.first)
|
||||
currCount++;
|
||||
if(currCount > bestCount)
|
||||
bestLedger = b.first;
|
||||
if(currCount == bestCount && currLedger == b.first)
|
||||
bestLedger = b.first;
|
||||
}
|
||||
return bestLedger;
|
||||
}
|
||||
return currLedger;
|
||||
}
|
||||
};
|
||||
|
||||
/** Proposal is a position taken in the consensus process and is represented
|
||||
directly from the generic types.
|
||||
*/
|
||||
using Proposal = ConsensusProposal<PeerID, Ledger::ID, TxSetType>;
|
||||
|
||||
struct Traits
|
||||
{
|
||||
using Ledger_t = Ledger;
|
||||
using NodeID_t = PeerID;
|
||||
using TxSet_t = TxSet;
|
||||
using MissingTxException_t = MissingTx;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/** Represents a single node participating in the consensus process.
|
||||
It implements the Callbacks required by Consensus.
|
||||
*/
|
||||
struct Peer : public Consensus<Peer, Traits>
|
||||
{
|
||||
using Base = Consensus<Peer, Traits>;
|
||||
|
||||
//! Our unique ID
|
||||
PeerID id;
|
||||
|
||||
//! openTxs that haven't been closed in a ledger yet
|
||||
TxSetType openTxs;
|
||||
|
||||
//! last ledger this peer closed
|
||||
Ledger lastClosedLedger;
|
||||
|
||||
//! Handle to network for sending messages
|
||||
BasicNetwork<Peer*> & net;
|
||||
|
||||
//! UNL of trusted peers
|
||||
UNL unl;
|
||||
|
||||
//! Most recent ledger completed by peers
|
||||
Validations peerValidations;
|
||||
|
||||
// The ledgers, proposals, TxSets and Txs this peer has seen
|
||||
bc::flat_map<Ledger::ID, Ledger> ledgers;
|
||||
|
||||
//! Map from Ledger::ID to vector of Positions with that ledger
|
||||
//! as the prior ledger
|
||||
bc::flat_map<Ledger::ID, std::vector<Proposal>> peerPositions_;
|
||||
bc::flat_map<TxSet::ID, TxSet> txSets;
|
||||
|
||||
int completedLedgers = 0;
|
||||
int targetLedgers = std::numeric_limits<int>::max();
|
||||
|
||||
//! Skew samples from the network clock; to be refactored into a
|
||||
//! clock time once it is provided separately from the network.
|
||||
std::chrono::seconds clockSkew{0};
|
||||
|
||||
//! Delay in processing validations from remote peers
|
||||
std::chrono::milliseconds validationDelay{0};
|
||||
|
||||
//! Delay in acquiring missing ledger from the network
|
||||
std::chrono::milliseconds missingLedgerDelay{0};
|
||||
|
||||
bool validating = true;
|
||||
bool proposing = true;
|
||||
|
||||
//! All peers start from the default constructed ledger
|
||||
Peer(PeerID i, BasicNetwork<Peer*> & n, UNL const & u)
|
||||
: Consensus<Peer, Traits>( n.clock(), beast::Journal{})
|
||||
, id{i}
|
||||
, net{n}
|
||||
, unl(u)
|
||||
{
|
||||
ledgers[lastClosedLedger.id()] = lastClosedLedger;
|
||||
}
|
||||
|
||||
|
||||
// @return whether we are proposing,validating
|
||||
// TODO: Bit akward that this is in callbacks, would be nice to extract
|
||||
std::pair<bool, bool>
|
||||
getMode()
|
||||
{
|
||||
// in RCL this hits NetworkOps to decide whether we are proposing
|
||||
// validating
|
||||
return{ proposing, validating };
|
||||
}
|
||||
|
||||
Ledger const *
|
||||
acquireLedger(Ledger::ID const & ledgerHash)
|
||||
{
|
||||
auto it = ledgers.find(ledgerHash);
|
||||
if (it != ledgers.end())
|
||||
return &(it->second);
|
||||
|
||||
// TODO Get from network/oracle properly!
|
||||
|
||||
for (auto const& link : net.links(this))
|
||||
{
|
||||
auto const & p = *link.to;
|
||||
auto it = p.ledgers.find(ledgerHash);
|
||||
if (it != p.ledgers.end())
|
||||
{
|
||||
schedule(missingLedgerDelay,
|
||||
[this, ledgerHash, ledger = it->second]()
|
||||
{
|
||||
ledgers.emplace(ledgerHash, ledger);
|
||||
});
|
||||
if(missingLedgerDelay == 0ms)
|
||||
return &ledgers[ledgerHash];
|
||||
break;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto const &
|
||||
proposals(Ledger::ID const & ledgerHash)
|
||||
{
|
||||
return peerPositions_[ledgerHash];
|
||||
}
|
||||
|
||||
TxSet const *
|
||||
acquireTxSet(TxSet::ID const & setId)
|
||||
{
|
||||
auto it = txSets.find(setId);
|
||||
if(it != txSets.end())
|
||||
return &(it->second);
|
||||
// TODO Get from network/oracle instead!
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
hasOpenTransactions() const
|
||||
{
|
||||
return !openTxs.empty();
|
||||
}
|
||||
|
||||
std::size_t
|
||||
proposersValidated(Ledger::ID const & prevLedger)
|
||||
{
|
||||
return peerValidations.proposersValidated(prevLedger);
|
||||
}
|
||||
|
||||
std::size_t
|
||||
proposersFinished(Ledger::ID const & prevLedger)
|
||||
{
|
||||
return peerValidations.proposersFinished(prevLedger);
|
||||
}
|
||||
|
||||
void
|
||||
onStartRound(Ledger const &) {}
|
||||
|
||||
void
|
||||
onClose(Ledger const &, bool ) {}
|
||||
|
||||
// don't really offload
|
||||
void
|
||||
dispatchAccept(TxSet const & f)
|
||||
{
|
||||
Base::accept(f);
|
||||
}
|
||||
|
||||
void
|
||||
share(TxSet const &s)
|
||||
{
|
||||
relay(s);
|
||||
}
|
||||
|
||||
Ledger::ID
|
||||
getLCL(Ledger::ID const & currLedger,
|
||||
Ledger::ID const & priorLedger,
|
||||
bool haveCorrectLCL)
|
||||
{
|
||||
// TODO: Use generic validation code
|
||||
if(currLedger.seq > 0 && priorLedger.seq > 0)
|
||||
return peerValidations.getBestLCL(currLedger, priorLedger);
|
||||
return currLedger;
|
||||
}
|
||||
|
||||
void
|
||||
propose(Proposal const & pos)
|
||||
{
|
||||
if(proposing)
|
||||
relay(pos);
|
||||
}
|
||||
|
||||
void
|
||||
relay(DisputedTx<Tx, PeerID> const & dispute)
|
||||
{
|
||||
relay(dispute.tx());
|
||||
}
|
||||
|
||||
std::pair <TxSet, Proposal>
|
||||
makeInitialPosition(
|
||||
Ledger const & prevLedger,
|
||||
bool isProposing,
|
||||
bool isCorrectLCL,
|
||||
NetClock::time_point closeTime,
|
||||
NetClock::time_point now)
|
||||
{
|
||||
TxSet res{ openTxs };
|
||||
|
||||
return { res,
|
||||
Proposal{prevLedger.id(), Proposal::seqJoin, res.id(), closeTime, now, id} };
|
||||
}
|
||||
|
||||
// Process the accepted transaction set, generating the newly closed ledger
|
||||
// and clearing out the openTxs that were included.
|
||||
// TODO: Kinda nasty it takes so many arguments . . . sign of bad coupling
|
||||
bool
|
||||
accept(TxSet const& set,
|
||||
NetClock::time_point consensusCloseTime,
|
||||
bool proposing_,
|
||||
bool validating_,
|
||||
bool haveCorrectLCL_,
|
||||
bool consensusFail_,
|
||||
Ledger::ID const & prevLedgerHash_,
|
||||
Ledger const & previousLedger_,
|
||||
NetClock::duration closeResolution_,
|
||||
NetClock::time_point const & now,
|
||||
std::chrono::milliseconds const & roundTime_,
|
||||
hash_map<Tx::ID, DisputedTx <Tx, PeerID>> const & disputes_,
|
||||
std::map <NetClock::time_point, int> closeTimes_,
|
||||
NetClock::time_point const & closeTime)
|
||||
{
|
||||
auto newLedger = previousLedger_.close(set.txs_, closeResolution_,
|
||||
closeTime, consensusCloseTime != NetClock::time_point{});
|
||||
ledgers[newLedger.id()] = newLedger;
|
||||
|
||||
lastClosedLedger = newLedger;
|
||||
|
||||
auto it = std::remove_if(openTxs.begin(), openTxs.end(),
|
||||
[&](Tx const & tx)
|
||||
{
|
||||
return set.exists(tx.id());
|
||||
});
|
||||
openTxs.erase(it, openTxs.end());
|
||||
|
||||
if(validating)
|
||||
relay(Validation{id, newLedger.id(), newLedger.parentID()});
|
||||
return validating_;
|
||||
}
|
||||
|
||||
void
|
||||
endConsensus(bool correct)
|
||||
{
|
||||
// kick off the next round...
|
||||
// in the actual implementation, this passes back through
|
||||
// network ops
|
||||
++completedLedgers;
|
||||
// startRound sets the LCL state, so we need to call it once after
|
||||
// the last requested round completes
|
||||
// TODO: reconsider this and instead just save LCL generated here?
|
||||
if(completedLedgers <= targetLedgers)
|
||||
{
|
||||
startRound(now(), lastClosedLedger.id(),
|
||||
lastClosedLedger);
|
||||
}
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
// non-callback helpers
|
||||
void
|
||||
receive(Proposal const & p)
|
||||
{
|
||||
if(unl.find(p.nodeID()) == unl.end())
|
||||
return;
|
||||
|
||||
// TODO: Be sure this is a new proposal!!!!!
|
||||
auto & dest = peerPositions_[p.prevLedger()];
|
||||
if(std::find(dest.begin(), dest.end(), p) != dest.end())
|
||||
return;
|
||||
dest.push_back(p);
|
||||
peerProposal(now(), p);
|
||||
|
||||
}
|
||||
|
||||
void
|
||||
receive(TxSet const & txs)
|
||||
{
|
||||
// save and map complete?
|
||||
auto it = txSets.insert(std::make_pair(txs.id(), txs));
|
||||
if(it.second)
|
||||
gotTxSet(now(), txs);
|
||||
}
|
||||
|
||||
void
|
||||
receive(Tx const & tx)
|
||||
{
|
||||
if (openTxs.find(tx.id()) == openTxs.end())
|
||||
{
|
||||
openTxs.insert(tx);
|
||||
// relay to peers???
|
||||
relay(tx);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
receive(Validation const & v)
|
||||
{
|
||||
if(unl.find(v.id) != unl.end())
|
||||
{
|
||||
schedule(validationDelay,
|
||||
[&, v]()
|
||||
{
|
||||
peerValidations.update(v);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void
|
||||
relay(T const & t)
|
||||
{
|
||||
for(auto const& link : net.links(this))
|
||||
net.send(this, link.to,
|
||||
[msg = t, to = link.to]
|
||||
{
|
||||
to->receive(msg);
|
||||
});
|
||||
}
|
||||
|
||||
// Receive and relay locally submitted transaction
|
||||
void
|
||||
submit(Tx const & tx)
|
||||
{
|
||||
receive(tx);
|
||||
relay(tx);
|
||||
}
|
||||
|
||||
void
|
||||
timerEntry()
|
||||
{
|
||||
Base::timerEntry(now());
|
||||
// only reschedule if not completed
|
||||
if(completedLedgers < targetLedgers)
|
||||
net.timer(LEDGER_GRANULARITY, [&]() { timerEntry(); });
|
||||
}
|
||||
void
|
||||
start()
|
||||
{
|
||||
net.timer(LEDGER_GRANULARITY, [&]() { timerEntry(); });
|
||||
// The ID is the one we have seen the most validations for
|
||||
// In practice, we might not actually have that ledger itself yet,
|
||||
// so there is no gaurantee that bestLCL == lastClosedLedger.id()
|
||||
auto bestLCL = peerValidations.getBestLCL(lastClosedLedger.id(),
|
||||
lastClosedLedger.parentID());
|
||||
startRound(now(), bestLCL,
|
||||
lastClosedLedger);
|
||||
}
|
||||
|
||||
NetClock::time_point
|
||||
now() const
|
||||
{
|
||||
// We don't care about the actual epochs, but do want the
|
||||
// generated NetClock time to be well past its epoch to ensure
|
||||
// any subtractions of two NetClock::time_point in the consensu
|
||||
// code are positive. (e.g. PROPOSE_FRESHNESS)
|
||||
using namespace std::chrono;
|
||||
return NetClock::time_point(duration_cast<NetClock::duration>
|
||||
(net.now().time_since_epoch()+ 86400s + clockSkew));
|
||||
}
|
||||
|
||||
// Schedule the provided callback in `when` duration, but if
|
||||
// `when` is 0, call immediately
|
||||
template <class T>
|
||||
void schedule(std::chrono::nanoseconds when, T && what)
|
||||
{
|
||||
if(when == 0ns)
|
||||
what();
|
||||
else
|
||||
net.timer(when, std::forward<T>(what));
|
||||
}
|
||||
};
|
||||
|
||||
} // csf
|
||||
} // test
|
||||
} // ripple
|
||||
#endif
|
||||
104
src/test/csf/Sim.h
Normal file
104
src/test/csf/Sim.h
Normal file
@@ -0,0 +1,104 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2012-2017 Ripple Labs Inc
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef RIPPLE_TEST_CSF_SIM_H_INCLUDED
|
||||
#define RIPPLE_TEST_CSF_SIM_H_INCLUDED
|
||||
|
||||
#include <test/csf/UNL.h>
|
||||
#include <test/csf/BasicNetwork.h>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
namespace csf {
|
||||
|
||||
class Sim
|
||||
{
|
||||
public:
|
||||
/** Create a simulator for the given trust graph and network topology.
|
||||
|
||||
Create a simulator for consensus over the given trust graph and connect
|
||||
the network links between nodes based on the provided topology.
|
||||
|
||||
Topology is is a functor with signature
|
||||
|
||||
boost::optional<std::chrono::duration> (NodeId i, NodeId j)
|
||||
|
||||
that returns the delay sending messages from node i to node j.
|
||||
|
||||
In general, this network graph is distinct from the trust graph, but
|
||||
users can use adaptors to present a TrustGraph as a Topology by
|
||||
specifying the delay between nodes.
|
||||
|
||||
@param g The trust graph between peers.
|
||||
@param top The network topology between peers.
|
||||
|
||||
*/
|
||||
template <class Topology>
|
||||
Sim(TrustGraph const & g, Topology const & top)
|
||||
{
|
||||
peers.reserve(g.numPeers());
|
||||
for(int i = 0; i < g.numPeers(); ++i)
|
||||
peers.emplace_back(i, net, g.unl(i));
|
||||
|
||||
for(int i = 0; i < peers.size(); ++i)
|
||||
{
|
||||
for(int j = 0; j < peers.size(); ++j)
|
||||
{
|
||||
if( i != j)
|
||||
{
|
||||
auto d = top(i,j);
|
||||
if (d)
|
||||
{
|
||||
net.connect(&peers[i], &peers[j], *d);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Run consensus protocol to generate the provided number of ledgers.
|
||||
|
||||
Has each peer run consensus until it creates `ledgers` more ledgers.
|
||||
|
||||
@param ledgers The number of additional ledgers to create
|
||||
*/
|
||||
void
|
||||
run(int ledgers)
|
||||
{
|
||||
for (auto & p : peers)
|
||||
{
|
||||
if(p.completedLedgers == 0)
|
||||
p.relay(Validation{p.id, p.LCL(), p.LCL()});
|
||||
p.targetLedgers = p.completedLedgers + ledgers;
|
||||
p.start();
|
||||
}
|
||||
net.step();
|
||||
}
|
||||
|
||||
std::vector<Peer> peers;
|
||||
BasicNetwork<Peer*> net;
|
||||
|
||||
};
|
||||
|
||||
|
||||
} // csf
|
||||
} // test
|
||||
} // ripple
|
||||
|
||||
#endif
|
||||
221
src/test/csf/Tx.h
Normal file
221
src/test/csf/Tx.h
Normal file
@@ -0,0 +1,221 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2012-2017 Ripple Labs Inc
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
#ifndef RIPPLE_TEST_CSF_TX_H_INCLUDED
|
||||
#define RIPPLE_TEST_CSF_TX_H_INCLUDED
|
||||
|
||||
#include <ripple/beast/hash/hash_append.h>
|
||||
#include <boost/container/flat_set.hpp>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
#include <map>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
namespace csf {
|
||||
|
||||
//! A single transaction
|
||||
class Tx
|
||||
{
|
||||
public:
|
||||
using ID = std::uint32_t;
|
||||
|
||||
Tx(ID i) : id_{ i } {}
|
||||
|
||||
ID
|
||||
id() const
|
||||
{
|
||||
return id_;
|
||||
}
|
||||
|
||||
bool
|
||||
operator<(Tx const & o) const
|
||||
{
|
||||
return id_ < o.id_;
|
||||
}
|
||||
|
||||
bool
|
||||
operator==(Tx const & o) const
|
||||
{
|
||||
return id_ == o.id_;
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
ID id_;
|
||||
|
||||
};
|
||||
|
||||
//!-------------------------------------------------------------------------
|
||||
//! All sets of Tx are represented as a flat_set.
|
||||
using TxSetType = boost::container::flat_set<Tx>;
|
||||
|
||||
//! TxSet is a set of transactions to consider including in the ledger
|
||||
class TxSet
|
||||
{
|
||||
public:
|
||||
using ID = TxSetType;
|
||||
using Tx = csf::Tx;
|
||||
using MutableTxSet = TxSet;
|
||||
|
||||
TxSet() = default;
|
||||
TxSet(TxSetType const & s) : txs_{ s } {}
|
||||
|
||||
bool
|
||||
insert(Tx const & t)
|
||||
{
|
||||
return txs_.insert(t).second;
|
||||
}
|
||||
|
||||
bool
|
||||
erase(Tx::ID const & txId)
|
||||
{
|
||||
return txs_.erase(Tx{ txId }) > 0;
|
||||
}
|
||||
|
||||
bool
|
||||
exists(Tx::ID const txId) const
|
||||
{
|
||||
auto it = txs_.find(Tx{ txId });
|
||||
return it != txs_.end();
|
||||
}
|
||||
|
||||
Tx const *
|
||||
find(Tx::ID const& txId) const
|
||||
{
|
||||
auto it = txs_.find(Tx{ txId });
|
||||
if (it != txs_.end())
|
||||
return &(*it);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto const &
|
||||
id() const
|
||||
{
|
||||
return txs_;
|
||||
}
|
||||
|
||||
/** @return Map of Tx::ID that are missing. True means
|
||||
it was in this set and not other. False means
|
||||
it was in the other set and not this
|
||||
*/
|
||||
std::map<Tx::ID, bool>
|
||||
compare(TxSet const& other) const
|
||||
{
|
||||
std::map<Tx::ID, bool> res;
|
||||
|
||||
auto populate_diffs = [&res](auto const & a, auto const & b, bool s)
|
||||
{
|
||||
auto populator = [&](auto const & tx)
|
||||
{
|
||||
res[tx.id()] = s;
|
||||
};
|
||||
std::set_difference(
|
||||
a.begin(), a.end(),
|
||||
b.begin(), b.end(),
|
||||
boost::make_function_output_iterator(
|
||||
std::ref(populator)
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
populate_diffs(txs_, other.txs_, true);
|
||||
populate_diffs(other.txs_, txs_, false);
|
||||
return res;
|
||||
}
|
||||
|
||||
//! The set contains the actual transactions
|
||||
TxSetType txs_;
|
||||
};
|
||||
|
||||
|
||||
/** The RCL consensus process catches missing node SHAMap error
|
||||
in several points. This exception is meant to represent a similar
|
||||
case for the unit test.
|
||||
*/
|
||||
class MissingTx : public std::runtime_error
|
||||
{
|
||||
public:
|
||||
MissingTx()
|
||||
: std::runtime_error("MissingTx")
|
||||
{}
|
||||
};
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Helper functions for debug printing
|
||||
|
||||
inline
|
||||
std::ostream&
|
||||
operator<<(std::ostream & o, const Tx & t)
|
||||
{
|
||||
return o << t.id();
|
||||
}
|
||||
|
||||
template <class T>
|
||||
inline
|
||||
std::ostream&
|
||||
operator<<(std::ostream & o, boost::container::flat_set<T> const & ts)
|
||||
{
|
||||
o << "{ ";
|
||||
bool do_comma = false;
|
||||
for (auto const & t : ts)
|
||||
{
|
||||
if (do_comma)
|
||||
o << ", ";
|
||||
else
|
||||
do_comma = true;
|
||||
o << t;
|
||||
|
||||
|
||||
}
|
||||
o << " }";
|
||||
return o;
|
||||
|
||||
}
|
||||
|
||||
inline
|
||||
std::string
|
||||
to_string(TxSetType const & txs)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << txs;
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
template <class Hasher>
|
||||
inline
|
||||
void
|
||||
hash_append(Hasher& h, Tx const & tx)
|
||||
{
|
||||
using beast::hash_append;
|
||||
hash_append(h, tx.id());
|
||||
}
|
||||
|
||||
std::ostream&
|
||||
operator<<(std::ostream & o, MissingTx const &m)
|
||||
{
|
||||
return o << m.what();
|
||||
}
|
||||
|
||||
|
||||
} // csf
|
||||
} // test
|
||||
} // ripple
|
||||
|
||||
#endif
|
||||
277
src/test/csf/UNL.h
Normal file
277
src/test/csf/UNL.h
Normal file
@@ -0,0 +1,277 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2012-2017 Ripple Labs Inc
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef RIPPLE_TEST_CSF_UNL_H_INCLUDED
|
||||
#define RIPPLE_TEST_CSF_UNL_H_INCLUDED
|
||||
|
||||
#include <boost/container/flat_set.hpp>
|
||||
#include <boost/optional.hpp>
|
||||
#include <vector>
|
||||
#include <random>
|
||||
#include <numeric>
|
||||
#include <chrono>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
namespace csf {
|
||||
|
||||
/** Return a randomly shuffled copy of vector based on weights w.
|
||||
|
||||
@param v The set of values
|
||||
@param w The set of weights of each value
|
||||
@param g A pseudo-random number generator
|
||||
@return A vector with entries randomly sampled without replacement
|
||||
from the original vector based on the provided weights.
|
||||
I.e. res[0] comes from sample v[i] with weight w[i]/sum_k w[k]
|
||||
*/
|
||||
template <class T, class G>
|
||||
std::vector<T>
|
||||
random_weighted_shuffle(std::vector<T> v, std::vector<double> w, G & g)
|
||||
{
|
||||
using std::swap;
|
||||
|
||||
for (int i = 0; i < v.size() - 1; ++i)
|
||||
{
|
||||
// pick a random item weighted by w
|
||||
std::discrete_distribution<> dd(w.begin() + i, w.end());
|
||||
auto idx = dd(g);
|
||||
std::swap(v[i], v[idx]);
|
||||
std::swap(w[i], w[idx]);
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
|
||||
/** Power-law distribution with PDF
|
||||
|
||||
P(x) = (x/xmin)^-a
|
||||
|
||||
for a >= 1 and xmin >= 1
|
||||
*/
|
||||
class PowerLawDistribution
|
||||
{
|
||||
double xmin_;
|
||||
double a_;
|
||||
double inv_;
|
||||
std::uniform_real_distribution<double> uf_{0,1};
|
||||
|
||||
public:
|
||||
PowerLawDistribution(double xmin, double a)
|
||||
: xmin_{xmin}, a_{a}
|
||||
{
|
||||
inv_ = 1.0/(1.0 - a_);
|
||||
}
|
||||
|
||||
template <class Generator>
|
||||
inline
|
||||
double
|
||||
operator()(Generator & g)
|
||||
{
|
||||
// use inverse transform of CDF to sample
|
||||
// CDF is P(X <= x): 1 - (x/xmin)^(1-a)
|
||||
return xmin_ * std::pow(1 - uf_(g), inv_);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
//< Unique identifier for each node in the network
|
||||
using PeerID = std::uint32_t;
|
||||
|
||||
//< A unique node list defines a set of trusted peers used in consensus
|
||||
using UNL = boost::container::flat_set<PeerID>;
|
||||
|
||||
/** Trust graph defining the consensus simulation
|
||||
|
||||
Trust is a directed relationship from a node i to node j.
|
||||
If node i trusts node j, then node i has node j in its UNL.
|
||||
|
||||
Note that each node implicitly trusts itself but that need not be
|
||||
explicitly modeled, e.g. UNLS[assignment
|
||||
*/
|
||||
class TrustGraph
|
||||
{
|
||||
//< Unique UNLs for the network
|
||||
std::vector<UNL> UNLs_;
|
||||
|
||||
std::vector<int> assignment_;
|
||||
public:
|
||||
|
||||
//< Constructor
|
||||
TrustGraph(std::vector<UNL> UNLs, std::vector<int> assignment)
|
||||
: UNLs_{UNLs}
|
||||
, assignment_{assignment}
|
||||
{}
|
||||
|
||||
//< Whether node `i` trusts node `j`
|
||||
inline
|
||||
bool
|
||||
trusts(PeerID i, PeerID j) const
|
||||
{
|
||||
return unl(i).find(j) != unl(i).end();
|
||||
}
|
||||
|
||||
//< Get the UNL for node `i`
|
||||
inline
|
||||
UNL const &
|
||||
unl(PeerID i) const
|
||||
{
|
||||
return UNLs_[assignment_[i]];
|
||||
}
|
||||
|
||||
//< Check whether this trust graph satisfies the no forking condition
|
||||
bool
|
||||
canFork(double quorum) const;
|
||||
|
||||
|
||||
auto
|
||||
numPeers() const
|
||||
{
|
||||
return assignment_.size();
|
||||
}
|
||||
|
||||
//< Save grapviz dot file reprentation of the trust graph
|
||||
void
|
||||
save_dot(std::string const & fileName);
|
||||
|
||||
/** Generate a random trust graph based on random ranking of peers
|
||||
|
||||
Generate a random trust graph by
|
||||
|
||||
1. Randomly ranking the peers acording to RankPDF
|
||||
2. Generating `numUNL` random UNLs by sampling without replacement
|
||||
from the ranked nodes.
|
||||
3. Restricting the size of the random UNLs according to SizePDF
|
||||
|
||||
@param size The number of nodes in the trust graph
|
||||
@param numUNLs The number of UNLs to create
|
||||
@param rankPDF Generates random positive real numbers to use as ranks
|
||||
@param unlSizePDF Generates random integeres between (0,size-1) to
|
||||
restrict the size of generated PDF
|
||||
@param Generator The uniform random bit generator to use
|
||||
|
||||
@note RankPDF/SizePDF can model the full RandomDistribution concept
|
||||
defined in the STL, but for the purposes of this function need
|
||||
only provide:
|
||||
|
||||
auto operator()(Generator & g)
|
||||
|
||||
which should return the random sample.
|
||||
|
||||
|
||||
*/
|
||||
template <class RankPDF, class SizePDF, class Generator>
|
||||
static
|
||||
TrustGraph
|
||||
makeRandomRanked(int size,
|
||||
int numUNLs,
|
||||
RankPDF rankPDF,
|
||||
SizePDF unlSizePDF,
|
||||
Generator & g)
|
||||
{
|
||||
|
||||
|
||||
// 1. Generate ranks
|
||||
std::vector<double> weights(size);
|
||||
std::generate(weights.begin(), weights.end(), [&]()
|
||||
{
|
||||
return rankPDF(g);
|
||||
});
|
||||
|
||||
// 2. Generate UNLs based on sampling without replacement according
|
||||
// to weights
|
||||
std::vector<UNL> unls(numUNLs);
|
||||
std::generate(unls.begin(), unls.end(), [&]()
|
||||
{
|
||||
std::vector<PeerID> ids(size);
|
||||
std::iota(ids.begin(), ids.end(), 0);
|
||||
auto res = random_weighted_shuffle(ids, weights, g);
|
||||
return UNL(res.begin(), res.begin() + unlSizePDF(g));
|
||||
});
|
||||
|
||||
// 3. Assign membership
|
||||
std::vector<int> assignment(size);
|
||||
std::uniform_int_distribution<int> u(0, numUNLs-1);
|
||||
std::generate(assignment.begin(), assignment.end(),
|
||||
[&]()
|
||||
{
|
||||
return u(g);
|
||||
});
|
||||
|
||||
return TrustGraph(unls, assignment);
|
||||
}
|
||||
|
||||
/** Generate a 2 UNL trust graph with some overlap.
|
||||
|
||||
Generates a trust graph for `size` peers formed from
|
||||
two cliques with the given overlap. Nodes in the overlap
|
||||
trust both all other nodes, while nodes outside the overlap
|
||||
only trust nodes in their clique.
|
||||
|
||||
@param size The number of nodes in the trust graph
|
||||
@param overlap The number of nodes trusting both cliques
|
||||
*/
|
||||
static
|
||||
TrustGraph
|
||||
makeClique(int size, int overlap);
|
||||
|
||||
/** Generate a complete (fully-connect) trust graph
|
||||
|
||||
Generatest a trust graph in which all peers trust all
|
||||
other peers.
|
||||
|
||||
@param size The number of nodes in the trust graph
|
||||
*/
|
||||
static
|
||||
TrustGraph
|
||||
makeComplete(int size);
|
||||
};
|
||||
|
||||
|
||||
|
||||
//< Make the TrustGraph into a topology with delays given by DelayModel
|
||||
template <class DelayModel>
|
||||
auto
|
||||
topology(TrustGraph const & tg, DelayModel const & d)
|
||||
{
|
||||
return [&](PeerID i, PeerID j)
|
||||
{
|
||||
return tg.trusts(i,j) ? boost::make_optional(d(i,j)) : boost::none;
|
||||
};
|
||||
}
|
||||
|
||||
class fixed
|
||||
{
|
||||
std::chrono::nanoseconds d_;
|
||||
|
||||
public:
|
||||
fixed(std::chrono::nanoseconds const & d) : d_{d} {}
|
||||
|
||||
inline
|
||||
std::chrono::nanoseconds
|
||||
operator()(PeerID const & i, PeerID const & j) const
|
||||
{
|
||||
return d_;
|
||||
}
|
||||
};
|
||||
|
||||
} // csf
|
||||
} // test
|
||||
} // ripple
|
||||
|
||||
#endif
|
||||
135
src/test/csf/impl/UNL.cpp
Normal file
135
src/test/csf/impl/UNL.cpp
Normal file
@@ -0,0 +1,135 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2012-2017 Ripple Labs Inc
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
#include <test/csf/UNL.h>
|
||||
#include <boost/iterator/counting_iterator.hpp>
|
||||
#include <fstream>
|
||||
#include <algorithm>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
namespace csf {
|
||||
|
||||
bool
|
||||
TrustGraph::canFork(double quorum) const
|
||||
{
|
||||
// Check the forking condition by looking at intersection
|
||||
// between all pairs of UNLs.
|
||||
|
||||
// First check if some nodes uses a UNL they are not members of, since
|
||||
// this creates an implicit UNL with that ndoe.
|
||||
|
||||
auto uniqueUNLs = UNLs_;
|
||||
|
||||
for (int i = 0; i < assignment_.size(); ++i)
|
||||
{
|
||||
auto const & myUNL = UNLs_[assignment_[i]];
|
||||
if(myUNL.find(i) == myUNL.end())
|
||||
{
|
||||
auto myUNLcopy = myUNL;
|
||||
myUNLcopy.insert(i);
|
||||
uniqueUNLs.push_back(std::move(myUNLcopy));
|
||||
}
|
||||
}
|
||||
|
||||
// Loop over all pairs of uniqueUNLs
|
||||
for (int i = 0; i < uniqueUNLs.size(); ++i)
|
||||
{
|
||||
for (int j = (i+1); j < uniqueUNLs.size(); ++j)
|
||||
{
|
||||
auto const & unlA = uniqueUNLs[i];
|
||||
auto const & unlB = uniqueUNLs[j];
|
||||
|
||||
double rhs = 2.0*(1.-quorum) *
|
||||
std::max(unlA.size(), unlB.size() );
|
||||
|
||||
int intersectionSize = std::count_if(unlA.begin(), unlA.end(),
|
||||
[&](PeerID id)
|
||||
{
|
||||
return unlB.find(id) != unlB.end();
|
||||
});
|
||||
|
||||
if(intersectionSize < rhs)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
TrustGraph
|
||||
TrustGraph::makeClique(int size, int overlap)
|
||||
{
|
||||
using bci = boost::counting_iterator<PeerID>;
|
||||
|
||||
// Split network into two cliques with the given overlap
|
||||
// Clique A has nodes [0,endA) and Clique B has [startB,numPeers)
|
||||
// Note: Clique B will have an extra peer when numPeers - overlap
|
||||
// is odd
|
||||
int endA = (size + overlap)/2;
|
||||
int startB = (size - overlap)/2;
|
||||
|
||||
std::vector<UNL> unls;
|
||||
unls.emplace_back(bci(0), bci(endA));
|
||||
unls.emplace_back(bci(startB), bci(size));
|
||||
unls.emplace_back(bci(0), bci(size));
|
||||
|
||||
std::vector<int> assignment(size,0);
|
||||
|
||||
for (int i = 0; i < size; ++i)
|
||||
{
|
||||
if(i < startB)
|
||||
assignment[i] = 0;
|
||||
else if(i > endA)
|
||||
assignment[i] = 1;
|
||||
else
|
||||
assignment[i] = 2;
|
||||
}
|
||||
|
||||
|
||||
return TrustGraph(unls, assignment);
|
||||
}
|
||||
|
||||
TrustGraph
|
||||
TrustGraph::makeComplete(int size)
|
||||
{
|
||||
UNL all{ boost::counting_iterator<PeerID>( 0 ),
|
||||
boost::counting_iterator<PeerID>( size ) };
|
||||
|
||||
return TrustGraph(std::vector<UNL>(1,all),
|
||||
std::vector<int>(size, 0));
|
||||
}
|
||||
|
||||
inline void TrustGraph::save_dot(std::string const & fileName)
|
||||
{
|
||||
std::ofstream out(fileName);
|
||||
out << "digraph {\n";
|
||||
for (int i = 0; i < assignment_.size(); ++i)
|
||||
{
|
||||
for (auto & j : UNLs_[assignment_[i]])
|
||||
{
|
||||
out << i << " -> " << j << ";\n";
|
||||
}
|
||||
|
||||
}
|
||||
out << "}\n";
|
||||
|
||||
}
|
||||
|
||||
} // csf
|
||||
} // test
|
||||
} // ripple
|
||||
@@ -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>
|
||||
|
||||
21
src/test/unity/consensus_test_unity.cpp
Normal file
21
src/test/unity/consensus_test_unity.cpp
Normal 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>
|
||||
23
src/test/unity/csf_unity.cpp
Normal file
23
src/test/unity/csf_unity.cpp
Normal 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>
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user