Redesign CSF framework (RIPD-1361):

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

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

View File

@@ -0,0 +1,104 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012-2017 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include <BeastConfig.h>
#include <ripple/beast/clock/manual_clock.h>
#include <ripple/beast/unit_test.h>
#include <test/csf.h>
#include <utility>
namespace ripple {
namespace test {
class ByzantineFailureSim_test : public beast::unit_test::suite
{
void
run() override
{
using namespace csf;
using namespace std::chrono;
// This test simulates a specific topology with nodes generating
// different ledgers due to a simulated byzantine failure (injecting
// an extra non-consensus transaction).
Sim sim;
ConsensusParms const parms{};
SimDuration const delay =
round<milliseconds>(0.2 * parms.ledgerGRANULARITY);
PeerGroup a = sim.createGroup(1);
PeerGroup b = sim.createGroup(1);
PeerGroup c = sim.createGroup(1);
PeerGroup d = sim.createGroup(1);
PeerGroup e = sim.createGroup(1);
PeerGroup f = sim.createGroup(1);
PeerGroup g = sim.createGroup(1);
a.trustAndConnect(a + b + c + g, delay);
b.trustAndConnect(b + a + c + d + e, delay);
c.trustAndConnect(c + a + b + d + e, delay);
d.trustAndConnect(d + b + c + e + f, delay);
e.trustAndConnect(e + b + c + d + f, delay);
f.trustAndConnect(f + d + e + g, delay);
g.trustAndConnect(g + a + f, delay);
PeerGroup network = a + b + c + d + e + f + g;
StreamCollector sc{std::cout};
sim.collectors.add(sc);
for (TrustGraph<Peer*>::ForkInfo const& fi :
sim.trustGraph.forkablePairs(0.8))
{
std::cout << "Can fork " << PeerGroup{fi.unlA} << " "
<< " " << PeerGroup{fi.unlB}
<< " overlap " << fi.overlap << " required "
<< fi.required << "\n";
};
// set prior state
sim.run(1);
PeerGroup byzantineNodes = a + b + c + g;
// All peers see some TX 0
for (Peer * peer : network)
{
peer->submit(Tx(0));
// Peers 0,1,2,6 will close the next ledger differently by injecting
// a non-consensus approved transaciton
if (byzantineNodes.contains(peer))
{
peer->txInjections.emplace(
peer->lastClosedLedger.seq(), Tx{42});
}
}
sim.run(4);
std::cout << "Branches: " << sim.branches() << "\n";
std::cout << "Fully synchronized: " << std::boolalpha
<< sim.synchronized() << "\n";
// Not tessting anything currently.
pass();
}
};
BEAST_DEFINE_TESTSUITE_MANUAL(ByzantineFailureSim, consensus, ripple);
} // namespace test
} // namespace ripple

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,273 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012-2016 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include <BeastConfig.h>
#include <ripple/beast/clock/manual_clock.h>
#include <ripple/beast/unit_test.h>
#include <test/csf.h>
#include <utility>
#include <boost/algorithm/string/split.hpp>
#include <boost/algorithm/string/classification.hpp>
#include <algorithm>
#include <sstream>
#include <fstream>
#include <string>
namespace ripple {
namespace test {
/** In progress simulations for diversifying and distributing validators
*/
class DistributedValidators_test : public beast::unit_test::suite
{
void
completeTrustCompleteConnectFixedDelay(
std::size_t numPeers,
std::chrono::milliseconds delay = std::chrono::milliseconds(200),
bool printHeaders = false)
{
using namespace csf;
using namespace std::chrono;
// Initialize persistent collector logs specific to this method
std::string const prefix =
"DistributedValidators_"
"completeTrustCompleteConnectFixedDelay";
std::fstream
txLog(prefix + "_tx.csv", std::ofstream::app),
ledgerLog(prefix + "_ledger.csv", std::ofstream::app);
// title
log << prefix << "(" << numPeers << "," << delay.count() << ")"
<< std::endl;
// number of peers, UNLs, connections
BEAST_EXPECT(numPeers >= 1);
Sim sim;
PeerGroup peers = sim.createGroup(numPeers);
// complete trust graph
peers.trust(peers);
// complete connect graph with fixed delay
peers.connect(peers, delay);
// Initialize collectors to track statistics to report
TxCollector txCollector;
LedgerCollector ledgerCollector;
auto colls = makeCollectors(txCollector, ledgerCollector);
sim.collectors.add(colls);
// Initial round to set prior state
sim.run(1);
// Run for 10 minues, submitting 100 tx/second
std::chrono::nanoseconds const simDuration = 10min;
std::chrono::nanoseconds const quiet = 10s;
Rate const rate{100, 1000ms};
// Initialize timers
HeartbeatTimer heart(sim.scheduler);
// txs, start/stop/step, target
auto peerSelector = makeSelector(peers.begin(),
peers.end(),
std::vector<double>(numPeers, 1.),
sim.rng);
auto txSubmitter = makeSubmitter(ConstantDistribution{rate.inv()},
sim.scheduler.now() + quiet,
sim.scheduler.now() + simDuration - quiet,
peerSelector,
sim.scheduler,
sim.rng);
// run simulation for given duration
heart.start();
sim.run(simDuration);
//BEAST_EXPECT(sim.branches() == 1);
//BEAST_EXPECT(sim.synchronized());
log << std::right;
log << "| Peers: "<< std::setw(2) << peers.size();
log << " | Duration: " << std::setw(6)
<< duration_cast<milliseconds>(simDuration).count() << " ms";
log << " | Branches: " << std::setw(1) << sim.branches();
log << " | Synchronized: " << std::setw(1)
<< (sim.synchronized() ? "Y" : "N");
log << " |" << std::endl;
txCollector.report(simDuration, log, true);
ledgerCollector.report(simDuration, log, false);
std::string const tag = std::to_string(numPeers);
txCollector.csv(simDuration, txLog, tag, printHeaders);
ledgerCollector.csv(simDuration, ledgerLog, tag, printHeaders);
log << std::endl;
}
void
completeTrustScaleFreeConnectFixedDelay(
std::size_t numPeers,
std::chrono::milliseconds delay = std::chrono::milliseconds(200),
bool printHeaders = false)
{
using namespace csf;
using namespace std::chrono;
// Initialize persistent collector logs specific to this method
std::string const prefix =
"DistributedValidators__"
"completeTrustScaleFreeConnectFixedDelay";
std::fstream
txLog(prefix + "_tx.csv", std::ofstream::app),
ledgerLog(prefix + "_ledger.csv", std::ofstream::app);
// title
log << prefix << "(" << numPeers << "," << delay.count() << ")"
<< std::endl;
// number of peers, UNLs, connections
int const numCNLs = std::max(int(1.00 * numPeers), 1);
int const minCNLSize = std::max(int(0.25 * numCNLs), 1);
int const maxCNLSize = std::max(int(0.50 * numCNLs), 1);
BEAST_EXPECT(numPeers >= 1);
BEAST_EXPECT(numCNLs >= 1);
BEAST_EXPECT(1 <= minCNLSize
&& minCNLSize <= maxCNLSize
&& maxCNLSize <= numPeers);
Sim sim;
PeerGroup peers = sim.createGroup(numPeers);
// complete trust graph
peers.trust(peers);
// scale-free connect graph with fixed delay
std::vector<double> const ranks =
sample(peers.size(), PowerLawDistribution{1, 3}, sim.rng);
randomRankedConnect(peers, ranks, numCNLs,
std::uniform_int_distribution<>{minCNLSize, maxCNLSize},
sim.rng, delay);
// Initialize collectors to track statistics to report
TxCollector txCollector;
LedgerCollector ledgerCollector;
auto colls = makeCollectors(txCollector, ledgerCollector);
sim.collectors.add(colls);
// Initial round to set prior state
sim.run(1);
// Run for 10 minues, submitting 100 tx/second
std::chrono::nanoseconds simDuration = 10min;
std::chrono::nanoseconds quiet = 10s;
Rate rate{100, 1000ms};
// Initialize timers
HeartbeatTimer heart(sim.scheduler);
// txs, start/stop/step, target
auto peerSelector = makeSelector(peers.begin(),
peers.end(),
std::vector<double>(numPeers, 1.),
sim.rng);
auto txSubmitter = makeSubmitter(ConstantDistribution{rate.inv()},
sim.scheduler.now() + quiet,
sim.scheduler.now() + simDuration - quiet,
peerSelector,
sim.scheduler,
sim.rng);
// run simulation for given duration
heart.start();
sim.run(simDuration);
//BEAST_EXPECT(sim.branches() == 1);
//BEAST_EXPECT(sim.synchronized());
log << std::right;
log << "| Peers: "<< std::setw(2) << peers.size();
log << " | Duration: " << std::setw(6)
<< duration_cast<milliseconds>(simDuration).count() << " ms";
log << " | Branches: " << std::setw(1) << sim.branches();
log << " | Synchronized: " << std::setw(1)
<< (sim.synchronized() ? "Y" : "N");
log << " |" << std::endl;
txCollector.report(simDuration, log, true);
ledgerCollector.report(simDuration, log, false);
std::string const tag = std::to_string(numPeers);
txCollector.csv(simDuration, txLog, tag, printHeaders);
ledgerCollector.csv(simDuration, ledgerLog, tag, printHeaders);
log << std::endl;
}
void
run() override
{
std::string const defaultArgs = "5 200";
std::string const args = arg().empty() ? defaultArgs : arg();
std::stringstream argStream(args);
int maxNumValidators = 0;
int delayCount(200);
argStream >> maxNumValidators;
argStream >> delayCount;
std::chrono::milliseconds const delay(delayCount);
log << "DistributedValidators: 1 to " << maxNumValidators << " Peers"
<< std::endl;
/**
* Simulate with N = 1 to N
* - complete trust graph is complete
* - complete network connectivity
* - fixed delay for network links
*/
completeTrustCompleteConnectFixedDelay(1, delay, true);
for(int i = 2; i <= maxNumValidators; i++)
{
completeTrustCompleteConnectFixedDelay(i, delay);
}
/**
* Simulate with N = 1 to N
* - complete trust graph is complete
* - scale-free network connectivity
* - fixed delay for network links
*/
completeTrustScaleFreeConnectFixedDelay(1, delay, true);
for(int i = 2; i <= maxNumValidators; i++)
{
completeTrustScaleFreeConnectFixedDelay(i, delay);
}
}
};
BEAST_DEFINE_TESTSUITE_MANUAL(DistributedValidators, consensus, ripple);
} // namespace test
} // namespace ripple

