mirror of
https://github.com/XRPLF/rippled.git
synced 2025-11-26 14:05:51 +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
|
||||
{
|
||||
auto const& sa = detail::supportedAmendments();
|
||||
std::vector<std::string> saHashes;
|
||||
saHashes.reserve(sa.size());
|
||||
for (auto const& name : sa)
|
||||
auto const supported = []() {
|
||||
auto const& amendments = detail::supportedAmendments();
|
||||
std::vector<AmendmentTable::FeatureInfo> supported;
|
||||
supported.reserve(amendments.size());
|
||||
for (auto const& [a, vote] : amendments)
|
||||
{
|
||||
auto const f = getRegisteredFeature(name);
|
||||
BOOST_ASSERT(f);
|
||||
auto const f = ripple::getRegisteredFeature(a);
|
||||
assert(f);
|
||||
if (f)
|
||||
saHashes.push_back(to_string(*f) + " " + name);
|
||||
supported.emplace_back(a, *f, vote);
|
||||
}
|
||||
Section supportedAmendments("Supported Amendments");
|
||||
supportedAmendments.append(saHashes);
|
||||
return supported;
|
||||
}();
|
||||
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(
|
||||
*this,
|
||||
config().AMENDMENT_MAJORITY_TIME,
|
||||
supportedAmendments,
|
||||
enabledAmendments,
|
||||
config_->section(SECTION_VETO_AMENDMENTS),
|
||||
supported,
|
||||
upVoted,
|
||||
downVoted,
|
||||
logs_->journal("Amendments"));
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
#include <ripple/app/ledger/Ledger.h>
|
||||
#include <ripple/core/ConfigSections.h>
|
||||
#include <ripple/protocol/Feature.h>
|
||||
#include <ripple/protocol/Protocol.h>
|
||||
#include <ripple/protocol/STValidation.h>
|
||||
|
||||
@@ -36,6 +37,19 @@ namespace ripple {
|
||||
class AmendmentTable
|
||||
{
|
||||
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 uint256
|
||||
@@ -168,7 +182,7 @@ std::unique_ptr<AmendmentTable>
|
||||
make_AmendmentTable(
|
||||
Application& app,
|
||||
std::chrono::seconds majorityTime,
|
||||
Section const& supported,
|
||||
std::vector<AmendmentTable::FeatureInfo> const& supported,
|
||||
Section const& enabled,
|
||||
Section const& vetoed,
|
||||
beast::Journal journal);
|
||||
|
||||
@@ -73,8 +73,8 @@ parseSection(Section const& section)
|
||||
*/
|
||||
struct AmendmentState
|
||||
{
|
||||
/** If an amendment is vetoed, a server will not support it */
|
||||
bool vetoed = false;
|
||||
/** If an amendment is down-voted, a server will not vote to enable it */
|
||||
AmendmentVote vote = AmendmentVote::down;
|
||||
|
||||
/** Indicates that the amendment has been enabled.
|
||||
This is a one-way switch: once an amendment is enabled
|
||||
@@ -224,15 +224,16 @@ private:
|
||||
DatabaseCon& db_;
|
||||
|
||||
// Finds or creates state. Must be called with mutex_ locked.
|
||||
AmendmentState*
|
||||
add(uint256 const& amendment, std::lock_guard<std::mutex> const& sl);
|
||||
AmendmentState&
|
||||
add(uint256 const& amendment, std::lock_guard<std::mutex> const& lock);
|
||||
|
||||
// Finds existing state. Must be called with mutex_ locked.
|
||||
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*
|
||||
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.
|
||||
void
|
||||
@@ -240,19 +241,19 @@ private:
|
||||
Json::Value& v,
|
||||
uint256 const& amendment,
|
||||
AmendmentState const& state,
|
||||
std::lock_guard<std::mutex> const& sl) const;
|
||||
std::lock_guard<std::mutex> const& lock) const;
|
||||
|
||||
void
|
||||
persistVote(
|
||||
uint256 const& amendment,
|
||||
std::string const& name,
|
||||
bool vote_to_veto) const;
|
||||
AmendmentVote vote) const;
|
||||
|
||||
public:
|
||||
AmendmentTableImpl(
|
||||
Application& app,
|
||||
std::chrono::seconds majorityTime,
|
||||
Section const& supported,
|
||||
std::vector<FeatureInfo> const& supported,
|
||||
Section const& enabled,
|
||||
Section const& vetoed,
|
||||
beast::Journal journal);
|
||||
@@ -313,7 +314,7 @@ public:
|
||||
AmendmentTableImpl::AmendmentTableImpl(
|
||||
Application& app,
|
||||
std::chrono::seconds majorityTime,
|
||||
Section const& supported,
|
||||
std::vector<FeatureInfo> const& supported,
|
||||
Section const& enabled,
|
||||
Section const& vetoed,
|
||||
beast::Journal journal)
|
||||
@@ -323,7 +324,7 @@ AmendmentTableImpl::AmendmentTableImpl(
|
||||
, j_(journal)
|
||||
, db_(app.getWalletDB())
|
||||
{
|
||||
std::lock_guard sl(mutex_);
|
||||
std::lock_guard lock(mutex_);
|
||||
|
||||
// Find out if the FeatureVotes table exists in WalletDB
|
||||
bool const featureVotesExist = [this]() {
|
||||
@@ -332,22 +333,24 @@ AmendmentTableImpl::AmendmentTableImpl(
|
||||
}();
|
||||
|
||||
// Parse supported amendments
|
||||
for (auto const& a : parseSection(supported))
|
||||
for (auto const& [name, amendment, defaultVote] : supported)
|
||||
{
|
||||
if (auto s = add(a.first, sl))
|
||||
{
|
||||
JLOG(j_.debug()) << "Amendment " << a.first << " is supported.";
|
||||
AmendmentState& s = add(amendment, lock);
|
||||
|
||||
if (!a.second.empty())
|
||||
s->name = a.second;
|
||||
s.name = name;
|
||||
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;
|
||||
// Parse enabled amendments from config
|
||||
for (auto const& a : parseSection(enabled))
|
||||
for (std::pair<uint256, std::string> const& a : parseSection(enabled))
|
||||
{
|
||||
if (featureVotesExist)
|
||||
{ // If the table existed, warn about duplicate config info
|
||||
@@ -358,7 +361,7 @@ AmendmentTableImpl::AmendmentTableImpl(
|
||||
else
|
||||
{ // Otherwise transfer config data into the table
|
||||
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
|
||||
if (detect_conflict.count(a.first) == 0)
|
||||
{
|
||||
persistVote(a.first, a.second, true); // veto
|
||||
persistVote(a.first, a.second, AmendmentVote::down);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -394,57 +397,62 @@ AmendmentTableImpl::AmendmentTableImpl(
|
||||
*db,
|
||||
[&](boost::optional<std::string> amendment_hash,
|
||||
boost::optional<std::string> amendment_name,
|
||||
boost::optional<int> vote_to_veto) {
|
||||
boost::optional<AmendmentVote> vote) {
|
||||
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))
|
||||
{
|
||||
Throw<std::runtime_error>(
|
||||
"Invalid amendment ID '" + *amendment_hash +
|
||||
" in wallet.db");
|
||||
}
|
||||
if (*vote_to_veto)
|
||||
if (*vote == AmendmentVote::down)
|
||||
{
|
||||
// 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 << ", "
|
||||
<< amend_hash << "} is vetoed.";
|
||||
<< amend_hash << "} is downvoted.";
|
||||
if (!amendment_name->empty())
|
||||
s->name = *amendment_name;
|
||||
s->vetoed = true;
|
||||
s->vote = *vote;
|
||||
}
|
||||
}
|
||||
else // un-veto
|
||||
{
|
||||
if (auto s = add(amend_hash, sl))
|
||||
else // up-vote
|
||||
{
|
||||
auto s = add(amend_hash, lock);
|
||||
|
||||
JLOG(j_.debug()) << "Amendment {" << *amendment_name << ", "
|
||||
<< amend_hash << "} is un-vetoed.";
|
||||
<< amend_hash << "} is upvoted.";
|
||||
if (!amendment_name->empty())
|
||||
s->name = *amendment_name;
|
||||
s->vetoed = false;
|
||||
}
|
||||
s.name = *amendment_name;
|
||||
s.vote = *vote;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
AmendmentState*
|
||||
AmendmentState&
|
||||
AmendmentTableImpl::add(
|
||||
uint256 const& amendmentHash,
|
||||
std::lock_guard<std::mutex> const&)
|
||||
{
|
||||
// call with the mutex held
|
||||
return &amendmentMap_[amendmentHash];
|
||||
return amendmentMap_[amendmentHash];
|
||||
}
|
||||
|
||||
AmendmentState*
|
||||
AmendmentTableImpl::get(
|
||||
uint256 const& amendmentHash,
|
||||
std::lock_guard<std::mutex> const& sl)
|
||||
std::lock_guard<std::mutex> const& lock)
|
||||
{
|
||||
// Forward to the const version of get.
|
||||
return const_cast<AmendmentState*>(
|
||||
std::as_const(*this).get(amendmentHash, sl));
|
||||
std::as_const(*this).get(amendmentHash, lock));
|
||||
}
|
||||
|
||||
AmendmentState const*
|
||||
@@ -464,7 +472,7 @@ AmendmentTableImpl::get(
|
||||
uint256
|
||||
AmendmentTableImpl::find(std::string const& name) const
|
||||
{
|
||||
std::lock_guard sl(mutex_);
|
||||
std::lock_guard lock(mutex_);
|
||||
|
||||
for (auto const& e : amendmentMap_)
|
||||
{
|
||||
@@ -479,50 +487,50 @@ void
|
||||
AmendmentTableImpl::persistVote(
|
||||
uint256 const& amendment,
|
||||
std::string const& name,
|
||||
bool vote_to_veto) const
|
||||
AmendmentVote vote) const
|
||||
{
|
||||
auto db = db_.checkoutDb();
|
||||
voteAmendment(*db, amendment, name, vote_to_veto);
|
||||
voteAmendment(*db, amendment, name, vote);
|
||||
}
|
||||
|
||||
bool
|
||||
AmendmentTableImpl::veto(uint256 const& amendment)
|
||||
{
|
||||
std::lock_guard sl(mutex_);
|
||||
auto s = add(amendment, sl);
|
||||
std::lock_guard lock(mutex_);
|
||||
AmendmentState& s = add(amendment, lock);
|
||||
|
||||
if (s->vetoed)
|
||||
if (s.vote == AmendmentVote::down)
|
||||
return false;
|
||||
s->vetoed = true;
|
||||
persistVote(amendment, s->name, s->vetoed);
|
||||
s.vote = AmendmentVote::down;
|
||||
persistVote(amendment, s.name, s.vote);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
AmendmentTableImpl::unVeto(uint256 const& amendment)
|
||||
{
|
||||
std::lock_guard sl(mutex_);
|
||||
auto s = get(amendment, sl);
|
||||
std::lock_guard lock(mutex_);
|
||||
AmendmentState* const s = get(amendment, lock);
|
||||
|
||||
if (!s || !s->vetoed)
|
||||
if (!s || s->vote == AmendmentVote::up)
|
||||
return false;
|
||||
s->vetoed = false;
|
||||
persistVote(amendment, s->name, s->vetoed);
|
||||
s->vote = AmendmentVote::up;
|
||||
persistVote(amendment, s->name, s->vote);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
AmendmentTableImpl::enable(uint256 const& amendment)
|
||||
{
|
||||
std::lock_guard sl(mutex_);
|
||||
auto s = add(amendment, sl);
|
||||
std::lock_guard lock(mutex_);
|
||||
AmendmentState& s = add(amendment, lock);
|
||||
|
||||
if (s->enabled)
|
||||
if (s.enabled)
|
||||
return false;
|
||||
|
||||
s->enabled = true;
|
||||
s.enabled = true;
|
||||
|
||||
if (!s->supported)
|
||||
if (!s.supported)
|
||||
{
|
||||
JLOG(j_.error()) << "Unsupported amendment " << amendment
|
||||
<< " activated.";
|
||||
@@ -535,30 +543,30 @@ AmendmentTableImpl::enable(uint256 const& amendment)
|
||||
bool
|
||||
AmendmentTableImpl::isEnabled(uint256 const& amendment) const
|
||||
{
|
||||
std::lock_guard sl(mutex_);
|
||||
auto s = get(amendment, sl);
|
||||
std::lock_guard lock(mutex_);
|
||||
AmendmentState const* s = get(amendment, lock);
|
||||
return s && s->enabled;
|
||||
}
|
||||
|
||||
bool
|
||||
AmendmentTableImpl::isSupported(uint256 const& amendment) const
|
||||
{
|
||||
std::lock_guard sl(mutex_);
|
||||
auto s = get(amendment, sl);
|
||||
std::lock_guard lock(mutex_);
|
||||
AmendmentState const* s = get(amendment, lock);
|
||||
return s && s->supported;
|
||||
}
|
||||
|
||||
bool
|
||||
AmendmentTableImpl::hasUnsupportedEnabled() const
|
||||
{
|
||||
std::lock_guard sl(mutex_);
|
||||
std::lock_guard lock(mutex_);
|
||||
return unsupportedEnabled_;
|
||||
}
|
||||
|
||||
std::optional<NetClock::time_point>
|
||||
AmendmentTableImpl::firstUnsupportedExpected() const
|
||||
{
|
||||
std::lock_guard sl(mutex_);
|
||||
std::lock_guard lock(mutex_);
|
||||
return firstUnsupportedExpected_;
|
||||
}
|
||||
|
||||
@@ -570,14 +578,15 @@ AmendmentTableImpl::doValidation(std::set<uint256> const& enabled) const
|
||||
std::vector<uint256> amendments;
|
||||
|
||||
{
|
||||
std::lock_guard sl(mutex_);
|
||||
std::lock_guard lock(mutex_);
|
||||
amendments.reserve(amendmentMap_.size());
|
||||
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))
|
||||
{
|
||||
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
|
||||
std::map<uint256, std::uint32_t> actions;
|
||||
|
||||
std::lock_guard sl(mutex_);
|
||||
std::lock_guard lock(mutex_);
|
||||
|
||||
// process all amendments we know of
|
||||
for (auto const& entry : amendmentMap_)
|
||||
@@ -638,7 +647,7 @@ AmendmentTableImpl::doVoting(
|
||||
}
|
||||
else if (
|
||||
hasValMajority && (majorityTime == NetClock::time_point{}) &&
|
||||
!entry.second.vetoed)
|
||||
entry.second.vote == AmendmentVote::up)
|
||||
{
|
||||
// Ledger says no majority, validators say yes
|
||||
JLOG(j_.debug()) << entry.first << ": amendment got majority";
|
||||
@@ -653,7 +662,7 @@ AmendmentTableImpl::doVoting(
|
||||
else if (
|
||||
(majorityTime != NetClock::time_point{}) &&
|
||||
((majorityTime + majorityTime_) <= closeTime) &&
|
||||
!entry.second.vetoed)
|
||||
entry.second.vote == AmendmentVote::up)
|
||||
{
|
||||
// Ledger says majority held
|
||||
JLOG(j_.debug()) << entry.first << ": amendment majority held";
|
||||
@@ -669,7 +678,7 @@ AmendmentTableImpl::doVoting(
|
||||
bool
|
||||
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
|
||||
// between these two ledger sequences?
|
||||
@@ -686,7 +695,7 @@ AmendmentTableImpl::doValidatedLedger(
|
||||
for (auto& e : enabled)
|
||||
enable(e);
|
||||
|
||||
std::lock_guard sl(mutex_);
|
||||
std::lock_guard lock(mutex_);
|
||||
|
||||
// Remember the ledger sequence of this update.
|
||||
lastUpdateSeq_ = ledgerSeq;
|
||||
@@ -697,12 +706,12 @@ AmendmentTableImpl::doValidatedLedger(
|
||||
firstUnsupportedExpected_.reset();
|
||||
for (auto const& [hash, time] : majority)
|
||||
{
|
||||
auto s = add(hash, sl);
|
||||
AmendmentState& s = add(hash, lock);
|
||||
|
||||
if (s->enabled)
|
||||
if (s.enabled)
|
||||
continue;
|
||||
|
||||
if (!s->supported)
|
||||
if (!s.supported)
|
||||
{
|
||||
JLOG(j_.info()) << "Unsupported amendment " << hash
|
||||
<< " reached majority at " << to_string(time);
|
||||
@@ -725,7 +734,7 @@ AmendmentTableImpl::injectJson(
|
||||
v[jss::name] = fs.name;
|
||||
|
||||
v[jss::supported] = fs.supported;
|
||||
v[jss::vetoed] = fs.vetoed;
|
||||
v[jss::vetoed] = fs.vote == AmendmentVote::down;
|
||||
v[jss::enabled] = fs.enabled;
|
||||
|
||||
if (!fs.enabled && lastVote_)
|
||||
@@ -747,14 +756,14 @@ AmendmentTableImpl::getJson() const
|
||||
{
|
||||
Json::Value ret(Json::objectValue);
|
||||
{
|
||||
std::lock_guard sl(mutex_);
|
||||
std::lock_guard lock(mutex_);
|
||||
for (auto const& e : amendmentMap_)
|
||||
{
|
||||
injectJson(
|
||||
ret[to_string(e.first)] = Json::objectValue,
|
||||
e.first,
|
||||
e.second,
|
||||
sl);
|
||||
lock);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
@@ -767,10 +776,10 @@ AmendmentTableImpl::getJson(uint256 const& amendmentID) const
|
||||
Json::Value& jAmendment = (ret[to_string(amendmentID)] = Json::objectValue);
|
||||
|
||||
{
|
||||
std::lock_guard sl(mutex_);
|
||||
auto a = get(amendmentID, sl);
|
||||
std::lock_guard lock(mutex_);
|
||||
AmendmentState const* a = get(amendmentID, lock);
|
||||
if (a)
|
||||
injectJson(jAmendment, amendmentID, *a, sl);
|
||||
injectJson(jAmendment, amendmentID, *a, lock);
|
||||
}
|
||||
|
||||
return ret;
|
||||
@@ -780,7 +789,7 @@ std::unique_ptr<AmendmentTable>
|
||||
make_AmendmentTable(
|
||||
Application& app,
|
||||
std::chrono::seconds majorityTime,
|
||||
Section const& supported,
|
||||
std::vector<AmendmentTable::FeatureInfo> const& supported,
|
||||
Section const& enabled,
|
||||
Section const& vetoed,
|
||||
beast::Journal journal)
|
||||
|
||||
@@ -132,11 +132,15 @@ deletePeerReservation(soci::session& session, PublicKey const& nodeId);
|
||||
bool
|
||||
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.
|
||||
* @param session Session with walletDB database.
|
||||
* @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
|
||||
readAmendments(
|
||||
@@ -144,21 +148,21 @@ readAmendments(
|
||||
std::function<void(
|
||||
boost::optional<std::string> amendment_hash,
|
||||
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.
|
||||
* @param session Session with walletDB database.
|
||||
* @param amendment Hash 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
|
||||
voteAmendment(
|
||||
soci::session& session,
|
||||
uint256 const& amendment,
|
||||
std::string const& name,
|
||||
bool vote_to_veto);
|
||||
AmendmentVote vote);
|
||||
|
||||
/* State DB */
|
||||
|
||||
|
||||
@@ -258,8 +258,14 @@ readAmendments(
|
||||
std::function<void(
|
||||
boost::optional<std::string> amendment_hash,
|
||||
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);
|
||||
std::string sql =
|
||||
"SELECT AmendmentHash, AmendmentName, Veto FROM FeatureVotes";
|
||||
@@ -275,7 +281,7 @@ readAmendments(
|
||||
st.execute();
|
||||
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,
|
||||
uint256 const& amendment,
|
||||
std::string const& name,
|
||||
bool vote_to_veto)
|
||||
AmendmentVote vote)
|
||||
{
|
||||
soci::transaction tr(session);
|
||||
std::string sql =
|
||||
@@ -292,7 +298,7 @@ voteAmendment(
|
||||
"('";
|
||||
sql += to_string(amendment);
|
||||
sql += "', '" + name;
|
||||
sql += "', '" + std::to_string(int{vote_to_veto}) + "');";
|
||||
sql += "', '" + std::to_string(safe_cast<int>(vote)) + "');";
|
||||
session << sql;
|
||||
tr.commit();
|
||||
}
|
||||
|
||||
@@ -32,121 +32,70 @@
|
||||
*
|
||||
* Steps required to add new features to the code:
|
||||
*
|
||||
* 1) add the new feature name to the featureNames array below
|
||||
* 2) add a uint256 declaration for the feature to the bottom of this file
|
||||
* 3) add a uint256 definition for the feature to the corresponding source
|
||||
* file (Feature.cpp)
|
||||
* 4) if the feature is going to be supported in the near future, add its
|
||||
* sha512half value and name (matching exactly the featureName here) to
|
||||
* the supportedAmendments in Feature.cpp.
|
||||
* 1) In this file, increment `numFeatures` and add a uint256 declaration
|
||||
* for the feature at the bottom
|
||||
* 2) Add a uint256 definition for the feature to the corresponding source
|
||||
* file (Feature.cpp). Use `registerFeature` to create the feature with
|
||||
* the feature's name, `Supported::no`, and `DefaultVote::no`. This
|
||||
* should be the only place the feature's name appears in code as a string.
|
||||
* 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 {
|
||||
|
||||
enum class DefaultVote : bool { no = false, yes };
|
||||
|
||||
namespace detail {
|
||||
|
||||
// *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.
|
||||
// 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
|
||||
// large to make the FeatureBitset, it MAY be larger. It MUST NOT be less than
|
||||
// the actual number of amendments. A LogicError on startup will verify this.
|
||||
static constexpr std::size_t numFeatures = 46;
|
||||
|
||||
class FeatureCollections
|
||||
{
|
||||
static constexpr char const* const featureNames[] = {
|
||||
"MultiSign", // Unconditionally supported.
|
||||
"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&
|
||||
/** Amendments that this server supports and the default voting behavior.
|
||||
Whether they are enabled depends on the Rules defined in the validated
|
||||
ledger */
|
||||
std::map<std::string, DefaultVote> const&
|
||||
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
|
||||
|
||||
std::optional<uint256>
|
||||
@@ -158,10 +107,12 @@ featureToBitsetIndex(uint256 const& f);
|
||||
uint256
|
||||
bitsetIndexToFeature(size_t i);
|
||||
|
||||
class FeatureBitset
|
||||
: private std::bitset<detail::FeatureCollections::numFeatures()>
|
||||
std::string
|
||||
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>
|
||||
void
|
||||
@@ -195,12 +146,14 @@ public:
|
||||
|
||||
explicit FeatureBitset(base const& b) : base(b)
|
||||
{
|
||||
assert(b.count() == count());
|
||||
}
|
||||
|
||||
template <class... Fs>
|
||||
explicit FeatureBitset(uint256 const& f, Fs&&... fs)
|
||||
{
|
||||
initFromFeatures(f, std::forward<Fs>(fs)...);
|
||||
assert(count() == (sizeof...(fs) + 1));
|
||||
}
|
||||
|
||||
template <class Col>
|
||||
@@ -208,6 +161,7 @@ public:
|
||||
{
|
||||
for (auto const& f : fs)
|
||||
set(featureToBitsetIndex(f));
|
||||
assert(fs.size() == count());
|
||||
}
|
||||
|
||||
auto
|
||||
|
||||
@@ -17,128 +17,317 @@
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <ripple/basics/contract.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>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
constexpr char const* const detail::FeatureCollections::featureNames[];
|
||||
|
||||
detail::FeatureCollections::FeatureCollections()
|
||||
inline std::size_t
|
||||
hash_value(ripple::uint256 const& feature)
|
||||
{
|
||||
features.reserve(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::size_t seed = 0;
|
||||
using namespace boost;
|
||||
for (auto const& n : feature)
|
||||
hash_combine(seed, n);
|
||||
return seed;
|
||||
}
|
||||
|
||||
std::optional<uint256>
|
||||
detail::FeatureCollections::getRegisteredFeature(std::string const& name) const
|
||||
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_)
|
||||
{
|
||||
auto const i = nameToFeature.find(name);
|
||||
if (i == nameToFeature.end())
|
||||
return std::nullopt;
|
||||
return i->second;
|
||||
}
|
||||
|
||||
size_t
|
||||
detail::FeatureCollections::featureToBitsetIndex(uint256 const& f) const
|
||||
// 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
|
||||
{
|
||||
auto const i = featureToIndex.find(f);
|
||||
if (i == featureToIndex.end())
|
||||
LogicError("Invalid Feature ID");
|
||||
return i->second;
|
||||
}
|
||||
};
|
||||
struct byName
|
||||
{
|
||||
};
|
||||
struct byFeature
|
||||
{
|
||||
};
|
||||
};
|
||||
|
||||
uint256 const&
|
||||
detail::FeatureCollections::bitsetIndexToFeature(size_t i) const
|
||||
// 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");
|
||||
return features[i];
|
||||
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;
|
||||
}
|
||||
|
||||
static detail::FeatureCollections const featureCollections;
|
||||
public:
|
||||
FeatureCollections();
|
||||
|
||||
/** Amendments that this server supports, but doesn't enable by default */
|
||||
std::vector<std::string> const&
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
FeatureCollections::FeatureCollections()
|
||||
{
|
||||
features.reserve(ripple::detail::numFeatures);
|
||||
}
|
||||
|
||||
std::optional<uint256>
|
||||
FeatureCollections::getRegisteredFeature(std::string const& name) const
|
||||
{
|
||||
assert(readOnly);
|
||||
Feature const* feature = getByName(name);
|
||||
if (feature)
|
||||
return feature->feature;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
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
|
||||
FeatureCollections::featureToBitsetIndex(uint256 const& f) const
|
||||
{
|
||||
assert(readOnly);
|
||||
|
||||
Feature const* feature = getByFeature(f);
|
||||
if (!feature)
|
||||
LogicError("Invalid Feature ID");
|
||||
|
||||
return getIndex(*feature);
|
||||
}
|
||||
|
||||
uint256 const&
|
||||
FeatureCollections::bitsetIndexToFeature(size_t i) const
|
||||
{
|
||||
assert(readOnly);
|
||||
Feature const& feature = getByIndex(i);
|
||||
return feature.feature;
|
||||
}
|
||||
|
||||
std::string
|
||||
FeatureCollections::featureToName(uint256 const& f) const
|
||||
{
|
||||
assert(readOnly);
|
||||
Feature const* feature = getByFeature(f);
|
||||
return feature ? feature->name : to_string(f);
|
||||
}
|
||||
|
||||
static FeatureCollections featureCollections;
|
||||
|
||||
} // 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()
|
||||
{
|
||||
// Commented out amendments will be supported in a future release (and
|
||||
// uncommented at that time).
|
||||
//
|
||||
// There are also unconditionally supported amendments in the list.
|
||||
// Those are amendments that were enabled some time ago and the
|
||||
// amendment conditional code has been removed.
|
||||
//
|
||||
// ** WARNING **
|
||||
// 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{
|
||||
"MultiSign", // Unconditionally supported.
|
||||
"TrustSetAuth", // Unconditionally supported.
|
||||
"FeeEscalation", // Unconditionally supported.
|
||||
// "OwnerPaysFee",
|
||||
"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;
|
||||
return featureCollections.supportedAmendments();
|
||||
}
|
||||
|
||||
/** Amendments that this server won't vote for by default. */
|
||||
std::size_t
|
||||
detail::numDownVotedAmendments()
|
||||
{
|
||||
return featureCollections.numDownVotedAmendments();
|
||||
}
|
||||
|
||||
/** Amendments that this server will vote for by default. */
|
||||
std::size_t
|
||||
detail::numUpVotedAmendments()
|
||||
{
|
||||
return featureCollections.numUpVotedAmendments();
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -149,6 +338,27 @@ getRegisteredFeature(std::string const& 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
|
||||
featureToBitsetIndex(uint256 const& f)
|
||||
{
|
||||
@@ -161,57 +371,109 @@ bitsetIndexToFeature(size_t 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
|
||||
|
||||
uint256 const
|
||||
featureOwnerPaysFee = *getRegisteredFeature("OwnerPaysFee"),
|
||||
featureFlow = *getRegisteredFeature("Flow"),
|
||||
featureFlowCross = *getRegisteredFeature("FlowCross"),
|
||||
featureCryptoConditionsSuite = *getRegisteredFeature("CryptoConditionsSuite"),
|
||||
fix1513 = *getRegisteredFeature("fix1513"),
|
||||
featureDepositAuth = *getRegisteredFeature("DepositAuth"),
|
||||
featureChecks = *getRegisteredFeature("Checks"),
|
||||
fix1571 = *getRegisteredFeature("fix1571"),
|
||||
fix1543 = *getRegisteredFeature("fix1543"),
|
||||
fix1623 = *getRegisteredFeature("fix1623"),
|
||||
featureDepositPreauth = *getRegisteredFeature("DepositPreauth"),
|
||||
fix1515 = *getRegisteredFeature("fix1515"),
|
||||
fix1578 = *getRegisteredFeature("fix1578"),
|
||||
featureMultiSignReserve = *getRegisteredFeature("MultiSignReserve"),
|
||||
fixTakerDryOfferRemoval = *getRegisteredFeature("fixTakerDryOfferRemoval"),
|
||||
fixMasterKeyAsRegularKey = *getRegisteredFeature("fixMasterKeyAsRegularKey"),
|
||||
fixCheckThreading = *getRegisteredFeature("fixCheckThreading"),
|
||||
fixPayChanRecipientOwnerDir = *getRegisteredFeature("fixPayChanRecipientOwnerDir"),
|
||||
featureDeletableAccounts = *getRegisteredFeature("DeletableAccounts"),
|
||||
fixQualityUpperBound = *getRegisteredFeature("fixQualityUpperBound"),
|
||||
featureRequireFullyCanonicalSig = *getRegisteredFeature("RequireFullyCanonicalSig"),
|
||||
fix1781 = *getRegisteredFeature("fix1781"),
|
||||
featureHardenedValidations = *getRegisteredFeature("HardenedValidations"),
|
||||
fixAmendmentMajorityCalc = *getRegisteredFeature("fixAmendmentMajorityCalc"),
|
||||
featureNegativeUNL = *getRegisteredFeature("NegativeUNL"),
|
||||
featureTicketBatch = *getRegisteredFeature("TicketBatch"),
|
||||
featureFlowSortStrands = *getRegisteredFeature("FlowSortStrands"),
|
||||
fixSTAmountCanonicalize = *getRegisteredFeature("fixSTAmountCanonicalize"),
|
||||
fixRmSmallIncreasedQOffers = *getRegisteredFeature("fixRmSmallIncreasedQOffers"),
|
||||
featureCheckCashMakesTrustLine = *getRegisteredFeature("CheckCashMakesTrustLine");
|
||||
// All known amendments must be registered either here or below with the
|
||||
// "retired" amendments
|
||||
REGISTER_FEATURE(OwnerPaysFee, Supported::no, DefaultVote::no);
|
||||
REGISTER_FEATURE(Flow, Supported::yes, DefaultVote::yes);
|
||||
REGISTER_FEATURE(FlowCross, Supported::yes, DefaultVote::yes);
|
||||
REGISTER_FEATURE(CryptoConditionsSuite, Supported::yes, DefaultVote::no);
|
||||
REGISTER_FIX (fix1513, Supported::yes, DefaultVote::yes);
|
||||
REGISTER_FEATURE(DepositAuth, Supported::yes, DefaultVote::yes);
|
||||
REGISTER_FEATURE(Checks, Supported::yes, DefaultVote::yes);
|
||||
REGISTER_FIX (fix1571, Supported::yes, DefaultVote::yes);
|
||||
REGISTER_FIX (fix1543, Supported::yes, DefaultVote::yes);
|
||||
REGISTER_FIX (fix1623, Supported::yes, DefaultVote::yes);
|
||||
REGISTER_FEATURE(DepositPreauth, Supported::yes, DefaultVote::yes);
|
||||
// Use liquidity from strands that consume max offers, but mark as dry
|
||||
REGISTER_FIX (fix1515, Supported::yes, DefaultVote::yes);
|
||||
REGISTER_FIX (fix1578, Supported::yes, DefaultVote::yes);
|
||||
REGISTER_FEATURE(MultiSignReserve, Supported::yes, DefaultVote::yes);
|
||||
REGISTER_FIX (fixTakerDryOfferRemoval, Supported::yes, DefaultVote::yes);
|
||||
REGISTER_FIX (fixMasterKeyAsRegularKey, Supported::yes, DefaultVote::yes);
|
||||
REGISTER_FIX (fixCheckThreading, Supported::yes, DefaultVote::yes);
|
||||
REGISTER_FIX (fixPayChanRecipientOwnerDir, Supported::yes, DefaultVote::yes);
|
||||
REGISTER_FEATURE(DeletableAccounts, Supported::yes, DefaultVote::yes);
|
||||
// fixQualityUpperBound should be activated before FlowCross
|
||||
REGISTER_FIX (fixQualityUpperBound, Supported::yes, DefaultVote::yes);
|
||||
REGISTER_FEATURE(RequireFullyCanonicalSig, Supported::yes, DefaultVote::yes);
|
||||
// fix1781: XRPEndpointSteps should be included in the circular payment check
|
||||
REGISTER_FIX (fix1781, Supported::yes, DefaultVote::yes);
|
||||
REGISTER_FEATURE(HardenedValidations, Supported::yes, DefaultVote::yes);
|
||||
REGISTER_FIX (fixAmendmentMajorityCalc, Supported::yes, DefaultVote::yes);
|
||||
REGISTER_FEATURE(NegativeUNL, Supported::yes, DefaultVote::no);
|
||||
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
|
||||
// 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]]
|
||||
uint256 const
|
||||
retiredPayChan = *getRegisteredFeature("PayChan"),
|
||||
retiredCryptoConditions = *getRegisteredFeature("CryptoConditions"),
|
||||
retiredTickSize = *getRegisteredFeature("TickSize"),
|
||||
retiredFix1368 = *getRegisteredFeature("fix1368"),
|
||||
retiredEscrow = *getRegisteredFeature("Escrow"),
|
||||
retiredFix1373 = *getRegisteredFeature("fix1373"),
|
||||
retiredEnforceInvariants = *getRegisteredFeature("EnforceInvariants"),
|
||||
retiredSortedDirectories = *getRegisteredFeature("SortedDirectories"),
|
||||
retiredFix1201 = *getRegisteredFeature("fix1201"),
|
||||
retiredFix1512 = *getRegisteredFeature("fix1512"),
|
||||
retiredFix1523 = *getRegisteredFeature("fix1523"),
|
||||
retiredFix1528 = *getRegisteredFeature("fix1528");
|
||||
retiredMultiSign = retireFeature("MultiSign"),
|
||||
retiredTrustSetAuth = retireFeature("TrustSetAuth"),
|
||||
retiredFeeEscalation = retireFeature("FeeEscalation"),
|
||||
retiredPayChan = retireFeature("PayChan"),
|
||||
retiredCryptoConditions = retireFeature("CryptoConditions"),
|
||||
retiredTickSize = retireFeature("TickSize"),
|
||||
retiredFix1368 = retireFeature("fix1368"),
|
||||
retiredEscrow = retireFeature("Escrow"),
|
||||
retiredFix1373 = retireFeature("fix1373"),
|
||||
retiredEnforceInvariants = retireFeature("EnforceInvariants"),
|
||||
retiredSortedDirectories = retireFeature("SortedDirectories"),
|
||||
retiredFix1201 = retireFeature("fix1201"),
|
||||
retiredFix1512 = retireFeature("fix1512"),
|
||||
retiredFix1523 = retireFeature("fix1523"),
|
||||
retiredFix1528 = retireFeature("fix1528");
|
||||
|
||||
// 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
|
||||
|
||||
@@ -62,6 +62,8 @@ doFeature(RPC::JsonContext& context)
|
||||
|
||||
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()))
|
||||
return rpcError(rpcBAD_FEATURE);
|
||||
|
||||
|
||||
@@ -86,11 +86,31 @@ private:
|
||||
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.
|
||||
// Enabled amendments are typically a subset of supported amendments.
|
||||
// Vetoed amendments should be supported but not enabled.
|
||||
// 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",
|
||||
"l", "m", "n", "o", "p", "q", "r", "s", "t", "u"};
|
||||
std::vector<std::string> const
|
||||
@@ -100,6 +120,7 @@ private:
|
||||
std::vector<std::string> const unsupportedMajority_{"y", "z"};
|
||||
|
||||
Section const emptySection;
|
||||
std::vector<AmendmentTable::FeatureInfo> const emptyYes;
|
||||
|
||||
test::SuiteJournal journal;
|
||||
|
||||
@@ -112,7 +133,7 @@ public:
|
||||
makeTable(
|
||||
Application& app,
|
||||
std::chrono::seconds majorityTime,
|
||||
Section const& supported,
|
||||
std::vector<AmendmentTable::FeatureInfo> const& supported,
|
||||
Section const& enabled,
|
||||
Section const& vetoed)
|
||||
{
|
||||
@@ -124,7 +145,7 @@ public:
|
||||
makeTable(
|
||||
test::jtx::Env& env,
|
||||
std::chrono::seconds majorityTime,
|
||||
Section const& supported,
|
||||
std::vector<AmendmentTable::FeatureInfo> const& supported,
|
||||
Section const& enabled,
|
||||
Section const& vetoed)
|
||||
{
|
||||
@@ -137,7 +158,7 @@ public:
|
||||
return makeTable(
|
||||
env.app(),
|
||||
majorityTime,
|
||||
makeSection(supported_),
|
||||
makeDefaultYes(supportedYes_),
|
||||
makeSection(enabled_),
|
||||
makeSection(vetoed_));
|
||||
}
|
||||
@@ -149,7 +170,7 @@ public:
|
||||
test::jtx::Env env{*this, makeConfig()};
|
||||
auto table = makeTable(env, weeks(1));
|
||||
|
||||
for (auto const& a : supported_)
|
||||
for (auto const& a : supportedYes_)
|
||||
{
|
||||
BEAST_EXPECT(table->isSupported(amendmentId(a)));
|
||||
}
|
||||
@@ -174,7 +195,7 @@ public:
|
||||
test::jtx::Env env{*this, makeConfig()};
|
||||
auto table = makeTable(env, weeks(1));
|
||||
|
||||
for (auto const& a : supported_)
|
||||
for (auto const& a : supportedYes_)
|
||||
BEAST_EXPECT(table->find(a) == amendmentId(a));
|
||||
for (auto const& a : enabled_)
|
||||
BEAST_EXPECT(table->find(a) == amendmentId(a));
|
||||
@@ -207,7 +228,8 @@ public:
|
||||
void
|
||||
testBadConfig()
|
||||
{
|
||||
auto const section = makeSection(supported_);
|
||||
auto const yesVotes = makeDefaultYes(supportedYes_);
|
||||
auto const section = makeSection(vetoed_);
|
||||
auto const id = to_string(amendmentId(enabled_[0]));
|
||||
|
||||
testcase("Bad Config");
|
||||
@@ -219,12 +241,13 @@ public:
|
||||
try
|
||||
{
|
||||
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");
|
||||
}
|
||||
catch (...)
|
||||
catch (std::exception const& e)
|
||||
{
|
||||
pass();
|
||||
BEAST_EXPECT(
|
||||
e.what() == "Invalid entry '" + id + "' in [Test]");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -235,12 +258,14 @@ public:
|
||||
try
|
||||
{
|
||||
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");
|
||||
}
|
||||
catch (...)
|
||||
catch (std::exception const& e)
|
||||
{
|
||||
pass();
|
||||
BEAST_EXPECT(
|
||||
e.what() ==
|
||||
"Invalid entry '" + id + " Test Name' in [Test]");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -254,12 +279,13 @@ public:
|
||||
try
|
||||
{
|
||||
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");
|
||||
}
|
||||
catch (...)
|
||||
catch (std::exception const& e)
|
||||
{
|
||||
pass();
|
||||
BEAST_EXPECT(
|
||||
e.what() == "Invalid entry '" + sid + " Name' in [Test]");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -273,12 +299,13 @@ public:
|
||||
try
|
||||
{
|
||||
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");
|
||||
}
|
||||
catch (...)
|
||||
catch (std::exception const& e)
|
||||
{
|
||||
pass();
|
||||
BEAST_EXPECT(
|
||||
e.what() == "Invalid entry '" + sid + " Name' in [Test]");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -293,12 +320,13 @@ public:
|
||||
try
|
||||
{
|
||||
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");
|
||||
}
|
||||
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()};
|
||||
std::unique_ptr<AmendmentTable> table = makeTable(env, weeks(2));
|
||||
|
||||
// Note which entries are enabled.
|
||||
// Note which entries are enabled
|
||||
std::set<uint256> allEnabled;
|
||||
|
||||
// Subset of amendments to enable
|
||||
allEnabled.insert(amendmentId(supported_[0]));
|
||||
allEnabled.insert(amendmentId(enabled_[0]));
|
||||
allEnabled.insert(amendmentId(vetoed_[0]));
|
||||
for (auto const& a : enabled_)
|
||||
allEnabled.insert(amendmentId(a));
|
||||
|
||||
for (uint256 const& a : allEnabled)
|
||||
table->enable(a);
|
||||
BEAST_EXPECT(table->enable(a));
|
||||
|
||||
// So far all enabled amendments are supported.
|
||||
BEAST_EXPECT(!table->hasUnsupportedEnabled());
|
||||
|
||||
// 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);
|
||||
BEAST_EXPECT(
|
||||
table->isEnabled(supportedID) ==
|
||||
(allEnabled.find(supportedID) != allEnabled.end()));
|
||||
bool const enabled = table->isEnabled(supportedID);
|
||||
bool const found = 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.
|
||||
@@ -347,14 +375,14 @@ public:
|
||||
// Unveto an amendment that is already not vetoed. Shouldn't
|
||||
// hurt anything, but the values returned by getDesired()
|
||||
// shouldn't change.
|
||||
table->unVeto(amendmentId(supported_[1]));
|
||||
BEAST_EXPECT(!table->unVeto(amendmentId(supportedYes_[1])));
|
||||
BEAST_EXPECT(desired == table->getDesired());
|
||||
}
|
||||
|
||||
// UnVeto one of the vetoed amendments. It should now be desired.
|
||||
{
|
||||
uint256 const unvetoedID = amendmentId(vetoed_[0]);
|
||||
table->unVeto(unvetoedID);
|
||||
BEAST_EXPECT(table->unVeto(unvetoedID));
|
||||
|
||||
std::vector<uint256> const desired = table->getDesired();
|
||||
BEAST_EXPECT(
|
||||
@@ -363,7 +391,7 @@ public:
|
||||
}
|
||||
|
||||
// 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));
|
||||
}
|
||||
@@ -505,7 +533,7 @@ public:
|
||||
|
||||
test::jtx::Env env{*this};
|
||||
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<uint256> ourVotes;
|
||||
@@ -566,11 +594,7 @@ public:
|
||||
|
||||
test::jtx::Env env{*this};
|
||||
auto table = makeTable(
|
||||
env,
|
||||
weeks(2),
|
||||
emptySection,
|
||||
emptySection,
|
||||
makeSection(testAmendment));
|
||||
env, weeks(2), emptyYes, emptySection, makeSection(testAmendment));
|
||||
|
||||
auto const validators = makeValidators(10);
|
||||
|
||||
@@ -629,7 +653,11 @@ public:
|
||||
|
||||
test::jtx::Env env{*this};
|
||||
auto table = makeTable(
|
||||
env, weeks(2), makeSection(supported_), emptySection, emptySection);
|
||||
env,
|
||||
weeks(2),
|
||||
makeDefaultYes(supportedYes_),
|
||||
emptySection,
|
||||
emptySection);
|
||||
|
||||
auto const validators = makeValidators(10);
|
||||
std::vector<std::pair<uint256, int>> votes;
|
||||
@@ -647,13 +675,13 @@ public:
|
||||
ourVotes,
|
||||
enabled,
|
||||
majority);
|
||||
BEAST_EXPECT(ourVotes.size() == supported_.size());
|
||||
BEAST_EXPECT(ourVotes.size() == supportedYes_.size());
|
||||
BEAST_EXPECT(enabled.empty());
|
||||
for (auto const& i : supported_)
|
||||
for (auto const& i : supportedYes_)
|
||||
BEAST_EXPECT(majority.find(amendmentId(i)) == majority.end());
|
||||
|
||||
// Now, everyone votes for this feature
|
||||
for (auto const& i : supported_)
|
||||
for (auto const& i : supportedYes_)
|
||||
votes.emplace_back(amendmentId(i), validators.size());
|
||||
|
||||
// Week 2: We should recognize a majority
|
||||
@@ -666,10 +694,10 @@ public:
|
||||
ourVotes,
|
||||
enabled,
|
||||
majority);
|
||||
BEAST_EXPECT(ourVotes.size() == supported_.size());
|
||||
BEAST_EXPECT(ourVotes.size() == supportedYes_.size());
|
||||
BEAST_EXPECT(enabled.empty());
|
||||
|
||||
for (auto const& i : supported_)
|
||||
for (auto const& i : supportedYes_)
|
||||
BEAST_EXPECT(majority[amendmentId(i)] == weekTime(weeks{2}));
|
||||
|
||||
// Week 5: We should enable the amendment
|
||||
@@ -682,7 +710,7 @@ public:
|
||||
ourVotes,
|
||||
enabled,
|
||||
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
|
||||
doRound(
|
||||
@@ -694,9 +722,9 @@ public:
|
||||
ourVotes,
|
||||
enabled,
|
||||
majority);
|
||||
BEAST_EXPECT(enabled.size() == supported_.size());
|
||||
BEAST_EXPECT(enabled.size() == supportedYes_.size());
|
||||
BEAST_EXPECT(ourVotes.empty());
|
||||
for (auto const& i : supported_)
|
||||
for (auto const& i : supportedYes_)
|
||||
BEAST_EXPECT(majority.find(amendmentId(i)) == majority.end());
|
||||
}
|
||||
|
||||
@@ -711,7 +739,7 @@ public:
|
||||
auto table = makeTable(
|
||||
env,
|
||||
weeks(2),
|
||||
makeSection(testAmendment),
|
||||
makeDefaultYes(testAmendment),
|
||||
emptySection,
|
||||
emptySection);
|
||||
|
||||
@@ -782,7 +810,7 @@ public:
|
||||
auto table = makeTable(
|
||||
env,
|
||||
weeks(8),
|
||||
makeSection(testAmendment),
|
||||
makeDefaultYes(testAmendment),
|
||||
emptySection,
|
||||
emptySection);
|
||||
|
||||
|
||||
@@ -136,11 +136,10 @@ class TxQ1_test : public beast::unit_test::suite
|
||||
for (auto i = env.current()->seq(); i <= 257; ++i)
|
||||
env.close();
|
||||
// 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
|
||||
// transactions as though they are ordinary transactions.
|
||||
auto const flagPerLedger =
|
||||
1 + ripple::detail::supportedAmendments().size();
|
||||
auto const flagPerLedger = 1 + ripple::detail::numUpVotedAmendments();
|
||||
auto const flagMaxQueue = ledgersInQueue * flagPerLedger;
|
||||
checkMetrics(env, 0, flagMaxQueue, 0, flagPerLedger, 256);
|
||||
|
||||
@@ -4157,8 +4156,7 @@ public:
|
||||
if (!getMajorityAmendments(*env.closed()).empty())
|
||||
break;
|
||||
}
|
||||
auto expectedPerLedger =
|
||||
ripple::detail::supportedAmendments().size() + 1;
|
||||
auto expectedPerLedger = ripple::detail::numUpVotedAmendments() + 1;
|
||||
checkMetrics(env, 0, 5 * expectedPerLedger, 0, expectedPerLedger, 256);
|
||||
|
||||
// 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
|
||||
// transactions, so we won't see any change in the transaction
|
||||
// outcomes. But code coverage is affected.
|
||||
env.close(closeDuration);
|
||||
expectedInQueue -= expectedPerLedger + 2;
|
||||
++expectedPerLedger;
|
||||
checkMetrics(
|
||||
env,
|
||||
expectedInQueue,
|
||||
5 * expectedPerLedger,
|
||||
expectedPerLedger + 1,
|
||||
expectedPerLedger,
|
||||
256);
|
||||
do
|
||||
{
|
||||
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);
|
||||
auto expectedInLedger = expectedInQueue;
|
||||
expectedInQueue =
|
||||
(expectedInQueue > expectedPerLedger + 2
|
||||
? expectedInQueue - (expectedPerLedger + 2)
|
||||
: 0);
|
||||
expectedInLedger -= expectedInQueue;
|
||||
++expectedPerLedger;
|
||||
checkMetrics(
|
||||
env,
|
||||
0,
|
||||
expectedInQueue,
|
||||
5 * expectedPerLedger,
|
||||
expectedInLedger,
|
||||
expectedPerLedger,
|
||||
@@ -4268,7 +4234,8 @@ public:
|
||||
BEAST_EXPECT(env.seq(alice) == seqAlice - expectedPerAccount);
|
||||
BEAST_EXPECT(
|
||||
env.seq(bob) ==
|
||||
seqBob - expectedPerAccount - (expectedRemainder > 4 ? 1 : 0));
|
||||
seqBob - expectedPerAccount -
|
||||
(expectedRemainder > 4 ? 1 : 0));
|
||||
BEAST_EXPECT(
|
||||
env.seq(carol) ==
|
||||
seqCarol - expectedPerAccount -
|
||||
@@ -4286,6 +4253,7 @@ public:
|
||||
seqFiona - expectedPerAccount -
|
||||
(expectedRemainder > 0 ? 1 : 0));
|
||||
}
|
||||
} while (expectedInQueue > 0);
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
@@ -73,8 +73,9 @@ supported_amendments()
|
||||
auto const& sa = ripple::detail::supportedAmendments();
|
||||
std::vector<uint256> feats;
|
||||
feats.reserve(sa.size());
|
||||
for (auto const& s : sa)
|
||||
for (auto const& [s, vote] : sa)
|
||||
{
|
||||
(void)vote;
|
||||
if (auto const f = getRegisteredFeature(s))
|
||||
feats.push_back(*f);
|
||||
else
|
||||
|
||||
@@ -812,6 +812,7 @@ public:
|
||||
|
||||
auto const missingSomeFeatures =
|
||||
supported_amendments() - featureMultiSignReserve - featureFlow;
|
||||
BEAST_EXPECT(missingSomeFeatures.count() == (supported.count() - 2));
|
||||
{
|
||||
// a Env supported_features_except is missing *only* those features
|
||||
Env env{*this, missingSomeFeatures};
|
||||
|
||||
@@ -26,6 +26,77 @@ namespace ripple {
|
||||
|
||||
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
|
||||
testNoParams()
|
||||
{
|
||||
@@ -34,6 +105,9 @@ class Feature_test : public beast::unit_test::suite
|
||||
using namespace test::jtx;
|
||||
Env env{*this};
|
||||
|
||||
std::map<std::string, DefaultVote> const& votes =
|
||||
ripple::detail::supportedAmendments();
|
||||
|
||||
auto jrr = env.rpc("feature")[jss::result];
|
||||
if (!BEAST_EXPECT(jrr.isMember(jss::features)))
|
||||
return;
|
||||
@@ -41,13 +115,15 @@ class Feature_test : public beast::unit_test::suite
|
||||
{
|
||||
if (!BEAST_EXPECT(feature.isMember(jss::name)))
|
||||
return;
|
||||
// default config - so all should be disabled, not vetoed, and
|
||||
// supported
|
||||
// default config - so all should be disabled, and
|
||||
// supported. Some may be vetoed.
|
||||
bool expectVeto =
|
||||
!(votes.at(feature[jss::name].asString()) == DefaultVote::yes);
|
||||
BEAST_EXPECTS(
|
||||
!feature[jss::enabled].asBool(),
|
||||
feature[jss::name].asString() + " enabled");
|
||||
BEAST_EXPECTS(
|
||||
!feature[jss::vetoed].asBool(),
|
||||
feature[jss::vetoed].asBool() == expectVeto,
|
||||
feature[jss::name].asString() + " vetoed");
|
||||
BEAST_EXPECTS(
|
||||
feature[jss::supported].asBool(),
|
||||
@@ -67,6 +143,9 @@ class Feature_test : public beast::unit_test::suite
|
||||
BEAST_EXPECTS(jrr[jss::status] == jss::success, "status");
|
||||
jrr.removeMember(jss::status);
|
||||
BEAST_EXPECT(jrr.size() == 1);
|
||||
BEAST_EXPECT(
|
||||
jrr.isMember("586480873651E106F1D6339B0C4A8945BA705A777F3F4524626FF"
|
||||
"1FC07EFE41D"));
|
||||
auto feature = *(jrr.begin());
|
||||
|
||||
BEAST_EXPECTS(feature[jss::name] == "MultiSignReserve", "name");
|
||||
@@ -121,6 +200,9 @@ class Feature_test : public beast::unit_test::suite
|
||||
Env env{
|
||||
*this, FeatureBitset(featureDepositAuth, featureDepositPreauth)};
|
||||
|
||||
std::map<std::string, DefaultVote> const& votes =
|
||||
ripple::detail::supportedAmendments();
|
||||
|
||||
auto jrr = env.rpc("feature")[jss::result];
|
||||
if (!BEAST_EXPECT(jrr.isMember(jss::features)))
|
||||
return;
|
||||
@@ -135,11 +217,13 @@ class Feature_test : public beast::unit_test::suite
|
||||
bool expectEnabled = env.app().getAmendmentTable().isEnabled(id);
|
||||
bool expectSupported =
|
||||
env.app().getAmendmentTable().isSupported(id);
|
||||
bool expectVeto =
|
||||
!(votes.at((*it)[jss::name].asString()) == DefaultVote::yes);
|
||||
BEAST_EXPECTS(
|
||||
(*it)[jss::enabled].asBool() == expectEnabled,
|
||||
(*it)[jss::name].asString() + " enabled");
|
||||
BEAST_EXPECTS(
|
||||
!(*it)[jss::vetoed].asBool(),
|
||||
(*it)[jss::vetoed].asBool() == expectVeto,
|
||||
(*it)[jss::name].asString() + " vetoed");
|
||||
BEAST_EXPECTS(
|
||||
(*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
|
||||
// to avoid maintenance as more amendments are added in the future.
|
||||
BEAST_EXPECT(majorities.size() >= 5);
|
||||
std::map<std::string, DefaultVote> const& votes =
|
||||
ripple::detail::supportedAmendments();
|
||||
|
||||
jrr = env.rpc("feature")[jss::result];
|
||||
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)))
|
||||
return;
|
||||
bool expectVeto =
|
||||
!(votes.at(feature[jss::name].asString()) == DefaultVote::yes);
|
||||
BEAST_EXPECTS(
|
||||
feature.isMember(jss::majority),
|
||||
expectVeto ^ feature.isMember(jss::majority),
|
||||
feature[jss::name].asString() + " majority");
|
||||
BEAST_EXPECTS(
|
||||
feature.isMember(jss::vetoed) &&
|
||||
feature[jss::vetoed].asBool() == expectVeto,
|
||||
feature[jss::name].asString() + " vetoed");
|
||||
BEAST_EXPECTS(
|
||||
feature.isMember(jss::count),
|
||||
feature[jss::name].asString() + " count");
|
||||
@@ -218,10 +310,12 @@ class Feature_test : public beast::unit_test::suite
|
||||
BEAST_EXPECTS(
|
||||
feature.isMember(jss::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::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
|
||||
run() override
|
||||
{
|
||||
testInternals();
|
||||
testFeatureLookups();
|
||||
testNoParams();
|
||||
testSingleFeature();
|
||||
testInvalidFeature();
|
||||
|
||||
Reference in New Issue
Block a user