Increase validation quorum to 80%

All listed validators are trusted and quorum is 80% of trusted
validators regardless of the number of:
* configured published lists
* listed or trusted validators
* recently seen validators

Exceptions:
* A listed validator whose master key has been revoked is not trusted
* Custom minimum quorum (specified with --quorum in the command line)
  is used if the normal quorum appears unreachable based on the number
  of recently received validators.

RIPD-1640
This commit is contained in:
wilsonianb
2018-06-28 16:11:30 -05:00
committed by Nik Bougalis
parent cff1abba5d
commit 7e30897ef4
4 changed files with 139 additions and 249 deletions

View File

@@ -47,6 +47,12 @@
# The default validator list publishers that the rippled instance # The default validator list publishers that the rippled instance
# trusts. # trusts.
#
# WARNING: Changing these values can cause your rippled instance to see a
# validated ledger that contradicts other rippled instances'
# validated ledgers (aka a ledger fork) if your validator list(s)
# do not sufficiently overlap with the list(s) used by others.
# See: https://arxiv.org/pdf/1802.07242.pdf
[validator_list_sites] [validator_list_sites]
https://vl.ripple.com https://vl.ripple.com

View File

@@ -148,17 +148,6 @@ class ValidatorList
// Currently supported version of publisher list format // Currently supported version of publisher list format
static constexpr std::uint32_t requiredListVersion = 1; static constexpr std::uint32_t requiredListVersion = 1;
// The minimum number of listed validators required to allow removing
// non-communicative validators from the trusted set. In other words, if the
// number of listed validators is less, then use all of them in the
// trusted set.
std::size_t const MINIMUM_RESIZEABLE_UNL {25};
// The maximum size of a trusted set for which greater than Byzantine fault
// tolerance isn't needed.
std::size_t const BYZANTINE_THRESHOLD {32};
public: public:
ValidatorList ( ValidatorList (
ManifestCache& validatorManifests, ManifestCache& validatorManifests,
@@ -393,15 +382,15 @@ private:
bool bool
removePublisherList (PublicKey const& publisherKey); removePublisherList (PublicKey const& publisherKey);
/** Return safe minimum quorum for listed validator set /** Return quorum for trusted validator set
@param nListedKeys Number of list validator keys @param trusted Number of trusted validator keys
@param unListedLocal Whether the local node is an unlisted validator @param seen Number of trusted validators that have signed
*/ recently received validations */
static std::size_t std::size_t
calculateMinimumQuorum ( calculateQuorum (
std::size_t nListedKeys, bool unlistedLocal=false); std::size_t trusted, std::size_t seen);
}; };
} // ripple } // ripple

View File

