mirror of
https://github.com/XRPLF/rippled.git
synced 2025-11-04 11:15:56 +00:00
Add [validator_list_threshold] to validators.txt to improve UNL security (#5112)
This commit is contained in:
@@ -87,9 +87,10 @@ The `network_id` field was added in the `server_info` response in version 1.5.0
|
||||
|
||||
As of 2025-01-23, version 2.4.0 is in development. You can use a pre-release version by building from source or [using the `nightly` package](https://xrpl.org/docs/infrastructure/installation/install-rippled-on-ubuntu).
|
||||
|
||||
### Addition in 2.4
|
||||
### Additions and bugfixes in 2.4.0
|
||||
|
||||
- `ledger_entry`: `state` is added an alias for `ripple_state`.
|
||||
- `validators`: Added new field `validator_list_threshold` in response.
|
||||
|
||||
## XRP Ledger server version 2.3.0
|
||||
|
||||
|
||||
@@ -70,3 +70,21 @@ ED45D1840EE724BE327ABE9146503D5848EFD5F38B6D5FEDE71E80ACCE5E6E738B
|
||||
#
|
||||
# [validator_list_keys]
|
||||
# ED264807102805220DA0F312E71FC2C69E1552C9C5790F6C25E3729DEB573D5860
|
||||
|
||||
|
||||
# [validator_list_threshold]
|
||||
#
|
||||
# Minimum number of validator lists on which a validator must be listed in
|
||||
# order to be used.
|
||||
#
|
||||
# This can be set explicitly to any positive integer number not greater than
|
||||
# the size of [validator_list_keys]. If it is not set, or set to 0, the
|
||||
# value will be calculated at startup from the size of [validator_list_keys],
|
||||
# where the calculation is:
|
||||
#
|
||||
# threshold = size(validator_list_keys) < 3
|
||||
# ? 1
|
||||
# : floor(size(validator_list_keys) / 2) + 1
|
||||
|
||||
[validator_list_threshold]
|
||||
0
|
||||
|
||||
@@ -664,27 +664,28 @@ JSS(validated); // out: NetworkOPs, RPCHelpers, AccountTx*
|
||||
JSS(validator_list_expires); // out: NetworkOps, ValidatorList
|
||||
JSS(validator_list); // out: NetworkOps, ValidatorList
|
||||
JSS(validators);
|
||||
JSS(validated_hash); // out: NetworkOPs
|
||||
JSS(validated_ledger); // out: NetworkOPs
|
||||
JSS(validated_ledger_index); // out: SubmitTransaction
|
||||
JSS(validated_ledgers); // out: NetworkOPs
|
||||
JSS(validation_key); // out: ValidationCreate, ValidationSeed
|
||||
JSS(validation_private_key); // out: ValidationCreate
|
||||
JSS(validation_public_key); // out: ValidationCreate, ValidationSeed
|
||||
JSS(validation_quorum); // out: NetworkOPs
|
||||
JSS(validation_seed); // out: ValidationCreate, ValidationSeed
|
||||
JSS(validations); // out: AmendmentTableImpl
|
||||
JSS(validator_sites); // out: ValidatorSites
|
||||
JSS(value); // out: STAmount
|
||||
JSS(version); // out: RPCVersion
|
||||
JSS(vetoed); // out: AmendmentTableImpl
|
||||
JSS(volume_a); // out: BookChanges
|
||||
JSS(volume_b); // out: BookChanges
|
||||
JSS(vote); // in: Feature
|
||||
JSS(vote_slots); // out: amm_info
|
||||
JSS(vote_weight); // out: amm_info
|
||||
JSS(warning); // rpc:
|
||||
JSS(warnings); // out: server_info, server_state
|
||||
JSS(validated_hash); // out: NetworkOPs
|
||||
JSS(validated_ledger); // out: NetworkOPs
|
||||
JSS(validated_ledger_index); // out: SubmitTransaction
|
||||
JSS(validated_ledgers); // out: NetworkOPs
|
||||
JSS(validation_key); // out: ValidationCreate, ValidationSeed
|
||||
JSS(validation_private_key); // out: ValidationCreate
|
||||
JSS(validation_public_key); // out: ValidationCreate, ValidationSeed
|
||||
JSS(validation_quorum); // out: NetworkOPs
|
||||
JSS(validation_seed); // out: ValidationCreate, ValidationSeed
|
||||
JSS(validations); // out: AmendmentTableImpl
|
||||
JSS(validator_list_threshold); // out: ValidatorList
|
||||
JSS(validator_sites); // out: ValidatorSites
|
||||
JSS(value); // out: STAmount
|
||||
JSS(version); // out: RPCVersion
|
||||
JSS(vetoed); // out: AmendmentTableImpl
|
||||
JSS(volume_a); // out: BookChanges
|
||||
JSS(volume_b); // out: BookChanges
|
||||
JSS(vote); // in: Feature
|
||||
JSS(vote_slots); // out: amm_info
|
||||
JSS(vote_weight); // out: amm_info
|
||||
JSS(warning); // rpc:
|
||||
JSS(warnings); // out: server_info, server_state
|
||||
JSS(workers);
|
||||
JSS(write_load); // out: GetCounts
|
||||
// clang-format on
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -22,6 +22,7 @@
|
||||
#include <xrpld/core/Config.h>
|
||||
#include <xrpld/core/ConfigSections.h>
|
||||
#include <xrpl/basics/contract.h>
|
||||
#include <xrpl/beast/unit_test/suite.h>
|
||||
#include <xrpl/server/Port.h>
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/format.hpp>
|
||||
@@ -222,6 +223,9 @@ moreripplevalidators.net
|
||||
[validator_list_keys]
|
||||
03E74EE14CB525AFBB9F1B7D86CD58ECC4B91452294B42AB4E78F260BD905C091D
|
||||
030775A669685BD6ABCEBD80385921C7851783D991A8055FD21D2F3966C96F1B56
|
||||
|
||||
[validator_list_threshold]
|
||||
2
|
||||
)rippleConfig");
|
||||
return configContents;
|
||||
}
|
||||
@@ -538,6 +542,7 @@ nHBu9PTL9dn2GuZtdW4U2WzBwffyX9qsQCd9CNU4Z5YG3PQfViM8
|
||||
c.loadFromString(toLoad);
|
||||
BEAST_EXPECT(c.legacy("validators_file").empty());
|
||||
BEAST_EXPECT(c.section(SECTION_VALIDATORS).values().size() == 5);
|
||||
BEAST_EXPECT(c.VALIDATOR_LIST_THRESHOLD == std::nullopt);
|
||||
}
|
||||
{
|
||||
// load validator list sites and keys from config
|
||||
@@ -549,6 +554,9 @@ trustthesevalidators.gov
|
||||
|
||||
[validator_list_keys]
|
||||
021A99A537FDEBC34E4FCA03B39BEADD04299BB19E85097EC92B15A3518801E566
|
||||
|
||||
[validator_list_threshold]
|
||||
1
|
||||
)rippleConfig");
|
||||
c.loadFromString(toLoad);
|
||||
BEAST_EXPECT(
|
||||
@@ -565,6 +573,133 @@ trustthesevalidators.gov
|
||||
c.section(SECTION_VALIDATOR_LIST_KEYS).values()[0] ==
|
||||
"021A99A537FDEBC34E4FCA03B39BEADD04299BB19E85097EC92B15A3518801"
|
||||
"E566");
|
||||
BEAST_EXPECT(
|
||||
c.section(SECTION_VALIDATOR_LIST_THRESHOLD).values().size() ==
|
||||
1);
|
||||
BEAST_EXPECT(
|
||||
c.section(SECTION_VALIDATOR_LIST_THRESHOLD).values()[0] == "1");
|
||||
BEAST_EXPECT(c.VALIDATOR_LIST_THRESHOLD == std::size_t(1));
|
||||
}
|
||||
{
|
||||
// load validator list sites and keys from config
|
||||
Config c;
|
||||
std::string toLoad(R"rippleConfig(
|
||||
[validator_list_sites]
|
||||
ripplevalidators.com
|
||||
trustthesevalidators.gov
|
||||
|
||||
[validator_list_keys]
|
||||
021A99A537FDEBC34E4FCA03B39BEADD04299BB19E85097EC92B15A3518801E566
|
||||
|
||||
[validator_list_threshold]
|
||||
0
|
||||
)rippleConfig");
|
||||
c.loadFromString(toLoad);
|
||||
BEAST_EXPECT(
|
||||
c.section(SECTION_VALIDATOR_LIST_SITES).values().size() == 2);
|
||||
BEAST_EXPECT(
|
||||
c.section(SECTION_VALIDATOR_LIST_SITES).values()[0] ==
|
||||
"ripplevalidators.com");
|
||||
BEAST_EXPECT(
|
||||
c.section(SECTION_VALIDATOR_LIST_SITES).values()[1] ==
|
||||
"trustthesevalidators.gov");
|
||||
BEAST_EXPECT(
|
||||
c.section(SECTION_VALIDATOR_LIST_KEYS).values().size() == 1);
|
||||
BEAST_EXPECT(
|
||||
c.section(SECTION_VALIDATOR_LIST_KEYS).values()[0] ==
|
||||
"021A99A537FDEBC34E4FCA03B39BEADD04299BB19E85097EC92B15A3518801"
|
||||
"E566");
|
||||
BEAST_EXPECT(
|
||||
c.section(SECTION_VALIDATOR_LIST_THRESHOLD).values().size() ==
|
||||
1);
|
||||
BEAST_EXPECT(
|
||||
c.section(SECTION_VALIDATOR_LIST_THRESHOLD).values()[0] == "0");
|
||||
BEAST_EXPECT(c.VALIDATOR_LIST_THRESHOLD == std::nullopt);
|
||||
}
|
||||
{
|
||||
// load should throw if [validator_list_threshold] is greater than
|
||||
// the number of [validator_list_keys]
|
||||
Config c;
|
||||
std::string toLoad(R"rippleConfig(
|
||||
[validator_list_sites]
|
||||
ripplevalidators.com
|
||||
trustthesevalidators.gov
|
||||
|
||||
[validator_list_keys]
|
||||
021A99A537FDEBC34E4FCA03B39BEADD04299BB19E85097EC92B15A3518801E566
|
||||
|
||||
[validator_list_threshold]
|
||||
2
|
||||
)rippleConfig");
|
||||
std::string error;
|
||||
auto const expectedError =
|
||||
"Value in config section [validator_list_threshold] exceeds "
|
||||
"the number of configured list keys";
|
||||
try
|
||||
{
|
||||
c.loadFromString(toLoad);
|
||||
fail();
|
||||
}
|
||||
catch (std::runtime_error& e)
|
||||
{
|
||||
error = e.what();
|
||||
}
|
||||
BEAST_EXPECT(error == expectedError);
|
||||
}
|
||||
{
|
||||
// load should throw if [validator_list_threshold] is malformed
|
||||
Config c;
|
||||
std::string toLoad(R"rippleConfig(
|
||||
[validator_list_sites]
|
||||
ripplevalidators.com
|
||||
trustthesevalidators.gov
|
||||
|
||||
[validator_list_keys]
|
||||
021A99A537FDEBC34E4FCA03B39BEADD04299BB19E85097EC92B15A3518801E566
|
||||
|
||||
[validator_list_threshold]
|
||||
value = 2
|
||||
)rippleConfig");
|
||||
std::string error;
|
||||
auto const expectedError =
|
||||
"Config section [validator_list_threshold] should contain "
|
||||
"single value only";
|
||||
try
|
||||
{
|
||||
c.loadFromString(toLoad);
|
||||
fail();
|
||||
}
|
||||
catch (std::runtime_error& e)
|
||||
{
|
||||
error = e.what();
|
||||
}
|
||||
BEAST_EXPECT(error == expectedError);
|
||||
}
|
||||
{
|
||||
// load should throw if [validator_list_threshold] is negative
|
||||
Config c;
|
||||
std::string toLoad(R"rippleConfig(
|
||||
[validator_list_sites]
|
||||
ripplevalidators.com
|
||||
trustthesevalidators.gov
|
||||
|
||||
[validator_list_keys]
|
||||
021A99A537FDEBC34E4FCA03B39BEADD04299BB19E85097EC92B15A3518801E566
|
||||
|
||||
[validator_list_threshold]
|
||||
-1
|
||||
)rippleConfig");
|
||||
bool error = false;
|
||||
try
|
||||
{
|
||||
c.loadFromString(toLoad);
|
||||
fail();
|
||||
}
|
||||
catch (std::bad_cast& e)
|
||||
{
|
||||
error = true;
|
||||
}
|
||||
BEAST_EXPECT(error);
|
||||
}
|
||||
{
|
||||
// load should throw if [validator_list_sites] is configured but
|
||||
@@ -581,6 +716,7 @@ trustthesevalidators.gov
|
||||
try
|
||||
{
|
||||
c.loadFromString(toLoad);
|
||||
fail();
|
||||
}
|
||||
catch (std::runtime_error& e)
|
||||
{
|
||||
@@ -602,6 +738,10 @@ trustthesevalidators.gov
|
||||
c.section(SECTION_VALIDATOR_LIST_SITES).values().size() == 2);
|
||||
BEAST_EXPECT(
|
||||
c.section(SECTION_VALIDATOR_LIST_KEYS).values().size() == 2);
|
||||
BEAST_EXPECT(
|
||||
c.section(SECTION_VALIDATOR_LIST_THRESHOLD).values().size() ==
|
||||
1);
|
||||
BEAST_EXPECT(c.VALIDATOR_LIST_THRESHOLD == 2);
|
||||
}
|
||||
{
|
||||
// load from specified [validators_file] file name
|
||||
@@ -620,6 +760,10 @@ trustthesevalidators.gov
|
||||
c.section(SECTION_VALIDATOR_LIST_SITES).values().size() == 2);
|
||||
BEAST_EXPECT(
|
||||
c.section(SECTION_VALIDATOR_LIST_KEYS).values().size() == 2);
|
||||
BEAST_EXPECT(
|
||||
c.section(SECTION_VALIDATOR_LIST_THRESHOLD).values().size() ==
|
||||
1);
|
||||
BEAST_EXPECT(c.VALIDATOR_LIST_THRESHOLD == 2);
|
||||
}
|
||||
{
|
||||
// load from specified [validators_file] relative path
|
||||
@@ -638,6 +782,10 @@ trustthesevalidators.gov
|
||||
c.section(SECTION_VALIDATOR_LIST_SITES).values().size() == 2);
|
||||
BEAST_EXPECT(
|
||||
c.section(SECTION_VALIDATOR_LIST_KEYS).values().size() == 2);
|
||||
BEAST_EXPECT(
|
||||
c.section(SECTION_VALIDATOR_LIST_THRESHOLD).values().size() ==
|
||||
1);
|
||||
BEAST_EXPECT(c.VALIDATOR_LIST_THRESHOLD == 2);
|
||||
}
|
||||
{
|
||||
// load from validators file in default location
|
||||
@@ -654,6 +802,10 @@ trustthesevalidators.gov
|
||||
c.section(SECTION_VALIDATOR_LIST_SITES).values().size() == 2);
|
||||
BEAST_EXPECT(
|
||||
c.section(SECTION_VALIDATOR_LIST_KEYS).values().size() == 2);
|
||||
BEAST_EXPECT(
|
||||
c.section(SECTION_VALIDATOR_LIST_THRESHOLD).values().size() ==
|
||||
1);
|
||||
BEAST_EXPECT(c.VALIDATOR_LIST_THRESHOLD == 2);
|
||||
}
|
||||
{
|
||||
// load from specified [validators_file] instead
|
||||
@@ -674,6 +826,10 @@ trustthesevalidators.gov
|
||||
c.section(SECTION_VALIDATOR_LIST_SITES).values().size() == 2);
|
||||
BEAST_EXPECT(
|
||||
c.section(SECTION_VALIDATOR_LIST_KEYS).values().size() == 2);
|
||||
BEAST_EXPECT(
|
||||
c.section(SECTION_VALIDATOR_LIST_THRESHOLD).values().size() ==
|
||||
1);
|
||||
BEAST_EXPECT(c.VALIDATOR_LIST_THRESHOLD == 2);
|
||||
}
|
||||
|
||||
{
|
||||
@@ -711,6 +867,39 @@ trustthesevalidators.gov
|
||||
c.section(SECTION_VALIDATOR_LIST_SITES).values().size() == 4);
|
||||
BEAST_EXPECT(
|
||||
c.section(SECTION_VALIDATOR_LIST_KEYS).values().size() == 3);
|
||||
BEAST_EXPECT(
|
||||
c.section(SECTION_VALIDATOR_LIST_THRESHOLD).values().size() ==
|
||||
1);
|
||||
BEAST_EXPECT(c.VALIDATOR_LIST_THRESHOLD == 2);
|
||||
}
|
||||
{
|
||||
// load should throw if [validator_list_threshold] is present both
|
||||
// in rippled cfg and validators file
|
||||
boost::format cc(R"rippleConfig(
|
||||
[validators_file]
|
||||
%1%
|
||||
|
||||
[validator_list_threshold]
|
||||
1
|
||||
)rippleConfig");
|
||||
std::string error;
|
||||
detail::ValidatorsTxtGuard const vtg(
|
||||
*this, "test_cfg", "validators.cfg");
|
||||
BEAST_EXPECT(vtg.validatorsFileExists());
|
||||
auto const expectedError =
|
||||
"Config section [validator_list_threshold] should contain "
|
||||
"single value only";
|
||||
try
|
||||
{
|
||||
Config c;
|
||||
c.loadFromString(boost::str(cc % vtg.validatorsFile()));
|
||||
fail();
|
||||
}
|
||||
catch (std::runtime_error& e)
|
||||
{
|
||||
error = e.what();
|
||||
}
|
||||
BEAST_EXPECT(error == expectedError);
|
||||
}
|
||||
{
|
||||
// load should throw if [validators], [validator_keys] and
|
||||
|
||||
@@ -1360,7 +1360,8 @@ ApplicationImp::setup(boost::program_options::variables_map const& cmdline)
|
||||
if (!validators_->load(
|
||||
localSigningKey,
|
||||
config().section(SECTION_VALIDATORS).values(),
|
||||
config().section(SECTION_VALIDATOR_LIST_KEYS).values()))
|
||||
config().section(SECTION_VALIDATOR_LIST_KEYS).values(),
|
||||
config().VALIDATOR_LIST_THRESHOLD))
|
||||
{
|
||||
JLOG(m_journal.fatal())
|
||||
<< "Invalid entry in validator configuration.";
|
||||
|
||||
@@ -242,6 +242,9 @@ class ValidatorList
|
||||
// The current list of trusted master keys
|
||||
hash_set<PublicKey> trustedMasterKeys_;
|
||||
|
||||
// Minimum number of lists on which a trusted validator must appear on
|
||||
std::size_t listThreshold_;
|
||||
|
||||
// The current list of trusted signing keys. For those validators using
|
||||
// a manifest, the signing key is the ephemeral key. For the ones using
|
||||
// a seed, the signing key is the same as the master key.
|
||||
@@ -343,7 +346,8 @@ public:
|
||||
load(
|
||||
std::optional<PublicKey> const& localSigningKey,
|
||||
std::vector<std::string> const& configKeys,
|
||||
std::vector<std::string> const& publisherKeys);
|
||||
std::vector<std::string> const& publisherKeys,
|
||||
std::optional<std::size_t> listThreshold = {});
|
||||
|
||||
/** Pull the blob/signature/manifest information out of the appropriate Json
|
||||
body fields depending on the version.
|
||||
@@ -679,6 +683,13 @@ public:
|
||||
hash_set<PublicKey>
|
||||
getTrustedMasterKeys() const;
|
||||
|
||||
/**
|
||||
* get the validator list threshold
|
||||
* @return the threshold
|
||||
*/
|
||||
std::size_t
|
||||
getListThreshold() const;
|
||||
|
||||
/**
|
||||
* get the master public keys of Negative UNL validators
|
||||
* @return the master public keys
|
||||
|
||||
@@ -129,6 +129,7 @@ ValidatorList::ValidatorList(
|
||||
, j_(j)
|
||||
, quorum_(minimumQuorum.value_or(1)) // Genesis ledger quorum
|
||||
, minimumQuorum_(minimumQuorum)
|
||||
, listThreshold_(1)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -136,7 +137,8 @@ bool
|
||||
ValidatorList::load(
|
||||
std::optional<PublicKey> const& localSigningKey,
|
||||
std::vector<std::string> const& configKeys,
|
||||
std::vector<std::string> const& publisherKeys)
|
||||
std::vector<std::string> const& publisherKeys,
|
||||
std::optional<std::size_t> listThreshold)
|
||||
{
|
||||
static boost::regex const re(
|
||||
"[[:space:]]*" // skip leading whitespace
|
||||
@@ -190,6 +192,26 @@ ValidatorList::load(
|
||||
++count;
|
||||
}
|
||||
|
||||
if (listThreshold)
|
||||
{
|
||||
listThreshold_ = *listThreshold;
|
||||
// This should be enforced by Config class
|
||||
XRPL_ASSERT(
|
||||
listThreshold_ > 0 && listThreshold_ <= publisherLists_.size(),
|
||||
"ripple::ValidatorList::load : list threshold inside range");
|
||||
JLOG(j_.debug()) << "Validator list threshold set in configuration to "
|
||||
<< listThreshold_;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Want truncated result when dividing an odd integer
|
||||
listThreshold_ = (publisherLists_.size() < 3)
|
||||
? 1 //
|
||||
: publisherLists_.size() / 2 + 1;
|
||||
JLOG(j_.debug()) << "Validator list threshold computed as "
|
||||
<< listThreshold_;
|
||||
}
|
||||
|
||||
JLOG(j_.debug()) << "Loaded " << count << " keys";
|
||||
|
||||
if (localSigningKey)
|
||||
@@ -197,7 +219,17 @@ ValidatorList::load(
|
||||
|
||||
// Treat local validator key as though it was listed in the config
|
||||
if (localPubKey_)
|
||||
keyListings_.insert({*localPubKey_, 1});
|
||||
{
|
||||
// The local validator must meet listThreshold_ so the validator does
|
||||
// not ignore itself.
|
||||
auto const [_, inserted] =
|
||||
keyListings_.insert({*localPubKey_, listThreshold_});
|
||||
if (inserted)
|
||||
{
|
||||
JLOG(j_.debug()) << "Added own master key "
|
||||
<< toBase58(TokenType::NodePublic, *localPubKey_);
|
||||
}
|
||||
}
|
||||
|
||||
JLOG(j_.debug()) << "Loading configured validator keys";
|
||||
|
||||
@@ -227,7 +259,7 @@ ValidatorList::load(
|
||||
if (*id == localPubKey_ || *id == localSigningKey)
|
||||
continue;
|
||||
|
||||
auto ret = keyListings_.insert({*id, 1});
|
||||
auto ret = keyListings_.insert({*id, listThreshold_});
|
||||
if (!ret.second)
|
||||
{
|
||||
JLOG(j_.warn()) << "Duplicate node identity: " << match[1];
|
||||
@@ -1615,6 +1647,8 @@ ValidatorList::getJson() const
|
||||
x[jss::status] = "unknown";
|
||||
x[jss::expiration] = "unknown";
|
||||
}
|
||||
|
||||
x[jss::validator_list_threshold] = Json::UInt(listThreshold_);
|
||||
}
|
||||
|
||||
// Validator keys listed in the local config file
|
||||
@@ -1794,11 +1828,42 @@ ValidatorList::calculateQuorum(
|
||||
return *minimumQuorum_;
|
||||
}
|
||||
|
||||
// Do not use achievable quorum until lists from all configured
|
||||
// publishers are available
|
||||
for (auto const& list : publisherLists_)
|
||||
if (!publisherLists_.empty())
|
||||
{
|
||||
if (list.second.status != PublisherStatus::available)
|
||||
// Do not use achievable quorum until lists from a sufficient number of
|
||||
// configured publishers are available
|
||||
std::size_t unavailable = 0;
|
||||
for (auto const& list : publisherLists_)
|
||||
{
|
||||
if (list.second.status != PublisherStatus::available)
|
||||
unavailable += 1;
|
||||
}
|
||||
// There are two, subtly different, sides to list threshold:
|
||||
//
|
||||
// 1. The minimum required intersection between lists listThreshold_
|
||||
// for a validator to be included in trustedMasterKeys_.
|
||||
// If this many (or more) publishers are unavailable, we are likely
|
||||
// to NOT include a validator which otherwise would have been used.
|
||||
// We disable quorum if this happens.
|
||||
// 2. The minimum number of publishers which, when unavailable, will
|
||||
// prevent us from hitting the above threshold on ANY validator.
|
||||
// This is calculated as:
|
||||
// N - M + 1
|
||||
// where
|
||||
// N: number of publishers i.e. publisherLists_.size()
|
||||
// M: minimum required intersection i.e. listThreshold_
|
||||
// If this happens, we still have this local validator and we do not
|
||||
// want it to form a quorum of 1, so we disable quorum as well.
|
||||
//
|
||||
// We disable quorum if the number of unavailable publishers exceeds
|
||||
// either of the above thresholds
|
||||
auto const errorThreshold = std::min(
|
||||
listThreshold_, //
|
||||
publisherLists_.size() - listThreshold_ + 1);
|
||||
XRPL_ASSERT(
|
||||
errorThreshold > 0,
|
||||
"ripple::ValidatorList::calculateQuorum : nonzero error threshold");
|
||||
if (unavailable >= errorThreshold)
|
||||
return std::numeric_limits<std::size_t>::max();
|
||||
}
|
||||
|
||||
@@ -1943,20 +2008,27 @@ ValidatorList::updateTrusted(
|
||||
auto it = trustedMasterKeys_.cbegin();
|
||||
while (it != trustedMasterKeys_.cend())
|
||||
{
|
||||
if (!keyListings_.count(*it) || validatorManifests_.revoked(*it))
|
||||
auto const kit = keyListings_.find(*it);
|
||||
if (kit == keyListings_.end() || //
|
||||
kit->second < listThreshold_ || //
|
||||
validatorManifests_.revoked(*it))
|
||||
{
|
||||
trustChanges.removed.insert(calcNodeID(*it));
|
||||
it = trustedMasterKeys_.erase(it);
|
||||
}
|
||||
else
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
kit->second >= listThreshold_,
|
||||
"ripple::ValidatorList::updateTrusted : count meets threshold");
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto const& val : keyListings_)
|
||||
{
|
||||
if (!validatorManifests_.revoked(val.first) &&
|
||||
if (val.second >= listThreshold_ &&
|
||||
!validatorManifests_.revoked(val.first) &&
|
||||
trustedMasterKeys_.emplace(val.first).second)
|
||||
trustChanges.added.insert(calcNodeID(val.first));
|
||||
}
|
||||
@@ -2036,6 +2108,13 @@ ValidatorList::getTrustedMasterKeys() const
|
||||
return trustedMasterKeys_;
|
||||
}
|
||||
|
||||
std::size_t
|
||||
ValidatorList::getListThreshold() const
|
||||
{
|
||||
std::shared_lock read_lock{mutex_};
|
||||
return listThreshold_;
|
||||
}
|
||||
|
||||
hash_set<PublicKey>
|
||||
ValidatorList::getNegativeUNL() const
|
||||
{
|
||||
|
||||
@@ -305,6 +305,8 @@ public:
|
||||
std::optional<std::pair<std::uint32_t, std::uint32_t>>
|
||||
FORCED_LEDGER_RANGE_PRESENT;
|
||||
|
||||
std::optional<std::size_t> VALIDATOR_LIST_THRESHOLD;
|
||||
|
||||
public:
|
||||
Config();
|
||||
|
||||
|
||||
@@ -91,6 +91,7 @@ struct ConfigSection
|
||||
#define SECTION_VALIDATOR_KEY_REVOCATION "validator_key_revocation"
|
||||
#define SECTION_VALIDATOR_LIST_KEYS "validator_list_keys"
|
||||
#define SECTION_VALIDATOR_LIST_SITES "validator_list_sites"
|
||||
#define SECTION_VALIDATOR_LIST_THRESHOLD "validator_list_threshold"
|
||||
#define SECTION_VALIDATORS "validators"
|
||||
#define SECTION_VALIDATOR_TOKEN "validator_token"
|
||||
#define SECTION_VETO_AMENDMENTS "veto_amendments"
|
||||
|
||||
@@ -912,6 +912,13 @@ Config::loadFromString(std::string const& fileContents)
|
||||
if (valListKeys)
|
||||
section(SECTION_VALIDATOR_LIST_KEYS).append(*valListKeys);
|
||||
|
||||
auto valListThreshold =
|
||||
getIniFileSection(iniFile, SECTION_VALIDATOR_LIST_THRESHOLD);
|
||||
|
||||
if (valListThreshold)
|
||||
section(SECTION_VALIDATOR_LIST_THRESHOLD)
|
||||
.append(*valListThreshold);
|
||||
|
||||
if (!entries && !valKeyEntries && !valListKeys)
|
||||
Throw<std::runtime_error>(
|
||||
"The file specified in [" SECTION_VALIDATORS_FILE
|
||||
@@ -926,6 +933,38 @@ Config::loadFromString(std::string const& fileContents)
|
||||
validatorsFile.string());
|
||||
}
|
||||
|
||||
VALIDATOR_LIST_THRESHOLD = [&]() -> std::optional<std::size_t> {
|
||||
auto const& listThreshold =
|
||||
section(SECTION_VALIDATOR_LIST_THRESHOLD);
|
||||
if (listThreshold.lines().empty())
|
||||
return std::nullopt;
|
||||
else if (listThreshold.values().size() == 1)
|
||||
{
|
||||
auto strTemp = listThreshold.values()[0];
|
||||
auto const listThreshold =
|
||||
beast::lexicalCastThrow<std::size_t>(strTemp);
|
||||
if (listThreshold == 0)
|
||||
return std::nullopt; // NOTE: Explicitly ask for computed
|
||||
else if (
|
||||
listThreshold >
|
||||
section(SECTION_VALIDATOR_LIST_KEYS).values().size())
|
||||
{
|
||||
Throw<std::runtime_error>(
|
||||
"Value in config section "
|
||||
"[" SECTION_VALIDATOR_LIST_THRESHOLD
|
||||
"] exceeds the number of configured list keys");
|
||||
}
|
||||
return listThreshold;
|
||||
}
|
||||
else
|
||||
{
|
||||
Throw<std::runtime_error>(
|
||||
"Config section "
|
||||
"[" SECTION_VALIDATOR_LIST_THRESHOLD
|
||||
"] should contain single value only");
|
||||
}
|
||||
}();
|
||||
|
||||
// Consolidate [validator_keys] and [validators]
|
||||
section(SECTION_VALIDATORS)
|
||||
.append(section(SECTION_VALIDATOR_KEYS).lines());
|
||||
|
||||
Reference in New Issue
Block a user