mirror of
https://github.com/XRPLF/rippled.git
synced 2025-11-21 03:26:01 +00:00
Improve amendment processing and activation logic:
* The amendment ballot counting code contained a minor technical flaw, caused by the use of integer arithmetic and rounding semantics, that could allow amendments to reach majority with slightly less than 80% support. This commit introduces an amendment which, if enabled, will ensure that activation requires at least 80% support. * This commit also introduces a configuration option to adjust the amendment activation hysteresis. This option is useful on test networks, but should not be used on the main network as is a network-wide consensus parameter that should not be changed on a per-server basis; doing so can result in a hard-fork. Fixes #3396
This commit is contained in:
committed by
Nik Bougalis
parent
fe9922d654
commit
df29e98ea5
@@ -77,9 +77,6 @@
|
|||||||
|
|
||||||
namespace ripple {
|
namespace ripple {
|
||||||
|
|
||||||
// 204/256 about 80%
|
|
||||||
static int const MAJORITY_FRACTION(204);
|
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
namespace detail {
|
namespace detail {
|
||||||
@@ -1533,8 +1530,7 @@ ApplicationImp::setup()
|
|||||||
Section enabledAmendments = config_->section(SECTION_AMENDMENTS);
|
Section enabledAmendments = config_->section(SECTION_AMENDMENTS);
|
||||||
|
|
||||||
m_amendmentTable = make_AmendmentTable(
|
m_amendmentTable = make_AmendmentTable(
|
||||||
weeks{2},
|
config().AMENDMENT_MAJORITY_TIME,
|
||||||
MAJORITY_FRACTION,
|
|
||||||
supportedAmendments,
|
supportedAmendments,
|
||||||
enabledAmendments,
|
enabledAmendments,
|
||||||
config_->section(SECTION_VETO_AMENDMENTS),
|
config_->section(SECTION_VETO_AMENDMENTS),
|
||||||
|
|||||||
@@ -99,6 +99,7 @@ public:
|
|||||||
// inject pseudo-transactions
|
// inject pseudo-transactions
|
||||||
virtual std::map<uint256, std::uint32_t>
|
virtual std::map<uint256, std::uint32_t>
|
||||||
doVoting(
|
doVoting(
|
||||||
|
Rules const& rules,
|
||||||
NetClock::time_point closeTime,
|
NetClock::time_point closeTime,
|
||||||
std::set<uint256> const& enabledAmendments,
|
std::set<uint256> const& enabledAmendments,
|
||||||
majorityAmendments_t const& majorityAmendments,
|
majorityAmendments_t const& majorityAmendments,
|
||||||
@@ -130,6 +131,7 @@ public:
|
|||||||
{
|
{
|
||||||
// Ask implementation what to do
|
// Ask implementation what to do
|
||||||
auto actions = doVoting(
|
auto actions = doVoting(
|
||||||
|
lastClosedLedger->rules(),
|
||||||
lastClosedLedger->parentCloseTime(),
|
lastClosedLedger->parentCloseTime(),
|
||||||
getEnabledAmendments(*lastClosedLedger),
|
getEnabledAmendments(*lastClosedLedger),
|
||||||
getMajorityAmendments(*lastClosedLedger),
|
getMajorityAmendments(*lastClosedLedger),
|
||||||
@@ -164,7 +166,6 @@ public:
|
|||||||
std::unique_ptr<AmendmentTable>
|
std::unique_ptr<AmendmentTable>
|
||||||
make_AmendmentTable(
|
make_AmendmentTable(
|
||||||
std::chrono::seconds majorityTime,
|
std::chrono::seconds majorityTime,
|
||||||
int majorityFraction,
|
|
||||||
Section const& supported,
|
Section const& supported,
|
||||||
Section const& enabled,
|
Section const& enabled,
|
||||||
Section const& vetoed,
|
Section const& vetoed,
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
#include <ripple/app/misc/AmendmentTable.h>
|
#include <ripple/app/misc/AmendmentTable.h>
|
||||||
#include <ripple/core/ConfigSections.h>
|
#include <ripple/core/ConfigSections.h>
|
||||||
#include <ripple/core/DatabaseCon.h>
|
#include <ripple/core/DatabaseCon.h>
|
||||||
|
#include <ripple/protocol/Feature.h>
|
||||||
#include <ripple/protocol/STValidation.h>
|
#include <ripple/protocol/STValidation.h>
|
||||||
#include <ripple/protocol/TxFlags.h>
|
#include <ripple/protocol/TxFlags.h>
|
||||||
#include <ripple/protocol/jss.h>
|
#include <ripple/protocol/jss.h>
|
||||||
@@ -92,28 +93,74 @@ struct AmendmentState
|
|||||||
};
|
};
|
||||||
|
|
||||||
/** The status of all amendments requested in a given window. */
|
/** The status of all amendments requested in a given window. */
|
||||||
struct AmendmentSet
|
class AmendmentSet
|
||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
// How many yes votes each amendment received
|
// How many yes votes each amendment received
|
||||||
hash_map<uint256, int> votes_;
|
hash_map<uint256, int> votes_;
|
||||||
|
Rules const& rules_;
|
||||||
|
// number of trusted validations
|
||||||
|
int trustedValidations_ = 0;
|
||||||
|
// number of votes needed
|
||||||
|
int threshold_ = 0;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
// number of trusted validations
|
AmendmentSet(
|
||||||
int mTrustedValidations = 0;
|
Rules const& rules,
|
||||||
|
std::vector<std::shared_ptr<STValidation>> const& valSet)
|
||||||
// number of votes needed
|
: rules_(rules)
|
||||||
int mThreshold = 0;
|
|
||||||
|
|
||||||
AmendmentSet() = default;
|
|
||||||
|
|
||||||
void
|
|
||||||
tally(std::set<uint256> const& amendments)
|
|
||||||
{
|
{
|
||||||
++mTrustedValidations;
|
// process validations for ledger before flag ledger
|
||||||
|
for (auto const& val : valSet)
|
||||||
|
{
|
||||||
|
if (val->isTrusted())
|
||||||
|
{
|
||||||
|
if (val->isFieldPresent(sfAmendments))
|
||||||
|
{
|
||||||
|
auto const choices = val->getFieldV256(sfAmendments);
|
||||||
|
std::for_each(
|
||||||
|
choices.begin(),
|
||||||
|
choices.end(),
|
||||||
|
[&](auto const& amendment) { ++votes_[amendment]; });
|
||||||
|
}
|
||||||
|
|
||||||
for (auto const& amendment : amendments)
|
++trustedValidations_;
|
||||||
++votes_[amendment];
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
threshold_ = !rules_.enabled(fixAmendmentMajorityCalc)
|
||||||
|
? std::max(
|
||||||
|
1L,
|
||||||
|
static_cast<long>(
|
||||||
|
(trustedValidations_ *
|
||||||
|
preFixAmendmentMajorityCalcThreshold.num) /
|
||||||
|
preFixAmendmentMajorityCalcThreshold.den))
|
||||||
|
: std::max(
|
||||||
|
1L,
|
||||||
|
static_cast<long>(
|
||||||
|
(trustedValidations_ *
|
||||||
|
postFixAmendmentMajorityCalcThreshold.num) /
|
||||||
|
postFixAmendmentMajorityCalcThreshold.den));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
passes(uint256 const& amendment) const
|
||||||
|
{
|
||||||
|
auto const& it = votes_.find(amendment);
|
||||||
|
|
||||||
|
if (it == votes_.end())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Before this fix, it was possible for an amendment to activate with a
|
||||||
|
// percentage slightly less than 80% because we compared for "greater
|
||||||
|
// than or equal to" instead of strictly "greater than".
|
||||||
|
// One validator is an exception, otherwise it is not possible
|
||||||
|
// to gain majority.
|
||||||
|
if (!rules_.enabled(fixAmendmentMajorityCalc) ||
|
||||||
|
trustedValidations_ == 1)
|
||||||
|
return it->second >= threshold_;
|
||||||
|
|
||||||
|
return it->second > threshold_;
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
@@ -126,6 +173,18 @@ public:
|
|||||||
|
|
||||||
return it->second;
|
return it->second;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
trustedValidations() const
|
||||||
|
{
|
||||||
|
return trustedValidations_;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
threshold() const
|
||||||
|
{
|
||||||
|
return threshold_;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
@@ -138,7 +197,7 @@ public:
|
|||||||
*/
|
*/
|
||||||
class AmendmentTableImpl final : public AmendmentTable
|
class AmendmentTableImpl final : public AmendmentTable
|
||||||
{
|
{
|
||||||
protected:
|
private:
|
||||||
mutable std::mutex mutex_;
|
mutable std::mutex mutex_;
|
||||||
|
|
||||||
hash_map<uint256, AmendmentState> amendmentMap_;
|
hash_map<uint256, AmendmentState> amendmentMap_;
|
||||||
@@ -147,10 +206,6 @@ protected:
|
|||||||
// Time that an amendment must hold a majority for
|
// Time that an amendment must hold a majority for
|
||||||
std::chrono::seconds const majorityTime_;
|
std::chrono::seconds const majorityTime_;
|
||||||
|
|
||||||
// The amount of support that an amendment must receive
|
|
||||||
// 0 = 0% and 256 = 100%
|
|
||||||
int const majorityFraction_;
|
|
||||||
|
|
||||||
// The results of the last voting round - may be empty if
|
// The results of the last voting round - may be empty if
|
||||||
// we haven't participated in one yet.
|
// we haven't participated in one yet.
|
||||||
std::unique_ptr<AmendmentSet> lastVote_;
|
std::unique_ptr<AmendmentSet> lastVote_;
|
||||||
@@ -187,7 +242,6 @@ protected:
|
|||||||
public:
|
public:
|
||||||
AmendmentTableImpl(
|
AmendmentTableImpl(
|
||||||
std::chrono::seconds majorityTime,
|
std::chrono::seconds majorityTime,
|
||||||
int majorityFraction,
|
|
||||||
Section const& supported,
|
Section const& supported,
|
||||||
Section const& enabled,
|
Section const& enabled,
|
||||||
Section const& vetoed,
|
Section const& vetoed,
|
||||||
@@ -237,6 +291,7 @@ public:
|
|||||||
|
|
||||||
std::map<uint256, std::uint32_t>
|
std::map<uint256, std::uint32_t>
|
||||||
doVoting(
|
doVoting(
|
||||||
|
Rules const& rules,
|
||||||
NetClock::time_point closeTime,
|
NetClock::time_point closeTime,
|
||||||
std::set<uint256> const& enabledAmendments,
|
std::set<uint256> const& enabledAmendments,
|
||||||
majorityAmendments_t const& majorityAmendments,
|
majorityAmendments_t const& majorityAmendments,
|
||||||
@@ -247,19 +302,15 @@ public:
|
|||||||
|
|
||||||
AmendmentTableImpl::AmendmentTableImpl(
|
AmendmentTableImpl::AmendmentTableImpl(
|
||||||
std::chrono::seconds majorityTime,
|
std::chrono::seconds majorityTime,
|
||||||
int majorityFraction,
|
|
||||||
Section const& supported,
|
Section const& supported,
|
||||||
Section const& enabled,
|
Section const& enabled,
|
||||||
Section const& vetoed,
|
Section const& vetoed,
|
||||||
beast::Journal journal)
|
beast::Journal journal)
|
||||||
: lastUpdateSeq_(0)
|
: lastUpdateSeq_(0)
|
||||||
, majorityTime_(majorityTime)
|
, majorityTime_(majorityTime)
|
||||||
, majorityFraction_(majorityFraction)
|
|
||||||
, unsupportedEnabled_(false)
|
, unsupportedEnabled_(false)
|
||||||
, j_(journal)
|
, j_(journal)
|
||||||
{
|
{
|
||||||
assert(majorityFraction_ != 0);
|
|
||||||
|
|
||||||
std::lock_guard sl(mutex_);
|
std::lock_guard sl(mutex_);
|
||||||
|
|
||||||
for (auto const& a : parseSection(supported))
|
for (auto const& a : parseSection(supported))
|
||||||
@@ -461,6 +512,7 @@ AmendmentTableImpl::getDesired() const
|
|||||||
|
|
||||||
std::map<uint256, std::uint32_t>
|
std::map<uint256, std::uint32_t>
|
||||||
AmendmentTableImpl::doVoting(
|
AmendmentTableImpl::doVoting(
|
||||||
|
Rules const& rules,
|
||||||
NetClock::time_point closeTime,
|
NetClock::time_point closeTime,
|
||||||
std::set<uint256> const& enabledAmendments,
|
std::set<uint256> const& enabledAmendments,
|
||||||
majorityAmendments_t const& majorityAmendments,
|
majorityAmendments_t const& majorityAmendments,
|
||||||
@@ -470,31 +522,11 @@ AmendmentTableImpl::doVoting(
|
|||||||
<< ": " << enabledAmendments.size() << ", "
|
<< ": " << enabledAmendments.size() << ", "
|
||||||
<< majorityAmendments.size() << ", " << valSet.size();
|
<< majorityAmendments.size() << ", " << valSet.size();
|
||||||
|
|
||||||
auto vote = std::make_unique<AmendmentSet>();
|
auto vote = std::make_unique<AmendmentSet>(rules, valSet);
|
||||||
|
|
||||||
// process validations for ledger before flag ledger
|
JLOG(j_.debug()) << "Received " << vote->trustedValidations()
|
||||||
for (auto const& val : valSet)
|
|
||||||
{
|
|
||||||
if (val->isTrusted())
|
|
||||||
{
|
|
||||||
std::set<uint256> ballot;
|
|
||||||
|
|
||||||
if (val->isFieldPresent(sfAmendments))
|
|
||||||
{
|
|
||||||
auto const choices = val->getFieldV256(sfAmendments);
|
|
||||||
ballot.insert(choices.begin(), choices.end());
|
|
||||||
}
|
|
||||||
|
|
||||||
vote->tally(ballot);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
vote->mThreshold =
|
|
||||||
std::max(1, (vote->mTrustedValidations * majorityFraction_) / 256);
|
|
||||||
|
|
||||||
JLOG(j_.debug()) << "Received " << vote->mTrustedValidations
|
|
||||||
<< " trusted validations, threshold is: "
|
<< " trusted validations, threshold is: "
|
||||||
<< vote->mThreshold;
|
<< vote->threshold();
|
||||||
|
|
||||||
// Map of amendments to the action to be taken for each one. The action is
|
// Map of amendments to the action to be taken for each one. The action is
|
||||||
// the value of the flags in the pseudo-transaction
|
// the value of the flags in the pseudo-transaction
|
||||||
@@ -507,8 +539,7 @@ AmendmentTableImpl::doVoting(
|
|||||||
{
|
{
|
||||||
NetClock::time_point majorityTime = {};
|
NetClock::time_point majorityTime = {};
|
||||||
|
|
||||||
bool const hasValMajority =
|
bool const hasValMajority = vote->passes(entry.first);
|
||||||
(vote->votes(entry.first) >= vote->mThreshold);
|
|
||||||
|
|
||||||
{
|
{
|
||||||
auto const it = majorityAmendments.find(entry.first);
|
auto const it = majorityAmendments.find(entry.first);
|
||||||
@@ -614,19 +645,16 @@ AmendmentTableImpl::injectJson(
|
|||||||
|
|
||||||
if (!fs.enabled && lastVote_)
|
if (!fs.enabled && lastVote_)
|
||||||
{
|
{
|
||||||
auto const votesTotal = lastVote_->mTrustedValidations;
|
auto const votesTotal = lastVote_->trustedValidations();
|
||||||
auto const votesNeeded = lastVote_->mThreshold;
|
auto const votesNeeded = lastVote_->threshold();
|
||||||
auto const votesFor = lastVote_->votes(id);
|
auto const votesFor = lastVote_->votes(id);
|
||||||
|
|
||||||
v[jss::count] = votesFor;
|
v[jss::count] = votesFor;
|
||||||
v[jss::validations] = votesTotal;
|
v[jss::validations] = votesTotal;
|
||||||
|
|
||||||
if (votesNeeded)
|
if (votesNeeded)
|
||||||
{
|
|
||||||
v[jss::vote] = votesFor * 256 / votesNeeded;
|
|
||||||
v[jss::threshold] = votesNeeded;
|
v[jss::threshold] = votesNeeded;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Json::Value
|
Json::Value
|
||||||
@@ -666,14 +694,13 @@ AmendmentTableImpl::getJson(uint256 const& amendmentID) const
|
|||||||
std::unique_ptr<AmendmentTable>
|
std::unique_ptr<AmendmentTable>
|
||||||
make_AmendmentTable(
|
make_AmendmentTable(
|
||||||
std::chrono::seconds majorityTime,
|
std::chrono::seconds majorityTime,
|
||||||
int majorityFraction,
|
|
||||||
Section const& supported,
|
Section const& supported,
|
||||||
Section const& enabled,
|
Section const& enabled,
|
||||||
Section const& vetoed,
|
Section const& vetoed,
|
||||||
beast::Journal journal)
|
beast::Journal journal)
|
||||||
{
|
{
|
||||||
return std::make_unique<AmendmentTableImpl>(
|
return std::make_unique<AmendmentTableImpl>(
|
||||||
majorityTime, majorityFraction, supported, enabled, vetoed, journal);
|
majorityTime, supported, enabled, vetoed, journal);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace ripple
|
} // namespace ripple
|
||||||
|
|||||||
@@ -31,6 +31,7 @@
|
|||||||
#include <boost/lexical_cast.hpp>
|
#include <boost/lexical_cast.hpp>
|
||||||
#include <boost/optional.hpp>
|
#include <boost/optional.hpp>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <chrono>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <string>
|
#include <string>
|
||||||
@@ -172,6 +173,9 @@ public:
|
|||||||
// Compression
|
// Compression
|
||||||
bool COMPRESSION = false;
|
bool COMPRESSION = false;
|
||||||
|
|
||||||
|
// Amendment majority time
|
||||||
|
std::chrono::seconds AMENDMENT_MAJORITY_TIME = defaultAmendmentMajorityTime;
|
||||||
|
|
||||||
// Thread pool configuration
|
// Thread pool configuration
|
||||||
std::size_t WORKERS = 0;
|
std::size_t WORKERS = 0;
|
||||||
|
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ struct ConfigSection
|
|||||||
#define SECTION_INSIGHT "insight"
|
#define SECTION_INSIGHT "insight"
|
||||||
#define SECTION_IPS "ips"
|
#define SECTION_IPS "ips"
|
||||||
#define SECTION_IPS_FIXED "ips_fixed"
|
#define SECTION_IPS_FIXED "ips_fixed"
|
||||||
|
#define SECTION_AMENDMENT_MAJORITY_TIME "amendment_majority_time"
|
||||||
#define SECTION_NETWORK_QUORUM "network_quorum"
|
#define SECTION_NETWORK_QUORUM "network_quorum"
|
||||||
#define SECTION_NODE_SEED "node_seed"
|
#define SECTION_NODE_SEED "node_seed"
|
||||||
#define SECTION_NODE_SIZE "node_size"
|
#define SECTION_NODE_SIZE "node_size"
|
||||||
|
|||||||
@@ -480,6 +480,37 @@ Config::loadFromString(std::string const& fileContents)
|
|||||||
if (getSingleSection(secConfig, SECTION_COMPRESSION, strTemp, j_))
|
if (getSingleSection(secConfig, SECTION_COMPRESSION, strTemp, j_))
|
||||||
COMPRESSION = beast::lexicalCastThrow<bool>(strTemp);
|
COMPRESSION = beast::lexicalCastThrow<bool>(strTemp);
|
||||||
|
|
||||||
|
if (getSingleSection(
|
||||||
|
secConfig, SECTION_AMENDMENT_MAJORITY_TIME, strTemp, j_))
|
||||||
|
{
|
||||||
|
using namespace std::chrono;
|
||||||
|
boost::regex const re(
|
||||||
|
"^\\s*(\\d+)\\s*(minutes|hours|days|weeks)\\s*(\\s+.*)?$");
|
||||||
|
boost::smatch match;
|
||||||
|
if (!boost::regex_match(strTemp, match, re))
|
||||||
|
Throw<std::runtime_error>(
|
||||||
|
"Invalid " SECTION_AMENDMENT_MAJORITY_TIME
|
||||||
|
", must be: [0-9]+ [minutes|hours|days|weeks]");
|
||||||
|
|
||||||
|
std::uint32_t duration =
|
||||||
|
beast::lexicalCastThrow<std::uint32_t>(match[1].str());
|
||||||
|
|
||||||
|
if (boost::iequals(match[2], "minutes"))
|
||||||
|
AMENDMENT_MAJORITY_TIME = minutes(duration);
|
||||||
|
else if (boost::iequals(match[2], "hours"))
|
||||||
|
AMENDMENT_MAJORITY_TIME = hours(duration);
|
||||||
|
else if (boost::iequals(match[2], "days"))
|
||||||
|
AMENDMENT_MAJORITY_TIME = days(duration);
|
||||||
|
else if (boost::iequals(match[2], "weeks"))
|
||||||
|
AMENDMENT_MAJORITY_TIME = weeks(duration);
|
||||||
|
|
||||||
|
if (AMENDMENT_MAJORITY_TIME < minutes(15))
|
||||||
|
Throw<std::runtime_error>(
|
||||||
|
"Invalid " SECTION_AMENDMENT_MAJORITY_TIME
|
||||||
|
", the minimum amount of time an amendment must hold a "
|
||||||
|
"majority is 15 minutes");
|
||||||
|
}
|
||||||
|
|
||||||
// Do not load trusted validator configuration for standalone mode
|
// Do not load trusted validator configuration for standalone mode
|
||||||
if (!RUN_STANDALONE)
|
if (!RUN_STANDALONE)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -111,7 +111,8 @@ class FeatureCollections
|
|||||||
"RequireFullyCanonicalSig",
|
"RequireFullyCanonicalSig",
|
||||||
"fix1781", // XRPEndpointSteps should be included in the circular
|
"fix1781", // XRPEndpointSteps should be included in the circular
|
||||||
// payment check
|
// payment check
|
||||||
"HardenedValidations"};
|
"HardenedValidations",
|
||||||
|
"fixAmendmentMajorityCalc"}; // Fix Amendment majority calculation
|
||||||
|
|
||||||
std::vector<uint256> features;
|
std::vector<uint256> features;
|
||||||
boost::container::flat_map<uint256, std::size_t> featureToIndex;
|
boost::container::flat_map<uint256, std::size_t> featureToIndex;
|
||||||
@@ -367,6 +368,7 @@ extern uint256 const fixQualityUpperBound;
|
|||||||
extern uint256 const featureRequireFullyCanonicalSig;
|
extern uint256 const featureRequireFullyCanonicalSig;
|
||||||
extern uint256 const fix1781;
|
extern uint256 const fix1781;
|
||||||
extern uint256 const featureHardenedValidations;
|
extern uint256 const featureHardenedValidations;
|
||||||
|
extern uint256 const fixAmendmentMajorityCalc;
|
||||||
|
|
||||||
} // namespace ripple
|
} // namespace ripple
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
#define RIPPLE_PROTOCOL_SYSTEMPARAMETERS_H_INCLUDED
|
#define RIPPLE_PROTOCOL_SYSTEMPARAMETERS_H_INCLUDED
|
||||||
|
|
||||||
#include <ripple/basics/XRPAmount.h>
|
#include <ripple/basics/XRPAmount.h>
|
||||||
|
#include <ripple/basics/chrono.h>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
@@ -59,6 +60,18 @@ systemCurrencyCode()
|
|||||||
/** The XRP ledger network's earliest allowed sequence */
|
/** The XRP ledger network's earliest allowed sequence */
|
||||||
static std::uint32_t constexpr XRP_LEDGER_EARLIEST_SEQ{32570};
|
static std::uint32_t constexpr XRP_LEDGER_EARLIEST_SEQ{32570};
|
||||||
|
|
||||||
|
/** The minimum amount of support an amendment should have.
|
||||||
|
|
||||||
|
@note This value is used by legacy code and will become obsolete
|
||||||
|
once the fixAmendmentMajorityCalc amendment activates.
|
||||||
|
*/
|
||||||
|
constexpr std::ratio<204, 256> preFixAmendmentMajorityCalcThreshold;
|
||||||
|
|
||||||
|
constexpr std::ratio<80, 100> postFixAmendmentMajorityCalcThreshold;
|
||||||
|
|
||||||
|
/** The minimum amount of time an amendment must hold a majority */
|
||||||
|
constexpr std::chrono::seconds const defaultAmendmentMajorityTime = weeks{2};
|
||||||
|
|
||||||
} // namespace ripple
|
} // namespace ripple
|
||||||
|
|
||||||
/** Default peer port (IANA registered) */
|
/** Default peer port (IANA registered) */
|
||||||
|
|||||||
@@ -130,7 +130,8 @@ detail::supportedAmendments()
|
|||||||
"fixQualityUpperBound",
|
"fixQualityUpperBound",
|
||||||
"RequireFullyCanonicalSig",
|
"RequireFullyCanonicalSig",
|
||||||
"fix1781",
|
"fix1781",
|
||||||
"HardenedValidations"};
|
"HardenedValidations",
|
||||||
|
"fixAmendmentMajorityCalc"};
|
||||||
return supported;
|
return supported;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,7 +182,8 @@ uint256 const
|
|||||||
fixQualityUpperBound = *getRegisteredFeature("fixQualityUpperBound"),
|
fixQualityUpperBound = *getRegisteredFeature("fixQualityUpperBound"),
|
||||||
featureRequireFullyCanonicalSig = *getRegisteredFeature("RequireFullyCanonicalSig"),
|
featureRequireFullyCanonicalSig = *getRegisteredFeature("RequireFullyCanonicalSig"),
|
||||||
fix1781 = *getRegisteredFeature("fix1781"),
|
fix1781 = *getRegisteredFeature("fix1781"),
|
||||||
featureHardenedValidations = *getRegisteredFeature("HardenedValidations");
|
featureHardenedValidations = *getRegisteredFeature("HardenedValidations"),
|
||||||
|
fixAmendmentMajorityCalc = *getRegisteredFeature("fixAmendmentMajorityCalc");
|
||||||
|
|
||||||
// The following amendments have been active for at least two years. Their
|
// The following amendments have been active for at least two years. Their
|
||||||
// pre-amendment code has been removed and the identifiers are deprecated.
|
// pre-amendment code has been removed and the identifiers are deprecated.
|
||||||
|
|||||||
@@ -38,9 +38,6 @@ namespace ripple {
|
|||||||
class AmendmentTable_test final : public beast::unit_test::suite
|
class AmendmentTable_test final : public beast::unit_test::suite
|
||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
// 204/256 about 80% (we round down because the implementation rounds up)
|
|
||||||
static int const majorityFraction{204};
|
|
||||||
|
|
||||||
static uint256
|
static uint256
|
||||||
amendmentId(std::string in)
|
amendmentId(std::string in)
|
||||||
{
|
{
|
||||||
@@ -100,12 +97,7 @@ public:
|
|||||||
Section const vetoed)
|
Section const vetoed)
|
||||||
{
|
{
|
||||||
return make_AmendmentTable(
|
return make_AmendmentTable(
|
||||||
majorityTime,
|
majorityTime, supported, enabled, vetoed, journal);
|
||||||
majorityFraction,
|
|
||||||
supported,
|
|
||||||
enabled,
|
|
||||||
vetoed,
|
|
||||||
journal);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<AmendmentTable>
|
std::unique_ptr<AmendmentTable>
|
||||||
@@ -373,6 +365,7 @@ public:
|
|||||||
// Execute a pretend consensus round for a flag ledger
|
// Execute a pretend consensus round for a flag ledger
|
||||||
void
|
void
|
||||||
doRound(
|
doRound(
|
||||||
|
uint256 const& feat,
|
||||||
AmendmentTable& table,
|
AmendmentTable& table,
|
||||||
weeks week,
|
weeks week,
|
||||||
std::vector<std::pair<PublicKey, SecretKey>> const& validators,
|
std::vector<std::pair<PublicKey, SecretKey>> const& validators,
|
||||||
@@ -399,25 +392,25 @@ public:
|
|||||||
validations.reserve(validators.size());
|
validations.reserve(validators.size());
|
||||||
|
|
||||||
int i = 0;
|
int i = 0;
|
||||||
for (auto const& val : validators)
|
for (auto const& [pub, sec] : validators)
|
||||||
{
|
{
|
||||||
++i;
|
++i;
|
||||||
std::vector<uint256> field;
|
std::vector<uint256> field;
|
||||||
|
|
||||||
for (auto const& amendment : votes)
|
for (auto const& [hash, nVotes] : votes)
|
||||||
{
|
{
|
||||||
if ((256 * i) < (validators.size() * amendment.second))
|
if (feat == fixAmendmentMajorityCalc ? nVotes >= i : nVotes > i)
|
||||||
{
|
{
|
||||||
// We vote yes on this amendment
|
// We vote yes on this amendment
|
||||||
field.push_back(amendment.first);
|
field.push_back(hash);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto v = std::make_shared<STValidation>(
|
auto v = std::make_shared<STValidation>(
|
||||||
ripple::NetClock::time_point{},
|
ripple::NetClock::time_point{},
|
||||||
val.first,
|
pub,
|
||||||
val.second,
|
sec,
|
||||||
calcNodeID(val.first),
|
calcNodeID(pub),
|
||||||
[&field](STValidation& v) {
|
[&field](STValidation& v) {
|
||||||
if (!field.empty())
|
if (!field.empty())
|
||||||
v.setFieldV256(
|
v.setFieldV256(
|
||||||
@@ -430,14 +423,13 @@ public:
|
|||||||
|
|
||||||
ourVotes = table.doValidation(enabled);
|
ourVotes = table.doValidation(enabled);
|
||||||
|
|
||||||
auto actions =
|
auto actions = table.doVoting(
|
||||||
table.doVoting(roundTime, enabled, majority, validations);
|
Rules({feat}), roundTime, enabled, majority, validations);
|
||||||
for (auto const& action : actions)
|
for (auto const& [hash, action] : actions)
|
||||||
{
|
{
|
||||||
// This code assumes other validators do as we do
|
// This code assumes other validators do as we do
|
||||||
|
|
||||||
auto const& hash = action.first;
|
switch (action)
|
||||||
switch (action.second)
|
|
||||||
{
|
{
|
||||||
case 0:
|
case 0:
|
||||||
// amendment goes from majority to enabled
|
// amendment goes from majority to enabled
|
||||||
@@ -471,7 +463,7 @@ public:
|
|||||||
|
|
||||||
// No vote on unknown amendment
|
// No vote on unknown amendment
|
||||||
void
|
void
|
||||||
testNoOnUnknown()
|
testNoOnUnknown(uint256 const& feat)
|
||||||
{
|
{
|
||||||
testcase("Vote NO on unknown");
|
testcase("Vote NO on unknown");
|
||||||
|
|
||||||
@@ -487,15 +479,29 @@ public:
|
|||||||
majorityAmendments_t majority;
|
majorityAmendments_t majority;
|
||||||
|
|
||||||
doRound(
|
doRound(
|
||||||
*table, weeks{1}, validators, votes, ourVotes, enabled, majority);
|
feat,
|
||||||
|
*table,
|
||||||
|
weeks{1},
|
||||||
|
validators,
|
||||||
|
votes,
|
||||||
|
ourVotes,
|
||||||
|
enabled,
|
||||||
|
majority);
|
||||||
BEAST_EXPECT(ourVotes.empty());
|
BEAST_EXPECT(ourVotes.empty());
|
||||||
BEAST_EXPECT(enabled.empty());
|
BEAST_EXPECT(enabled.empty());
|
||||||
BEAST_EXPECT(majority.empty());
|
BEAST_EXPECT(majority.empty());
|
||||||
|
|
||||||
votes.emplace_back(testAmendment, 256);
|
votes.emplace_back(testAmendment, validators.size());
|
||||||
|
|
||||||
doRound(
|
doRound(
|
||||||
*table, weeks{2}, validators, votes, ourVotes, enabled, majority);
|
feat,
|
||||||
|
*table,
|
||||||
|
weeks{2},
|
||||||
|
validators,
|
||||||
|
votes,
|
||||||
|
ourVotes,
|
||||||
|
enabled,
|
||||||
|
majority);
|
||||||
BEAST_EXPECT(ourVotes.empty());
|
BEAST_EXPECT(ourVotes.empty());
|
||||||
BEAST_EXPECT(enabled.empty());
|
BEAST_EXPECT(enabled.empty());
|
||||||
|
|
||||||
@@ -504,14 +510,21 @@ public:
|
|||||||
// Note that the simulation code assumes others behave as we do,
|
// Note that the simulation code assumes others behave as we do,
|
||||||
// so the amendment won't get enabled
|
// so the amendment won't get enabled
|
||||||
doRound(
|
doRound(
|
||||||
*table, weeks{5}, validators, votes, ourVotes, enabled, majority);
|
feat,
|
||||||
|
*table,
|
||||||
|
weeks{5},
|
||||||
|
validators,
|
||||||
|
votes,
|
||||||
|
ourVotes,
|
||||||
|
enabled,
|
||||||
|
majority);
|
||||||
BEAST_EXPECT(ourVotes.empty());
|
BEAST_EXPECT(ourVotes.empty());
|
||||||
BEAST_EXPECT(enabled.empty());
|
BEAST_EXPECT(enabled.empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
// No vote on vetoed amendment
|
// No vote on vetoed amendment
|
||||||
void
|
void
|
||||||
testNoOnVetoed()
|
testNoOnVetoed(uint256 const& feat)
|
||||||
{
|
{
|
||||||
testcase("Vote NO on vetoed");
|
testcase("Vote NO on vetoed");
|
||||||
|
|
||||||
@@ -528,29 +541,50 @@ public:
|
|||||||
majorityAmendments_t majority;
|
majorityAmendments_t majority;
|
||||||
|
|
||||||
doRound(
|
doRound(
|
||||||
*table, weeks{1}, validators, votes, ourVotes, enabled, majority);
|
feat,
|
||||||
|
*table,
|
||||||
|
weeks{1},
|
||||||
|
validators,
|
||||||
|
votes,
|
||||||
|
ourVotes,
|
||||||
|
enabled,
|
||||||
|
majority);
|
||||||
BEAST_EXPECT(ourVotes.empty());
|
BEAST_EXPECT(ourVotes.empty());
|
||||||
BEAST_EXPECT(enabled.empty());
|
BEAST_EXPECT(enabled.empty());
|
||||||
BEAST_EXPECT(majority.empty());
|
BEAST_EXPECT(majority.empty());
|
||||||
|
|
||||||
votes.emplace_back(testAmendment, 256);
|
votes.emplace_back(testAmendment, validators.size());
|
||||||
|
|
||||||
doRound(
|
doRound(
|
||||||
*table, weeks{2}, validators, votes, ourVotes, enabled, majority);
|
feat,
|
||||||
|
*table,
|
||||||
|
weeks{2},
|
||||||
|
validators,
|
||||||
|
votes,
|
||||||
|
ourVotes,
|
||||||
|
enabled,
|
||||||
|
majority);
|
||||||
BEAST_EXPECT(ourVotes.empty());
|
BEAST_EXPECT(ourVotes.empty());
|
||||||
BEAST_EXPECT(enabled.empty());
|
BEAST_EXPECT(enabled.empty());
|
||||||
|
|
||||||
majority[testAmendment] = weekTime(weeks{1});
|
majority[testAmendment] = weekTime(weeks{1});
|
||||||
|
|
||||||
doRound(
|
doRound(
|
||||||
*table, weeks{5}, validators, votes, ourVotes, enabled, majority);
|
feat,
|
||||||
|
*table,
|
||||||
|
weeks{5},
|
||||||
|
validators,
|
||||||
|
votes,
|
||||||
|
ourVotes,
|
||||||
|
enabled,
|
||||||
|
majority);
|
||||||
BEAST_EXPECT(ourVotes.empty());
|
BEAST_EXPECT(ourVotes.empty());
|
||||||
BEAST_EXPECT(enabled.empty());
|
BEAST_EXPECT(enabled.empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Vote on and enable known, not-enabled amendment
|
// Vote on and enable known, not-enabled amendment
|
||||||
void
|
void
|
||||||
testVoteEnable()
|
testVoteEnable(uint256 const& feat)
|
||||||
{
|
{
|
||||||
testcase("voteEnable");
|
testcase("voteEnable");
|
||||||
|
|
||||||
@@ -565,7 +599,14 @@ public:
|
|||||||
|
|
||||||
// Week 1: We should vote for all known amendments not enabled
|
// Week 1: We should vote for all known amendments not enabled
|
||||||
doRound(
|
doRound(
|
||||||
*table, weeks{1}, validators, votes, ourVotes, enabled, majority);
|
feat,
|
||||||
|
*table,
|
||||||
|
weeks{1},
|
||||||
|
validators,
|
||||||
|
votes,
|
||||||
|
ourVotes,
|
||||||
|
enabled,
|
||||||
|
majority);
|
||||||
BEAST_EXPECT(ourVotes.size() == supported_.size());
|
BEAST_EXPECT(ourVotes.size() == supported_.size());
|
||||||
BEAST_EXPECT(enabled.empty());
|
BEAST_EXPECT(enabled.empty());
|
||||||
for (auto const& i : supported_)
|
for (auto const& i : supported_)
|
||||||
@@ -573,11 +614,18 @@ public:
|
|||||||
|
|
||||||
// Now, everyone votes for this feature
|
// Now, everyone votes for this feature
|
||||||
for (auto const& i : supported_)
|
for (auto const& i : supported_)
|
||||||
votes.emplace_back(amendmentId(i), 256);
|
votes.emplace_back(amendmentId(i), validators.size());
|
||||||
|
|
||||||
// Week 2: We should recognize a majority
|
// Week 2: We should recognize a majority
|
||||||
doRound(
|
doRound(
|
||||||
*table, weeks{2}, validators, votes, ourVotes, enabled, majority);
|
feat,
|
||||||
|
*table,
|
||||||
|
weeks{2},
|
||||||
|
validators,
|
||||||
|
votes,
|
||||||
|
ourVotes,
|
||||||
|
enabled,
|
||||||
|
majority);
|
||||||
BEAST_EXPECT(ourVotes.size() == supported_.size());
|
BEAST_EXPECT(ourVotes.size() == supported_.size());
|
||||||
BEAST_EXPECT(enabled.empty());
|
BEAST_EXPECT(enabled.empty());
|
||||||
|
|
||||||
@@ -586,12 +634,26 @@ public:
|
|||||||
|
|
||||||
// Week 5: We should enable the amendment
|
// Week 5: We should enable the amendment
|
||||||
doRound(
|
doRound(
|
||||||
*table, weeks{5}, validators, votes, ourVotes, enabled, majority);
|
feat,
|
||||||
|
*table,
|
||||||
|
weeks{5},
|
||||||
|
validators,
|
||||||
|
votes,
|
||||||
|
ourVotes,
|
||||||
|
enabled,
|
||||||
|
majority);
|
||||||
BEAST_EXPECT(enabled.size() == supported_.size());
|
BEAST_EXPECT(enabled.size() == supported_.size());
|
||||||
|
|
||||||
// Week 6: We should remove it from our votes and from having a majority
|
// Week 6: We should remove it from our votes and from having a majority
|
||||||
doRound(
|
doRound(
|
||||||
*table, weeks{6}, validators, votes, ourVotes, enabled, majority);
|
feat,
|
||||||
|
*table,
|
||||||
|
weeks{6},
|
||||||
|
validators,
|
||||||
|
votes,
|
||||||
|
ourVotes,
|
||||||
|
enabled,
|
||||||
|
majority);
|
||||||
BEAST_EXPECT(enabled.size() == supported_.size());
|
BEAST_EXPECT(enabled.size() == supported_.size());
|
||||||
BEAST_EXPECT(ourVotes.empty());
|
BEAST_EXPECT(ourVotes.empty());
|
||||||
for (auto const& i : supported_)
|
for (auto const& i : supported_)
|
||||||
@@ -600,7 +662,7 @@ public:
|
|||||||
|
|
||||||
// Detect majority at 80%, enable later
|
// Detect majority at 80%, enable later
|
||||||
void
|
void
|
||||||
testDetectMajority()
|
testDetectMajority(uint256 const& feat)
|
||||||
{
|
{
|
||||||
testcase("detectMajority");
|
testcase("detectMajority");
|
||||||
|
|
||||||
@@ -619,9 +681,10 @@ public:
|
|||||||
std::vector<uint256> ourVotes;
|
std::vector<uint256> ourVotes;
|
||||||
|
|
||||||
if ((i > 0) && (i < 17))
|
if ((i > 0) && (i < 17))
|
||||||
votes.emplace_back(testAmendment, i * 16);
|
votes.emplace_back(testAmendment, i);
|
||||||
|
|
||||||
doRound(
|
doRound(
|
||||||
|
feat,
|
||||||
*table,
|
*table,
|
||||||
weeks{i},
|
weeks{i},
|
||||||
validators,
|
validators,
|
||||||
@@ -630,7 +693,7 @@ public:
|
|||||||
enabled,
|
enabled,
|
||||||
majority);
|
majority);
|
||||||
|
|
||||||
if (i < 13)
|
if (i < 13) // 13 => 13/16 = 0.8125 => > 80%
|
||||||
{
|
{
|
||||||
// We are voting yes, not enabled, no majority
|
// We are voting yes, not enabled, no majority
|
||||||
BEAST_EXPECT(!ourVotes.empty());
|
BEAST_EXPECT(!ourVotes.empty());
|
||||||
@@ -663,7 +726,7 @@ public:
|
|||||||
|
|
||||||
// Detect loss of majority
|
// Detect loss of majority
|
||||||
void
|
void
|
||||||
testLostMajority()
|
testLostMajority(uint256 const& feat)
|
||||||
{
|
{
|
||||||
testcase("lostMajority");
|
testcase("lostMajority");
|
||||||
|
|
||||||
@@ -681,9 +744,10 @@ public:
|
|||||||
std::vector<std::pair<uint256, int>> votes;
|
std::vector<std::pair<uint256, int>> votes;
|
||||||
std::vector<uint256> ourVotes;
|
std::vector<uint256> ourVotes;
|
||||||
|
|
||||||
votes.emplace_back(testAmendment, 250);
|
votes.emplace_back(testAmendment, validators.size());
|
||||||
|
|
||||||
doRound(
|
doRound(
|
||||||
|
feat,
|
||||||
*table,
|
*table,
|
||||||
weeks{1},
|
weeks{1},
|
||||||
validators,
|
validators,
|
||||||
@@ -696,15 +760,16 @@ public:
|
|||||||
BEAST_EXPECT(!majority.empty());
|
BEAST_EXPECT(!majority.empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 1; i < 16; ++i)
|
for (int i = 1; i < 8; ++i)
|
||||||
{
|
{
|
||||||
std::vector<std::pair<uint256, int>> votes;
|
std::vector<std::pair<uint256, int>> votes;
|
||||||
std::vector<uint256> ourVotes;
|
std::vector<uint256> ourVotes;
|
||||||
|
|
||||||
// Gradually reduce support
|
// Gradually reduce support
|
||||||
votes.emplace_back(testAmendment, 256 - i * 8);
|
votes.emplace_back(testAmendment, validators.size() - i);
|
||||||
|
|
||||||
doRound(
|
doRound(
|
||||||
|
feat,
|
||||||
*table,
|
*table,
|
||||||
weeks{i + 1},
|
weeks{i + 1},
|
||||||
validators,
|
validators,
|
||||||
@@ -713,8 +778,8 @@ public:
|
|||||||
enabled,
|
enabled,
|
||||||
majority);
|
majority);
|
||||||
|
|
||||||
if (i < 8)
|
if (i < 4) // 16 - 3 = 13 => 13/16 = 0.8125 => > 80%
|
||||||
{
|
{ // 16 - 4 = 12 => 12/16 = 0.75 => < 80%
|
||||||
// We are voting yes, not enabled, majority
|
// We are voting yes, not enabled, majority
|
||||||
BEAST_EXPECT(!ourVotes.empty());
|
BEAST_EXPECT(!ourVotes.empty());
|
||||||
BEAST_EXPECT(enabled.empty());
|
BEAST_EXPECT(enabled.empty());
|
||||||
@@ -775,6 +840,16 @@ public:
|
|||||||
BEAST_EXPECT(table->needValidatedLedger(257));
|
BEAST_EXPECT(table->needValidatedLedger(257));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
testFeature(uint256 const& feat)
|
||||||
|
{
|
||||||
|
testNoOnUnknown(feat);
|
||||||
|
testNoOnVetoed(feat);
|
||||||
|
testVoteEnable(feat);
|
||||||
|
testDetectMajority(feat);
|
||||||
|
testLostMajority(feat);
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
run() override
|
run() override
|
||||||
{
|
{
|
||||||
@@ -782,12 +857,9 @@ public:
|
|||||||
testGet();
|
testGet();
|
||||||
testBadConfig();
|
testBadConfig();
|
||||||
testEnableVeto();
|
testEnableVeto();
|
||||||
testNoOnUnknown();
|
|
||||||
testNoOnVetoed();
|
|
||||||
testVoteEnable();
|
|
||||||
testDetectMajority();
|
|
||||||
testLostMajority();
|
|
||||||
testHasUnsupported();
|
testHasUnsupported();
|
||||||
|
testFeature({});
|
||||||
|
testFeature(fixAmendmentMajorityCalc);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1014,6 +1014,57 @@ r.ripple.com 51235
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
testAmendment()
|
||||||
|
{
|
||||||
|
testcase("amendment");
|
||||||
|
struct ConfigUnit
|
||||||
|
{
|
||||||
|
std::string unit;
|
||||||
|
std::uint32_t numSeconds;
|
||||||
|
std::uint32_t configVal;
|
||||||
|
bool shouldPass;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<ConfigUnit> units = {
|
||||||
|
{"seconds", 1, 15 * 60, false},
|
||||||
|
{"minutes", 60, 14, false},
|
||||||
|
{"minutes", 60, 15, true},
|
||||||
|
{"hours", 3600, 10, true},
|
||||||
|
{"days", 86400, 10, true},
|
||||||
|
{"weeks", 604800, 2, true},
|
||||||
|
{"months", 2592000, 1, false},
|
||||||
|
{"years", 31536000, 1, false}};
|
||||||
|
|
||||||
|
std::string space = "";
|
||||||
|
for (auto& [unit, sec, val, shouldPass] : units)
|
||||||
|
{
|
||||||
|
Config c;
|
||||||
|
std::string toLoad(R"rippleConfig(
|
||||||
|
[amendment_majority_time]
|
||||||
|
)rippleConfig");
|
||||||
|
toLoad += std::to_string(val) + space + unit;
|
||||||
|
space = space == "" ? " " : "";
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
c.loadFromString(toLoad);
|
||||||
|
if (shouldPass)
|
||||||
|
BEAST_EXPECT(
|
||||||
|
c.AMENDMENT_MAJORITY_TIME.count() == val * sec);
|
||||||
|
else
|
||||||
|
fail();
|
||||||
|
}
|
||||||
|
catch (std::runtime_error&)
|
||||||
|
{
|
||||||
|
if (!shouldPass)
|
||||||
|
pass();
|
||||||
|
else
|
||||||
|
fail();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
run() override
|
run() override
|
||||||
{
|
{
|
||||||
@@ -1027,6 +1078,7 @@ r.ripple.com 51235
|
|||||||
testWhitespace();
|
testWhitespace();
|
||||||
testComments();
|
testComments();
|
||||||
testGetters();
|
testGetters();
|
||||||
|
testAmendment();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -218,10 +218,9 @@ class Feature_test : public beast::unit_test::suite
|
|||||||
BEAST_EXPECTS(
|
BEAST_EXPECTS(
|
||||||
feature.isMember(jss::validations),
|
feature.isMember(jss::validations),
|
||||||
feature[jss::name].asString() + " validations");
|
feature[jss::name].asString() + " validations");
|
||||||
BEAST_EXPECTS(
|
BEAST_EXPECT(feature[jss::count] == 1);
|
||||||
feature.isMember(jss::vote),
|
BEAST_EXPECT(feature[jss::threshold] == 1);
|
||||||
feature[jss::name].asString() + " vote");
|
BEAST_EXPECT(feature[jss::validations] == 1);
|
||||||
BEAST_EXPECT(feature[jss::vote] == 256);
|
|
||||||
BEAST_EXPECT(feature[jss::majority] == 2740);
|
BEAST_EXPECT(feature[jss::majority] == 2740);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user