mirror of
https://github.com/XRPLF/rippled.git
synced 2025-12-06 17:27:55 +00:00
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:
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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 ("
|
||||||
|
|||||||
@@ -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));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user