View File

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

View File

@@ -0,0 +1,123 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012-2016 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include <BeastConfig.h>
#include <ripple/beast/clock/manual_clock.h>
#include <ripple/beast/unit_test.h>
#include <test/csf.h>
#include <test/csf/random.h>
#include <utility>
namespace ripple {
namespace test {
class ScaleFreeSim_test : public beast::unit_test::suite
{
void
run() override
{
using namespace std::chrono;
using namespace csf;
// Generate a quasi-random scale free network and simulate consensus
// as we vary transaction submission rates
int const N = 100; // Peers
int const numUNLs = 15; // UNL lists
int const minUNLSize = N / 4, maxUNLSize = N / 2;
ConsensusParms const parms{};
Sim sim;
PeerGroup network = sim.createGroup(N);
// generate trust ranks
std::vector<double> const ranks =
sample(network.size(), PowerLawDistribution{1, 3}, sim.rng);
// generate scale-free trust graph
randomRankedTrust(network, ranks, numUNLs,
std::uniform_int_distribution<>{minUNLSize, maxUNLSize},
sim.rng);
// nodes with a trust line in either direction are network-connected
network.connectFromTrust(
round<milliseconds>(0.2 * parms.ledgerGRANULARITY));
// Initialize collectors to track statistics to report
TxCollector txCollector;
LedgerCollector ledgerCollector;
auto colls = makeCollectors(txCollector, ledgerCollector);
sim.collectors.add(colls);
// Initial round to set prior state
sim.run(1);
// Initialize timers
HeartbeatTimer heart(sim.scheduler, seconds(10s));
// Run for 10 minues, submitting 100 tx/second
std::chrono::nanoseconds const simDuration = 10min;
std::chrono::nanoseconds const quiet = 10s;
Rate const rate{100, 1000ms};
// txs, start/stop/step, target
auto peerSelector = makeSelector(network.begin(),
network.end(),
ranks,
sim.rng);
auto txSubmitter = makeSubmitter(ConstantDistribution{rate.inv()},
sim.scheduler.now() + quiet,
sim.scheduler.now() + (simDuration - quiet),
peerSelector,
sim.scheduler,
sim.rng);
// run simulation for given duration
heart.start();
sim.run(simDuration);
BEAST_EXPECT(sim.branches() == 1);
BEAST_EXPECT(sim.synchronized());
// TODO: Clean up this formatting mess!!
log << "Peers: " << network.size() << std::endl;
log << "Simulated Duration: "
<< duration_cast<milliseconds>(simDuration).count()
<< " ms" << std::endl;
log << "Branches: " << sim.branches() << std::endl;
log << "Synchronized: " << (sim.synchronized() ? "Y" : "N")
<< std::endl;
log << std::endl;
txCollector.report(simDuration, log);
ledgerCollector.report(simDuration, log);
// Print summary?
// # forks? # of LCLs?
// # peers
// # tx submitted
// # ledgers/sec etc.?
}
};
BEAST_DEFINE_TESTSUITE_MANUAL(ScaleFreeSim, consensus, ripple);
} // namespace test
} // namespace ripple

View File

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