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:
Gregory Tsipenyuk
2020-05-18 17:40:53 -04:00
committed by Nik Bougalis
parent fe9922d654
commit df29e98ea5
12 changed files with 320 additions and 120 deletions

View File

@@ -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),

View File

@@ -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,

View File

@@ -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

View File

@@ -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;

View File

@@ -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"

View File

@@ -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)
{ {

View File

@@ -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

View File

@@ -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) */

View File

@@ -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.

View File

@@ -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);
} }
}; };

View File

@@ -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();
} }
}; };

View File

@@ -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);
} }
} }