mirror of
https://github.com/XRPLF/rippled.git
synced 2025-11-26 22:15:52 +00:00
Refactor Feature name management and creation:
* Only require adding the new feature names in one place. (Also need to increment a counter, but a check on startup will catch that.) * Allows rippled to have the code to support a given amendment, but not vote for it by default. This allows the amendment to be enabled in a future version without necessarily amendment blocking these older versions. * The default vote is carried with the amendment name in the list of supported amendments. * The amendment table is constructed with the amendment and default vote.
This commit is contained in:
committed by
manojsdoshi
parent
1ca8898703
commit
4a9bd7ed6d
@@ -1250,27 +1250,29 @@ ApplicationImp::setup()
|
|||||||
|
|
||||||
// Configure the amendments the server supports
|
// Configure the amendments the server supports
|
||||||
{
|
{
|
||||||
auto const& sa = detail::supportedAmendments();
|
auto const supported = []() {
|
||||||
std::vector<std::string> saHashes;
|
auto const& amendments = detail::supportedAmendments();
|
||||||
saHashes.reserve(sa.size());
|
std::vector<AmendmentTable::FeatureInfo> supported;
|
||||||
for (auto const& name : sa)
|
supported.reserve(amendments.size());
|
||||||
|
for (auto const& [a, vote] : amendments)
|
||||||
{
|
{
|
||||||
auto const f = getRegisteredFeature(name);
|
auto const f = ripple::getRegisteredFeature(a);
|
||||||
BOOST_ASSERT(f);
|
assert(f);
|
||||||
if (f)
|
if (f)
|
||||||
saHashes.push_back(to_string(*f) + " " + name);
|
supported.emplace_back(a, *f, vote);
|
||||||
}
|
}
|
||||||
Section supportedAmendments("Supported Amendments");
|
return supported;
|
||||||
supportedAmendments.append(saHashes);
|
}();
|
||||||
|
Section const& downVoted = config_->section(SECTION_VETO_AMENDMENTS);
|
||||||
|
|
||||||
Section enabledAmendments = config_->section(SECTION_AMENDMENTS);
|
Section const& upVoted = config_->section(SECTION_AMENDMENTS);
|
||||||
|
|
||||||
m_amendmentTable = make_AmendmentTable(
|
m_amendmentTable = make_AmendmentTable(
|
||||||
*this,
|
*this,
|
||||||
config().AMENDMENT_MAJORITY_TIME,
|
config().AMENDMENT_MAJORITY_TIME,
|
||||||
supportedAmendments,
|
supported,
|
||||||
enabledAmendments,
|
upVoted,
|
||||||
config_->section(SECTION_VETO_AMENDMENTS),
|
downVoted,
|
||||||
logs_->journal("Amendments"));
|
logs_->journal("Amendments"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
|
|
||||||
#include <ripple/app/ledger/Ledger.h>
|
#include <ripple/app/ledger/Ledger.h>
|
||||||
#include <ripple/core/ConfigSections.h>
|
#include <ripple/core/ConfigSections.h>
|
||||||
|
#include <ripple/protocol/Feature.h>
|
||||||
#include <ripple/protocol/Protocol.h>
|
#include <ripple/protocol/Protocol.h>
|
||||||
#include <ripple/protocol/STValidation.h>
|
#include <ripple/protocol/STValidation.h>
|
||||||
|
|
||||||
@@ -36,6 +37,19 @@ namespace ripple {
|
|||||||
class AmendmentTable
|
class AmendmentTable
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
struct FeatureInfo
|
||||||
|
{
|
||||||
|
FeatureInfo() = delete;
|
||||||
|
FeatureInfo(std::string const& n, uint256 const& f, DefaultVote v)
|
||||||
|
: name(n), feature(f), vote(v)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string const name;
|
||||||
|
uint256 const feature;
|
||||||
|
DefaultVote const vote;
|
||||||
|
};
|
||||||
|
|
||||||
virtual ~AmendmentTable() = default;
|
virtual ~AmendmentTable() = default;
|
||||||
|
|
||||||
virtual uint256
|
virtual uint256
|
||||||
@@ -168,7 +182,7 @@ std::unique_ptr<AmendmentTable>
|
|||||||
make_AmendmentTable(
|
make_AmendmentTable(
|
||||||
Application& app,
|
Application& app,
|
||||||
std::chrono::seconds majorityTime,
|
std::chrono::seconds majorityTime,
|
||||||
Section const& supported,
|
std::vector<AmendmentTable::FeatureInfo> const& supported,
|
||||||
Section const& enabled,
|
Section const& enabled,
|
||||||
Section const& vetoed,
|
Section const& vetoed,
|
||||||
beast::Journal journal);
|
beast::Journal journal);
|
||||||
|
|||||||
@@ -73,8 +73,8 @@ parseSection(Section const& section)
|
|||||||
*/
|
*/
|
||||||
struct AmendmentState
|
struct AmendmentState
|
||||||
{
|
{
|
||||||
/** If an amendment is vetoed, a server will not support it */
|
/** If an amendment is down-voted, a server will not vote to enable it */
|
||||||
bool vetoed = false;
|
AmendmentVote vote = AmendmentVote::down;
|
||||||
|
|
||||||
/** Indicates that the amendment has been enabled.
|
/** Indicates that the amendment has been enabled.
|
||||||
This is a one-way switch: once an amendment is enabled
|
This is a one-way switch: once an amendment is enabled
|
||||||
@@ -224,15 +224,16 @@ private:
|
|||||||
DatabaseCon& db_;
|
DatabaseCon& db_;
|
||||||
|
|
||||||
// Finds or creates state. Must be called with mutex_ locked.
|
// Finds or creates state. Must be called with mutex_ locked.
|
||||||
AmendmentState*
|
AmendmentState&
|
||||||
add(uint256 const& amendment, std::lock_guard<std::mutex> const& sl);
|
add(uint256 const& amendment, std::lock_guard<std::mutex> const& lock);
|
||||||
|
|
||||||
// Finds existing state. Must be called with mutex_ locked.
|
// Finds existing state. Must be called with mutex_ locked.
|
||||||
AmendmentState*
|
AmendmentState*
|
||||||
get(uint256 const& amendment, std::lock_guard<std::mutex> const& sl);
|
get(uint256 const& amendment, std::lock_guard<std::mutex> const& lock);
|
||||||
|
|
||||||
AmendmentState const*
|
AmendmentState const*
|
||||||
get(uint256 const& amendment, std::lock_guard<std::mutex> const& sl) const;
|
get(uint256 const& amendment,
|
||||||
|
std::lock_guard<std::mutex> const& lock) const;
|
||||||
|
|
||||||
// Injects amendment json into v. Must be called with mutex_ locked.
|
// Injects amendment json into v. Must be called with mutex_ locked.
|
||||||
void
|
void
|
||||||
@@ -240,19 +241,19 @@ private:
|
|||||||
Json::Value& v,
|
Json::Value& v,
|
||||||
uint256 const& amendment,
|
uint256 const& amendment,
|
||||||
AmendmentState const& state,
|
AmendmentState const& state,
|
||||||
std::lock_guard<std::mutex> const& sl) const;
|
std::lock_guard<std::mutex> const& lock) const;
|
||||||
|
|
||||||
void
|
void
|
||||||
persistVote(
|
persistVote(
|
||||||
uint256 const& amendment,
|
uint256 const& amendment,
|
||||||
std::string const& name,
|
std::string const& name,
|
||||||
bool vote_to_veto) const;
|
AmendmentVote vote) const;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
AmendmentTableImpl(
|
AmendmentTableImpl(
|
||||||
Application& app,
|
Application& app,
|
||||||
std::chrono::seconds majorityTime,
|
std::chrono::seconds majorityTime,
|
||||||
Section const& supported,
|
std::vector<FeatureInfo> const& supported,
|
||||||
Section const& enabled,
|
Section const& enabled,
|
||||||
Section const& vetoed,
|
Section const& vetoed,
|
||||||
beast::Journal journal);
|
beast::Journal journal);
|
||||||
@@ -313,7 +314,7 @@ public:
|
|||||||
AmendmentTableImpl::AmendmentTableImpl(
|
AmendmentTableImpl::AmendmentTableImpl(
|
||||||
Application& app,
|
Application& app,
|
||||||
std::chrono::seconds majorityTime,
|
std::chrono::seconds majorityTime,
|
||||||
Section const& supported,
|
std::vector<FeatureInfo> const& supported,
|
||||||
Section const& enabled,
|
Section const& enabled,
|
||||||
Section const& vetoed,
|
Section const& vetoed,
|
||||||
beast::Journal journal)
|
beast::Journal journal)
|
||||||
@@ -323,7 +324,7 @@ AmendmentTableImpl::AmendmentTableImpl(
|
|||||||
, j_(journal)
|
, j_(journal)
|
||||||
, db_(app.getWalletDB())
|
, db_(app.getWalletDB())
|
||||||
{
|
{
|
||||||
std::lock_guard sl(mutex_);
|
std::lock_guard lock(mutex_);
|
||||||
|
|
||||||
// Find out if the FeatureVotes table exists in WalletDB
|
// Find out if the FeatureVotes table exists in WalletDB
|
||||||
bool const featureVotesExist = [this]() {
|
bool const featureVotesExist = [this]() {
|
||||||
@@ -332,22 +333,24 @@ AmendmentTableImpl::AmendmentTableImpl(
|
|||||||
}();
|
}();
|
||||||
|
|
||||||
// Parse supported amendments
|
// Parse supported amendments
|
||||||
for (auto const& a : parseSection(supported))
|
for (auto const& [name, amendment, defaultVote] : supported)
|
||||||
{
|
{
|
||||||
if (auto s = add(a.first, sl))
|
AmendmentState& s = add(amendment, lock);
|
||||||
{
|
|
||||||
JLOG(j_.debug()) << "Amendment " << a.first << " is supported.";
|
|
||||||
|
|
||||||
if (!a.second.empty())
|
s.name = name;
|
||||||
s->name = a.second;
|
s.supported = true;
|
||||||
|
s.vote = defaultVote == DefaultVote::yes ? AmendmentVote::up
|
||||||
|
: AmendmentVote::down;
|
||||||
|
|
||||||
s->supported = true;
|
JLOG(j_.debug()) << "Amendment " << amendment << " (" << s.name
|
||||||
}
|
<< ") is supported and will be "
|
||||||
|
<< (s.vote == AmendmentVote::up ? "up" : "down")
|
||||||
|
<< " voted if not enabled on the ledger.";
|
||||||
}
|
}
|
||||||
|
|
||||||
hash_set<uint256> detect_conflict;
|
hash_set<uint256> detect_conflict;
|
||||||
// Parse enabled amendments from config
|
// Parse enabled amendments from config
|
||||||
for (auto const& a : parseSection(enabled))
|
for (std::pair<uint256, std::string> const& a : parseSection(enabled))
|
||||||
{
|
{
|
||||||
if (featureVotesExist)
|
if (featureVotesExist)
|
||||||
{ // If the table existed, warn about duplicate config info
|
{ // If the table existed, warn about duplicate config info
|
||||||
@@ -358,7 +361,7 @@ AmendmentTableImpl::AmendmentTableImpl(
|
|||||||
else
|
else
|
||||||
{ // Otherwise transfer config data into the table
|
{ // Otherwise transfer config data into the table
|
||||||
detect_conflict.insert(a.first);
|
detect_conflict.insert(a.first);
|
||||||
persistVote(a.first, a.second, false); // un-veto
|
persistVote(a.first, a.second, AmendmentVote::up);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -376,7 +379,7 @@ AmendmentTableImpl::AmendmentTableImpl(
|
|||||||
{ // Otherwise transfer config data into the table
|
{ // Otherwise transfer config data into the table
|
||||||
if (detect_conflict.count(a.first) == 0)
|
if (detect_conflict.count(a.first) == 0)
|
||||||
{
|
{
|
||||||
persistVote(a.first, a.second, true); // veto
|
persistVote(a.first, a.second, AmendmentVote::down);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -394,57 +397,62 @@ AmendmentTableImpl::AmendmentTableImpl(
|
|||||||
*db,
|
*db,
|
||||||
[&](boost::optional<std::string> amendment_hash,
|
[&](boost::optional<std::string> amendment_hash,
|
||||||
boost::optional<std::string> amendment_name,
|
boost::optional<std::string> amendment_name,
|
||||||
boost::optional<int> vote_to_veto) {
|
boost::optional<AmendmentVote> vote) {
|
||||||
uint256 amend_hash;
|
uint256 amend_hash;
|
||||||
|
if (!amendment_hash || !amendment_name || !vote)
|
||||||
|
{
|
||||||
|
// These fields should never have nulls, but check
|
||||||
|
Throw<std::runtime_error>(
|
||||||
|
"Invalid FeatureVotes row in wallet.db");
|
||||||
|
}
|
||||||
if (!amend_hash.parseHex(*amendment_hash))
|
if (!amend_hash.parseHex(*amendment_hash))
|
||||||
{
|
{
|
||||||
Throw<std::runtime_error>(
|
Throw<std::runtime_error>(
|
||||||
"Invalid amendment ID '" + *amendment_hash +
|
"Invalid amendment ID '" + *amendment_hash +
|
||||||
" in wallet.db");
|
" in wallet.db");
|
||||||
}
|
}
|
||||||
if (*vote_to_veto)
|
if (*vote == AmendmentVote::down)
|
||||||
{
|
{
|
||||||
// Unknown amendments are effectively vetoed already
|
// Unknown amendments are effectively vetoed already
|
||||||
if (auto s = get(amend_hash, sl))
|
if (auto s = get(amend_hash, lock))
|
||||||
{
|
{
|
||||||
JLOG(j_.info()) << "Amendment {" << *amendment_name << ", "
|
JLOG(j_.info()) << "Amendment {" << *amendment_name << ", "
|
||||||
<< amend_hash << "} is vetoed.";
|
<< amend_hash << "} is downvoted.";
|
||||||
if (!amendment_name->empty())
|
if (!amendment_name->empty())
|
||||||
s->name = *amendment_name;
|
s->name = *amendment_name;
|
||||||
s->vetoed = true;
|
s->vote = *vote;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else // un-veto
|
else // up-vote
|
||||||
{
|
|
||||||
if (auto s = add(amend_hash, sl))
|
|
||||||
{
|
{
|
||||||
|
auto s = add(amend_hash, lock);
|
||||||
|
|
||||||
JLOG(j_.debug()) << "Amendment {" << *amendment_name << ", "
|
JLOG(j_.debug()) << "Amendment {" << *amendment_name << ", "
|
||||||
<< amend_hash << "} is un-vetoed.";
|
<< amend_hash << "} is upvoted.";
|
||||||
if (!amendment_name->empty())
|
if (!amendment_name->empty())
|
||||||
s->name = *amendment_name;
|
s.name = *amendment_name;
|
||||||
s->vetoed = false;
|
s.vote = *vote;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
AmendmentState*
|
AmendmentState&
|
||||||
AmendmentTableImpl::add(
|
AmendmentTableImpl::add(
|
||||||
uint256 const& amendmentHash,
|
uint256 const& amendmentHash,
|
||||||
std::lock_guard<std::mutex> const&)
|
std::lock_guard<std::mutex> const&)
|
||||||
{
|
{
|
||||||
// call with the mutex held
|
// call with the mutex held
|
||||||
return &amendmentMap_[amendmentHash];
|
return amendmentMap_[amendmentHash];
|
||||||
}
|
}
|
||||||
|
|
||||||
AmendmentState*
|
AmendmentState*
|
||||||
AmendmentTableImpl::get(
|
AmendmentTableImpl::get(
|
||||||
uint256 const& amendmentHash,
|
uint256 const& amendmentHash,
|
||||||
std::lock_guard<std::mutex> const& sl)
|
std::lock_guard<std::mutex> const& lock)
|
||||||
{
|
{
|
||||||
// Forward to the const version of get.
|
// Forward to the const version of get.
|
||||||
return const_cast<AmendmentState*>(
|
return const_cast<AmendmentState*>(
|
||||||
std::as_const(*this).get(amendmentHash, sl));
|
std::as_const(*this).get(amendmentHash, lock));
|
||||||
}
|
}
|
||||||
|
|
||||||
AmendmentState const*
|
AmendmentState const*
|
||||||
@@ -464,7 +472,7 @@ AmendmentTableImpl::get(
|
|||||||
uint256
|
uint256
|
||||||
AmendmentTableImpl::find(std::string const& name) const
|
AmendmentTableImpl::find(std::string const& name) const
|
||||||
{
|
{
|
||||||
std::lock_guard sl(mutex_);
|
std::lock_guard lock(mutex_);
|
||||||
|
|
||||||
for (auto const& e : amendmentMap_)
|
for (auto const& e : amendmentMap_)
|
||||||
{
|
{
|
||||||
@@ -479,50 +487,50 @@ void
|
|||||||
AmendmentTableImpl::persistVote(
|
AmendmentTableImpl::persistVote(
|
||||||
uint256 const& amendment,
|
uint256 const& amendment,
|
||||||
std::string const& name,
|
std::string const& name,
|
||||||
bool vote_to_veto) const
|
AmendmentVote vote) const
|
||||||
{
|
{
|
||||||
auto db = db_.checkoutDb();
|
auto db = db_.checkoutDb();
|
||||||
voteAmendment(*db, amendment, name, vote_to_veto);
|
voteAmendment(*db, amendment, name, vote);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
AmendmentTableImpl::veto(uint256 const& amendment)
|
AmendmentTableImpl::veto(uint256 const& amendment)
|
||||||
{
|
{
|
||||||
std::lock_guard sl(mutex_);
|
std::lock_guard lock(mutex_);
|
||||||
auto s = add(amendment, sl);
|
AmendmentState& s = add(amendment, lock);
|
||||||
|
|
||||||
if (s->vetoed)
|
if (s.vote == AmendmentVote::down)
|
||||||
return false;
|
return false;
|
||||||
s->vetoed = true;
|
s.vote = AmendmentVote::down;
|
||||||
persistVote(amendment, s->name, s->vetoed);
|
persistVote(amendment, s.name, s.vote);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
AmendmentTableImpl::unVeto(uint256 const& amendment)
|
AmendmentTableImpl::unVeto(uint256 const& amendment)
|
||||||
{
|
{
|
||||||
std::lock_guard sl(mutex_);
|
std::lock_guard lock(mutex_);
|
||||||
auto s = get(amendment, sl);
|
AmendmentState* const s = get(amendment, lock);
|
||||||
|
|
||||||
if (!s || !s->vetoed)
|
if (!s || s->vote == AmendmentVote::up)
|
||||||
return false;
|
return false;
|
||||||
s->vetoed = false;
|
s->vote = AmendmentVote::up;
|
||||||
persistVote(amendment, s->name, s->vetoed);
|
persistVote(amendment, s->name, s->vote);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
AmendmentTableImpl::enable(uint256 const& amendment)
|
AmendmentTableImpl::enable(uint256 const& amendment)
|
||||||
{
|
{
|
||||||
std::lock_guard sl(mutex_);
|
std::lock_guard lock(mutex_);
|
||||||
auto s = add(amendment, sl);
|
AmendmentState& s = add(amendment, lock);
|
||||||
|
|
||||||
if (s->enabled)
|
if (s.enabled)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
s->enabled = true;
|
s.enabled = true;
|
||||||
|
|
||||||
if (!s->supported)
|
if (!s.supported)
|
||||||
{
|
{
|
||||||
JLOG(j_.error()) << "Unsupported amendment " << amendment
|
JLOG(j_.error()) << "Unsupported amendment " << amendment
|
||||||
<< " activated.";
|
<< " activated.";
|
||||||
@@ -535,30 +543,30 @@ AmendmentTableImpl::enable(uint256 const& amendment)
|
|||||||
bool
|
bool
|
||||||
AmendmentTableImpl::isEnabled(uint256 const& amendment) const
|
AmendmentTableImpl::isEnabled(uint256 const& amendment) const
|
||||||
{
|
{
|
||||||
std::lock_guard sl(mutex_);
|
std::lock_guard lock(mutex_);
|
||||||
auto s = get(amendment, sl);
|
AmendmentState const* s = get(amendment, lock);
|
||||||
return s && s->enabled;
|
return s && s->enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
AmendmentTableImpl::isSupported(uint256 const& amendment) const
|
AmendmentTableImpl::isSupported(uint256 const& amendment) const
|
||||||
{
|
{
|
||||||
std::lock_guard sl(mutex_);
|
std::lock_guard lock(mutex_);
|
||||||
auto s = get(amendment, sl);
|
AmendmentState const* s = get(amendment, lock);
|
||||||
return s && s->supported;
|
return s && s->supported;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
AmendmentTableImpl::hasUnsupportedEnabled() const
|
AmendmentTableImpl::hasUnsupportedEnabled() const
|
||||||
{
|
{
|
||||||
std::lock_guard sl(mutex_);
|
std::lock_guard lock(mutex_);
|
||||||
return unsupportedEnabled_;
|
return unsupportedEnabled_;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<NetClock::time_point>
|
std::optional<NetClock::time_point>
|
||||||
AmendmentTableImpl::firstUnsupportedExpected() const
|
AmendmentTableImpl::firstUnsupportedExpected() const
|
||||||
{
|
{
|
||||||
std::lock_guard sl(mutex_);
|
std::lock_guard lock(mutex_);
|
||||||
return firstUnsupportedExpected_;
|
return firstUnsupportedExpected_;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -570,14 +578,15 @@ AmendmentTableImpl::doValidation(std::set<uint256> const& enabled) const
|
|||||||
std::vector<uint256> amendments;
|
std::vector<uint256> amendments;
|
||||||
|
|
||||||
{
|
{
|
||||||
std::lock_guard sl(mutex_);
|
std::lock_guard lock(mutex_);
|
||||||
amendments.reserve(amendmentMap_.size());
|
amendments.reserve(amendmentMap_.size());
|
||||||
for (auto const& e : amendmentMap_)
|
for (auto const& e : amendmentMap_)
|
||||||
{
|
{
|
||||||
if (e.second.supported && !e.second.vetoed &&
|
if (e.second.supported && e.second.vote == AmendmentVote::up &&
|
||||||
(enabled.count(e.first) == 0))
|
(enabled.count(e.first) == 0))
|
||||||
{
|
{
|
||||||
amendments.push_back(e.first);
|
amendments.push_back(e.first);
|
||||||
|
JLOG(j_.info()) << "Voting for amendment " << e.second.name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -617,7 +626,7 @@ AmendmentTableImpl::doVoting(
|
|||||||
// the value of the flags in the pseudo-transaction
|
// the value of the flags in the pseudo-transaction
|
||||||
std::map<uint256, std::uint32_t> actions;
|
std::map<uint256, std::uint32_t> actions;
|
||||||
|
|
||||||
std::lock_guard sl(mutex_);
|
std::lock_guard lock(mutex_);
|
||||||
|
|
||||||
// process all amendments we know of
|
// process all amendments we know of
|
||||||
for (auto const& entry : amendmentMap_)
|
for (auto const& entry : amendmentMap_)
|
||||||
@@ -638,7 +647,7 @@ AmendmentTableImpl::doVoting(
|
|||||||
}
|
}
|
||||||
else if (
|
else if (
|
||||||
hasValMajority && (majorityTime == NetClock::time_point{}) &&
|
hasValMajority && (majorityTime == NetClock::time_point{}) &&
|
||||||
!entry.second.vetoed)
|
entry.second.vote == AmendmentVote::up)
|
||||||
{
|
{
|
||||||
// Ledger says no majority, validators say yes
|
// Ledger says no majority, validators say yes
|
||||||
JLOG(j_.debug()) << entry.first << ": amendment got majority";
|
JLOG(j_.debug()) << entry.first << ": amendment got majority";
|
||||||
@@ -653,7 +662,7 @@ AmendmentTableImpl::doVoting(
|
|||||||
else if (
|
else if (
|
||||||
(majorityTime != NetClock::time_point{}) &&
|
(majorityTime != NetClock::time_point{}) &&
|
||||||
((majorityTime + majorityTime_) <= closeTime) &&
|
((majorityTime + majorityTime_) <= closeTime) &&
|
||||||
!entry.second.vetoed)
|
entry.second.vote == AmendmentVote::up)
|
||||||
{
|
{
|
||||||
// Ledger says majority held
|
// Ledger says majority held
|
||||||
JLOG(j_.debug()) << entry.first << ": amendment majority held";
|
JLOG(j_.debug()) << entry.first << ": amendment majority held";
|
||||||
@@ -669,7 +678,7 @@ AmendmentTableImpl::doVoting(
|
|||||||
bool
|
bool
|
||||||
AmendmentTableImpl::needValidatedLedger(LedgerIndex ledgerSeq) const
|
AmendmentTableImpl::needValidatedLedger(LedgerIndex ledgerSeq) const
|
||||||
{
|
{
|
||||||
std::lock_guard sl(mutex_);
|
std::lock_guard lock(mutex_);
|
||||||
|
|
||||||
// Is there a ledger in which an amendment could have been enabled
|
// Is there a ledger in which an amendment could have been enabled
|
||||||
// between these two ledger sequences?
|
// between these two ledger sequences?
|
||||||
@@ -686,7 +695,7 @@ AmendmentTableImpl::doValidatedLedger(
|
|||||||
for (auto& e : enabled)
|
for (auto& e : enabled)
|
||||||
enable(e);
|
enable(e);
|
||||||
|
|
||||||
std::lock_guard sl(mutex_);
|
std::lock_guard lock(mutex_);
|
||||||
|
|
||||||
// Remember the ledger sequence of this update.
|
// Remember the ledger sequence of this update.
|
||||||
lastUpdateSeq_ = ledgerSeq;
|
lastUpdateSeq_ = ledgerSeq;
|
||||||
@@ -697,12 +706,12 @@ AmendmentTableImpl::doValidatedLedger(
|
|||||||
firstUnsupportedExpected_.reset();
|
firstUnsupportedExpected_.reset();
|
||||||
for (auto const& [hash, time] : majority)
|
for (auto const& [hash, time] : majority)
|
||||||
{
|
{
|
||||||
auto s = add(hash, sl);
|
AmendmentState& s = add(hash, lock);
|
||||||
|
|
||||||
if (s->enabled)
|
if (s.enabled)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (!s->supported)
|
if (!s.supported)
|
||||||
{
|
{
|
||||||
JLOG(j_.info()) << "Unsupported amendment " << hash
|
JLOG(j_.info()) << "Unsupported amendment " << hash
|
||||||
<< " reached majority at " << to_string(time);
|
<< " reached majority at " << to_string(time);
|
||||||
@@ -725,7 +734,7 @@ AmendmentTableImpl::injectJson(
|
|||||||
v[jss::name] = fs.name;
|
v[jss::name] = fs.name;
|
||||||
|
|
||||||
v[jss::supported] = fs.supported;
|
v[jss::supported] = fs.supported;
|
||||||
v[jss::vetoed] = fs.vetoed;
|
v[jss::vetoed] = fs.vote == AmendmentVote::down;
|
||||||
v[jss::enabled] = fs.enabled;
|
v[jss::enabled] = fs.enabled;
|
||||||
|
|
||||||
if (!fs.enabled && lastVote_)
|
if (!fs.enabled && lastVote_)
|
||||||
@@ -747,14 +756,14 @@ AmendmentTableImpl::getJson() const
|
|||||||
{
|
{
|
||||||
Json::Value ret(Json::objectValue);
|
Json::Value ret(Json::objectValue);
|
||||||
{
|
{
|
||||||
std::lock_guard sl(mutex_);
|
std::lock_guard lock(mutex_);
|
||||||
for (auto const& e : amendmentMap_)
|
for (auto const& e : amendmentMap_)
|
||||||
{
|
{
|
||||||
injectJson(
|
injectJson(
|
||||||
ret[to_string(e.first)] = Json::objectValue,
|
ret[to_string(e.first)] = Json::objectValue,
|
||||||
e.first,
|
e.first,
|
||||||
e.second,
|
e.second,
|
||||||
sl);
|
lock);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
@@ -767,10 +776,10 @@ AmendmentTableImpl::getJson(uint256 const& amendmentID) const
|
|||||||
Json::Value& jAmendment = (ret[to_string(amendmentID)] = Json::objectValue);
|
Json::Value& jAmendment = (ret[to_string(amendmentID)] = Json::objectValue);
|
||||||
|
|
||||||
{
|
{
|
||||||
std::lock_guard sl(mutex_);
|
std::lock_guard lock(mutex_);
|
||||||
auto a = get(amendmentID, sl);
|
AmendmentState const* a = get(amendmentID, lock);
|
||||||
if (a)
|
if (a)
|
||||||
injectJson(jAmendment, amendmentID, *a, sl);
|
injectJson(jAmendment, amendmentID, *a, lock);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
@@ -780,7 +789,7 @@ std::unique_ptr<AmendmentTable>
|
|||||||
make_AmendmentTable(
|
make_AmendmentTable(
|
||||||
Application& app,
|
Application& app,
|
||||||
std::chrono::seconds majorityTime,
|
std::chrono::seconds majorityTime,
|
||||||
Section const& supported,
|
std::vector<AmendmentTable::FeatureInfo> const& supported,
|
||||||
Section const& enabled,
|
Section const& enabled,
|
||||||
Section const& vetoed,
|
Section const& vetoed,
|
||||||
beast::Journal journal)
|
beast::Journal journal)
|
||||||
|
|||||||
@@ -132,11 +132,15 @@ deletePeerReservation(soci::session& session, PublicKey const& nodeId);
|
|||||||
bool
|
bool
|
||||||
createFeatureVotes(soci::session& session);
|
createFeatureVotes(soci::session& session);
|
||||||
|
|
||||||
|
// For historical reasons the up-vote and down-vote integer representations
|
||||||
|
// are unintuitive.
|
||||||
|
enum class AmendmentVote : int { up = 0, down = 1 };
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief readAmendments Read all amendments from FeatureVotes table.
|
* @brief readAmendments Read all amendments from FeatureVotes table.
|
||||||
* @param session Session with walletDB database.
|
* @param session Session with walletDB database.
|
||||||
* @param callback Callback called for each amendment passing its hash, name
|
* @param callback Callback called for each amendment passing its hash, name
|
||||||
* and teh flag if it should be vetoed as callback parameters
|
* and the flag if it should be vetoed as callback parameters
|
||||||
*/
|
*/
|
||||||
void
|
void
|
||||||
readAmendments(
|
readAmendments(
|
||||||
@@ -144,21 +148,21 @@ readAmendments(
|
|||||||
std::function<void(
|
std::function<void(
|
||||||
boost::optional<std::string> amendment_hash,
|
boost::optional<std::string> amendment_hash,
|
||||||
boost::optional<std::string> amendment_name,
|
boost::optional<std::string> amendment_name,
|
||||||
boost::optional<int> vote_to_veto)> const& callback);
|
boost::optional<AmendmentVote> vote)> const& callback);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief voteAmendment Set veto value for particular amendment.
|
* @brief voteAmendment Set veto value for particular amendment.
|
||||||
* @param session Session with walletDB database.
|
* @param session Session with walletDB database.
|
||||||
* @param amendment Hash of amendment.
|
* @param amendment Hash of amendment.
|
||||||
* @param name Name of amendment.
|
* @param name Name of amendment.
|
||||||
* @param vote_to_veto Trus if this amendment should be vetoed.
|
* @param vote Whether to vote in favor of this amendment.
|
||||||
*/
|
*/
|
||||||
void
|
void
|
||||||
voteAmendment(
|
voteAmendment(
|
||||||
soci::session& session,
|
soci::session& session,
|
||||||
uint256 const& amendment,
|
uint256 const& amendment,
|
||||||
std::string const& name,
|
std::string const& name,
|
||||||
bool vote_to_veto);
|
AmendmentVote vote);
|
||||||
|
|
||||||
/* State DB */
|
/* State DB */
|
||||||
|
|
||||||
|
|||||||
@@ -258,8 +258,14 @@ readAmendments(
|
|||||||
std::function<void(
|
std::function<void(
|
||||||
boost::optional<std::string> amendment_hash,
|
boost::optional<std::string> amendment_hash,
|
||||||
boost::optional<std::string> amendment_name,
|
boost::optional<std::string> amendment_name,
|
||||||
boost::optional<int> vote_to_veto)> const& callback)
|
boost::optional<AmendmentVote> vote)> const& callback)
|
||||||
{
|
{
|
||||||
|
// lambda that converts the internally stored int to an AmendmentVote.
|
||||||
|
auto intToVote = [](boost::optional<int> const& dbVote)
|
||||||
|
-> boost::optional<AmendmentVote> {
|
||||||
|
return safe_cast<AmendmentVote>(dbVote.value_or(1));
|
||||||
|
};
|
||||||
|
|
||||||
soci::transaction tr(session);
|
soci::transaction tr(session);
|
||||||
std::string sql =
|
std::string sql =
|
||||||
"SELECT AmendmentHash, AmendmentName, Veto FROM FeatureVotes";
|
"SELECT AmendmentHash, AmendmentName, Veto FROM FeatureVotes";
|
||||||
@@ -275,7 +281,7 @@ readAmendments(
|
|||||||
st.execute();
|
st.execute();
|
||||||
while (st.fetch())
|
while (st.fetch())
|
||||||
{
|
{
|
||||||
callback(amendment_hash, amendment_name, vote_to_veto);
|
callback(amendment_hash, amendment_name, intToVote(vote_to_veto));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -284,7 +290,7 @@ voteAmendment(
|
|||||||
soci::session& session,
|
soci::session& session,
|
||||||
uint256 const& amendment,
|
uint256 const& amendment,
|
||||||
std::string const& name,
|
std::string const& name,
|
||||||
bool vote_to_veto)
|
AmendmentVote vote)
|
||||||
{
|
{
|
||||||
soci::transaction tr(session);
|
soci::transaction tr(session);
|
||||||
std::string sql =
|
std::string sql =
|
||||||
@@ -292,7 +298,7 @@ voteAmendment(
|
|||||||
"('";
|
"('";
|
||||||
sql += to_string(amendment);
|
sql += to_string(amendment);
|
||||||
sql += "', '" + name;
|
sql += "', '" + name;
|
||||||
sql += "', '" + std::to_string(int{vote_to_veto}) + "');";
|
sql += "', '" + std::to_string(safe_cast<int>(vote)) + "');";
|
||||||
session << sql;
|
session << sql;
|
||||||
tr.commit();
|
tr.commit();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,121 +32,70 @@
|
|||||||
*
|
*
|
||||||
* Steps required to add new features to the code:
|
* Steps required to add new features to the code:
|
||||||
*
|
*
|
||||||
* 1) add the new feature name to the featureNames array below
|
* 1) In this file, increment `numFeatures` and add a uint256 declaration
|
||||||
* 2) add a uint256 declaration for the feature to the bottom of this file
|
* for the feature at the bottom
|
||||||
* 3) add a uint256 definition for the feature to the corresponding source
|
* 2) Add a uint256 definition for the feature to the corresponding source
|
||||||
* file (Feature.cpp)
|
* file (Feature.cpp). Use `registerFeature` to create the feature with
|
||||||
* 4) if the feature is going to be supported in the near future, add its
|
* the feature's name, `Supported::no`, and `DefaultVote::no`. This
|
||||||
* sha512half value and name (matching exactly the featureName here) to
|
* should be the only place the feature's name appears in code as a string.
|
||||||
* the supportedAmendments in Feature.cpp.
|
* 3) Use the uint256 as the parameter to `view.rules.enabled()` to
|
||||||
|
* control flow into new code that this feature limits.
|
||||||
|
* 4) If the feature development is COMPLETE, and the feature is ready to be
|
||||||
|
* SUPPORTED, change the `registerFeature` parameter to Supported::yes.
|
||||||
|
* 5) When the feature is ready to be ENABLED, change the `registerFeature`
|
||||||
|
* parameter to `DefaultVote::yes`.
|
||||||
|
* In general, any newly supported amendments (`Supported::yes`) should have
|
||||||
|
* a `DefaultVote::no` for at least one full release cycle. High priority
|
||||||
|
* bug fixes can be an exception to this rule of thumb.
|
||||||
|
*
|
||||||
|
* When a feature has been enabled for several years, the conditional code
|
||||||
|
* may be removed, and the feature "retired". To retire a feature:
|
||||||
|
* 1) Remove the uint256 declaration from this file.
|
||||||
|
* 2) MOVE the uint256 definition in Feature.cpp to the "retired features"
|
||||||
|
* section at the end of the file.
|
||||||
|
* 3) CHANGE the name of the variable to start with "retired".
|
||||||
|
* 4) CHANGE the parameters of the `registerFeature` call to `Supported::yes`
|
||||||
|
* and `DefaultVote::no`.
|
||||||
|
* The feature must remain registered and supported indefinitely because it
|
||||||
|
* still exists in the ledger, but there is no need to vote for it because
|
||||||
|
* there's nothing to vote for. If it is removed completely from the code, any
|
||||||
|
* instances running that code will get amendment blocked. Removing the
|
||||||
|
* feature from the ledger is beyond the scope of these instructions.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace ripple {
|
namespace ripple {
|
||||||
|
|
||||||
|
enum class DefaultVote : bool { no = false, yes };
|
||||||
|
|
||||||
namespace detail {
|
namespace detail {
|
||||||
|
|
||||||
// *NOTE*
|
// This value SHOULD be equal to the number of amendments registered in
|
||||||
//
|
// Feature.cpp. Because it's only used to reserve storage, and determine how
|
||||||
// Features, or Amendments as they are called elsewhere, are enabled on the
|
// large to make the FeatureBitset, it MAY be larger. It MUST NOT be less than
|
||||||
// network at some specific time based on Validator voting. Features are
|
// the actual number of amendments. A LogicError on startup will verify this.
|
||||||
// enabled using run-time conditionals based on the state of the amendment.
|
static constexpr std::size_t numFeatures = 46;
|
||||||
// There is value in retaining that conditional code for some time after
|
|
||||||
// the amendment is enabled to make it simple to replay old transactions.
|
|
||||||
// However, once an Amendment has been enabled for, say, more than two years
|
|
||||||
// then retaining that conditional code has less value since it is
|
|
||||||
// uncommon to replay such old transactions.
|
|
||||||
//
|
|
||||||
// Starting in January of 2020 Amendment conditionals from before January
|
|
||||||
// 2018 are being removed. So replaying any ledger from before January
|
|
||||||
// 2018 needs to happen on an older version of the server code. There's
|
|
||||||
// a log message in Application.cpp that warns about replaying old ledgers.
|
|
||||||
//
|
|
||||||
// At some point in the future someone may wish to remove Amendment
|
|
||||||
// conditional code for Amendments that were enabled after January 2018.
|
|
||||||
// When that happens then the log message in Application.cpp should be
|
|
||||||
// updated.
|
|
||||||
|
|
||||||
class FeatureCollections
|
/** Amendments that this server supports and the default voting behavior.
|
||||||
{
|
Whether they are enabled depends on the Rules defined in the validated
|
||||||
static constexpr char const* const featureNames[] = {
|
ledger */
|
||||||
"MultiSign", // Unconditionally supported.
|
std::map<std::string, DefaultVote> const&
|
||||||
"TrustSetAuth", // Unconditionally supported.
|
|
||||||
"FeeEscalation", // Unconditionally supported.
|
|
||||||
"OwnerPaysFee",
|
|
||||||
"PayChan",
|
|
||||||
"Flow", // Unconditionally supported.
|
|
||||||
"CompareTakerFlowCross",
|
|
||||||
"FlowCross",
|
|
||||||
"CryptoConditions",
|
|
||||||
"TickSize",
|
|
||||||
"fix1368",
|
|
||||||
"Escrow",
|
|
||||||
"CryptoConditionsSuite",
|
|
||||||
"fix1373",
|
|
||||||
"EnforceInvariants",
|
|
||||||
"SortedDirectories",
|
|
||||||
"fix1201",
|
|
||||||
"fix1512",
|
|
||||||
"fix1513",
|
|
||||||
"fix1523",
|
|
||||||
"fix1528",
|
|
||||||
"DepositAuth",
|
|
||||||
"Checks",
|
|
||||||
"fix1571",
|
|
||||||
"fix1543",
|
|
||||||
"fix1623",
|
|
||||||
"DepositPreauth",
|
|
||||||
"fix1515",
|
|
||||||
"fix1578",
|
|
||||||
"MultiSignReserve",
|
|
||||||
"fixTakerDryOfferRemoval",
|
|
||||||
"fixMasterKeyAsRegularKey",
|
|
||||||
"fixCheckThreading",
|
|
||||||
"fixPayChanRecipientOwnerDir",
|
|
||||||
"DeletableAccounts",
|
|
||||||
// fixQualityUpperBound should be activated before FlowCross
|
|
||||||
"fixQualityUpperBound",
|
|
||||||
"RequireFullyCanonicalSig",
|
|
||||||
"fix1781", // XRPEndpointSteps should be included in the circular
|
|
||||||
// payment check
|
|
||||||
"HardenedValidations",
|
|
||||||
"fixAmendmentMajorityCalc", // Fix Amendment majority calculation
|
|
||||||
"NegativeUNL",
|
|
||||||
"TicketBatch",
|
|
||||||
"FlowSortStrands",
|
|
||||||
"fixSTAmountCanonicalize",
|
|
||||||
"fixRmSmallIncreasedQOffers",
|
|
||||||
"CheckCashMakesTrustLine",
|
|
||||||
};
|
|
||||||
|
|
||||||
std::vector<uint256> features;
|
|
||||||
boost::container::flat_map<uint256, std::size_t> featureToIndex;
|
|
||||||
boost::container::flat_map<std::string, uint256> nameToFeature;
|
|
||||||
|
|
||||||
public:
|
|
||||||
FeatureCollections();
|
|
||||||
|
|
||||||
static constexpr std::size_t
|
|
||||||
numFeatures()
|
|
||||||
{
|
|
||||||
return sizeof(featureNames) / sizeof(featureNames[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<uint256>
|
|
||||||
getRegisteredFeature(std::string const& name) const;
|
|
||||||
|
|
||||||
std::size_t
|
|
||||||
featureToBitsetIndex(uint256 const& f) const;
|
|
||||||
|
|
||||||
uint256 const&
|
|
||||||
bitsetIndexToFeature(size_t i) const;
|
|
||||||
};
|
|
||||||
|
|
||||||
/** Amendments that this server supports, but doesn't enable by default */
|
|
||||||
std::vector<std::string> const&
|
|
||||||
supportedAmendments();
|
supportedAmendments();
|
||||||
|
|
||||||
|
/** Amendments that this server won't vote for by default.
|
||||||
|
|
||||||
|
This function is only used in unit tests.
|
||||||
|
*/
|
||||||
|
std::size_t
|
||||||
|
numDownVotedAmendments();
|
||||||
|
|
||||||
|
/** Amendments that this server will vote for by default.
|
||||||
|
|
||||||
|
This function is only used in unit tests.
|
||||||
|
*/
|
||||||
|
std::size_t
|
||||||
|
numUpVotedAmendments();
|
||||||
|
|
||||||
} // namespace detail
|
} // namespace detail
|
||||||
|
|
||||||
std::optional<uint256>
|
std::optional<uint256>
|
||||||
@@ -158,10 +107,12 @@ featureToBitsetIndex(uint256 const& f);
|
|||||||
uint256
|
uint256
|
||||||
bitsetIndexToFeature(size_t i);
|
bitsetIndexToFeature(size_t i);
|
||||||
|
|
||||||
class FeatureBitset
|
std::string
|
||||||
: private std::bitset<detail::FeatureCollections::numFeatures()>
|
featureToName(uint256 const& f);
|
||||||
|
|
||||||
|
class FeatureBitset : private std::bitset<detail::numFeatures>
|
||||||
{
|
{
|
||||||
using base = std::bitset<detail::FeatureCollections::numFeatures()>;
|
using base = std::bitset<detail::numFeatures>;
|
||||||
|
|
||||||
template <class... Fs>
|
template <class... Fs>
|
||||||
void
|
void
|
||||||
@@ -195,12 +146,14 @@ public:
|
|||||||
|
|
||||||
explicit FeatureBitset(base const& b) : base(b)
|
explicit FeatureBitset(base const& b) : base(b)
|
||||||
{
|
{
|
||||||
|
assert(b.count() == count());
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class... Fs>
|
template <class... Fs>
|
||||||
explicit FeatureBitset(uint256 const& f, Fs&&... fs)
|
explicit FeatureBitset(uint256 const& f, Fs&&... fs)
|
||||||
{
|
{
|
||||||
initFromFeatures(f, std::forward<Fs>(fs)...);
|
initFromFeatures(f, std::forward<Fs>(fs)...);
|
||||||
|
assert(count() == (sizeof...(fs) + 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class Col>
|
template <class Col>
|
||||||
@@ -208,6 +161,7 @@ public:
|
|||||||
{
|
{
|
||||||
for (auto const& f : fs)
|
for (auto const& f : fs)
|
||||||
set(featureToBitsetIndex(f));
|
set(featureToBitsetIndex(f));
|
||||||
|
assert(fs.size() == count());
|
||||||
}
|
}
|
||||||
|
|
||||||
auto
|
auto
|
||||||
|
|||||||
@@ -17,128 +17,317 @@
|
|||||||
*/
|
*/
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
|
|
||||||
#include <ripple/basics/contract.h>
|
|
||||||
#include <ripple/protocol/Feature.h>
|
#include <ripple/protocol/Feature.h>
|
||||||
#include <ripple/protocol/digest.h>
|
|
||||||
|
|
||||||
|
#include <ripple/basics/Slice.h>
|
||||||
|
#include <ripple/basics/contract.h>
|
||||||
|
#include <ripple/protocol/digest.h>
|
||||||
|
#include <boost/container_hash/hash.hpp>
|
||||||
|
#include <boost/multi_index/hashed_index.hpp>
|
||||||
|
#include <boost/multi_index/key_extractors.hpp>
|
||||||
|
#include <boost/multi_index/random_access_index.hpp>
|
||||||
|
#include <boost/multi_index_container.hpp>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
||||||
namespace ripple {
|
namespace ripple {
|
||||||
|
|
||||||
|
inline std::size_t
|
||||||
|
hash_value(ripple::uint256 const& feature)
|
||||||
|
{
|
||||||
|
std::size_t seed = 0;
|
||||||
|
using namespace boost;
|
||||||
|
for (auto const& n : feature)
|
||||||
|
hash_combine(seed, n);
|
||||||
|
return seed;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
enum class Supported : bool { no = false, yes };
|
||||||
|
|
||||||
|
// *NOTE*
|
||||||
|
//
|
||||||
|
// Features, or Amendments as they are called elsewhere, are enabled on the
|
||||||
|
// network at some specific time based on Validator voting. Features are
|
||||||
|
// enabled using run-time conditionals based on the state of the amendment.
|
||||||
|
// There is value in retaining that conditional code for some time after
|
||||||
|
// the amendment is enabled to make it simple to replay old transactions.
|
||||||
|
// However, once an Amendment has been enabled for, say, more than two years
|
||||||
|
// then retaining that conditional code has less value since it is
|
||||||
|
// uncommon to replay such old transactions.
|
||||||
|
//
|
||||||
|
// Starting in January of 2020 Amendment conditionals from before January
|
||||||
|
// 2018 are being removed. So replaying any ledger from before January
|
||||||
|
// 2018 needs to happen on an older version of the server code. There's
|
||||||
|
// a log message in Application.cpp that warns about replaying old ledgers.
|
||||||
|
//
|
||||||
|
// At some point in the future someone may wish to remove Amendment
|
||||||
|
// conditional code for Amendments that were enabled after January 2018.
|
||||||
|
// When that happens then the log message in Application.cpp should be
|
||||||
|
// updated.
|
||||||
|
|
||||||
|
class FeatureCollections
|
||||||
|
{
|
||||||
|
struct Feature
|
||||||
|
{
|
||||||
|
std::string name;
|
||||||
|
uint256 feature;
|
||||||
|
|
||||||
|
Feature() = delete;
|
||||||
|
explicit Feature(std::string const& name_, uint256 const& feature_)
|
||||||
|
: name(name_), feature(feature_)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
// These structs are used by the `features` multi_index_container to
|
||||||
|
// provide access to the features collection by size_t index, string
|
||||||
|
// name, and uint256 feature identifier
|
||||||
|
struct byIndex
|
||||||
|
{
|
||||||
|
};
|
||||||
|
struct byName
|
||||||
|
{
|
||||||
|
};
|
||||||
|
struct byFeature
|
||||||
|
{
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Intermediate types to help with readability
|
||||||
|
template <class tag, typename Type, Type Feature::*PtrToMember>
|
||||||
|
using feature_hashed_unique = boost::multi_index::hashed_unique<
|
||||||
|
boost::multi_index::tag<tag>,
|
||||||
|
boost::multi_index::member<Feature, Type, PtrToMember>>;
|
||||||
|
|
||||||
|
// Intermediate types to help with readability
|
||||||
|
using feature_indexing = boost::multi_index::indexed_by<
|
||||||
|
boost::multi_index::random_access<
|
||||||
|
boost::multi_index::tag<Feature::byIndex>>,
|
||||||
|
feature_hashed_unique<Feature::byFeature, uint256, &Feature::feature>,
|
||||||
|
feature_hashed_unique<Feature::byName, std::string, &Feature::name>>;
|
||||||
|
|
||||||
|
// This multi_index_container provides access to the features collection by
|
||||||
|
// name, index, and uint256 feature identifier
|
||||||
|
boost::multi_index::multi_index_container<Feature, feature_indexing>
|
||||||
|
features;
|
||||||
|
std::map<std::string, DefaultVote> supported;
|
||||||
|
std::size_t upVotes = 0;
|
||||||
|
std::size_t downVotes = 0;
|
||||||
|
mutable std::atomic<bool> readOnly = false;
|
||||||
|
|
||||||
|
// These helper functions provide access to the features collection by name,
|
||||||
|
// index, and uint256 feature identifier, so the details of
|
||||||
|
// multi_index_container can be hidden
|
||||||
|
Feature const&
|
||||||
|
getByIndex(size_t i) const
|
||||||
|
{
|
||||||
|
if (i >= features.size())
|
||||||
|
LogicError("Invalid FeatureBitset index");
|
||||||
|
const auto& sequence = features.get<Feature::byIndex>();
|
||||||
|
return sequence[i];
|
||||||
|
}
|
||||||
|
size_t
|
||||||
|
getIndex(Feature const& feature) const
|
||||||
|
{
|
||||||
|
const auto& sequence = features.get<Feature::byIndex>();
|
||||||
|
auto const it_to = sequence.iterator_to(feature);
|
||||||
|
return it_to - sequence.begin();
|
||||||
|
}
|
||||||
|
Feature const*
|
||||||
|
getByFeature(uint256 const& feature) const
|
||||||
|
{
|
||||||
|
const auto& feature_index = features.get<Feature::byFeature>();
|
||||||
|
auto const feature_it = feature_index.find(feature);
|
||||||
|
return feature_it == feature_index.end() ? nullptr : &*feature_it;
|
||||||
|
}
|
||||||
|
Feature const*
|
||||||
|
getByName(std::string const& name) const
|
||||||
|
{
|
||||||
|
const auto& name_index = features.get<Feature::byName>();
|
||||||
|
auto const name_it = name_index.find(name);
|
||||||
|
return name_it == name_index.end() ? nullptr : &*name_it;
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
FeatureCollections();
|
||||||
|
|
||||||
|
std::optional<uint256>
|
||||||
|
getRegisteredFeature(std::string const& name) const;
|
||||||
|
|
||||||
|
uint256
|
||||||
|
registerFeature(
|
||||||
|
std::string const& name,
|
||||||
|
Supported support,
|
||||||
|
DefaultVote vote);
|
||||||
|
|
||||||
|
/** Tell FeatureCollections when registration is complete. */
|
||||||
|
bool
|
||||||
|
registrationIsDone();
|
||||||
|
|
||||||
|
std::size_t
|
||||||
|
featureToBitsetIndex(uint256 const& f) const;
|
||||||
|
|
||||||
|
uint256 const&
|
||||||
|
bitsetIndexToFeature(size_t i) const;
|
||||||
|
|
||||||
|
std::string
|
||||||
|
featureToName(uint256 const& f) const;
|
||||||
|
|
||||||
|
/** Amendments that this server supports.
|
||||||
|
Whether they are enabled depends on the Rules defined in the validated
|
||||||
|
ledger */
|
||||||
|
std::map<std::string, DefaultVote> const&
|
||||||
|
supportedAmendments() const
|
||||||
|
{
|
||||||
|
return supported;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Amendments that this server WON'T vote for by default. */
|
||||||
|
std::size_t
|
||||||
|
numDownVotedAmendments() const
|
||||||
|
{
|
||||||
|
return downVotes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Amendments that this server WILL vote for by default. */
|
||||||
|
std::size_t
|
||||||
|
numUpVotedAmendments() const
|
||||||
|
{
|
||||||
|
return upVotes;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
constexpr char const* const detail::FeatureCollections::featureNames[];
|
FeatureCollections::FeatureCollections()
|
||||||
|
|
||||||
detail::FeatureCollections::FeatureCollections()
|
|
||||||
{
|
{
|
||||||
features.reserve(numFeatures());
|
features.reserve(ripple::detail::numFeatures);
|
||||||
featureToIndex.reserve(numFeatures());
|
|
||||||
nameToFeature.reserve(numFeatures());
|
|
||||||
|
|
||||||
for (std::size_t i = 0; i < numFeatures(); ++i)
|
|
||||||
{
|
|
||||||
auto const name = featureNames[i];
|
|
||||||
sha512_half_hasher h;
|
|
||||||
h(name, std::strlen(name));
|
|
||||||
auto const f = static_cast<uint256>(h);
|
|
||||||
|
|
||||||
features.push_back(f);
|
|
||||||
featureToIndex[f] = i;
|
|
||||||
nameToFeature[name] = f;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<uint256>
|
std::optional<uint256>
|
||||||
detail::FeatureCollections::getRegisteredFeature(std::string const& name) const
|
FeatureCollections::getRegisteredFeature(std::string const& name) const
|
||||||
{
|
{
|
||||||
auto const i = nameToFeature.find(name);
|
assert(readOnly);
|
||||||
if (i == nameToFeature.end())
|
Feature const* feature = getByName(name);
|
||||||
|
if (feature)
|
||||||
|
return feature->feature;
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
return i->second;
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
check(bool condition, const char* logicErrorMessage)
|
||||||
|
{
|
||||||
|
if (!condition)
|
||||||
|
LogicError(logicErrorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint256
|
||||||
|
FeatureCollections::registerFeature(
|
||||||
|
std::string const& name,
|
||||||
|
Supported support,
|
||||||
|
DefaultVote vote)
|
||||||
|
{
|
||||||
|
check(!readOnly, "Attempting to register a feature after startup.");
|
||||||
|
check(
|
||||||
|
support == Supported::yes || vote == DefaultVote::no,
|
||||||
|
"Invalid feature parameters. Must be supported to be up-voted.");
|
||||||
|
Feature const* i = getByName(name);
|
||||||
|
if (!i)
|
||||||
|
{
|
||||||
|
// If this check fails, and you just added a feature, increase the
|
||||||
|
// numFeatures value in Feature.h
|
||||||
|
check(
|
||||||
|
features.size() < detail::numFeatures,
|
||||||
|
"More features defined than allocated. Adjust numFeatures in "
|
||||||
|
"Feature.h.");
|
||||||
|
|
||||||
|
auto const f = sha512Half(Slice(name.data(), name.size()));
|
||||||
|
|
||||||
|
features.emplace_back(name, f);
|
||||||
|
|
||||||
|
if (support == Supported::yes)
|
||||||
|
{
|
||||||
|
supported.emplace(name, vote);
|
||||||
|
|
||||||
|
if (vote == DefaultVote::yes)
|
||||||
|
++upVotes;
|
||||||
|
else
|
||||||
|
++downVotes;
|
||||||
|
}
|
||||||
|
check(
|
||||||
|
upVotes + downVotes == supported.size(),
|
||||||
|
"Feature counting logic broke");
|
||||||
|
check(
|
||||||
|
supported.size() <= features.size(),
|
||||||
|
"More supported features than defined features");
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
// Each feature should only be registered once
|
||||||
|
LogicError("Duplicate feature registration");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Tell FeatureCollections when registration is complete. */
|
||||||
|
bool
|
||||||
|
FeatureCollections::registrationIsDone()
|
||||||
|
{
|
||||||
|
readOnly = true;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t
|
size_t
|
||||||
detail::FeatureCollections::featureToBitsetIndex(uint256 const& f) const
|
FeatureCollections::featureToBitsetIndex(uint256 const& f) const
|
||||||
{
|
{
|
||||||
auto const i = featureToIndex.find(f);
|
assert(readOnly);
|
||||||
if (i == featureToIndex.end())
|
|
||||||
|
Feature const* feature = getByFeature(f);
|
||||||
|
if (!feature)
|
||||||
LogicError("Invalid Feature ID");
|
LogicError("Invalid Feature ID");
|
||||||
return i->second;
|
|
||||||
|
return getIndex(*feature);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint256 const&
|
uint256 const&
|
||||||
detail::FeatureCollections::bitsetIndexToFeature(size_t i) const
|
FeatureCollections::bitsetIndexToFeature(size_t i) const
|
||||||
{
|
{
|
||||||
if (i >= features.size())
|
assert(readOnly);
|
||||||
LogicError("Invalid FeatureBitset index");
|
Feature const& feature = getByIndex(i);
|
||||||
return features[i];
|
return feature.feature;
|
||||||
}
|
}
|
||||||
|
|
||||||
static detail::FeatureCollections const featureCollections;
|
std::string
|
||||||
|
FeatureCollections::featureToName(uint256 const& f) const
|
||||||
|
{
|
||||||
|
assert(readOnly);
|
||||||
|
Feature const* feature = getByFeature(f);
|
||||||
|
return feature ? feature->name : to_string(f);
|
||||||
|
}
|
||||||
|
|
||||||
/** Amendments that this server supports, but doesn't enable by default */
|
static FeatureCollections featureCollections;
|
||||||
std::vector<std::string> const&
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
/** Amendments that this server supports.
|
||||||
|
Whether they are enabled depends on the Rules defined in the validated
|
||||||
|
ledger */
|
||||||
|
std::map<std::string, DefaultVote> const&
|
||||||
detail::supportedAmendments()
|
detail::supportedAmendments()
|
||||||
{
|
{
|
||||||
// Commented out amendments will be supported in a future release (and
|
return featureCollections.supportedAmendments();
|
||||||
// uncommented at that time).
|
}
|
||||||
//
|
|
||||||
// There are also unconditionally supported amendments in the list.
|
/** Amendments that this server won't vote for by default. */
|
||||||
// Those are amendments that were enabled some time ago and the
|
std::size_t
|
||||||
// amendment conditional code has been removed.
|
detail::numDownVotedAmendments()
|
||||||
//
|
{
|
||||||
// ** WARNING **
|
return featureCollections.numDownVotedAmendments();
|
||||||
// Unconditionally supported amendments need to remain in the list.
|
}
|
||||||
// Removing them will cause servers to become amendment blocked.
|
|
||||||
static std::vector<std::string> const supported{
|
/** Amendments that this server will vote for by default. */
|
||||||
"MultiSign", // Unconditionally supported.
|
std::size_t
|
||||||
"TrustSetAuth", // Unconditionally supported.
|
detail::numUpVotedAmendments()
|
||||||
"FeeEscalation", // Unconditionally supported.
|
{
|
||||||
// "OwnerPaysFee",
|
return featureCollections.numUpVotedAmendments();
|
||||||
"PayChan",
|
|
||||||
"Flow",
|
|
||||||
"CryptoConditions",
|
|
||||||
"TickSize",
|
|
||||||
"fix1368",
|
|
||||||
"Escrow",
|
|
||||||
"CryptoConditionsSuite",
|
|
||||||
"fix1373",
|
|
||||||
"EnforceInvariants",
|
|
||||||
"FlowCross",
|
|
||||||
"SortedDirectories",
|
|
||||||
"fix1201",
|
|
||||||
"fix1512",
|
|
||||||
"fix1513",
|
|
||||||
"fix1523",
|
|
||||||
"fix1528",
|
|
||||||
"DepositAuth",
|
|
||||||
"Checks",
|
|
||||||
"fix1571",
|
|
||||||
"fix1543",
|
|
||||||
"fix1623",
|
|
||||||
"DepositPreauth",
|
|
||||||
// Use liquidity from strands that consume max offers, but mark as dry
|
|
||||||
"fix1515",
|
|
||||||
"fix1578",
|
|
||||||
"MultiSignReserve",
|
|
||||||
"fixTakerDryOfferRemoval",
|
|
||||||
"fixMasterKeyAsRegularKey",
|
|
||||||
"fixCheckThreading",
|
|
||||||
"fixPayChanRecipientOwnerDir",
|
|
||||||
"DeletableAccounts",
|
|
||||||
"fixQualityUpperBound",
|
|
||||||
"RequireFullyCanonicalSig",
|
|
||||||
"fix1781",
|
|
||||||
"HardenedValidations",
|
|
||||||
"fixAmendmentMajorityCalc",
|
|
||||||
"NegativeUNL",
|
|
||||||
"TicketBatch",
|
|
||||||
"FlowSortStrands",
|
|
||||||
"fixSTAmountCanonicalize",
|
|
||||||
"fixRmSmallIncreasedQOffers",
|
|
||||||
"CheckCashMakesTrustLine",
|
|
||||||
};
|
|
||||||
return supported;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
@@ -149,6 +338,27 @@ getRegisteredFeature(std::string const& name)
|
|||||||
return featureCollections.getRegisteredFeature(name);
|
return featureCollections.getRegisteredFeature(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint256
|
||||||
|
registerFeature(std::string const& name, Supported support, DefaultVote vote)
|
||||||
|
{
|
||||||
|
return featureCollections.registerFeature(name, support, vote);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retired features are in the ledger and have no code controlled by the
|
||||||
|
// feature. They need to be supported, but do not need to be voted on.
|
||||||
|
uint256
|
||||||
|
retireFeature(std::string const& name)
|
||||||
|
{
|
||||||
|
return registerFeature(name, Supported::yes, DefaultVote::no);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Tell FeatureCollections when registration is complete. */
|
||||||
|
bool
|
||||||
|
registrationIsDone()
|
||||||
|
{
|
||||||
|
return featureCollections.registrationIsDone();
|
||||||
|
}
|
||||||
|
|
||||||
size_t
|
size_t
|
||||||
featureToBitsetIndex(uint256 const& f)
|
featureToBitsetIndex(uint256 const& f)
|
||||||
{
|
{
|
||||||
@@ -161,57 +371,109 @@ bitsetIndexToFeature(size_t i)
|
|||||||
return featureCollections.bitsetIndexToFeature(i);
|
return featureCollections.bitsetIndexToFeature(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string
|
||||||
|
featureToName(uint256 const& f)
|
||||||
|
{
|
||||||
|
return featureCollections.featureToName(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma push_macro("REGISTER_FEATURE")
|
||||||
|
#undef REGISTER_FEATURE
|
||||||
|
|
||||||
|
/**
|
||||||
|
Takes the name of a feature, whether it's supported, and the default vote. Will
|
||||||
|
register the feature, and create a variable whose name is "feature" plus the
|
||||||
|
feature name.
|
||||||
|
*/
|
||||||
|
#define REGISTER_FEATURE(fName, supported, defaultvote) \
|
||||||
|
uint256 const feature##fName = \
|
||||||
|
registerFeature(#fName, supported, defaultvote)
|
||||||
|
|
||||||
|
#pragma push_macro("REGISTER_FIX")
|
||||||
|
#undef REGISTER_FIX
|
||||||
|
|
||||||
|
/**
|
||||||
|
Takes the name of a feature, whether it's supported, and the default vote. Will
|
||||||
|
register the feature, and create a variable whose name is the unmodified feature
|
||||||
|
name.
|
||||||
|
*/
|
||||||
|
#define REGISTER_FIX(fName, supported, defaultvote) \
|
||||||
|
uint256 const fName = registerFeature(#fName, supported, defaultvote)
|
||||||
|
|
||||||
// clang-format off
|
// clang-format off
|
||||||
|
|
||||||
uint256 const
|
// All known amendments must be registered either here or below with the
|
||||||
featureOwnerPaysFee = *getRegisteredFeature("OwnerPaysFee"),
|
// "retired" amendments
|
||||||
featureFlow = *getRegisteredFeature("Flow"),
|
REGISTER_FEATURE(OwnerPaysFee, Supported::no, DefaultVote::no);
|
||||||
featureFlowCross = *getRegisteredFeature("FlowCross"),
|
REGISTER_FEATURE(Flow, Supported::yes, DefaultVote::yes);
|
||||||
featureCryptoConditionsSuite = *getRegisteredFeature("CryptoConditionsSuite"),
|
REGISTER_FEATURE(FlowCross, Supported::yes, DefaultVote::yes);
|
||||||
fix1513 = *getRegisteredFeature("fix1513"),
|
REGISTER_FEATURE(CryptoConditionsSuite, Supported::yes, DefaultVote::no);
|
||||||
featureDepositAuth = *getRegisteredFeature("DepositAuth"),
|
REGISTER_FIX (fix1513, Supported::yes, DefaultVote::yes);
|
||||||
featureChecks = *getRegisteredFeature("Checks"),
|
REGISTER_FEATURE(DepositAuth, Supported::yes, DefaultVote::yes);
|
||||||
fix1571 = *getRegisteredFeature("fix1571"),
|
REGISTER_FEATURE(Checks, Supported::yes, DefaultVote::yes);
|
||||||
fix1543 = *getRegisteredFeature("fix1543"),
|
REGISTER_FIX (fix1571, Supported::yes, DefaultVote::yes);
|
||||||
fix1623 = *getRegisteredFeature("fix1623"),
|
REGISTER_FIX (fix1543, Supported::yes, DefaultVote::yes);
|
||||||
featureDepositPreauth = *getRegisteredFeature("DepositPreauth"),
|
REGISTER_FIX (fix1623, Supported::yes, DefaultVote::yes);
|
||||||
fix1515 = *getRegisteredFeature("fix1515"),
|
REGISTER_FEATURE(DepositPreauth, Supported::yes, DefaultVote::yes);
|
||||||
fix1578 = *getRegisteredFeature("fix1578"),
|
// Use liquidity from strands that consume max offers, but mark as dry
|
||||||
featureMultiSignReserve = *getRegisteredFeature("MultiSignReserve"),
|
REGISTER_FIX (fix1515, Supported::yes, DefaultVote::yes);
|
||||||
fixTakerDryOfferRemoval = *getRegisteredFeature("fixTakerDryOfferRemoval"),
|
REGISTER_FIX (fix1578, Supported::yes, DefaultVote::yes);
|
||||||
fixMasterKeyAsRegularKey = *getRegisteredFeature("fixMasterKeyAsRegularKey"),
|
REGISTER_FEATURE(MultiSignReserve, Supported::yes, DefaultVote::yes);
|
||||||
fixCheckThreading = *getRegisteredFeature("fixCheckThreading"),
|
REGISTER_FIX (fixTakerDryOfferRemoval, Supported::yes, DefaultVote::yes);
|
||||||
fixPayChanRecipientOwnerDir = *getRegisteredFeature("fixPayChanRecipientOwnerDir"),
|
REGISTER_FIX (fixMasterKeyAsRegularKey, Supported::yes, DefaultVote::yes);
|
||||||
featureDeletableAccounts = *getRegisteredFeature("DeletableAccounts"),
|
REGISTER_FIX (fixCheckThreading, Supported::yes, DefaultVote::yes);
|
||||||
fixQualityUpperBound = *getRegisteredFeature("fixQualityUpperBound"),
|
REGISTER_FIX (fixPayChanRecipientOwnerDir, Supported::yes, DefaultVote::yes);
|
||||||
featureRequireFullyCanonicalSig = *getRegisteredFeature("RequireFullyCanonicalSig"),
|
REGISTER_FEATURE(DeletableAccounts, Supported::yes, DefaultVote::yes);
|
||||||
fix1781 = *getRegisteredFeature("fix1781"),
|
// fixQualityUpperBound should be activated before FlowCross
|
||||||
featureHardenedValidations = *getRegisteredFeature("HardenedValidations"),
|
REGISTER_FIX (fixQualityUpperBound, Supported::yes, DefaultVote::yes);
|
||||||
fixAmendmentMajorityCalc = *getRegisteredFeature("fixAmendmentMajorityCalc"),
|
REGISTER_FEATURE(RequireFullyCanonicalSig, Supported::yes, DefaultVote::yes);
|
||||||
featureNegativeUNL = *getRegisteredFeature("NegativeUNL"),
|
// fix1781: XRPEndpointSteps should be included in the circular payment check
|
||||||
featureTicketBatch = *getRegisteredFeature("TicketBatch"),
|
REGISTER_FIX (fix1781, Supported::yes, DefaultVote::yes);
|
||||||
featureFlowSortStrands = *getRegisteredFeature("FlowSortStrands"),
|
REGISTER_FEATURE(HardenedValidations, Supported::yes, DefaultVote::yes);
|
||||||
fixSTAmountCanonicalize = *getRegisteredFeature("fixSTAmountCanonicalize"),
|
REGISTER_FIX (fixAmendmentMajorityCalc, Supported::yes, DefaultVote::yes);
|
||||||
fixRmSmallIncreasedQOffers = *getRegisteredFeature("fixRmSmallIncreasedQOffers"),
|
REGISTER_FEATURE(NegativeUNL, Supported::yes, DefaultVote::no);
|
||||||
featureCheckCashMakesTrustLine = *getRegisteredFeature("CheckCashMakesTrustLine");
|
REGISTER_FEATURE(TicketBatch, Supported::yes, DefaultVote::yes);
|
||||||
|
REGISTER_FEATURE(FlowSortStrands, Supported::yes, DefaultVote::yes);
|
||||||
|
REGISTER_FIX (fixSTAmountCanonicalize, Supported::yes, DefaultVote::yes);
|
||||||
|
REGISTER_FIX (fixRmSmallIncreasedQOffers, Supported::yes, DefaultVote::yes);
|
||||||
|
REGISTER_FEATURE(CheckCashMakesTrustLine, Supported::yes, DefaultVote::no);
|
||||||
|
|
||||||
// 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.
|
||||||
|
// All known amendments and amendments that may appear in a validated
|
||||||
|
// ledger must be registered either here or above with the "active" amendments
|
||||||
[[deprecated("The referenced amendment has been retired"), maybe_unused]]
|
[[deprecated("The referenced amendment has been retired"), maybe_unused]]
|
||||||
uint256 const
|
uint256 const
|
||||||
retiredPayChan = *getRegisteredFeature("PayChan"),
|
retiredMultiSign = retireFeature("MultiSign"),
|
||||||
retiredCryptoConditions = *getRegisteredFeature("CryptoConditions"),
|
retiredTrustSetAuth = retireFeature("TrustSetAuth"),
|
||||||
retiredTickSize = *getRegisteredFeature("TickSize"),
|
retiredFeeEscalation = retireFeature("FeeEscalation"),
|
||||||
retiredFix1368 = *getRegisteredFeature("fix1368"),
|
retiredPayChan = retireFeature("PayChan"),
|
||||||
retiredEscrow = *getRegisteredFeature("Escrow"),
|
retiredCryptoConditions = retireFeature("CryptoConditions"),
|
||||||
retiredFix1373 = *getRegisteredFeature("fix1373"),
|
retiredTickSize = retireFeature("TickSize"),
|
||||||
retiredEnforceInvariants = *getRegisteredFeature("EnforceInvariants"),
|
retiredFix1368 = retireFeature("fix1368"),
|
||||||
retiredSortedDirectories = *getRegisteredFeature("SortedDirectories"),
|
retiredEscrow = retireFeature("Escrow"),
|
||||||
retiredFix1201 = *getRegisteredFeature("fix1201"),
|
retiredFix1373 = retireFeature("fix1373"),
|
||||||
retiredFix1512 = *getRegisteredFeature("fix1512"),
|
retiredEnforceInvariants = retireFeature("EnforceInvariants"),
|
||||||
retiredFix1523 = *getRegisteredFeature("fix1523"),
|
retiredSortedDirectories = retireFeature("SortedDirectories"),
|
||||||
retiredFix1528 = *getRegisteredFeature("fix1528");
|
retiredFix1201 = retireFeature("fix1201"),
|
||||||
|
retiredFix1512 = retireFeature("fix1512"),
|
||||||
|
retiredFix1523 = retireFeature("fix1523"),
|
||||||
|
retiredFix1528 = retireFeature("fix1528");
|
||||||
|
|
||||||
// clang-format on
|
// clang-format on
|
||||||
|
|
||||||
|
#undef REGISTER_FIX
|
||||||
|
#pragma pop_macro("REGISTER_FIX")
|
||||||
|
|
||||||
|
#undef REGISTER_FEATURE
|
||||||
|
#pragma pop_macro("REGISTER_FEATURE")
|
||||||
|
|
||||||
|
// All of the features should now be registered, since variables in a cpp file
|
||||||
|
// are initialized from top to bottom.
|
||||||
|
//
|
||||||
|
// Use initialization of one final static variable to set
|
||||||
|
// featureCollections::readOnly.
|
||||||
|
[[maybe_unused]] static const bool readOnlySet =
|
||||||
|
featureCollections.registrationIsDone();
|
||||||
|
|
||||||
} // namespace ripple
|
} // namespace ripple
|
||||||
|
|||||||
@@ -62,6 +62,8 @@ doFeature(RPC::JsonContext& context)
|
|||||||
|
|
||||||
auto feature = table.find(context.params[jss::feature].asString());
|
auto feature = table.find(context.params[jss::feature].asString());
|
||||||
|
|
||||||
|
// If the feature is not found by name, try to parse the `feature` param as
|
||||||
|
// a feature ID. If that fails, return an error.
|
||||||
if (!feature && !feature.parseHex(context.params[jss::feature].asString()))
|
if (!feature && !feature.parseHex(context.params[jss::feature].asString()))
|
||||||
return rpcError(rpcBAD_FEATURE);
|
return rpcError(rpcBAD_FEATURE);
|
||||||
|
|
||||||
|
|||||||
@@ -86,11 +86,31 @@ private:
|
|||||||
return cfg;
|
return cfg;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static std::vector<AmendmentTable::FeatureInfo>
|
||||||
|
makeDefaultYes(std::vector<std::string> const& amendments)
|
||||||
|
{
|
||||||
|
std::vector<AmendmentTable::FeatureInfo> result;
|
||||||
|
result.reserve(amendments.size());
|
||||||
|
for (auto const& a : amendments)
|
||||||
|
{
|
||||||
|
result.emplace_back(a, amendmentId(a), DefaultVote::yes);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::vector<AmendmentTable::FeatureInfo>
|
||||||
|
makeDefaultYes(uint256 const amendment)
|
||||||
|
{
|
||||||
|
std::vector<AmendmentTable::FeatureInfo> result{
|
||||||
|
{to_string(amendment), amendment, DefaultVote::yes}};
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
// All useful amendments are supported amendments.
|
// All useful amendments are supported amendments.
|
||||||
// Enabled amendments are typically a subset of supported amendments.
|
// Enabled amendments are typically a subset of supported amendments.
|
||||||
// Vetoed amendments should be supported but not enabled.
|
// Vetoed amendments should be supported but not enabled.
|
||||||
// Unsupported amendments may be added to the AmendmentTable.
|
// Unsupported amendments may be added to the AmendmentTable.
|
||||||
std::vector<std::string> const supported_{
|
std::vector<std::string> const supportedYes_{
|
||||||
"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k",
|
"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k",
|
||||||
"l", "m", "n", "o", "p", "q", "r", "s", "t", "u"};
|
"l", "m", "n", "o", "p", "q", "r", "s", "t", "u"};
|
||||||
std::vector<std::string> const
|
std::vector<std::string> const
|
||||||
@@ -100,6 +120,7 @@ private:
|
|||||||
std::vector<std::string> const unsupportedMajority_{"y", "z"};
|
std::vector<std::string> const unsupportedMajority_{"y", "z"};
|
||||||
|
|
||||||
Section const emptySection;
|
Section const emptySection;
|
||||||
|
std::vector<AmendmentTable::FeatureInfo> const emptyYes;
|
||||||
|
|
||||||
test::SuiteJournal journal;
|
test::SuiteJournal journal;
|
||||||
|
|
||||||
@@ -112,7 +133,7 @@ public:
|
|||||||
makeTable(
|
makeTable(
|
||||||
Application& app,
|
Application& app,
|
||||||
std::chrono::seconds majorityTime,
|
std::chrono::seconds majorityTime,
|
||||||
Section const& supported,
|
std::vector<AmendmentTable::FeatureInfo> const& supported,
|
||||||
Section const& enabled,
|
Section const& enabled,
|
||||||
Section const& vetoed)
|
Section const& vetoed)
|
||||||
{
|
{
|
||||||
@@ -124,7 +145,7 @@ public:
|
|||||||
makeTable(
|
makeTable(
|
||||||
test::jtx::Env& env,
|
test::jtx::Env& env,
|
||||||
std::chrono::seconds majorityTime,
|
std::chrono::seconds majorityTime,
|
||||||
Section const& supported,
|
std::vector<AmendmentTable::FeatureInfo> const& supported,
|
||||||
Section const& enabled,
|
Section const& enabled,
|
||||||
Section const& vetoed)
|
Section const& vetoed)
|
||||||
{
|
{
|
||||||
@@ -137,7 +158,7 @@ public:
|
|||||||
return makeTable(
|
return makeTable(
|
||||||
env.app(),
|
env.app(),
|
||||||
majorityTime,
|
majorityTime,
|
||||||
makeSection(supported_),
|
makeDefaultYes(supportedYes_),
|
||||||
makeSection(enabled_),
|
makeSection(enabled_),
|
||||||
makeSection(vetoed_));
|
makeSection(vetoed_));
|
||||||
}
|
}
|
||||||
@@ -149,7 +170,7 @@ public:
|
|||||||
test::jtx::Env env{*this, makeConfig()};
|
test::jtx::Env env{*this, makeConfig()};
|
||||||
auto table = makeTable(env, weeks(1));
|
auto table = makeTable(env, weeks(1));
|
||||||
|
|
||||||
for (auto const& a : supported_)
|
for (auto const& a : supportedYes_)
|
||||||
{
|
{
|
||||||
BEAST_EXPECT(table->isSupported(amendmentId(a)));
|
BEAST_EXPECT(table->isSupported(amendmentId(a)));
|
||||||
}
|
}
|
||||||
@@ -174,7 +195,7 @@ public:
|
|||||||
test::jtx::Env env{*this, makeConfig()};
|
test::jtx::Env env{*this, makeConfig()};
|
||||||
auto table = makeTable(env, weeks(1));
|
auto table = makeTable(env, weeks(1));
|
||||||
|
|
||||||
for (auto const& a : supported_)
|
for (auto const& a : supportedYes_)
|
||||||
BEAST_EXPECT(table->find(a) == amendmentId(a));
|
BEAST_EXPECT(table->find(a) == amendmentId(a));
|
||||||
for (auto const& a : enabled_)
|
for (auto const& a : enabled_)
|
||||||
BEAST_EXPECT(table->find(a) == amendmentId(a));
|
BEAST_EXPECT(table->find(a) == amendmentId(a));
|
||||||
@@ -207,7 +228,8 @@ public:
|
|||||||
void
|
void
|
||||||
testBadConfig()
|
testBadConfig()
|
||||||
{
|
{
|
||||||
auto const section = makeSection(supported_);
|
auto const yesVotes = makeDefaultYes(supportedYes_);
|
||||||
|
auto const section = makeSection(vetoed_);
|
||||||
auto const id = to_string(amendmentId(enabled_[0]));
|
auto const id = to_string(amendmentId(enabled_[0]));
|
||||||
|
|
||||||
testcase("Bad Config");
|
testcase("Bad Config");
|
||||||
@@ -219,12 +241,13 @@ public:
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
test::jtx::Env env{*this, makeConfig()};
|
test::jtx::Env env{*this, makeConfig()};
|
||||||
if (makeTable(env, weeks(2), test, emptySection, emptySection))
|
if (makeTable(env, weeks(2), yesVotes, test, emptySection))
|
||||||
fail("Accepted only amendment ID");
|
fail("Accepted only amendment ID");
|
||||||
}
|
}
|
||||||
catch (...)
|
catch (std::exception const& e)
|
||||||
{
|
{
|
||||||
pass();
|
BEAST_EXPECT(
|
||||||
|
e.what() == "Invalid entry '" + id + "' in [Test]");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -235,12 +258,14 @@ public:
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
test::jtx::Env env{*this, makeConfig()};
|
test::jtx::Env env{*this, makeConfig()};
|
||||||
if (makeTable(env, weeks(2), test, emptySection, emptySection))
|
if (makeTable(env, weeks(2), yesVotes, test, emptySection))
|
||||||
fail("Accepted extra arguments");
|
fail("Accepted extra arguments");
|
||||||
}
|
}
|
||||||
catch (...)
|
catch (std::exception const& e)
|
||||||
{
|
{
|
||||||
pass();
|
BEAST_EXPECT(
|
||||||
|
e.what() ==
|
||||||
|
"Invalid entry '" + id + " Test Name' in [Test]");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -254,12 +279,13 @@ public:
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
test::jtx::Env env{*this, makeConfig()};
|
test::jtx::Env env{*this, makeConfig()};
|
||||||
if (makeTable(env, weeks(2), test, emptySection, emptySection))
|
if (makeTable(env, weeks(2), yesVotes, test, emptySection))
|
||||||
fail("Accepted short amendment ID");
|
fail("Accepted short amendment ID");
|
||||||
}
|
}
|
||||||
catch (...)
|
catch (std::exception const& e)
|
||||||
{
|
{
|
||||||
pass();
|
BEAST_EXPECT(
|
||||||
|
e.what() == "Invalid entry '" + sid + " Name' in [Test]");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -273,12 +299,13 @@ public:
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
test::jtx::Env env{*this, makeConfig()};
|
test::jtx::Env env{*this, makeConfig()};
|
||||||
if (makeTable(env, weeks(2), test, emptySection, emptySection))
|
if (makeTable(env, weeks(2), yesVotes, test, emptySection))
|
||||||
fail("Accepted long amendment ID");
|
fail("Accepted long amendment ID");
|
||||||
}
|
}
|
||||||
catch (...)
|
catch (std::exception const& e)
|
||||||
{
|
{
|
||||||
pass();
|
BEAST_EXPECT(
|
||||||
|
e.what() == "Invalid entry '" + sid + " Name' in [Test]");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -293,12 +320,13 @@ public:
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
test::jtx::Env env{*this, makeConfig()};
|
test::jtx::Env env{*this, makeConfig()};
|
||||||
if (makeTable(env, weeks(2), test, emptySection, emptySection))
|
if (makeTable(env, weeks(2), yesVotes, test, emptySection))
|
||||||
fail("Accepted non-hex amendment ID");
|
fail("Accepted non-hex amendment ID");
|
||||||
}
|
}
|
||||||
catch (...)
|
catch (std::exception const& e)
|
||||||
{
|
{
|
||||||
pass();
|
BEAST_EXPECT(
|
||||||
|
e.what() == "Invalid entry '" + sid + " Name' in [Test]");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -311,27 +339,27 @@ public:
|
|||||||
test::jtx::Env env{*this, makeConfig()};
|
test::jtx::Env env{*this, makeConfig()};
|
||||||
std::unique_ptr<AmendmentTable> table = makeTable(env, weeks(2));
|
std::unique_ptr<AmendmentTable> table = makeTable(env, weeks(2));
|
||||||
|
|
||||||
// Note which entries are enabled.
|
// Note which entries are enabled
|
||||||
std::set<uint256> allEnabled;
|
std::set<uint256> allEnabled;
|
||||||
|
for (auto const& a : enabled_)
|
||||||
// Subset of amendments to enable
|
allEnabled.insert(amendmentId(a));
|
||||||
allEnabled.insert(amendmentId(supported_[0]));
|
|
||||||
allEnabled.insert(amendmentId(enabled_[0]));
|
|
||||||
allEnabled.insert(amendmentId(vetoed_[0]));
|
|
||||||
|
|
||||||
for (uint256 const& a : allEnabled)
|
for (uint256 const& a : allEnabled)
|
||||||
table->enable(a);
|
BEAST_EXPECT(table->enable(a));
|
||||||
|
|
||||||
// So far all enabled amendments are supported.
|
// So far all enabled amendments are supported.
|
||||||
BEAST_EXPECT(!table->hasUnsupportedEnabled());
|
BEAST_EXPECT(!table->hasUnsupportedEnabled());
|
||||||
|
|
||||||
// Verify all enables are enabled and nothing else.
|
// Verify all enables are enabled and nothing else.
|
||||||
for (std::string const& a : supported_)
|
for (std::string const& a : supportedYes_)
|
||||||
{
|
{
|
||||||
uint256 const supportedID = amendmentId(a);
|
uint256 const supportedID = amendmentId(a);
|
||||||
BEAST_EXPECT(
|
bool const enabled = table->isEnabled(supportedID);
|
||||||
table->isEnabled(supportedID) ==
|
bool const found = allEnabled.find(supportedID) != allEnabled.end();
|
||||||
(allEnabled.find(supportedID) != allEnabled.end()));
|
BEAST_EXPECTS(
|
||||||
|
enabled == found,
|
||||||
|
a + (enabled ? " enabled " : " disabled ") +
|
||||||
|
(found ? " found" : " not found"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// All supported and unVetoed amendments should be returned as desired.
|
// All supported and unVetoed amendments should be returned as desired.
|
||||||
@@ -347,14 +375,14 @@ public:
|
|||||||
// Unveto an amendment that is already not vetoed. Shouldn't
|
// Unveto an amendment that is already not vetoed. Shouldn't
|
||||||
// hurt anything, but the values returned by getDesired()
|
// hurt anything, but the values returned by getDesired()
|
||||||
// shouldn't change.
|
// shouldn't change.
|
||||||
table->unVeto(amendmentId(supported_[1]));
|
BEAST_EXPECT(!table->unVeto(amendmentId(supportedYes_[1])));
|
||||||
BEAST_EXPECT(desired == table->getDesired());
|
BEAST_EXPECT(desired == table->getDesired());
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnVeto one of the vetoed amendments. It should now be desired.
|
// UnVeto one of the vetoed amendments. It should now be desired.
|
||||||
{
|
{
|
||||||
uint256 const unvetoedID = amendmentId(vetoed_[0]);
|
uint256 const unvetoedID = amendmentId(vetoed_[0]);
|
||||||
table->unVeto(unvetoedID);
|
BEAST_EXPECT(table->unVeto(unvetoedID));
|
||||||
|
|
||||||
std::vector<uint256> const desired = table->getDesired();
|
std::vector<uint256> const desired = table->getDesired();
|
||||||
BEAST_EXPECT(
|
BEAST_EXPECT(
|
||||||
@@ -363,7 +391,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Veto all supported amendments. Now desired should be empty.
|
// Veto all supported amendments. Now desired should be empty.
|
||||||
for (std::string const& a : supported_)
|
for (std::string const& a : supportedYes_)
|
||||||
{
|
{
|
||||||
table->veto(amendmentId(a));
|
table->veto(amendmentId(a));
|
||||||
}
|
}
|
||||||
@@ -505,7 +533,7 @@ public:
|
|||||||
|
|
||||||
test::jtx::Env env{*this};
|
test::jtx::Env env{*this};
|
||||||
auto table =
|
auto table =
|
||||||
makeTable(env, weeks(2), emptySection, emptySection, emptySection);
|
makeTable(env, weeks(2), emptyYes, emptySection, emptySection);
|
||||||
|
|
||||||
std::vector<std::pair<uint256, int>> votes;
|
std::vector<std::pair<uint256, int>> votes;
|
||||||
std::vector<uint256> ourVotes;
|
std::vector<uint256> ourVotes;
|
||||||
@@ -566,11 +594,7 @@ public:
|
|||||||
|
|
||||||
test::jtx::Env env{*this};
|
test::jtx::Env env{*this};
|
||||||
auto table = makeTable(
|
auto table = makeTable(
|
||||||
env,
|
env, weeks(2), emptyYes, emptySection, makeSection(testAmendment));
|
||||||
weeks(2),
|
|
||||||
emptySection,
|
|
||||||
emptySection,
|
|
||||||
makeSection(testAmendment));
|
|
||||||
|
|
||||||
auto const validators = makeValidators(10);
|
auto const validators = makeValidators(10);
|
||||||
|
|
||||||
@@ -629,7 +653,11 @@ public:
|
|||||||
|
|
||||||
test::jtx::Env env{*this};
|
test::jtx::Env env{*this};
|
||||||
auto table = makeTable(
|
auto table = makeTable(
|
||||||
env, weeks(2), makeSection(supported_), emptySection, emptySection);
|
env,
|
||||||
|
weeks(2),
|
||||||
|
makeDefaultYes(supportedYes_),
|
||||||
|
emptySection,
|
||||||
|
emptySection);
|
||||||
|
|
||||||
auto const validators = makeValidators(10);
|
auto const validators = makeValidators(10);
|
||||||
std::vector<std::pair<uint256, int>> votes;
|
std::vector<std::pair<uint256, int>> votes;
|
||||||
@@ -647,13 +675,13 @@ public:
|
|||||||
ourVotes,
|
ourVotes,
|
||||||
enabled,
|
enabled,
|
||||||
majority);
|
majority);
|
||||||
BEAST_EXPECT(ourVotes.size() == supported_.size());
|
BEAST_EXPECT(ourVotes.size() == supportedYes_.size());
|
||||||
BEAST_EXPECT(enabled.empty());
|
BEAST_EXPECT(enabled.empty());
|
||||||
for (auto const& i : supported_)
|
for (auto const& i : supportedYes_)
|
||||||
BEAST_EXPECT(majority.find(amendmentId(i)) == majority.end());
|
BEAST_EXPECT(majority.find(amendmentId(i)) == majority.end());
|
||||||
|
|
||||||
// Now, everyone votes for this feature
|
// Now, everyone votes for this feature
|
||||||
for (auto const& i : supported_)
|
for (auto const& i : supportedYes_)
|
||||||
votes.emplace_back(amendmentId(i), validators.size());
|
votes.emplace_back(amendmentId(i), validators.size());
|
||||||
|
|
||||||
// Week 2: We should recognize a majority
|
// Week 2: We should recognize a majority
|
||||||
@@ -666,10 +694,10 @@ public:
|
|||||||
ourVotes,
|
ourVotes,
|
||||||
enabled,
|
enabled,
|
||||||
majority);
|
majority);
|
||||||
BEAST_EXPECT(ourVotes.size() == supported_.size());
|
BEAST_EXPECT(ourVotes.size() == supportedYes_.size());
|
||||||
BEAST_EXPECT(enabled.empty());
|
BEAST_EXPECT(enabled.empty());
|
||||||
|
|
||||||
for (auto const& i : supported_)
|
for (auto const& i : supportedYes_)
|
||||||
BEAST_EXPECT(majority[amendmentId(i)] == weekTime(weeks{2}));
|
BEAST_EXPECT(majority[amendmentId(i)] == weekTime(weeks{2}));
|
||||||
|
|
||||||
// Week 5: We should enable the amendment
|
// Week 5: We should enable the amendment
|
||||||
@@ -682,7 +710,7 @@ public:
|
|||||||
ourVotes,
|
ourVotes,
|
||||||
enabled,
|
enabled,
|
||||||
majority);
|
majority);
|
||||||
BEAST_EXPECT(enabled.size() == supported_.size());
|
BEAST_EXPECT(enabled.size() == supportedYes_.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(
|
||||||
@@ -694,9 +722,9 @@ public:
|
|||||||
ourVotes,
|
ourVotes,
|
||||||
enabled,
|
enabled,
|
||||||
majority);
|
majority);
|
||||||
BEAST_EXPECT(enabled.size() == supported_.size());
|
BEAST_EXPECT(enabled.size() == supportedYes_.size());
|
||||||
BEAST_EXPECT(ourVotes.empty());
|
BEAST_EXPECT(ourVotes.empty());
|
||||||
for (auto const& i : supported_)
|
for (auto const& i : supportedYes_)
|
||||||
BEAST_EXPECT(majority.find(amendmentId(i)) == majority.end());
|
BEAST_EXPECT(majority.find(amendmentId(i)) == majority.end());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -711,7 +739,7 @@ public:
|
|||||||
auto table = makeTable(
|
auto table = makeTable(
|
||||||
env,
|
env,
|
||||||
weeks(2),
|
weeks(2),
|
||||||
makeSection(testAmendment),
|
makeDefaultYes(testAmendment),
|
||||||
emptySection,
|
emptySection,
|
||||||
emptySection);
|
emptySection);
|
||||||
|
|
||||||
@@ -782,7 +810,7 @@ public:
|
|||||||
auto table = makeTable(
|
auto table = makeTable(
|
||||||
env,
|
env,
|
||||||
weeks(8),
|
weeks(8),
|
||||||
makeSection(testAmendment),
|
makeDefaultYes(testAmendment),
|
||||||
emptySection,
|
emptySection,
|
||||||
emptySection);
|
emptySection);
|
||||||
|
|
||||||
|
|||||||
@@ -136,11 +136,10 @@ class TxQ1_test : public beast::unit_test::suite
|
|||||||
for (auto i = env.current()->seq(); i <= 257; ++i)
|
for (auto i = env.current()->seq(); i <= 257; ++i)
|
||||||
env.close();
|
env.close();
|
||||||
// The ledger after the flag ledger creates all the
|
// The ledger after the flag ledger creates all the
|
||||||
// fee (1) and amendment (supportedAmendments().size())
|
// fee (1) and amendment (numUpVotedAmendments())
|
||||||
// pseudotransactions. The queue treats the fees on these
|
// pseudotransactions. The queue treats the fees on these
|
||||||
// transactions as though they are ordinary transactions.
|
// transactions as though they are ordinary transactions.
|
||||||
auto const flagPerLedger =
|
auto const flagPerLedger = 1 + ripple::detail::numUpVotedAmendments();
|
||||||
1 + ripple::detail::supportedAmendments().size();
|
|
||||||
auto const flagMaxQueue = ledgersInQueue * flagPerLedger;
|
auto const flagMaxQueue = ledgersInQueue * flagPerLedger;
|
||||||
checkMetrics(env, 0, flagMaxQueue, 0, flagPerLedger, 256);
|
checkMetrics(env, 0, flagMaxQueue, 0, flagPerLedger, 256);
|
||||||
|
|
||||||
@@ -4157,8 +4156,7 @@ public:
|
|||||||
if (!getMajorityAmendments(*env.closed()).empty())
|
if (!getMajorityAmendments(*env.closed()).empty())
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
auto expectedPerLedger =
|
auto expectedPerLedger = ripple::detail::numUpVotedAmendments() + 1;
|
||||||
ripple::detail::supportedAmendments().size() + 1;
|
|
||||||
checkMetrics(env, 0, 5 * expectedPerLedger, 0, expectedPerLedger, 256);
|
checkMetrics(env, 0, 5 * expectedPerLedger, 0, expectedPerLedger, 256);
|
||||||
|
|
||||||
// Now wait 2 weeks modulo 256 ledgers for the amendments to be
|
// Now wait 2 weeks modulo 256 ledgers for the amendments to be
|
||||||
@@ -4213,51 +4211,19 @@ public:
|
|||||||
// These particular amendments don't impact any of the queued
|
// These particular amendments don't impact any of the queued
|
||||||
// transactions, so we won't see any change in the transaction
|
// transactions, so we won't see any change in the transaction
|
||||||
// outcomes. But code coverage is affected.
|
// outcomes. But code coverage is affected.
|
||||||
env.close(closeDuration);
|
do
|
||||||
expectedInQueue -= expectedPerLedger + 2;
|
|
||||||
++expectedPerLedger;
|
|
||||||
checkMetrics(
|
|
||||||
env,
|
|
||||||
expectedInQueue,
|
|
||||||
5 * expectedPerLedger,
|
|
||||||
expectedPerLedger + 1,
|
|
||||||
expectedPerLedger,
|
|
||||||
256);
|
|
||||||
{
|
{
|
||||||
auto const expectedPerAccount = expectedInQueue / 6;
|
|
||||||
auto const expectedRemainder = expectedInQueue % 6;
|
|
||||||
BEAST_EXPECT(env.seq(alice) == seqAlice - expectedPerAccount);
|
|
||||||
BEAST_EXPECT(
|
|
||||||
env.seq(bob) ==
|
|
||||||
seqBob - expectedPerAccount - (expectedRemainder > 4 ? 1 : 0));
|
|
||||||
BEAST_EXPECT(
|
|
||||||
env.seq(carol) ==
|
|
||||||
seqCarol - expectedPerAccount -
|
|
||||||
(expectedRemainder > 3 ? 1 : 0));
|
|
||||||
BEAST_EXPECT(
|
|
||||||
env.seq(daria) ==
|
|
||||||
seqDaria - expectedPerAccount -
|
|
||||||
(expectedRemainder > 2 ? 1 : 0));
|
|
||||||
BEAST_EXPECT(
|
|
||||||
env.seq(ellie) ==
|
|
||||||
seqEllie - expectedPerAccount -
|
|
||||||
(expectedRemainder > 1 ? 1 : 0));
|
|
||||||
BEAST_EXPECT(
|
|
||||||
env.seq(fiona) ==
|
|
||||||
seqFiona - expectedPerAccount -
|
|
||||||
(expectedRemainder > 0 ? 1 : 0));
|
|
||||||
}
|
|
||||||
|
|
||||||
env.close(closeDuration);
|
env.close(closeDuration);
|
||||||
auto expectedInLedger = expectedInQueue;
|
auto expectedInLedger = expectedInQueue;
|
||||||
expectedInQueue =
|
expectedInQueue =
|
||||||
(expectedInQueue > expectedPerLedger + 2
|
(expectedInQueue > expectedPerLedger + 2
|
||||||
? expectedInQueue - (expectedPerLedger + 2)
|
? expectedInQueue - (expectedPerLedger + 2)
|
||||||
: 0);
|
: 0);
|
||||||
|
expectedInLedger -= expectedInQueue;
|
||||||
++expectedPerLedger;
|
++expectedPerLedger;
|
||||||
checkMetrics(
|
checkMetrics(
|
||||||
env,
|
env,
|
||||||
0,
|
expectedInQueue,
|
||||||
5 * expectedPerLedger,
|
5 * expectedPerLedger,
|
||||||
expectedInLedger,
|
expectedInLedger,
|
||||||
expectedPerLedger,
|
expectedPerLedger,
|
||||||
@@ -4268,7 +4234,8 @@ public:
|
|||||||
BEAST_EXPECT(env.seq(alice) == seqAlice - expectedPerAccount);
|
BEAST_EXPECT(env.seq(alice) == seqAlice - expectedPerAccount);
|
||||||
BEAST_EXPECT(
|
BEAST_EXPECT(
|
||||||
env.seq(bob) ==
|
env.seq(bob) ==
|
||||||
seqBob - expectedPerAccount - (expectedRemainder > 4 ? 1 : 0));
|
seqBob - expectedPerAccount -
|
||||||
|
(expectedRemainder > 4 ? 1 : 0));
|
||||||
BEAST_EXPECT(
|
BEAST_EXPECT(
|
||||||
env.seq(carol) ==
|
env.seq(carol) ==
|
||||||
seqCarol - expectedPerAccount -
|
seqCarol - expectedPerAccount -
|
||||||
@@ -4286,6 +4253,7 @@ public:
|
|||||||
seqFiona - expectedPerAccount -
|
seqFiona - expectedPerAccount -
|
||||||
(expectedRemainder > 0 ? 1 : 0));
|
(expectedRemainder > 0 ? 1 : 0));
|
||||||
}
|
}
|
||||||
|
} while (expectedInQueue > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
|||||||
@@ -73,8 +73,9 @@ supported_amendments()
|
|||||||
auto const& sa = ripple::detail::supportedAmendments();
|
auto const& sa = ripple::detail::supportedAmendments();
|
||||||
std::vector<uint256> feats;
|
std::vector<uint256> feats;
|
||||||
feats.reserve(sa.size());
|
feats.reserve(sa.size());
|
||||||
for (auto const& s : sa)
|
for (auto const& [s, vote] : sa)
|
||||||
{
|
{
|
||||||
|
(void)vote;
|
||||||
if (auto const f = getRegisteredFeature(s))
|
if (auto const f = getRegisteredFeature(s))
|
||||||
feats.push_back(*f);
|
feats.push_back(*f);
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -812,6 +812,7 @@ public:
|
|||||||
|
|
||||||
auto const missingSomeFeatures =
|
auto const missingSomeFeatures =
|
||||||
supported_amendments() - featureMultiSignReserve - featureFlow;
|
supported_amendments() - featureMultiSignReserve - featureFlow;
|
||||||
|
BEAST_EXPECT(missingSomeFeatures.count() == (supported.count() - 2));
|
||||||
{
|
{
|
||||||
// a Env supported_features_except is missing *only* those features
|
// a Env supported_features_except is missing *only* those features
|
||||||
Env env{*this, missingSomeFeatures};
|
Env env{*this, missingSomeFeatures};
|
||||||
|
|||||||
@@ -26,6 +26,77 @@ namespace ripple {
|
|||||||
|
|
||||||
class Feature_test : public beast::unit_test::suite
|
class Feature_test : public beast::unit_test::suite
|
||||||
{
|
{
|
||||||
|
void
|
||||||
|
testInternals()
|
||||||
|
{
|
||||||
|
testcase("internals");
|
||||||
|
|
||||||
|
std::map<std::string, DefaultVote> const& supported =
|
||||||
|
ripple::detail::supportedAmendments();
|
||||||
|
BEAST_EXPECT(
|
||||||
|
supported.size() ==
|
||||||
|
ripple::detail::numDownVotedAmendments() +
|
||||||
|
ripple::detail::numUpVotedAmendments());
|
||||||
|
std::size_t up = 0, down = 0;
|
||||||
|
for (std::pair<std::string const, DefaultVote> const& amendment :
|
||||||
|
supported)
|
||||||
|
{
|
||||||
|
if (amendment.second == DefaultVote::no)
|
||||||
|
++down;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (BEAST_EXPECT(amendment.second == DefaultVote::yes))
|
||||||
|
++up;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BEAST_EXPECT(down == ripple::detail::numDownVotedAmendments());
|
||||||
|
BEAST_EXPECT(up == ripple::detail::numUpVotedAmendments());
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
testFeatureLookups()
|
||||||
|
{
|
||||||
|
testcase("featureToName");
|
||||||
|
|
||||||
|
// Test all the supported features. In a perfect world, this would test
|
||||||
|
// FeatureCollections::featureNames, but that's private. Leave it that
|
||||||
|
// way.
|
||||||
|
auto const supported = ripple::detail::supportedAmendments();
|
||||||
|
|
||||||
|
for (auto const& [feature, vote] : supported)
|
||||||
|
{
|
||||||
|
(void)vote;
|
||||||
|
auto const registered = getRegisteredFeature(feature);
|
||||||
|
if (BEAST_EXPECT(registered))
|
||||||
|
{
|
||||||
|
BEAST_EXPECT(featureToName(*registered) == feature);
|
||||||
|
BEAST_EXPECT(
|
||||||
|
bitsetIndexToFeature(featureToBitsetIndex(*registered)) ==
|
||||||
|
*registered);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test an arbitrary unknown feature
|
||||||
|
uint256 zero{0};
|
||||||
|
BEAST_EXPECT(featureToName(zero) == to_string(zero));
|
||||||
|
BEAST_EXPECT(
|
||||||
|
featureToName(zero) ==
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000000");
|
||||||
|
|
||||||
|
// Test looking up an unknown feature
|
||||||
|
BEAST_EXPECT(!getRegisteredFeature("unknown"));
|
||||||
|
|
||||||
|
// Test a random sampling of the variables. If any of these get retired
|
||||||
|
// or removed, swap out for any other feature.
|
||||||
|
BEAST_EXPECT(featureToName(featureOwnerPaysFee) == "OwnerPaysFee");
|
||||||
|
BEAST_EXPECT(featureToName(featureFlow) == "Flow");
|
||||||
|
BEAST_EXPECT(featureToName(featureNegativeUNL) == "NegativeUNL");
|
||||||
|
BEAST_EXPECT(featureToName(fix1578) == "fix1578");
|
||||||
|
BEAST_EXPECT(
|
||||||
|
featureToName(fixTakerDryOfferRemoval) ==
|
||||||
|
"fixTakerDryOfferRemoval");
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
testNoParams()
|
testNoParams()
|
||||||
{
|
{
|
||||||
@@ -34,6 +105,9 @@ class Feature_test : public beast::unit_test::suite
|
|||||||
using namespace test::jtx;
|
using namespace test::jtx;
|
||||||
Env env{*this};
|
Env env{*this};
|
||||||
|
|
||||||
|
std::map<std::string, DefaultVote> const& votes =
|
||||||
|
ripple::detail::supportedAmendments();
|
||||||
|
|
||||||
auto jrr = env.rpc("feature")[jss::result];
|
auto jrr = env.rpc("feature")[jss::result];
|
||||||
if (!BEAST_EXPECT(jrr.isMember(jss::features)))
|
if (!BEAST_EXPECT(jrr.isMember(jss::features)))
|
||||||
return;
|
return;
|
||||||
@@ -41,13 +115,15 @@ class Feature_test : public beast::unit_test::suite
|
|||||||
{
|
{
|
||||||
if (!BEAST_EXPECT(feature.isMember(jss::name)))
|
if (!BEAST_EXPECT(feature.isMember(jss::name)))
|
||||||
return;
|
return;
|
||||||
// default config - so all should be disabled, not vetoed, and
|
// default config - so all should be disabled, and
|
||||||
// supported
|
// supported. Some may be vetoed.
|
||||||
|
bool expectVeto =
|
||||||
|
!(votes.at(feature[jss::name].asString()) == DefaultVote::yes);
|
||||||
BEAST_EXPECTS(
|
BEAST_EXPECTS(
|
||||||
!feature[jss::enabled].asBool(),
|
!feature[jss::enabled].asBool(),
|
||||||
feature[jss::name].asString() + " enabled");
|
feature[jss::name].asString() + " enabled");
|
||||||
BEAST_EXPECTS(
|
BEAST_EXPECTS(
|
||||||
!feature[jss::vetoed].asBool(),
|
feature[jss::vetoed].asBool() == expectVeto,
|
||||||
feature[jss::name].asString() + " vetoed");
|
feature[jss::name].asString() + " vetoed");
|
||||||
BEAST_EXPECTS(
|
BEAST_EXPECTS(
|
||||||
feature[jss::supported].asBool(),
|
feature[jss::supported].asBool(),
|
||||||
@@ -67,6 +143,9 @@ class Feature_test : public beast::unit_test::suite
|
|||||||
BEAST_EXPECTS(jrr[jss::status] == jss::success, "status");
|
BEAST_EXPECTS(jrr[jss::status] == jss::success, "status");
|
||||||
jrr.removeMember(jss::status);
|
jrr.removeMember(jss::status);
|
||||||
BEAST_EXPECT(jrr.size() == 1);
|
BEAST_EXPECT(jrr.size() == 1);
|
||||||
|
BEAST_EXPECT(
|
||||||
|
jrr.isMember("586480873651E106F1D6339B0C4A8945BA705A777F3F4524626FF"
|
||||||
|
"1FC07EFE41D"));
|
||||||
auto feature = *(jrr.begin());
|
auto feature = *(jrr.begin());
|
||||||
|
|
||||||
BEAST_EXPECTS(feature[jss::name] == "MultiSignReserve", "name");
|
BEAST_EXPECTS(feature[jss::name] == "MultiSignReserve", "name");
|
||||||
@@ -121,6 +200,9 @@ class Feature_test : public beast::unit_test::suite
|
|||||||
Env env{
|
Env env{
|
||||||
*this, FeatureBitset(featureDepositAuth, featureDepositPreauth)};
|
*this, FeatureBitset(featureDepositAuth, featureDepositPreauth)};
|
||||||
|
|
||||||
|
std::map<std::string, DefaultVote> const& votes =
|
||||||
|
ripple::detail::supportedAmendments();
|
||||||
|
|
||||||
auto jrr = env.rpc("feature")[jss::result];
|
auto jrr = env.rpc("feature")[jss::result];
|
||||||
if (!BEAST_EXPECT(jrr.isMember(jss::features)))
|
if (!BEAST_EXPECT(jrr.isMember(jss::features)))
|
||||||
return;
|
return;
|
||||||
@@ -135,11 +217,13 @@ class Feature_test : public beast::unit_test::suite
|
|||||||
bool expectEnabled = env.app().getAmendmentTable().isEnabled(id);
|
bool expectEnabled = env.app().getAmendmentTable().isEnabled(id);
|
||||||
bool expectSupported =
|
bool expectSupported =
|
||||||
env.app().getAmendmentTable().isSupported(id);
|
env.app().getAmendmentTable().isSupported(id);
|
||||||
|
bool expectVeto =
|
||||||
|
!(votes.at((*it)[jss::name].asString()) == DefaultVote::yes);
|
||||||
BEAST_EXPECTS(
|
BEAST_EXPECTS(
|
||||||
(*it)[jss::enabled].asBool() == expectEnabled,
|
(*it)[jss::enabled].asBool() == expectEnabled,
|
||||||
(*it)[jss::name].asString() + " enabled");
|
(*it)[jss::name].asString() + " enabled");
|
||||||
BEAST_EXPECTS(
|
BEAST_EXPECTS(
|
||||||
!(*it)[jss::vetoed].asBool(),
|
(*it)[jss::vetoed].asBool() == expectVeto,
|
||||||
(*it)[jss::name].asString() + " vetoed");
|
(*it)[jss::name].asString() + " vetoed");
|
||||||
BEAST_EXPECTS(
|
BEAST_EXPECTS(
|
||||||
(*it)[jss::supported].asBool() == expectSupported,
|
(*it)[jss::supported].asBool() == expectSupported,
|
||||||
@@ -198,6 +282,8 @@ class Feature_test : public beast::unit_test::suite
|
|||||||
// There should be at least 5 amendments. Don't do exact comparison
|
// There should be at least 5 amendments. Don't do exact comparison
|
||||||
// to avoid maintenance as more amendments are added in the future.
|
// to avoid maintenance as more amendments are added in the future.
|
||||||
BEAST_EXPECT(majorities.size() >= 5);
|
BEAST_EXPECT(majorities.size() >= 5);
|
||||||
|
std::map<std::string, DefaultVote> const& votes =
|
||||||
|
ripple::detail::supportedAmendments();
|
||||||
|
|
||||||
jrr = env.rpc("feature")[jss::result];
|
jrr = env.rpc("feature")[jss::result];
|
||||||
if (!BEAST_EXPECT(jrr.isMember(jss::features)))
|
if (!BEAST_EXPECT(jrr.isMember(jss::features)))
|
||||||
@@ -206,9 +292,15 @@ class Feature_test : public beast::unit_test::suite
|
|||||||
{
|
{
|
||||||
if (!BEAST_EXPECT(feature.isMember(jss::name)))
|
if (!BEAST_EXPECT(feature.isMember(jss::name)))
|
||||||
return;
|
return;
|
||||||
|
bool expectVeto =
|
||||||
|
!(votes.at(feature[jss::name].asString()) == DefaultVote::yes);
|
||||||
BEAST_EXPECTS(
|
BEAST_EXPECTS(
|
||||||
feature.isMember(jss::majority),
|
expectVeto ^ feature.isMember(jss::majority),
|
||||||
feature[jss::name].asString() + " majority");
|
feature[jss::name].asString() + " majority");
|
||||||
|
BEAST_EXPECTS(
|
||||||
|
feature.isMember(jss::vetoed) &&
|
||||||
|
feature[jss::vetoed].asBool() == expectVeto,
|
||||||
|
feature[jss::name].asString() + " vetoed");
|
||||||
BEAST_EXPECTS(
|
BEAST_EXPECTS(
|
||||||
feature.isMember(jss::count),
|
feature.isMember(jss::count),
|
||||||
feature[jss::name].asString() + " count");
|
feature[jss::name].asString() + " count");
|
||||||
@@ -218,10 +310,12 @@ 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_EXPECT(feature[jss::count] == 1);
|
BEAST_EXPECT(feature[jss::count] == (expectVeto ? 0 : 1));
|
||||||
BEAST_EXPECT(feature[jss::threshold] == 1);
|
BEAST_EXPECT(feature[jss::threshold] == 1);
|
||||||
BEAST_EXPECT(feature[jss::validations] == 1);
|
BEAST_EXPECT(feature[jss::validations] == 1);
|
||||||
BEAST_EXPECT(feature[jss::majority] == 2540);
|
BEAST_EXPECTS(
|
||||||
|
expectVeto || feature[jss::majority] == 2540,
|
||||||
|
"Majority: " + feature[jss::majority].asString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -273,6 +367,8 @@ public:
|
|||||||
void
|
void
|
||||||
run() override
|
run() override
|
||||||
{
|
{
|
||||||
|
testInternals();
|
||||||
|
testFeatureLookups();
|
||||||
testNoParams();
|
testNoParams();
|
||||||
testSingleFeature();
|
testSingleFeature();
|
||||||
testInvalidFeature();
|
testInvalidFeature();
|
||||||
|
|||||||
Reference in New Issue
Block a user