@@ -436,8 +436,7 @@ ValidatorList::removePublisherList (PublicKey const& publisherKey)
return false; return false;
JLOG (j_.debug()) << JLOG (j_.debug()) <<
"Removing validator list for revoked publisher " << "Removing validator list for publisher " << strHex(publisherKey);
toBase58(TokenType::NodePublic, publisherKey);
for (auto const& val : iList->second.list) for (auto const& val : iList->second.list)
{ {
@@ -569,33 +568,62 @@ ValidatorList::for_each_listed (
} }
std::size_t std::size_t
ValidatorList::calculateMinimumQuorum ( ValidatorList::calculateQuorum (
std::size_t nListedKeys, bool unlistedLocal) std::size_t trusted, std::size_t seen)
{ {
// Only require 51% quorum for small number of validators to facilitate // Do not use achievable quorum until lists from all configured
// bootstrapping a network. // publishers are available
if (nListedKeys <= 6) for (auto const& list : publisherLists_)
return nListedKeys/2 + 1; {
if (! list.second.available)
return std::numeric_limits<std::size_t>::max();
}
// The number of listed validators is increased to preserve the safety // Use an 80% quorum to balance fork safety, liveness, and required UNL
// guarantee for two unlisted validators using the same set of listed // overlap.
// validators. //
if (unlistedLocal) // Theorem 8 of the Analysis of the XRP Ledger Consensus Protocol
++nListedKeys; // (https://arxiv.org/abs/1802.07242) says:
// XRP LCP guarantees fork safety if Oi,j > nj/2 + ni qi + ti,j for
// every pair of nodes Pi, Pj.
//
// ni: size of Pi's UNL
// nj: size of Pj's UNL
// Oi,j: number of validators in both UNLs
// qi: validation quorum for Pi's UNL
// ti, tj: maximum number of allowed Byzantine faults in Pi and Pj's UNLs
// ti,j: min{ti, tj, Oi,j}
//
// Assume ni < nj, meaning and ti,j = ti
//
// For qi = .8*ni, we make ti <= .2*ni
// (We could make ti lower and tolerate less UNL overlap. However in order
// to prioritize safety over liveness, we need ti >= ni - qi)
//
// An 80% quorum allows two UNLs to safely have < .2*ni unique validators
// between them:
//
// pi = ni - Oi,j
// pj = nj - Oi,j
//
// Oi,j > nj/2 + ni qi + ti,j
// ni - pi > (ni - pi + pj)/2 + ni .8*ni + .2*ni
// pi + pj < .2*ni
auto quorum = static_cast<std::size_t>(std::ceil(trusted * 0.8f));
// Guarantee safety with up to 1/3 listed validators being malicious. // Use lower quorum specified via command line if the normal quorum appears
// This prioritizes safety (Byzantine fault tolerance) over liveness. // unreachable based on the number of recently received validations.
// It takes at least as many malicious nodes to split/fork the network as if (minimumQuorum_ && *minimumQuorum_ < quorum && seen < quorum)
// to stall the network. {
// At 67%, the overlap of two quorums is 34% quorum = *minimumQuorum_;
// 67 + 67 - 100 = 34
// So under certain conditions, 34% of validators could vote for two JLOG (j_.warn())
// different ledgers and split the network. << "Using unsafe quorum of "
// Similarly 34% could prevent quorum from being met (by not voting) and << quorum
// stall the network. << " as specified in the command line";
// If/when the quorum is subsequently raised to/towards 80%, it becomes }
// harder to split the network (more safe) and easier to stall it (less live).
return nListedKeys * 2/3 + 1; return quorum;
} }
TrustChanges TrustChanges
@@ -603,120 +631,43 @@ ValidatorList::updateTrusted(hash_set<NodeID> const& seenValidators)
{ {
boost::unique_lock<boost::shared_mutex> lock{mutex_}; boost::unique_lock<boost::shared_mutex> lock{mutex_};
// Check that lists from all configured publishers are available // Remove any expired published lists
bool allListsAvailable = true;
for (auto const& list : publisherLists_) for (auto const& list : publisherLists_)
{ {
// Remove any expired published lists if (list.second.available &&
if (TimeKeeper::time_point{} < list.second.expiration &&
list.second.expiration <= timeKeeper_.now()) list.second.expiration <= timeKeeper_.now())
removePublisherList(list.first); removePublisherList(list.first);
if (! list.second.available)
allListsAvailable = false;
} }
std::multimap<std::size_t, PublicKey> rankedKeys; TrustChanges trustChanges;
bool localKeyListed = false;
// "Iterate" the listed keys in random order so that the rank of multiple auto it = trustedKeys_.cbegin();
// keys with the same number of listings is not deterministic while (it != trustedKeys_.cend())
std::vector<std::size_t> indexes (keyListings_.size());
std::iota (indexes.begin(), indexes.end(), 0);
std::shuffle (indexes.begin(), indexes.end(), crypto_prng());
for (auto const& index : indexes)
{ {
auto const& val = std::next (keyListings_.begin(), index); if (! keyListings_.count(*it) ||
validatorManifests_.revoked(*it))
if (validatorManifests_.revoked (val->first))
continue;
if (val->first == localPubKey_)
{ {
localKeyListed = val->second > 1; trustChanges.removed.insert(calcNodeID(*it));
rankedKeys.insert ( it = trustedKeys_.erase(it);
std::pair<std::size_t,PublicKey>(
std::numeric_limits<std::size_t>::max(), localPubKey_));
}
// If the total number of validators is too small, or
// no validations are being received, use all validators.
// Otherwise, do not use validators whose validations aren't
// being received.
else if (
keyListings_.size() < MINIMUM_RESIZEABLE_UNL ||
seenValidators.empty() ||
seenValidators.find(calcNodeID(val->first)) != seenValidators.end())
{
rankedKeys.insert (
std::pair<std::size_t,PublicKey>(val->second, val->first));
}
}
// This minimum quorum guarantees safe overlap with the trusted sets of
// other nodes using the same set of published lists.
std::size_t quorum = calculateMinimumQuorum (keyListings_.size(),
localPubKey_.size() && !localKeyListed);
JLOG (j_.debug()) <<
rankedKeys.size() << " of " << keyListings_.size() <<
" listed validators eligible for inclusion in the trusted set";
auto size = rankedKeys.size();
// Require 80% quorum if there are lots of validators.
if (rankedKeys.size() > BYZANTINE_THRESHOLD)
{
// Use all eligible keys if there is only one trusted list
if (publisherLists_.size() == 1 ||
keyListings_.size() < MINIMUM_RESIZEABLE_UNL)
{
// Try to raise the quorum to at least 80% of the trusted set
quorum = std::max(quorum, size - size / 5);
} }
else else
{ {
// Reduce the trusted set size so that the quorum represents ++it;
// at least 80%
size = quorum * 1.25;
} }
} }
if (minimumQuorum_ && seenValidators.size() < quorum) for (auto const& val : keyListings_)
{ {
quorum = *minimumQuorum_; if (! validatorManifests_.revoked(val.first) &&
JLOG (j_.warn()) trustedKeys_.emplace(val.first).second)
<< "Using unsafe quorum of " trustChanges.added.insert(calcNodeID(val.first));
<< quorum_
<< " as specified in the command line";
} }
// Do not use achievable quorum until lists from all configured JLOG (j_.debug()) <<
// publishers are available trustedKeys_.size() << " of " << keyListings_.size() <<
else if (! allListsAvailable) " listed validators eligible for inclusion in the trusted set";
quorum = std::numeric_limits<std::size_t>::max();
TrustChanges trustChanges;
{
hash_set<PublicKey> newTrustedKeys;
for (auto const& val : boost::adaptors::reverse(rankedKeys))
{
if (size <= newTrustedKeys.size())
break;
newTrustedKeys.insert(val.second);
if (trustedKeys_.erase(val.second) == 0)
trustChanges.added.insert(calcNodeID(val.second));
}
for (auto const& k : trustedKeys_)
trustChanges.removed.insert(calcNodeID(k));
trustedKeys_ = std::move(newTrustedKeys);
}
quorum_ = quorum;
quorum_ = calculateQuorum (trustedKeys_.size(), seenValidators.size());
JLOG(j_.debug()) << "Using quorum of " << quorum_ << " for new set of " JLOG(j_.debug()) << "Using quorum of " << quorum_ << " for new set of "
<< trustedKeys_.size() << " trusted validators (" << trustedKeys_.size() << " trusted validators ("

View File

@@ -531,14 +531,12 @@ private:
std::vector<std::string> cfgPublishers; std::vector<std::string> cfgPublishers;
hash_set<NodeID> activeValidators; hash_set<NodeID> activeValidators;
hash_set<NodeID> secondAddedValidators;
// BFT: n >= 3f+1
std::size_t const n = 40; std::size_t const n = 40;
std::size_t const f = 13;
{ {
std::vector<std::string> cfgKeys; std::vector<std::string> cfgKeys;
cfgKeys.reserve(n); cfgKeys.reserve(n);
hash_set<NodeID> unseenValidators;
while (cfgKeys.size () != n) while (cfgKeys.size () != n)
{ {
@@ -547,52 +545,43 @@ private:
TokenType::NodePublic, valKey)); TokenType::NodePublic, valKey));
if (cfgKeys.size () <= n - 5) if (cfgKeys.size () <= n - 5)
activeValidators.emplace (calcNodeID(valKey)); activeValidators.emplace (calcNodeID(valKey));
else
unseenValidators.emplace (calcNodeID(valKey));
} }
BEAST_EXPECT(trustedKeys->load ( BEAST_EXPECT(trustedKeys->load (
emptyLocalKey, cfgKeys, cfgPublishers)); emptyLocalKey, cfgKeys, cfgPublishers));
// updateTrusted should make all available configured // updateTrusted should make all configured validators trusted
// validators trusted // even if they are not active/seen
TrustChanges changes = TrustChanges changes =
trustedKeys->updateTrusted(activeValidators); trustedKeys->updateTrusted(activeValidators);
for (auto const& val : unseenValidators)
activeValidators.emplace (val);
BEAST_EXPECT(changes.added == activeValidators); BEAST_EXPECT(changes.added == activeValidators);
BEAST_EXPECT(changes.removed.empty()); BEAST_EXPECT(changes.removed.empty());
// Add 1 to n because I'm not on a published list. BEAST_EXPECT(trustedKeys->quorum () ==
BEAST_EXPECT(trustedKeys->quorum () == n + 1 - f); std::ceil(cfgKeys.size() * 0.8f));
std::size_t i = 0;
for (auto const& val : cfgKeys) for (auto const& val : cfgKeys)
{ {
if (auto const valKey = parseBase58<PublicKey>( if (auto const valKey = parseBase58<PublicKey>(
TokenType::NodePublic, val)) TokenType::NodePublic, val))
{ {
BEAST_EXPECT(trustedKeys->listed (*valKey)); BEAST_EXPECT(trustedKeys->listed (*valKey));
if (i++ < activeValidators.size ()) BEAST_EXPECT(trustedKeys->trusted (*valKey));
BEAST_EXPECT(trustedKeys->trusted (*valKey));
else
BEAST_EXPECT(!trustedKeys->trusted (*valKey));
} }
else else
fail (); fail ();
} }
{ changes =
// Quorum should be 80% with all listed validators active trustedKeys->updateTrusted(activeValidators);
hash_set<NodeID> activeValidatorsNew{activeValidators}; BEAST_EXPECT(changes.added.empty());
for (auto const valKey : cfgKeys) BEAST_EXPECT(changes.removed.empty());
{ BEAST_EXPECT(trustedKeys->quorum () ==
auto const ins = activeValidatorsNew.emplace( std::ceil(cfgKeys.size() * 0.8f));
calcNodeID(*parseBase58<PublicKey>(
TokenType::NodePublic, valKey)));
if(ins.second)
secondAddedValidators.insert(*ins.first);
}
TrustChanges changes =
trustedKeys->updateTrusted(activeValidatorsNew);
BEAST_EXPECT(changes.added == secondAddedValidators);
BEAST_EXPECT(changes.removed.empty());
BEAST_EXPECT(trustedKeys->quorum () == cfgKeys.size() * 4/5);
}
} }
{ {
// update with manifests // update with manifests
@@ -614,7 +603,8 @@ private:
TrustChanges changes = TrustChanges changes =
trustedKeys->updateTrusted(activeValidators); trustedKeys->updateTrusted(activeValidators);
BEAST_EXPECT(changes.added == asNodeIDs({masterPublic})); BEAST_EXPECT(changes.added == asNodeIDs({masterPublic}));
BEAST_EXPECT(changes.removed == secondAddedValidators); BEAST_EXPECT(changes.removed.empty());
BEAST_EXPECT(trustedKeys->quorum () == std::ceil((n + 1) * 0.8f));
BEAST_EXPECT(trustedKeys->listed (masterPublic)); BEAST_EXPECT(trustedKeys->listed (masterPublic));
BEAST_EXPECT(trustedKeys->trusted (masterPublic)); BEAST_EXPECT(trustedKeys->trusted (masterPublic));
BEAST_EXPECT(!trustedKeys->listed (signingPublic1)); BEAST_EXPECT(!trustedKeys->listed (signingPublic1));
@@ -628,10 +618,6 @@ private:
BEAST_EXPECT( BEAST_EXPECT(
manifests.applyManifest(std::move (*m1)) == manifests.applyManifest(std::move (*m1)) ==
ManifestDisposition::accepted); ManifestDisposition::accepted);
changes = trustedKeys->updateTrusted(activeValidators);
BEAST_EXPECT(changes.removed.empty());
BEAST_EXPECT(changes.added.empty());
BEAST_EXPECT(trustedKeys->quorum () == n + 2 - f);
BEAST_EXPECT(trustedKeys->listed (masterPublic)); BEAST_EXPECT(trustedKeys->listed (masterPublic));
BEAST_EXPECT(trustedKeys->trusted (masterPublic)); BEAST_EXPECT(trustedKeys->trusted (masterPublic));
BEAST_EXPECT(trustedKeys->listed (signingPublic1)); BEAST_EXPECT(trustedKeys->listed (signingPublic1));
@@ -647,10 +633,6 @@ private:
BEAST_EXPECT( BEAST_EXPECT(
manifests.applyManifest(std::move (*m2)) == manifests.applyManifest(std::move (*m2)) ==
ManifestDisposition::accepted); ManifestDisposition::accepted);
changes = trustedKeys->updateTrusted (activeValidators);
BEAST_EXPECT(changes.removed.empty());
BEAST_EXPECT(changes.added.empty());
BEAST_EXPECT(trustedKeys->quorum () == n + 2 - f);
BEAST_EXPECT(trustedKeys->listed (masterPublic)); BEAST_EXPECT(trustedKeys->listed (masterPublic));
BEAST_EXPECT(trustedKeys->trusted (masterPublic)); BEAST_EXPECT(trustedKeys->trusted (masterPublic));
BEAST_EXPECT(trustedKeys->listed (signingPublic2)); BEAST_EXPECT(trustedKeys->listed (signingPublic2));
@@ -673,10 +655,15 @@ private:
ManifestDisposition::accepted); ManifestDisposition::accepted);
BEAST_EXPECT(manifests.getSigningKey (masterPublic) == masterPublic); BEAST_EXPECT(manifests.getSigningKey (masterPublic) == masterPublic);
BEAST_EXPECT(manifests.revoked (masterPublic)); BEAST_EXPECT(manifests.revoked (masterPublic));
// Revoked key remains trusted until list is updated
BEAST_EXPECT(trustedKeys->listed (masterPublic));
BEAST_EXPECT(trustedKeys->trusted (masterPublic));
changes = trustedKeys->updateTrusted (activeValidators); changes = trustedKeys->updateTrusted (activeValidators);
BEAST_EXPECT(changes.removed == asNodeIDs({masterPublic})); BEAST_EXPECT(changes.removed == asNodeIDs({masterPublic}));
BEAST_EXPECT(changes.added.empty()); BEAST_EXPECT(changes.added.empty());
BEAST_EXPECT(trustedKeys->quorum () == n + 1 - f); BEAST_EXPECT(trustedKeys->quorum () == std::ceil(n * 0.8f));
BEAST_EXPECT(trustedKeys->listed (masterPublic)); BEAST_EXPECT(trustedKeys->listed (masterPublic));
BEAST_EXPECT(!trustedKeys->trusted (masterPublic)); BEAST_EXPECT(!trustedKeys->trusted (masterPublic));
BEAST_EXPECT(!trustedKeys->listed (signingPublicMax)); BEAST_EXPECT(!trustedKeys->listed (signingPublicMax));
@@ -708,29 +695,6 @@ private:
BEAST_EXPECT(trustedKeys->quorum () == BEAST_EXPECT(trustedKeys->quorum () ==
std::numeric_limits<std::size_t>::max()); std::numeric_limits<std::size_t>::max());
} }
{
// Trust all listed validators if none are active
auto trustedKeys = std::make_unique <ValidatorList> (
manifests, manifests, env.timeKeeper(), beast::Journal ());
std::vector<PublicKey> keys ({ randomNode (), randomNode () });
hash_set<NodeID> activeValidators;
std::vector<std::string> cfgKeys ({
toBase58 (TokenType::NodePublic, keys[0]),
toBase58 (TokenType::NodePublic, keys[1])});
BEAST_EXPECT(trustedKeys->load (
emptyLocalKey, cfgKeys, cfgPublishers));
TrustChanges changes =
trustedKeys->updateTrusted(activeValidators);
BEAST_EXPECT(changes.removed.empty());
BEAST_EXPECT(changes.added == asNodeIDs({keys[0], keys[1]}));
BEAST_EXPECT(trustedKeys->quorum () == 2);
for (auto const& key : keys)
BEAST_EXPECT(trustedKeys->trusted (key));
}
{ {
// Should use custom minimum quorum // Should use custom minimum quorum
std::size_t const minQuorum = 1; std::size_t const minQuorum = 1;
@@ -738,10 +702,24 @@ private:
auto trustedKeys = std::make_unique <ValidatorList> ( auto trustedKeys = std::make_unique <ValidatorList> (
manifests, manifests, env.timeKeeper(), beast::Journal (), minQuorum); manifests, manifests, env.timeKeeper(), beast::Journal (), minQuorum);
auto const node = randomNode (); std::size_t n = 10;
std::vector<std::string> cfgKeys ({ std::vector<std::string> cfgKeys;
toBase58 (TokenType::NodePublic, node)}); cfgKeys.reserve(n);
hash_set<NodeID> expectedTrusted;
hash_set<NodeID> activeValidators; hash_set<NodeID> activeValidators;
NodeID toBeSeen;
while (cfgKeys.size () < n)
{
auto const valKey = randomNode();
cfgKeys.push_back (toBase58(
TokenType::NodePublic, valKey));
expectedTrusted.emplace (calcNodeID(valKey));
if (cfgKeys.size () < std::ceil(n*0.8f))
activeValidators.emplace (calcNodeID(valKey));
else if (cfgKeys.size () < std::ceil(n*0.8f))
toBeSeen = calcNodeID(valKey);
}
BEAST_EXPECT(trustedKeys->load ( BEAST_EXPECT(trustedKeys->load (
emptyLocalKey, cfgKeys, cfgPublishers)); emptyLocalKey, cfgKeys, cfgPublishers));
@@ -749,39 +727,15 @@ private:
TrustChanges changes = TrustChanges changes =
trustedKeys->updateTrusted(activeValidators); trustedKeys->updateTrusted(activeValidators);
BEAST_EXPECT(changes.removed.empty()); BEAST_EXPECT(changes.removed.empty());
BEAST_EXPECT(changes.added == asNodeIDs({node})); BEAST_EXPECT(changes.added == expectedTrusted);
BEAST_EXPECT(trustedKeys->quorum () == minQuorum); BEAST_EXPECT(trustedKeys->quorum () == minQuorum);
activeValidators.emplace (calcNodeID(node)); // Use normal quorum when seen validators >= quorum
activeValidators.emplace (toBeSeen);
changes = trustedKeys->updateTrusted(activeValidators); changes = trustedKeys->updateTrusted(activeValidators);
BEAST_EXPECT(changes.removed.empty()); BEAST_EXPECT(changes.removed.empty());
BEAST_EXPECT(changes.added.empty()); BEAST_EXPECT(changes.added.empty());
BEAST_EXPECT(trustedKeys->quorum () == 1); BEAST_EXPECT(trustedKeys->quorum () == std::ceil(n * 0.8f));
}
{
// Increase quorum when running as an unlisted validator
auto trustedKeys = std::make_unique <ValidatorList> (
manifests, manifests, env.timeKeeper(), beast::Journal ());
std::vector<PublicKey> keys ({ randomNode (), randomNode () });
hash_set<NodeID> activeValidators (asNodeIDs({ keys[0] }));
std::vector<std::string> cfgKeys ({
toBase58 (TokenType::NodePublic, keys[0]),
toBase58 (TokenType::NodePublic, keys[1])});
auto const localKey = randomNode ();
BEAST_EXPECT(trustedKeys->load (
localKey, cfgKeys, cfgPublishers));
TrustChanges changes =
trustedKeys->updateTrusted(activeValidators);
BEAST_EXPECT(changes.removed.empty());
BEAST_EXPECT(
changes.added == asNodeIDs({keys[0], keys[1], localKey}));
BEAST_EXPECT(trustedKeys->quorum () == 2);
// local validator key is always trusted
BEAST_EXPECT(trustedKeys->trusted (localKey));
} }
{ {
// Remove expired published list // Remove expired published list
@@ -894,8 +848,7 @@ private:
BEAST_EXPECT(changes.removed.empty()); BEAST_EXPECT(changes.removed.empty());
BEAST_EXPECT(changes.added == asNodeIDs({valKey})); BEAST_EXPECT(changes.added == asNodeIDs({valKey}));
BEAST_EXPECT(trustedKeys->quorum () == BEAST_EXPECT(trustedKeys->quorum () ==
((cfgKeys.size() <= 6) ? cfgKeys.size()/2 + 1 : std::ceil(cfgKeys.size() * 0.8f));
cfgKeys.size() * 2/3 + 1));
for (auto const& key : activeKeys) for (auto const& key : activeKeys)
BEAST_EXPECT(trustedKeys->trusted (key)); BEAST_EXPECT(trustedKeys->trusted (key));
} }
@@ -933,15 +886,14 @@ private:
changes.added == asNodeIDs({localKey, valKey})); changes.added == asNodeIDs({localKey, valKey}));
BEAST_EXPECT(trustedKeys->quorum () == BEAST_EXPECT(trustedKeys->quorum () ==
((cfgKeys.size() <= 6) ? cfgKeys.size()/2 + 1 : std::ceil(cfgKeys.size() * 0.8f));
(cfgKeys.size() + 1) * 2/3 + 1));
for (auto const& key : activeKeys) for (auto const& key : activeKeys)
BEAST_EXPECT(trustedKeys->trusted (key)); BEAST_EXPECT(trustedKeys->trusted (key));
} }
} }
{ {
// Trusted set should be trimmed with multiple validator lists // Trusted set should include all validators from multiple lists
ManifestCache manifests; ManifestCache manifests;
auto trustedKeys = std::make_unique <ValidatorList> ( auto trustedKeys = std::make_unique <ValidatorList> (
manifests, manifests, env.timeKeeper(), beast::Journal ()); manifests, manifests, env.timeKeeper(), beast::Journal ());
@@ -994,25 +946,17 @@ private:
TrustChanges changes = TrustChanges changes =
trustedKeys->updateTrusted(activeValidators); trustedKeys->updateTrusted(activeValidators);
// Minimum quorum should be used BEAST_EXPECT(trustedKeys->quorum () ==
BEAST_EXPECT(trustedKeys->quorum () == (valKeys.size() * 2/3 + 1)); std::ceil(valKeys.size() * 0.8f));
hash_set<NodeID> added; hash_set<NodeID> added;
std::size_t nTrusted = 0;
for (auto const& val : valKeys) for (auto const& val : valKeys)
{ {
if (trustedKeys->trusted (val.masterPublic)) BEAST_EXPECT(trustedKeys->trusted (val.masterPublic));
{ added.insert(calcNodeID(val.masterPublic));
added.insert(calcNodeID(val.masterPublic));
++nTrusted;
}
} }
BEAST_EXPECT(changes.added == added); BEAST_EXPECT(changes.added == added);
BEAST_EXPECT(changes.removed.empty()); BEAST_EXPECT(changes.removed.empty());
// The number of trusted keys should be 125% of the minimum quorum
BEAST_EXPECT(nTrusted ==
static_cast<std::size_t>(trustedKeys->quorum () * 5 / 4));
} }
} }