mirror of
https://github.com/Xahau/xahaud.git
synced 2025-12-06 17:27:52 +00:00
Each validator will generate a random cookie on startup that it will include in each of its validations. This will allow validators to detect when more than one validator is accidentally operating with the same validation keys.
1216 lines
40 KiB
C++
1216 lines
40 KiB
C++
//------------------------------------------------------------------------------
|
|
/*
|
|
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 <ripple/basics/random.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 <memory>
|
|
#include <tuple>
|
|
#include <type_traits>
|
|
#include <vector>
|
|
|
|
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;
|
|
|
|
// Helper to convert steady_clock to a reasonable NetClock
|
|
// This allows a single manual clock in the unit tests
|
|
static NetClock::time_point
|
|
toNetClock(clock_type const& c)
|
|
{
|
|
// 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 are positive
|
|
using namespace std::chrono;
|
|
return NetClock::time_point(duration_cast<NetClock::duration>(
|
|
c.now().time_since_epoch() + 86400s));
|
|
}
|
|
|
|
// Represents a node that can issue validations
|
|
class Node
|
|
{
|
|
clock_type const& c_;
|
|
PeerID nodeID_;
|
|
bool trusted_ = true;
|
|
std::size_t signIdx_ = 1;
|
|
boost::optional<std::uint32_t> loadFee_;
|
|
std::uint64_t cookie_;
|
|
|
|
public:
|
|
Node(PeerID nodeID, clock_type const& c)
|
|
: c_(c)
|
|
, nodeID_(nodeID)
|
|
, cookie_(rand_int<std::uint64_t>(
|
|
1,
|
|
std::numeric_limits<std::uint64_t>::max()))
|
|
{
|
|
}
|
|
|
|
void
|
|
untrust()
|
|
{
|
|
trusted_ = false;
|
|
}
|
|
|
|
void
|
|
trust()
|
|
{
|
|
trusted_ = true;
|
|
}
|
|
|
|
void
|
|
setLoadFee(std::uint32_t fee)
|
|
{
|
|
loadFee_ = fee;
|
|
}
|
|
|
|
PeerID
|
|
nodeID() const
|
|
{
|
|
return nodeID_;
|
|
}
|
|
|
|
void
|
|
advanceKey()
|
|
{
|
|
signIdx_++;
|
|
}
|
|
|
|
PeerKey
|
|
currKey() const
|
|
{
|
|
return std::make_pair(nodeID_, signIdx_);
|
|
}
|
|
|
|
PeerKey
|
|
masterKey() const
|
|
{
|
|
return std::make_pair(nodeID_, 0);
|
|
}
|
|
NetClock::time_point
|
|
now() const
|
|
{
|
|
return toNetClock(c_);
|
|
}
|
|
|
|
// Issue a new validation with given sequence number and id and
|
|
// with signing and seen times offset from the common clock
|
|
Validation
|
|
validate(
|
|
Ledger::ID id,
|
|
Ledger::Seq seq,
|
|
NetClock::duration signOffset,
|
|
NetClock::duration seenOffset,
|
|
bool full) const
|
|
{
|
|
Validation v{id,
|
|
seq,
|
|
now() + signOffset,
|
|
now() + seenOffset,
|
|
currKey(),
|
|
nodeID_,
|
|
full,
|
|
cookie_,
|
|
loadFee_};
|
|
if (trusted_)
|
|
v.setTrusted();
|
|
return v;
|
|
}
|
|
|
|
Validation
|
|
validate(
|
|
Ledger ledger,
|
|
NetClock::duration signOffset,
|
|
NetClock::duration seenOffset) const
|
|
{
|
|
return validate(
|
|
ledger.id(), ledger.seq(), signOffset, seenOffset, true);
|
|
}
|
|
|
|
Validation
|
|
validate(Ledger ledger) const
|
|
{
|
|
return validate(
|
|
ledger.id(),
|
|
ledger.seq(),
|
|
NetClock::duration{0},
|
|
NetClock::duration{0},
|
|
true);
|
|
}
|
|
|
|
Validation
|
|
partial(Ledger ledger) const
|
|
{
|
|
return validate(
|
|
ledger.id(),
|
|
ledger.seq(),
|
|
NetClock::duration{0},
|
|
NetClock::duration{0},
|
|
false);
|
|
}
|
|
};
|
|
|
|
// Saved StaleData for inspection in test
|
|
struct StaleData
|
|
{
|
|
std::vector<Validation> stale;
|
|
hash_map<PeerID, Validation> flushed;
|
|
};
|
|
|
|
// Generic Validations adaptor that saves stale/flushed data into
|
|
// a StaleData instance.
|
|
class Adaptor
|
|
{
|
|
StaleData& staleData_;
|
|
clock_type& c_;
|
|
LedgerOracle& oracle_;
|
|
|
|
public:
|
|
// Non-locking mutex to avoid locks in generic Validations
|
|
struct Mutex
|
|
{
|
|
void
|
|
lock()
|
|
{
|
|
}
|
|
|
|
void
|
|
unlock()
|
|
{
|
|
}
|
|
};
|
|
|
|
using Validation = csf::Validation;
|
|
using Ledger = csf::Ledger;
|
|
|
|
Adaptor(StaleData& sd, clock_type& c, LedgerOracle& o)
|
|
: staleData_{sd}, c_{c}, oracle_{o}
|
|
{
|
|
}
|
|
|
|
NetClock::time_point
|
|
now() const
|
|
{
|
|
return toNetClock(c_);
|
|
}
|
|
|
|
void
|
|
onStale(Validation&& v)
|
|
{
|
|
staleData_.stale.emplace_back(std::move(v));
|
|
}
|
|
|
|
void
|
|
flush(hash_map<PeerID, Validation>&& remaining)
|
|
{
|
|
staleData_.flushed = std::move(remaining);
|
|
}
|
|
|
|
boost::optional<Ledger>
|
|
acquire(Ledger::ID const& id)
|
|
{
|
|
return oracle_.lookup(id);
|
|
}
|
|
};
|
|
|
|
// Specialize generic Validations using the above types
|
|
using TestValidations = Validations<Adaptor>;
|
|
|
|
// Gather the dependencies of TestValidations in a single class and provide
|
|
// accessors for simplifying test logic
|
|
class TestHarness
|
|
{
|
|
StaleData staleData_;
|
|
ValidationParms p_;
|
|
beast::manual_clock<std::chrono::steady_clock> clock_;
|
|
TestValidations tv_;
|
|
PeerID nextNodeId_{0};
|
|
|
|
public:
|
|
TestHarness(LedgerOracle& o)
|
|
: tv_(p_, clock_, staleData_, clock_, o)
|
|
{
|
|
}
|
|
|
|
ValStatus
|
|
add(Validation const& v)
|
|
{
|
|
return tv_.add(v.nodeID(), v);
|
|
}
|
|
|
|
TestValidations&
|
|
vals()
|
|
{
|
|
return tv_;
|
|
}
|
|
|
|
Node
|
|
makeNode()
|
|
{
|
|
return Node(nextNodeId_++, clock_);
|
|
}
|
|
|
|
ValidationParms
|
|
parms() const
|
|
{
|
|
return p_;
|
|
}
|
|
|
|
auto&
|
|
clock()
|
|
{
|
|
return clock_;
|
|
}
|
|
|
|
std::vector<Validation> const&
|
|
stale() const
|
|
{
|
|
return staleData_.stale;
|
|
}
|
|
|
|
hash_map<PeerID, Validation> const&
|
|
flushed() const
|
|
{
|
|
return staleData_.flushed;
|
|
}
|
|
};
|
|
|
|
Ledger const genesisLedger{Ledger::MakeGenesis{}};
|
|
|
|
void
|
|
testAddValidation()
|
|
{
|
|
using namespace std::chrono_literals;
|
|
|
|
testcase("Add validation");
|
|
LedgerHistoryHelper h;
|
|
Ledger ledgerA = h["a"];
|
|
Ledger ledgerAB = h["ab"];
|
|
Ledger ledgerAZ = h["az"];
|
|
Ledger ledgerABC = h["abc"];
|
|
Ledger ledgerABCD = h["abcd"];
|
|
Ledger ledgerABCDE = h["abcde"];
|
|
|
|
{
|
|
TestHarness harness(h.oracle);
|
|
Node n = harness.makeNode();
|
|
|
|
auto const v = n.validate(ledgerA);
|
|
|
|
// Add a current validation
|
|
BEAST_EXPECT(ValStatus::current == harness.add(v));
|
|
|
|
// Re-adding violates the increasing seq requirement for full
|
|
// validations
|
|
BEAST_EXPECT(ValStatus::badSeq == harness.add(v));
|
|
|
|
harness.clock().advance(1s);
|
|
// Replace with a new validation and ensure the old one is stale
|
|
BEAST_EXPECT(harness.stale().empty());
|
|
|
|
BEAST_EXPECT(
|
|
ValStatus::current == harness.add(n.validate(ledgerAB)));
|
|
|
|
BEAST_EXPECT(harness.stale().size() == 1);
|
|
|
|
BEAST_EXPECT(harness.stale()[0].ledgerID() == ledgerA.id());
|
|
|
|
// Test the node changing signing key
|
|
|
|
// Confirm old ledger on hand, but not new ledger
|
|
BEAST_EXPECT(
|
|
harness.vals().numTrustedForLedger(ledgerAB.id()) == 1);
|
|
BEAST_EXPECT(
|
|
harness.vals().numTrustedForLedger(ledgerABC.id()) == 0);
|
|
|
|
// Rotate signing keys
|
|
n.advanceKey();
|
|
|
|
harness.clock().advance(1s);
|
|
|
|
// Cannot re-do the same full validation sequence
|
|
BEAST_EXPECT(
|
|
ValStatus::badSeq == harness.add(n.validate(ledgerAB)));
|
|
// Cannot send the same partial validation sequence
|
|
BEAST_EXPECT(
|
|
ValStatus::badSeq == harness.add(n.partial(ledgerAB)));
|
|
|
|
// Now trusts the newest ledger too
|
|
harness.clock().advance(1s);
|
|
BEAST_EXPECT(
|
|
ValStatus::current == harness.add(n.validate(ledgerABC)));
|
|
BEAST_EXPECT(
|
|
harness.vals().numTrustedForLedger(ledgerAB.id()) == 1);
|
|
BEAST_EXPECT(
|
|
harness.vals().numTrustedForLedger(ledgerABC.id()) == 1);
|
|
|
|
// Processing validations out of order should ignore the older
|
|
// validation
|
|
harness.clock().advance(2s);
|
|
auto const valABCDE = n.validate(ledgerABCDE);
|
|
|
|
harness.clock().advance(4s);
|
|
auto const valABCD = n.validate(ledgerABCD);
|
|
|
|
BEAST_EXPECT(ValStatus::current == harness.add(valABCD));
|
|
|
|
BEAST_EXPECT(ValStatus::stale == harness.add(valABCDE));
|
|
}
|
|
|
|
{
|
|
// Process validations out of order with shifted times
|
|
|
|
TestHarness harness(h.oracle);
|
|
Node n = harness.makeNode();
|
|
|
|
// Establish a new current validation
|
|
BEAST_EXPECT(
|
|
ValStatus::current == harness.add(n.validate(ledgerA)));
|
|
|
|
// Process a validation that has "later" seq but early sign time
|
|
BEAST_EXPECT(
|
|
ValStatus::stale ==
|
|
harness.add(n.validate(ledgerAB, -1s, -1s)));
|
|
|
|
// Process a validation that has a later seq and later sign
|
|
// time
|
|
BEAST_EXPECT(
|
|
ValStatus::current ==
|
|
harness.add(n.validate(ledgerABC, 1s, 1s)));
|
|
}
|
|
|
|
{
|
|
// Test stale on arrival validations
|
|
TestHarness harness(h.oracle);
|
|
Node n = harness.makeNode();
|
|
|
|
BEAST_EXPECT(
|
|
ValStatus::stale ==
|
|
harness.add(n.validate(
|
|
ledgerA, -harness.parms().validationCURRENT_EARLY, 0s)));
|
|
|
|
BEAST_EXPECT(
|
|
ValStatus::stale ==
|
|
harness.add(n.validate(
|
|
ledgerA, harness.parms().validationCURRENT_WALL, 0s)));
|
|
|
|
BEAST_EXPECT(
|
|
ValStatus::stale ==
|
|
harness.add(n.validate(
|
|
ledgerA, 0s, harness.parms().validationCURRENT_LOCAL)));
|
|
}
|
|
|
|
{
|
|
// Test that full or partials cannot be sent for older sequence
|
|
// numbers, unless time-out has happened
|
|
for (bool doFull : {true, false})
|
|
{
|
|
TestHarness harness(h.oracle);
|
|
Node n = harness.makeNode();
|
|
|
|
auto process = [&](Ledger & lgr)
|
|
{
|
|
if(doFull)
|
|
return harness.add(n.validate(lgr));
|
|
return harness.add(n.partial(lgr));
|
|
};
|
|
|
|
BEAST_EXPECT(ValStatus::current == process(ledgerABC));
|
|
harness.clock().advance(1s);
|
|
BEAST_EXPECT(ledgerAB.seq() < ledgerABC.seq());
|
|
BEAST_EXPECT(ValStatus::badSeq == process(ledgerAB));
|
|
|
|
// If we advance far enough for AB to expire, we can fully
|
|
// validate or partially validate that sequence number again
|
|
BEAST_EXPECT(ValStatus::badSeq == process(ledgerAZ));
|
|
harness.clock().advance(
|
|
harness.parms().validationSET_EXPIRES + 1ms);
|
|
BEAST_EXPECT(ValStatus::current == process(ledgerAZ));
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
testOnStale()
|
|
{
|
|
testcase("Stale validation");
|
|
// Verify validation becomes stale based solely on time passing, but
|
|
// use different functions to trigger the check for staleness
|
|
|
|
LedgerHistoryHelper h;
|
|
Ledger ledgerA = h["a"];
|
|
Ledger ledgerAB = h["ab"];
|
|
|
|
|
|
using Trigger = std::function<void(TestValidations&)>;
|
|
|
|
std::vector<Trigger> triggers = {
|
|
[&](TestValidations& vals) { vals.currentTrusted(); },
|
|
[&](TestValidations& vals) { vals.getCurrentNodeIDs(); },
|
|
[&](TestValidations& vals) { vals.getPreferred(genesisLedger); },
|
|
[&](TestValidations& vals) {
|
|
vals.getNodesAfter(ledgerA, ledgerA.id());
|
|
}};
|
|
for (Trigger trigger : triggers)
|
|
{
|
|
TestHarness harness(h.oracle);
|
|
Node n = harness.makeNode();
|
|
|
|
BEAST_EXPECT(
|
|
ValStatus::current == harness.add(n.validate(ledgerAB)));
|
|
trigger(harness.vals());
|
|
BEAST_EXPECT(
|
|
harness.vals().getNodesAfter(ledgerA, ledgerA.id()) == 1);
|
|
BEAST_EXPECT(
|
|
harness.vals().getPreferred(genesisLedger) ==
|
|
std::make_pair(ledgerAB.seq(), ledgerAB.id()));
|
|
BEAST_EXPECT(harness.stale().empty());
|
|
harness.clock().advance(harness.parms().validationCURRENT_LOCAL);
|
|
|
|
// trigger check for stale
|
|
trigger(harness.vals());
|
|
|
|
BEAST_EXPECT(harness.stale().size() == 1);
|
|
BEAST_EXPECT(harness.stale()[0].ledgerID() == ledgerAB.id());
|
|
BEAST_EXPECT(
|
|
harness.vals().getNodesAfter(ledgerA, ledgerA.id()) == 0);
|
|
BEAST_EXPECT(
|
|
harness.vals().getPreferred(genesisLedger) ==
|
|
std::make_pair(Ledger::Seq{0}, Ledger::ID{0}));
|
|
}
|
|
}
|
|
|
|
void
|
|
testGetNodesAfter()
|
|
{
|
|
// Test getting number of nodes working on a validation descending
|
|
// a prescribed one. This count should only be for trusted nodes, but
|
|
// includes partial and full validations
|
|
|
|
using namespace std::chrono_literals;
|
|
testcase("Get nodes after");
|
|
|
|
LedgerHistoryHelper h;
|
|
Ledger ledgerA = h["a"];
|
|
Ledger ledgerAB = h["ab"];
|
|
Ledger ledgerABC = h["abc"];
|
|
Ledger ledgerAD = h["ad"];
|
|
|
|
TestHarness harness(h.oracle);
|
|
Node a = harness.makeNode(), b = harness.makeNode(),
|
|
c = harness.makeNode(), d = harness.makeNode();
|
|
c.untrust();
|
|
|
|
// first round a,b,c agree, d has is partial
|
|
BEAST_EXPECT(ValStatus::current == harness.add(a.validate(ledgerA)));
|
|
BEAST_EXPECT(ValStatus::current == harness.add(b.validate(ledgerA)));
|
|
BEAST_EXPECT(ValStatus::current == harness.add(c.validate(ledgerA)));
|
|
BEAST_EXPECT(ValStatus::current == harness.add(d.partial(ledgerA)));
|
|
|
|
for (Ledger const& ledger : {ledgerA, ledgerAB, ledgerABC, ledgerAD})
|
|
BEAST_EXPECT(
|
|
harness.vals().getNodesAfter(ledger, ledger.id()) == 0);
|
|
|
|
harness.clock().advance(5s);
|
|
|
|
BEAST_EXPECT(ValStatus::current == harness.add(a.validate(ledgerAB)));
|
|
BEAST_EXPECT(ValStatus::current == harness.add(b.validate(ledgerABC)));
|
|
BEAST_EXPECT(ValStatus::current == harness.add(c.validate(ledgerAB)));
|
|
BEAST_EXPECT(ValStatus::current == harness.add(d.partial(ledgerABC)));
|
|
|
|
BEAST_EXPECT(harness.vals().getNodesAfter(ledgerA, ledgerA.id()) == 3);
|
|
BEAST_EXPECT(
|
|
harness.vals().getNodesAfter(ledgerAB, ledgerAB.id()) == 2);
|
|
BEAST_EXPECT(
|
|
harness.vals().getNodesAfter(ledgerABC, ledgerABC.id()) == 0);
|
|
BEAST_EXPECT(
|
|
harness.vals().getNodesAfter(ledgerAD, ledgerAD.id()) == 0);
|
|
|
|
// If given a ledger inconsistent with the id, is still able to check
|
|
// using slower method
|
|
BEAST_EXPECT(harness.vals().getNodesAfter(ledgerAD, ledgerA.id()) == 1);
|
|
BEAST_EXPECT(
|
|
harness.vals().getNodesAfter(ledgerAD, ledgerAB.id()) == 2);
|
|
}
|
|
|
|
void
|
|
testCurrentTrusted()
|
|
{
|
|
using namespace std::chrono_literals;
|
|
testcase("Current trusted validations");
|
|
|
|
LedgerHistoryHelper h;
|
|
Ledger ledgerA = h["a"];
|
|
Ledger ledgerB = h["b"];
|
|
Ledger ledgerAC = h["ac"];
|
|
|
|
TestHarness harness(h.oracle);
|
|
Node a = harness.makeNode(), b = harness.makeNode();
|
|
b.untrust();
|
|
|
|
BEAST_EXPECT(ValStatus::current == harness.add(a.validate(ledgerA)));
|
|
BEAST_EXPECT(ValStatus::current == harness.add(b.validate(ledgerB)));
|
|
|
|
// Only a is trusted
|
|
BEAST_EXPECT(harness.vals().currentTrusted().size() == 1);
|
|
BEAST_EXPECT(
|
|
harness.vals().currentTrusted()[0].ledgerID() == ledgerA.id());
|
|
BEAST_EXPECT(harness.vals().currentTrusted()[0].seq() == ledgerA.seq());
|
|
|
|
harness.clock().advance(3s);
|
|
|
|
for (auto const& node : {a, b})
|
|
BEAST_EXPECT(
|
|
ValStatus::current == harness.add(node.validate(ledgerAC)));
|
|
|
|
// New validation for a
|
|
BEAST_EXPECT(harness.vals().currentTrusted().size() == 1);
|
|
BEAST_EXPECT(
|
|
harness.vals().currentTrusted()[0].ledgerID() == ledgerAC.id());
|
|
BEAST_EXPECT(
|
|
harness.vals().currentTrusted()[0].seq() == ledgerAC.seq());
|
|
|
|
// Pass enough time for it to go stale
|
|
harness.clock().advance(harness.parms().validationCURRENT_LOCAL);
|
|
BEAST_EXPECT(harness.vals().currentTrusted().empty());
|
|
}
|
|
|
|
void
|
|
testGetCurrentPublicKeys()
|
|
{
|
|
using namespace std::chrono_literals;
|
|
testcase("Current public keys");
|
|
|
|
LedgerHistoryHelper h;
|
|
Ledger ledgerA = h["a"];
|
|
Ledger ledgerAC = h["ac"];
|
|
|
|
TestHarness harness(h.oracle);
|
|
Node a = harness.makeNode(), b = harness.makeNode();
|
|
b.untrust();
|
|
|
|
for (auto const& node : {a, b})
|
|
BEAST_EXPECT(
|
|
ValStatus::current == harness.add(node.validate(ledgerA)));
|
|
|
|
{
|
|
hash_set<PeerID> const expectedKeys = {a.nodeID(),
|
|
b.nodeID()};
|
|
BEAST_EXPECT(harness.vals().getCurrentNodeIDs() == expectedKeys);
|
|
}
|
|
|
|
harness.clock().advance(3s);
|
|
|
|
// Change keys and issue partials
|
|
a.advanceKey();
|
|
b.advanceKey();
|
|
|
|
for (auto const& node : {a, b})
|
|
BEAST_EXPECT(
|
|
ValStatus::current == harness.add(node.partial(ledgerAC)));
|
|
|
|
{
|
|
hash_set<PeerID> const expectedKeys = {a.nodeID(),
|
|
b.nodeID()};
|
|
BEAST_EXPECT(harness.vals().getCurrentNodeIDs() == expectedKeys);
|
|
}
|
|
|
|
// Pass enough time for them to go stale
|
|
harness.clock().advance(harness.parms().validationCURRENT_LOCAL);
|
|
BEAST_EXPECT(harness.vals().getCurrentNodeIDs().empty());
|
|
}
|
|
|
|
void
|
|
testTrustedByLedgerFunctions()
|
|
{
|
|
// Test the Validations functions that calculate a value by ledger ID
|
|
using namespace std::chrono_literals;
|
|
testcase("By ledger functions");
|
|
|
|
// Several Validations functions return a set of values associated
|
|
// with trusted ledgers sharing the same ledger ID. The tests below
|
|
// exercise this logic by saving the set of trusted Validations, and
|
|
// verifying that the Validations member functions all calculate the
|
|
// proper transformation of the available ledgers.
|
|
|
|
LedgerHistoryHelper h;
|
|
TestHarness harness(h.oracle);
|
|
|
|
Node a = harness.makeNode(), b = harness.makeNode(),
|
|
c = harness.makeNode(), d = harness.makeNode(),
|
|
e = harness.makeNode();
|
|
|
|
c.untrust();
|
|
// Mix of load fees
|
|
a.setLoadFee(12);
|
|
b.setLoadFee(1);
|
|
c.setLoadFee(12);
|
|
e.setLoadFee(12);
|
|
|
|
hash_map<Ledger::ID, std::vector<Validation>> trustedValidations;
|
|
|
|
//----------------------------------------------------------------------
|
|
// checkers
|
|
auto sorted = [](auto vec) {
|
|
std::sort(vec.begin(), vec.end());
|
|
return vec;
|
|
};
|
|
auto compare = [&]() {
|
|
for (auto& it : trustedValidations)
|
|
{
|
|
auto const& id = it.first;
|
|
auto const& expectedValidations = it.second;
|
|
|
|
BEAST_EXPECT(
|
|
harness.vals().numTrustedForLedger(id) ==
|
|
expectedValidations.size());
|
|
BEAST_EXPECT(
|
|
sorted(harness.vals().getTrustedForLedger(id)) ==
|
|
sorted(expectedValidations));
|
|
|
|
std::uint32_t baseFee = 0;
|
|
std::vector<uint32_t> expectedFees;
|
|
for (auto const& val : expectedValidations)
|
|
{
|
|
expectedFees.push_back(val.loadFee().value_or(baseFee));
|
|
}
|
|
|
|
BEAST_EXPECT(
|
|
sorted(harness.vals().fees(id, baseFee)) ==
|
|
sorted(expectedFees));
|
|
|
|
}
|
|
};
|
|
|
|
//----------------------------------------------------------------------
|
|
Ledger ledgerA = h["a"];
|
|
Ledger ledgerB = h["b"];
|
|
Ledger ledgerAC = h["ac"];
|
|
|
|
// Add a dummy ID to cover unknown ledger identifiers
|
|
trustedValidations[Ledger::ID{100}] = {};
|
|
|
|
// first round a,b,c agree
|
|
for (auto const& node : {a, b, c})
|
|
{
|
|
auto const val = node.validate(ledgerA);
|
|
BEAST_EXPECT(ValStatus::current == harness.add(val));
|
|
if (val.trusted())
|
|
trustedValidations[val.ledgerID()].emplace_back(val);
|
|
}
|
|
// d disagrees
|
|
{
|
|
auto const val = d.validate(ledgerB);
|
|
BEAST_EXPECT(ValStatus::current == harness.add(val));
|
|
trustedValidations[val.ledgerID()].emplace_back(val);
|
|
}
|
|
// e only issues partials
|
|
{
|
|
BEAST_EXPECT(ValStatus::current == harness.add(e.partial(ledgerA)));
|
|
}
|
|
|
|
harness.clock().advance(5s);
|
|
// second round, a,b,c move to ledger 2
|
|
for (auto const& node : {a, b, c})
|
|
{
|
|
auto const val = node.validate(ledgerAC);
|
|
BEAST_EXPECT(ValStatus::current == harness.add(val));
|
|
if (val.trusted())
|
|
trustedValidations[val.ledgerID()].emplace_back(val);
|
|
}
|
|
// d now thinks ledger 1, but cannot re-issue a previously used seq
|
|
{
|
|
BEAST_EXPECT(ValStatus::badSeq == harness.add(d.partial(ledgerA)));
|
|
}
|
|
// e only issues partials
|
|
{
|
|
BEAST_EXPECT(
|
|
ValStatus::current == harness.add(e.partial(ledgerAC)));
|
|
}
|
|
|
|
compare();
|
|
}
|
|
|
|
void
|
|
testExpire()
|
|
{
|
|
// Verify expiring clears out validations stored by ledger
|
|
testcase("Expire validations");
|
|
LedgerHistoryHelper h;
|
|
TestHarness harness(h.oracle);
|
|
Node a = harness.makeNode();
|
|
|
|
Ledger ledgerA = h["a"];
|
|
|
|
BEAST_EXPECT(ValStatus::current == harness.add(a.validate(ledgerA)));
|
|
BEAST_EXPECT(harness.vals().numTrustedForLedger(ledgerA.id()));
|
|
harness.clock().advance(harness.parms().validationSET_EXPIRES);
|
|
harness.vals().expire();
|
|
BEAST_EXPECT(!harness.vals().numTrustedForLedger(ledgerA.id()));
|
|
}
|
|
|
|
void
|
|
testFlush()
|
|
{
|
|
// Test final flush of validations
|
|
using namespace std::chrono_literals;
|
|
testcase("Flush validations");
|
|
|
|
LedgerHistoryHelper h;
|
|
TestHarness harness(h.oracle);
|
|
Node a = harness.makeNode(), b = harness.makeNode(),
|
|
c = harness.makeNode();
|
|
c.untrust();
|
|
|
|
Ledger ledgerA = h["a"];
|
|
Ledger ledgerAB = h["ab"];
|
|
|
|
hash_map<PeerID, Validation> expected;
|
|
for (auto const& node : {a, b, c})
|
|
{
|
|
auto const val = node.validate(ledgerA);
|
|
BEAST_EXPECT(ValStatus::current == harness.add(val));
|
|
expected.emplace(node.nodeID(), val);
|
|
}
|
|
Validation staleA = expected.find(a.nodeID())->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.validate(ledgerAB);
|
|
BEAST_EXPECT(ValStatus::current == harness.add(newVal));
|
|
expected.find(a.nodeID())->second = newVal;
|
|
|
|
// Now flush
|
|
harness.vals().flush();
|
|
|
|
// Original a validation was stale
|
|
BEAST_EXPECT(harness.stale().size() == 1);
|
|
BEAST_EXPECT(harness.stale()[0] == staleA);
|
|
BEAST_EXPECT(harness.stale()[0].nodeID() == a.nodeID());
|
|
|
|
auto const& flushed = harness.flushed();
|
|
|
|
BEAST_EXPECT(flushed == expected);
|
|
}
|
|
|
|
void
|
|
testGetPreferredLedger()
|
|
{
|
|
using namespace std::chrono_literals;
|
|
testcase("Preferred Ledger");
|
|
|
|
LedgerHistoryHelper h;
|
|
TestHarness harness(h.oracle);
|
|
Node a = harness.makeNode(), b = harness.makeNode(),
|
|
c = harness.makeNode(), d = harness.makeNode();
|
|
c.untrust();
|
|
|
|
Ledger ledgerA = h["a"];
|
|
Ledger ledgerB = h["b"];
|
|
Ledger ledgerAC = h["ac"];
|
|
Ledger ledgerACD = h["acd"];
|
|
|
|
using Seq = Ledger::Seq;
|
|
using ID = Ledger::ID;
|
|
|
|
auto pref = [](Ledger ledger) {
|
|
return std::make_pair(ledger.seq(), ledger.id());
|
|
};
|
|
|
|
// Empty (no ledgers)
|
|
BEAST_EXPECT(
|
|
harness.vals().getPreferred(ledgerA) == pref(genesisLedger));
|
|
|
|
// Single ledger
|
|
BEAST_EXPECT(ValStatus::current == harness.add(a.validate(ledgerB)));
|
|
BEAST_EXPECT(harness.vals().getPreferred(ledgerA) == pref(ledgerB));
|
|
BEAST_EXPECT(harness.vals().getPreferred(ledgerB) == pref(ledgerB));
|
|
|
|
// Minimum valid sequence
|
|
BEAST_EXPECT(
|
|
harness.vals().getPreferred(ledgerA, Seq{10}) == ledgerA.id());
|
|
|
|
// Untrusted doesn't impact preferred ledger
|
|
// (ledgerB has tie-break over ledgerA)
|
|
BEAST_EXPECT(ValStatus::current == harness.add(b.validate(ledgerA)));
|
|
BEAST_EXPECT(ValStatus::current == harness.add(c.validate(ledgerA)));
|
|
BEAST_EXPECT(ledgerB.id() > ledgerA.id());
|
|
BEAST_EXPECT(harness.vals().getPreferred(ledgerA) == pref(ledgerB));
|
|
BEAST_EXPECT(harness.vals().getPreferred(ledgerB) == pref(ledgerB));
|
|
|
|
// Partial does break ties
|
|
BEAST_EXPECT(ValStatus::current == harness.add(d.partial(ledgerA)));
|
|
BEAST_EXPECT(harness.vals().getPreferred(ledgerA) == pref(ledgerA));
|
|
BEAST_EXPECT(harness.vals().getPreferred(ledgerB) == pref(ledgerA));
|
|
|
|
harness.clock().advance(5s);
|
|
|
|
// Parent of preferred-> stick with ledger
|
|
for (auto const& node : {a, b, c, d})
|
|
BEAST_EXPECT(
|
|
ValStatus::current == harness.add(node.validate(ledgerAC)));
|
|
// Parent of preferred stays put
|
|
BEAST_EXPECT(harness.vals().getPreferred(ledgerA) == pref(ledgerA));
|
|
// Earlier different chain, switch
|
|
BEAST_EXPECT(harness.vals().getPreferred(ledgerB) == pref(ledgerAC));
|
|
// Later on chain, stays where it is
|
|
BEAST_EXPECT(harness.vals().getPreferred(ledgerACD) == pref(ledgerACD));
|
|
|
|
// Any later grandchild or different chain is preferred
|
|
harness.clock().advance(5s);
|
|
for (auto const& node : {a, b, c, d})
|
|
BEAST_EXPECT(
|
|
ValStatus::current == harness.add(node.validate(ledgerACD)));
|
|
for (auto const& ledger : {ledgerA, ledgerB, ledgerACD})
|
|
BEAST_EXPECT(
|
|
harness.vals().getPreferred(ledger) == pref(ledgerACD));
|
|
}
|
|
|
|
void
|
|
testGetPreferredLCL()
|
|
{
|
|
using namespace std::chrono_literals;
|
|
testcase("Get preferred LCL");
|
|
|
|
LedgerHistoryHelper h;
|
|
TestHarness harness(h.oracle);
|
|
Node a = harness.makeNode();
|
|
|
|
Ledger ledgerA = h["a"];
|
|
Ledger ledgerB = h["b"];
|
|
Ledger ledgerC = h["c"];
|
|
|
|
using ID = Ledger::ID;
|
|
using Seq = Ledger::Seq;
|
|
|
|
hash_map<ID, std::uint32_t> peerCounts;
|
|
|
|
// No trusted validations or counts sticks with current ledger
|
|
BEAST_EXPECT(
|
|
harness.vals().getPreferredLCL(ledgerA, Seq{0}, peerCounts) ==
|
|
ledgerA.id());
|
|
|
|
++peerCounts[ledgerB.id()];
|
|
|
|
// No trusted validations, rely on peer counts
|
|
BEAST_EXPECT(
|
|
harness.vals().getPreferredLCL(ledgerA, Seq{0}, peerCounts) ==
|
|
ledgerB.id());
|
|
|
|
++peerCounts[ledgerC.id()];
|
|
// No trusted validations, tied peers goes with larger ID
|
|
BEAST_EXPECT(ledgerC.id() > ledgerB.id());
|
|
|
|
BEAST_EXPECT(
|
|
harness.vals().getPreferredLCL(ledgerA, Seq{0}, peerCounts) ==
|
|
ledgerC.id());
|
|
|
|
peerCounts[ledgerC.id()] += 1000;
|
|
|
|
// Single trusted always wins over peer counts
|
|
BEAST_EXPECT(ValStatus::current == harness.add(a.validate(ledgerA)));
|
|
BEAST_EXPECT(
|
|
harness.vals().getPreferredLCL(ledgerA, Seq{0}, peerCounts) ==
|
|
ledgerA.id());
|
|
BEAST_EXPECT(
|
|
harness.vals().getPreferredLCL(ledgerB, Seq{0}, peerCounts) ==
|
|
ledgerA.id());
|
|
BEAST_EXPECT(
|
|
harness.vals().getPreferredLCL(ledgerC, Seq{0}, peerCounts) ==
|
|
ledgerA.id());
|
|
|
|
// Stick with current ledger if trusted validation ledger has too old
|
|
// of a sequence
|
|
BEAST_EXPECT(
|
|
harness.vals().getPreferredLCL(ledgerB, Seq{2}, peerCounts) ==
|
|
ledgerB.id());
|
|
}
|
|
|
|
void
|
|
testAcquireValidatedLedger()
|
|
{
|
|
using namespace std::chrono_literals;
|
|
testcase("Acquire validated ledger");
|
|
|
|
LedgerHistoryHelper h;
|
|
TestHarness harness(h.oracle);
|
|
Node a = harness.makeNode();
|
|
Node b = harness.makeNode();
|
|
|
|
using ID = Ledger::ID;
|
|
using Seq = Ledger::Seq;
|
|
|
|
// Validate the ledger before it is actually available
|
|
Validation val = a.validate(ID{2}, Seq{2}, 0s, 0s, true);
|
|
|
|
BEAST_EXPECT(ValStatus::current == harness.add(val));
|
|
// Validation is available
|
|
BEAST_EXPECT(harness.vals().numTrustedForLedger(ID{2}) == 1);
|
|
// but ledger based data is not
|
|
BEAST_EXPECT(harness.vals().getNodesAfter(genesisLedger, ID{0}) == 0);
|
|
// Initial preferred branch falls back to the ledger we are trying to
|
|
// acquire
|
|
BEAST_EXPECT(
|
|
harness.vals().getPreferred(genesisLedger) ==
|
|
std::make_pair(Seq{2}, ID{2}));
|
|
|
|
// After adding another unavailable validation, the preferred ledger
|
|
// breaks ties via higher ID
|
|
BEAST_EXPECT(
|
|
ValStatus::current ==
|
|
harness.add(b.validate(ID{3}, Seq{2}, 0s, 0s, true)));
|
|
BEAST_EXPECT(
|
|
harness.vals().getPreferred(genesisLedger) ==
|
|
std::make_pair(Seq{2}, ID{3}));
|
|
|
|
// Create the ledger
|
|
Ledger ledgerAB = h["ab"];
|
|
// Now it should be available
|
|
BEAST_EXPECT(harness.vals().getNodesAfter(genesisLedger, ID{0}) == 1);
|
|
|
|
// Create a validation that is not available
|
|
harness.clock().advance(5s);
|
|
Validation val2 = a.validate(ID{4}, Seq{4}, 0s, 0s, true);
|
|
BEAST_EXPECT(ValStatus::current == harness.add(val2));
|
|
BEAST_EXPECT(harness.vals().numTrustedForLedger(ID{4}) == 1);
|
|
BEAST_EXPECT(
|
|
harness.vals().getPreferred(genesisLedger) ==
|
|
std::make_pair(ledgerAB.seq(), ledgerAB.id()));
|
|
|
|
// Another node requesting that ledger still doesn't change things
|
|
Validation val3 = b.validate(ID{4}, Seq{4}, 0s, 0s, true);
|
|
BEAST_EXPECT(ValStatus::current == harness.add(val3));
|
|
BEAST_EXPECT(harness.vals().numTrustedForLedger(ID{4}) == 2);
|
|
BEAST_EXPECT(
|
|
harness.vals().getPreferred(genesisLedger) ==
|
|
std::make_pair(ledgerAB.seq(), ledgerAB.id()));
|
|
|
|
// Switch to validation that is available
|
|
harness.clock().advance(5s);
|
|
Ledger ledgerABCDE = h["abcde"];
|
|
BEAST_EXPECT(ValStatus::current == harness.add(a.partial(ledgerABCDE)));
|
|
BEAST_EXPECT(ValStatus::current == harness.add(b.partial(ledgerABCDE)));
|
|
BEAST_EXPECT(
|
|
harness.vals().getPreferred(genesisLedger) ==
|
|
std::make_pair(ledgerABCDE.seq(), ledgerABCDE.id()));
|
|
}
|
|
|
|
void
|
|
testNumTrustedForLedger()
|
|
{
|
|
testcase("NumTrustedForLedger");
|
|
LedgerHistoryHelper h;
|
|
TestHarness harness(h.oracle);
|
|
Node a = harness.makeNode();
|
|
Node b = harness.makeNode();
|
|
Ledger ledgerA = h["a"];
|
|
|
|
BEAST_EXPECT(ValStatus::current == harness.add(a.partial(ledgerA)));
|
|
BEAST_EXPECT(harness.vals().numTrustedForLedger(ledgerA.id()) == 0);
|
|
|
|
BEAST_EXPECT(ValStatus::current == harness.add(b.validate(ledgerA)));
|
|
BEAST_EXPECT(harness.vals().numTrustedForLedger(ledgerA.id()) == 1);
|
|
}
|
|
|
|
void
|
|
testSeqEnforcer()
|
|
{
|
|
testcase("SeqEnforcer");
|
|
using Seq = Ledger::Seq;
|
|
using namespace std::chrono;
|
|
|
|
beast::manual_clock<steady_clock> clock;
|
|
SeqEnforcer<Seq> enforcer;
|
|
|
|
ValidationParms p;
|
|
|
|
BEAST_EXPECT(enforcer(clock.now(), Seq{1}, p));
|
|
BEAST_EXPECT(enforcer(clock.now(), Seq{10}, p));
|
|
BEAST_EXPECT(!enforcer(clock.now(), Seq{5}, p));
|
|
BEAST_EXPECT(!enforcer(clock.now(), Seq{9}, p));
|
|
clock.advance(p.validationSET_EXPIRES - 1ms);
|
|
BEAST_EXPECT(!enforcer(clock.now(), Seq{1}, p));
|
|
clock.advance(2ms);
|
|
BEAST_EXPECT(enforcer(clock.now(), Seq{1}, p));
|
|
}
|
|
|
|
void
|
|
testTrustChanged()
|
|
{
|
|
testcase("TrustChanged");
|
|
using namespace std::chrono;
|
|
|
|
auto checker = [this](
|
|
TestValidations& vals,
|
|
hash_set<PeerID> const& listed,
|
|
std::vector<Validation> const& trustedVals) {
|
|
Ledger::ID testID = trustedVals.empty() ? this->genesisLedger.id()
|
|
: trustedVals[0].ledgerID();
|
|
BEAST_EXPECT(vals.currentTrusted() == trustedVals);
|
|
BEAST_EXPECT(vals.getCurrentNodeIDs() == listed);
|
|
BEAST_EXPECT(
|
|
vals.getNodesAfter(this->genesisLedger, genesisLedger.id()) ==
|
|
trustedVals.size());
|
|
BEAST_EXPECT(
|
|
vals.getPreferred(this->genesisLedger).second == testID);
|
|
BEAST_EXPECT(vals.getTrustedForLedger(testID) == trustedVals);
|
|
BEAST_EXPECT(
|
|
vals.numTrustedForLedger(testID) == trustedVals.size());
|
|
};
|
|
|
|
{
|
|
// Trusted to untrusted
|
|
LedgerHistoryHelper h;
|
|
TestHarness harness(h.oracle);
|
|
Node a = harness.makeNode();
|
|
Ledger ledgerAB = h["ab"];
|
|
Validation v = a.validate(ledgerAB);
|
|
BEAST_EXPECT(ValStatus::current == harness.add(v));
|
|
|
|
hash_set<PeerID> listed({a.nodeID()});
|
|
std::vector<Validation> trustedVals({v});
|
|
checker(harness.vals(), listed, trustedVals);
|
|
|
|
trustedVals.clear();
|
|
harness.vals().trustChanged({}, {a.nodeID()});
|
|
checker(harness.vals(), listed, trustedVals);
|
|
}
|
|
|
|
{
|
|
// Untrusted to trusted
|
|
LedgerHistoryHelper h;
|
|
TestHarness harness(h.oracle);
|
|
Node a = harness.makeNode();
|
|
a.untrust();
|
|
Ledger ledgerAB = h["ab"];
|
|
Validation v = a.validate(ledgerAB);
|
|
BEAST_EXPECT(ValStatus::current == harness.add(v));
|
|
|
|
hash_set<PeerID> listed({a.nodeID()});
|
|
std::vector<Validation> trustedVals;
|
|
checker(harness.vals(), listed, trustedVals);
|
|
|
|
trustedVals.push_back(v);
|
|
harness.vals().trustChanged({a.nodeID()}, {});
|
|
checker(harness.vals(), listed, trustedVals);
|
|
}
|
|
|
|
{
|
|
// Trusted but not acquired -> untrusted
|
|
LedgerHistoryHelper h;
|
|
TestHarness harness(h.oracle);
|
|
Node a = harness.makeNode();
|
|
Validation v =
|
|
a.validate(Ledger::ID{2}, Ledger::Seq{2}, 0s, 0s, true);
|
|
BEAST_EXPECT(ValStatus::current == harness.add(v));
|
|
|
|
hash_set<PeerID> listed({a.nodeID()});
|
|
std::vector<Validation> trustedVals({v});
|
|
auto& vals = harness.vals();
|
|
BEAST_EXPECT(vals.currentTrusted() == trustedVals);
|
|
BEAST_EXPECT(
|
|
vals.getPreferred(genesisLedger).second == v.ledgerID());
|
|
BEAST_EXPECT(
|
|
vals.getNodesAfter(genesisLedger, genesisLedger.id()) == 0);
|
|
|
|
trustedVals.clear();
|
|
harness.vals().trustChanged({}, {a.nodeID()});
|
|
// make acquiring ledger available
|
|
h["ab"];
|
|
BEAST_EXPECT(vals.currentTrusted() == trustedVals);
|
|
BEAST_EXPECT(
|
|
vals.getPreferred(genesisLedger).second == genesisLedger.id());
|
|
BEAST_EXPECT(
|
|
vals.getNodesAfter(genesisLedger, genesisLedger.id()) == 0);
|
|
}
|
|
}
|
|
|
|
void
|
|
testCookie()
|
|
{
|
|
testcase("Bad cookie");
|
|
|
|
LedgerHistoryHelper h;
|
|
TestHarness harness(h.oracle);
|
|
Node a = harness.makeNode();
|
|
Node aReuse{a.nodeID(), harness.clock()};
|
|
Node b = harness.makeNode();
|
|
|
|
BEAST_EXPECT(ValStatus::current == harness.add(a.validate(h["a"])));
|
|
BEAST_EXPECT(ValStatus::current == harness.add(b.validate(h["b"])));
|
|
|
|
BEAST_EXPECT(harness.vals().numTrustedForLedger(h["a"].id()) == 1);
|
|
BEAST_EXPECT(harness.vals().numTrustedForLedger(h["b"].id()) == 1);
|
|
BEAST_EXPECT(harness.vals().currentTrusted().size() == 2);
|
|
// Re-issuing for the same ledger gives badCookie status, but does not
|
|
// ignore that ledger
|
|
BEAST_EXPECT(
|
|
ValStatus::badCookie == harness.add(aReuse.validate(h["a"])));
|
|
|
|
BEAST_EXPECT(harness.vals().numTrustedForLedger(h["a"].id()) == 1);
|
|
BEAST_EXPECT(harness.vals().numTrustedForLedger(h["b"].id()) == 1);
|
|
BEAST_EXPECT(harness.vals().currentTrusted().size() == 2);
|
|
|
|
// Re-issuing for a different ledger gives badCookie status and ignores
|
|
// the prior validated ledger
|
|
BEAST_EXPECT(
|
|
ValStatus::badCookie == harness.add(aReuse.validate(h["b"])));
|
|
|
|
BEAST_EXPECT(harness.vals().numTrustedForLedger(h["a"].id()) == 0);
|
|
BEAST_EXPECT(harness.vals().numTrustedForLedger(h["b"].id()) == 1);
|
|
BEAST_EXPECT(harness.vals().currentTrusted().size() == 1);
|
|
|
|
BEAST_EXPECT(
|
|
ValStatus::badCookie == harness.add(aReuse.validate(h["b"])));
|
|
}
|
|
|
|
void
|
|
run() override
|
|
{
|
|
testAddValidation();
|
|
testOnStale();
|
|
testGetNodesAfter();
|
|
testCurrentTrusted();
|
|
testGetCurrentPublicKeys();
|
|
testTrustedByLedgerFunctions();
|
|
testExpire();
|
|
testFlush();
|
|
testGetPreferredLedger();
|
|
testGetPreferredLCL();
|
|
testAcquireValidatedLedger();
|
|
testNumTrustedForLedger();
|
|
testSeqEnforcer();
|
|
testTrustChanged();
|
|
testCookie();
|
|
}
|
|
};
|
|
|
|
BEAST_DEFINE_TESTSUITE(Validations, consensus, ripple);
|
|
} // namespace csf
|
|
} // namespace test
|
|
} // namespace ripple
|