AmendmentTable improvements

This commit is contained in:
Scott Schurr
2020-01-27 18:28:28 -08:00
committed by manojsdoshi
parent 16f79d160a
commit 68494a308e
5 changed files with 312 additions and 265 deletions

View File

@@ -37,7 +37,7 @@ public:
virtual ~AmendmentTable() = default; virtual ~AmendmentTable() = default;
virtual uint256 virtual uint256
find(std::string const& name) = 0; find(std::string const& name) const = 0;
virtual bool virtual bool
veto(uint256 const& amendment) = 0; veto(uint256 const& amendment) = 0;
@@ -46,13 +46,11 @@ public:
virtual bool virtual bool
enable(uint256 const& amendment) = 0; enable(uint256 const& amendment) = 0;
virtual bool
disable(uint256 const& amendment) = 0;
virtual bool virtual bool
isEnabled(uint256 const& amendment) = 0; isEnabled(uint256 const& amendment) const = 0;
virtual bool virtual bool
isSupported(uint256 const& amendment) = 0; isSupported(uint256 const& amendment) const = 0;
/** /**
* @brief returns true if one or more amendments on the network * @brief returns true if one or more amendments on the network
@@ -61,16 +59,17 @@ public:
* @return true if an unsupported feature is enabled on the network * @return true if an unsupported feature is enabled on the network
*/ */
virtual bool virtual bool
hasUnsupportedEnabled() = 0; hasUnsupportedEnabled() const = 0;
virtual boost::optional<NetClock::time_point> virtual boost::optional<NetClock::time_point>
firstUnsupportedExpected() = 0; firstUnsupportedExpected() const = 0;
virtual Json::Value virtual Json::Value
getJson(int) = 0; getJson() const = 0;
/** Returns a Json::objectValue. */ /** Returns a Json::objectValue. */
virtual Json::Value virtual Json::Value
getJson(uint256 const&) = 0; getJson(uint256 const& amendment) const = 0;
/** Called when a new fully-validated ledger is accepted. */ /** Called when a new fully-validated ledger is accepted. */
void void
@@ -88,7 +87,7 @@ public:
a new validated ledger. (If it could have changed things.) a new validated ledger. (If it could have changed things.)
*/ */
virtual bool virtual bool
needValidatedLedger(LedgerIndex seq) = 0; needValidatedLedger(LedgerIndex seq) const = 0;
virtual void virtual void
doValidatedLedger( doValidatedLedger(
@@ -108,14 +107,14 @@ public:
// Called by the consensus code when we need to // Called by the consensus code when we need to
// add feature entries to a validation // add feature entries to a validation
virtual std::vector<uint256> virtual std::vector<uint256>
doValidation(std::set<uint256> const& enabled) = 0; doValidation(std::set<uint256> const& enabled) const = 0;
// The set of amendments to enable in the genesis ledger // The set of amendments to enable in the genesis ledger
// This will return all known, non-vetoed amendments. // This will return all known, non-vetoed amendments.
// If we ever have two amendments that should not both be // If we ever have two amendments that should not both be
// enabled at the same time, we should ensure one is vetoed. // enabled at the same time, we should ensure one is vetoed.
virtual std::vector<uint256> virtual std::vector<uint256>
getDesired() = 0; getDesired() const = 0;
// The function below adapts the API callers expect to the // The function below adapts the API callers expect to the
// internal amendment table API. This allows the amendment // internal amendment table API. This allows the amendment

View File

@@ -71,8 +71,8 @@ Amendment must receive at least an 80% approval rate from validating nodes for
a period of two weeks before being accepted. The following example outlines the a period of two weeks before being accepted. The following example outlines the
process of an Amendment from its conception to approval and usage. process of an Amendment from its conception to approval and usage.
* A community member makes proposes to change transaction processing in some * A community member proposes to change transaction processing in some way.
way. The proposal is discussed amongst the community and receives its support The proposal is discussed amongst the community and receives its support
creating a community or human consensus. creating a community or human consensus.
* Some members contribute their time and work to develop the Amendment. * Some members contribute their time and work to develop the Amendment.
@@ -101,7 +101,7 @@ the majority status from the ledger.
If an amendment holds majority status for two weeks, validators will If an amendment holds majority status for two weeks, validators will
introduce a pseudo-transaction to enable the amendment. introduce a pseudo-transaction to enable the amendment.
All amednements are assumed to be critical and irreversible. Thus there All amendments are assumed to be critical and irreversible. Thus there
is no mechanism to disable or revoke an amendment, nor is there a way is no mechanism to disable or revoke an amendment, nor is there a way
for a server to operate while an amendment it does not understand is for a server to operate while an amendment it does not understand is
enabled. enabled.

View File

@@ -139,7 +139,7 @@ public:
class AmendmentTableImpl final : public AmendmentTable class AmendmentTableImpl final : public AmendmentTable
{ {
protected: protected:
std::mutex mutex_; mutable std::mutex mutex_;
hash_map<uint256, AmendmentState> amendmentMap_; hash_map<uint256, AmendmentState> amendmentMap_;
std::uint32_t lastUpdateSeq_; std::uint32_t lastUpdateSeq_;
@@ -157,6 +157,7 @@ protected:
// True if an unsupported amendment is enabled // True if an unsupported amendment is enabled
bool unsupportedEnabled_; bool unsupportedEnabled_;
// Unset if no unsupported amendments reach majority, // Unset if no unsupported amendments reach majority,
// else set to the earliest time an unsupported amendment // else set to the earliest time an unsupported amendment
// will be enabled. // will be enabled.
@@ -164,16 +165,24 @@ protected:
beast::Journal const j_; beast::Journal const j_;
// Finds or creates state // Finds or creates state. Must be called with mutex_ locked.
AmendmentState* AmendmentState*
add(uint256 const& amendment); add(uint256 const& amendment, std::lock_guard<std::mutex> const& sl);
// Finds existing state // Finds existing state. Must be called with mutex_ locked.
AmendmentState* AmendmentState*
get(uint256 const& amendment); get(uint256 const& amendment, std::lock_guard<std::mutex> const& sl);
AmendmentState const*
get(uint256 const& amendment, std::lock_guard<std::mutex> const& sl) const;
// Injects amendment json into v. Must be called with mutex_ locked.
void void
setJson(Json::Value& v, uint256 const& amendment, const AmendmentState&); injectJson(
Json::Value& v,
uint256 const& amendment,
AmendmentState const& state,
std::lock_guard<std::mutex> const& sl) const;
public: public:
AmendmentTableImpl( AmendmentTableImpl(
@@ -185,7 +194,7 @@ public:
beast::Journal journal); beast::Journal journal);
uint256 uint256
find(std::string const& name) override; find(std::string const& name) const override;
bool bool
veto(uint256 const& amendment) override; veto(uint256 const& amendment) override;
@@ -194,26 +203,25 @@ public:
bool bool
enable(uint256 const& amendment) override; enable(uint256 const& amendment) override;
bool
disable(uint256 const& amendment) override;
bool bool
isEnabled(uint256 const& amendment) override; isEnabled(uint256 const& amendment) const override;
bool bool
isSupported(uint256 const& amendment) override; isSupported(uint256 const& amendment) const override;
bool bool
hasUnsupportedEnabled() override; hasUnsupportedEnabled() const override;
boost::optional<NetClock::time_point> boost::optional<NetClock::time_point>
firstUnsupportedExpected() override; firstUnsupportedExpected() const override;
Json::Value Json::Value
getJson(int) override; getJson() const override;
Json::Value Json::Value
getJson(uint256 const&) override; getJson(uint256 const&) const override;
bool bool
needValidatedLedger(LedgerIndex seq) override; needValidatedLedger(LedgerIndex seq) const override;
void void
doValidatedLedger( doValidatedLedger(
@@ -222,10 +230,10 @@ public:
majorityAmendments_t const& majority) override; majorityAmendments_t const& majority) override;
std::vector<uint256> std::vector<uint256>
doValidation(std::set<uint256> const& enabledAmendments) override; doValidation(std::set<uint256> const& enabledAmendments) const override;
std::vector<uint256> std::vector<uint256>
getDesired() override; getDesired() const override;
std::map<uint256, std::uint32_t> std::map<uint256, std::uint32_t>
doVoting( doVoting(
@@ -256,7 +264,7 @@ AmendmentTableImpl::AmendmentTableImpl(
for (auto const& a : parseSection(supported)) for (auto const& a : parseSection(supported))
{ {
if (auto s = add(a.first)) if (auto s = add(a.first, sl))
{ {
JLOG(j_.debug()) << "Amendment " << a.first << " is supported."; JLOG(j_.debug()) << "Amendment " << a.first << " is supported.";
@@ -269,7 +277,7 @@ AmendmentTableImpl::AmendmentTableImpl(
for (auto const& a : parseSection(enabled)) for (auto const& a : parseSection(enabled))
{ {
if (auto s = add(a.first)) if (auto s = add(a.first, sl))
{ {
JLOG(j_.debug()) << "Amendment " << a.first << " is enabled."; JLOG(j_.debug()) << "Amendment " << a.first << " is enabled.";
@@ -284,7 +292,7 @@ AmendmentTableImpl::AmendmentTableImpl(
for (auto const& a : parseSection(vetoed)) for (auto const& a : parseSection(vetoed))
{ {
// Unknown amendments are effectively vetoed already // Unknown amendments are effectively vetoed already
if (auto s = get(a.first)) if (auto s = get(a.first, sl))
{ {
JLOG(j_.info()) << "Amendment " << a.first << " is vetoed."; JLOG(j_.info()) << "Amendment " << a.first << " is vetoed.";
@@ -297,14 +305,28 @@ AmendmentTableImpl::AmendmentTableImpl(
} }
AmendmentState* AmendmentState*
AmendmentTableImpl::add(uint256 const& amendmentHash) AmendmentTableImpl::add(
uint256 const& amendmentHash,
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(uint256 const& amendmentHash) AmendmentTableImpl::get(
uint256 const& amendmentHash,
std::lock_guard<std::mutex> const& sl)
{
// Forward to the const version of get.
return const_cast<AmendmentState*>(
std::as_const(*this).get(amendmentHash, sl));
}
AmendmentState const*
AmendmentTableImpl::get(
uint256 const& amendmentHash,
std::lock_guard<std::mutex> const&) const
{ {
// call with the mutex held // call with the mutex held
auto ret = amendmentMap_.find(amendmentHash); auto ret = amendmentMap_.find(amendmentHash);
@@ -316,7 +338,7 @@ AmendmentTableImpl::get(uint256 const& amendmentHash)
} }
uint256 uint256
AmendmentTableImpl::find(std::string const& name) AmendmentTableImpl::find(std::string const& name) const
{ {
std::lock_guard sl(mutex_); std::lock_guard sl(mutex_);
@@ -333,7 +355,7 @@ bool
AmendmentTableImpl::veto(uint256 const& amendment) AmendmentTableImpl::veto(uint256 const& amendment)
{ {
std::lock_guard sl(mutex_); std::lock_guard sl(mutex_);
auto s = add(amendment); auto s = add(amendment, sl);
if (s->vetoed) if (s->vetoed)
return false; return false;
@@ -345,7 +367,7 @@ bool
AmendmentTableImpl::unVeto(uint256 const& amendment) AmendmentTableImpl::unVeto(uint256 const& amendment)
{ {
std::lock_guard sl(mutex_); std::lock_guard sl(mutex_);
auto s = get(amendment); auto s = get(amendment, sl);
if (!s || !s->vetoed) if (!s || !s->vetoed)
return false; return false;
@@ -357,7 +379,7 @@ bool
AmendmentTableImpl::enable(uint256 const& amendment) AmendmentTableImpl::enable(uint256 const& amendment)
{ {
std::lock_guard sl(mutex_); std::lock_guard sl(mutex_);
auto s = add(amendment); auto s = add(amendment, sl);
if (s->enabled) if (s->enabled)
return false; return false;
@@ -375,58 +397,45 @@ AmendmentTableImpl::enable(uint256 const& amendment)
} }
bool bool
AmendmentTableImpl::disable(uint256 const& amendment) AmendmentTableImpl::isEnabled(uint256 const& amendment) const
{ {
std::lock_guard sl(mutex_); std::lock_guard sl(mutex_);
auto s = get(amendment); auto s = get(amendment, sl);
if (!s || !s->enabled)
return false;
s->enabled = false;
return true;
}
bool
AmendmentTableImpl::isEnabled(uint256 const& amendment)
{
std::lock_guard sl(mutex_);
auto s = get(amendment);
return s && s->enabled; return s && s->enabled;
} }
bool bool
AmendmentTableImpl::isSupported(uint256 const& amendment) AmendmentTableImpl::isSupported(uint256 const& amendment) const
{ {
std::lock_guard sl(mutex_); std::lock_guard sl(mutex_);
auto s = get(amendment); auto s = get(amendment, sl);
return s && s->supported; return s && s->supported;
} }
bool bool
AmendmentTableImpl::hasUnsupportedEnabled() AmendmentTableImpl::hasUnsupportedEnabled() const
{ {
std::lock_guard sl(mutex_); std::lock_guard sl(mutex_);
return unsupportedEnabled_; return unsupportedEnabled_;
} }
boost::optional<NetClock::time_point> boost::optional<NetClock::time_point>
AmendmentTableImpl::firstUnsupportedExpected() AmendmentTableImpl::firstUnsupportedExpected() const
{ {
std::lock_guard sl(mutex_); std::lock_guard sl(mutex_);
return firstUnsupportedExpected_; return firstUnsupportedExpected_;
} }
std::vector<uint256> std::vector<uint256>
AmendmentTableImpl::doValidation(std::set<uint256> const& enabled) AmendmentTableImpl::doValidation(std::set<uint256> const& enabled) const
{ {
// Get the list of amendments we support and do not // Get the list of amendments we support and do not
// veto, but that are not already enabled // veto, but that are not already enabled
std::vector<uint256> amendments; std::vector<uint256> amendments;
amendments.reserve(amendmentMap_.size());
{ {
std::lock_guard sl(mutex_); std::lock_guard sl(mutex_);
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.vetoed &&
@@ -444,7 +453,7 @@ AmendmentTableImpl::doValidation(std::set<uint256> const& enabled)
} }
std::vector<uint256> std::vector<uint256>
AmendmentTableImpl::getDesired() AmendmentTableImpl::getDesired() const
{ {
// Get the list of amendments we support and do not veto // Get the list of amendments we support and do not veto
return doValidation({}); return doValidation({});
@@ -491,7 +500,6 @@ 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 sl(mutex_);
// process all amendments we know of // process all amendments we know of
@@ -510,8 +518,7 @@ AmendmentTableImpl::doVoting(
if (enabledAmendments.count(entry.first) != 0) if (enabledAmendments.count(entry.first) != 0)
{ {
JLOG(j_.debug()) JLOG(j_.debug()) << entry.first << ": amendment already enabled";
<< entry.first << ": amendment already enabled";
} }
else if ( else if (
hasValMajority && (majorityTime == NetClock::time_point{}) && hasValMajority && (majorityTime == NetClock::time_point{}) &&
@@ -521,8 +528,7 @@ AmendmentTableImpl::doVoting(
JLOG(j_.debug()) << entry.first << ": amendment got majority"; JLOG(j_.debug()) << entry.first << ": amendment got majority";
actions[entry.first] = tfGotMajority; actions[entry.first] = tfGotMajority;
} }
else if ( else if (!hasValMajority && (majorityTime != NetClock::time_point{}))
!hasValMajority && (majorityTime != NetClock::time_point{}))
{ {
// Ledger says majority, validators say no // Ledger says majority, validators say no
JLOG(j_.debug()) << entry.first << ": amendment lost majority"; JLOG(j_.debug()) << entry.first << ": amendment lost majority";
@@ -541,13 +547,11 @@ AmendmentTableImpl::doVoting(
// Stash for reporting // Stash for reporting
lastVote_ = std::move(vote); lastVote_ = std::move(vote);
}
return actions; return actions;
} }
bool bool
AmendmentTableImpl::needValidatedLedger(LedgerIndex ledgerSeq) AmendmentTableImpl::needValidatedLedger(LedgerIndex ledgerSeq) const
{ {
std::lock_guard sl(mutex_); std::lock_guard sl(mutex_);
@@ -567,13 +571,17 @@ AmendmentTableImpl::doValidatedLedger(
enable(e); enable(e);
std::lock_guard sl(mutex_); std::lock_guard sl(mutex_);
// Since we have the whole list in `majority`, reset the time flag, even if
// it's currently set. If it's not set when the loop is done, then any // Remember the ledger sequence of this update.
lastUpdateSeq_ = ledgerSeq;
// Since we have the whole list in `majority`, reset the time flag, even
// if it's currently set. If it's not set when the loop is done, then any
// prior unknown amendments have lost majority. // prior unknown amendments have lost majority.
firstUnsupportedExpected_.reset(); firstUnsupportedExpected_.reset();
for (auto const& [hash, time] : majority) for (auto const& [hash, time] : majority)
{ {
auto s = add(hash); auto s = add(hash, sl);
if (s->enabled) if (s->enabled)
continue; continue;
@@ -591,10 +599,11 @@ AmendmentTableImpl::doValidatedLedger(
} }
void void
AmendmentTableImpl::setJson( AmendmentTableImpl::injectJson(
Json::Value& v, Json::Value& v,
const uint256& id, const uint256& id,
const AmendmentState& fs) const AmendmentState& fs,
std::lock_guard<std::mutex> const&) const
{ {
if (!fs.name.empty()) if (!fs.name.empty())
v[jss::name] = fs.name; v[jss::name] = fs.name;
@@ -621,30 +630,34 @@ AmendmentTableImpl::setJson(
} }
Json::Value Json::Value
AmendmentTableImpl::getJson(int) AmendmentTableImpl::getJson() const
{ {
Json::Value ret(Json::objectValue); Json::Value ret(Json::objectValue);
{ {
std::lock_guard sl(mutex_); std::lock_guard sl(mutex_);
for (auto const& e : amendmentMap_) for (auto const& e : amendmentMap_)
{ {
setJson( injectJson(
ret[to_string(e.first)] = Json::objectValue, e.first, e.second); ret[to_string(e.first)] = Json::objectValue,
e.first,
e.second,
sl);
} }
} }
return ret; return ret;
} }
Json::Value Json::Value
AmendmentTableImpl::getJson(uint256 const& amendmentID) AmendmentTableImpl::getJson(uint256 const& amendmentID) const
{ {
Json::Value ret = Json::objectValue; Json::Value ret = Json::objectValue;
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 sl(mutex_);
auto a = add(amendmentID); auto a = get(amendmentID, sl);
setJson(jAmendment, amendmentID, *a); if (a)
injectJson(jAmendment, amendmentID, *a, sl);
} }
return ret; return ret;

View File

@@ -45,7 +45,7 @@ doFeature(RPC::JsonContext& context)
if (!context.params.isMember(jss::feature)) if (!context.params.isMember(jss::feature))
{ {
auto features = table.getJson(0); auto features = table.getJson();
for (auto const& [h, t] : majorities) for (auto const& [h, t] : majorities)
{ {
@@ -67,9 +67,9 @@ doFeature(RPC::JsonContext& context)
if (context.params.isMember(jss::vetoed)) if (context.params.isMember(jss::vetoed))
{ {
if (context.params[jss::vetoed].asBool()) if (context.params[jss::vetoed].asBool())
context.app.getAmendmentTable().veto(feature); table.veto(feature);
else else
context.app.getAmendmentTable().unVeto(feature); table.unVeto(feature);
} }
Json::Value jvReply = table.getJson(feature); Json::Value jvReply = table.getJson(feature);

View File

@@ -29,6 +29,8 @@
#include <ripple/protocol/SecretKey.h> #include <ripple/protocol/SecretKey.h>
#include <ripple/protocol/TxFlags.h> #include <ripple/protocol/TxFlags.h>
#include <ripple/protocol/digest.h> #include <ripple/protocol/digest.h>
#include <ripple/protocol/jss.h>
#include <test/jtx/Env.h>
#include <test/unit_test/SuiteJournal.h> #include <test/unit_test/SuiteJournal.h>
namespace ripple { namespace ripple {
@@ -51,16 +53,6 @@ private:
return result; return result;
} }
static std::vector<std::string>
createSet(int group, int count)
{
std::vector<std::string> amendments;
for (int i = 0; i < count; i++)
amendments.push_back(
"Amendment" + std::to_string((1000000 * group) + i));
return amendments;
}
static Section static Section
makeSection(std::vector<std::string> const& amendments) makeSection(std::vector<std::string> const& amendments)
{ {
@@ -78,43 +70,52 @@ private:
return section; return section;
} }
std::vector<std::string> const m_set1; // All useful amendments are supported amendments.
std::vector<std::string> const m_set2; // Enabled amendments are typically a subset of supported amendments.
std::vector<std::string> const m_set3; // Vetoed amendments should be supported but not enabled.
std::vector<std::string> const m_set4; // Unsupported amendments may be added to the AmendmentTable.
std::vector<std::string> const m_set5; std::vector<std::string> const supported_{
"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
enabled_{"b", "d", "f", "h", "j", "l", "n", "p"};
std::vector<std::string> const vetoed_{"a", "c", "e"};
std::vector<std::string> const unsupported_{"v", "w", "x"};
std::vector<std::string> const unsupportedMajority_{"y", "z"};
Section const emptySection; Section const emptySection;
test::SuiteJournal journal; test::SuiteJournal journal;
public: public:
AmendmentTable_test() AmendmentTable_test() : journal("AmendmentTable_test", *this)
: m_set1(createSet(1, 12))
, m_set2(createSet(2, 12))
, m_set3(createSet(3, 12))
, m_set4(createSet(4, 12))
, m_set5(createSet(5, 12))
, journal("AmendmentTable_test", *this)
{ {
} }
std::unique_ptr<AmendmentTable> std::unique_ptr<AmendmentTable>
makeTable( makeTable(
int w, std::chrono::seconds majorityTime,
Section const supported, Section const supported,
Section const enabled, Section const enabled,
Section const vetoed) Section const vetoed)
{ {
return make_AmendmentTable( return make_AmendmentTable(
weeks(w), majorityFraction, supported, enabled, vetoed, journal); majorityTime,
majorityFraction,
supported,
enabled,
vetoed,
journal);
} }
std::unique_ptr<AmendmentTable> std::unique_ptr<AmendmentTable>
makeTable(int w) makeTable(std::chrono::seconds majorityTime)
{ {
return makeTable( return makeTable(
w, makeSection(m_set1), makeSection(m_set2), makeSection(m_set3)); majorityTime,
makeSection(supported_),
makeSection(enabled_),
makeSection(vetoed_));
} }
void void
@@ -122,23 +123,22 @@ public:
{ {
testcase("Construction"); testcase("Construction");
auto table = makeTable(1); auto table = makeTable(weeks(1));
for (auto const& a : m_set1) for (auto const& a : supported_)
{ {
BEAST_EXPECT(table->isSupported(amendmentId(a))); BEAST_EXPECT(table->isSupported(amendmentId(a)));
BEAST_EXPECT(!table->isEnabled(amendmentId(a)));
} }
for (auto const& a : m_set2) for (auto const& a : enabled_)
{ {
BEAST_EXPECT(table->isSupported(amendmentId(a))); BEAST_EXPECT(table->isSupported(amendmentId(a)));
BEAST_EXPECT(table->isEnabled(amendmentId(a))); BEAST_EXPECT(table->isEnabled(amendmentId(a)));
} }
for (auto const& a : m_set3) for (auto const& a : vetoed_)
{ {
BEAST_EXPECT(!table->isSupported(amendmentId(a))); BEAST_EXPECT(table->isSupported(amendmentId(a)));
BEAST_EXPECT(!table->isEnabled(amendmentId(a))); BEAST_EXPECT(!table->isEnabled(amendmentId(a)));
} }
} }
@@ -148,26 +148,43 @@ public:
{ {
testcase("Name to ID mapping"); testcase("Name to ID mapping");
auto table = makeTable(1); auto table = makeTable(weeks(1));
for (auto const& a : m_set1) for (auto const& a : supported_)
BEAST_EXPECT(table->find(a) == amendmentId(a)); BEAST_EXPECT(table->find(a) == amendmentId(a));
for (auto const& a : m_set2) for (auto const& a : enabled_)
BEAST_EXPECT(table->find(a) == amendmentId(a)); BEAST_EXPECT(table->find(a) == amendmentId(a));
for (auto const& a : m_set3) for (auto const& a : vetoed_)
BEAST_EXPECT(table->find(a) == amendmentId(a));
for (auto const& a : unsupported_)
BEAST_EXPECT(!table->find(a)); BEAST_EXPECT(!table->find(a));
for (auto const& a : m_set4) for (auto const& a : unsupportedMajority_)
BEAST_EXPECT(!table->find(a));
for (auto const& a : m_set5)
BEAST_EXPECT(!table->find(a)); BEAST_EXPECT(!table->find(a));
// Vetoing an unsupported amendment should add the amendment to table.
// Verify that unsupportedID is not in table.
uint256 const unsupportedID = amendmentId(unsupported_[0]);
{
Json::Value const unsupp =
table->getJson(unsupportedID)[to_string(unsupportedID)];
BEAST_EXPECT(unsupp.size() == 0);
}
// After vetoing unsupportedID verify that it is in table.
table->veto(unsupportedID);
{
Json::Value const unsupp =
table->getJson(unsupportedID)[to_string(unsupportedID)];
BEAST_EXPECT(unsupp[jss::vetoed].asBool());
}
} }
void void
testBadConfig() testBadConfig()
{ {
auto const section = makeSection(m_set1); auto const section = makeSection(supported_);
auto const id = to_string(amendmentId(m_set2[0])); auto const id = to_string(amendmentId(enabled_[0]));
testcase("Bad Config"); testcase("Bad Config");
@@ -177,7 +194,7 @@ public:
try try
{ {
if (makeTable(2, test, emptySection, emptySection)) if (makeTable(weeks(2), test, emptySection, emptySection))
fail("Accepted only amendment ID"); fail("Accepted only amendment ID");
} }
catch (...) catch (...)
@@ -192,7 +209,7 @@ public:
try try
{ {
if (makeTable(2, test, emptySection, emptySection)) if (makeTable(weeks(2), test, emptySection, emptySection))
fail("Accepted extra arguments"); fail("Accepted extra arguments");
} }
catch (...) catch (...)
@@ -210,7 +227,7 @@ public:
try try
{ {
if (makeTable(2, test, emptySection, emptySection)) if (makeTable(weeks(2), test, emptySection, emptySection))
fail("Accepted short amendment ID"); fail("Accepted short amendment ID");
} }
catch (...) catch (...)
@@ -228,7 +245,7 @@ public:
try try
{ {
if (makeTable(2, test, emptySection, emptySection)) if (makeTable(weeks(2), test, emptySection, emptySection))
fail("Accepted long amendment ID"); fail("Accepted long amendment ID");
} }
catch (...) catch (...)
@@ -247,7 +264,7 @@ public:
try try
{ {
if (makeTable(2, test, emptySection, emptySection)) if (makeTable(weeks(2), test, emptySection, emptySection))
fail("Accepted non-hex amendment ID"); fail("Accepted non-hex amendment ID");
} }
catch (...) catch (...)
@@ -257,77 +274,82 @@ public:
} }
} }
std::map<uint256, bool>
getState(AmendmentTable* table, std::set<uint256> const& exclude)
{
std::map<uint256, bool> state;
auto track = [&state, table](std::vector<std::string> const& v) {
for (auto const& a : v)
{
auto const id = amendmentId(a);
state[id] = table->isEnabled(id);
}
};
track(m_set1);
track(m_set2);
track(m_set3);
track(m_set4);
track(m_set5);
for (auto const& a : exclude)
state.erase(a);
return state;
}
void void
testEnableDisable() testEnableVeto()
{ {
testcase("enable & disable"); testcase("enable and veto");
auto const testAmendment = amendmentId("TestAmendment"); std::unique_ptr<AmendmentTable> table = makeTable(weeks(2));
auto table = makeTable(2);
// Subset of amendments to enable // Note which entries are pre-enabled.
std::set<uint256> enabled; std::set<uint256> allEnabled;
enabled.insert(testAmendment); for (std::string const& a : enabled_)
enabled.insert(amendmentId(m_set1[0])); allEnabled.insert(amendmentId(a));
enabled.insert(amendmentId(m_set2[0]));
enabled.insert(amendmentId(m_set3[0]));
enabled.insert(amendmentId(m_set4[0]));
enabled.insert(amendmentId(m_set5[0]));
// Get the state before, excluding the items we'll change: // Subset of amendments to late-enable
auto const pre_state = getState(table.get(), enabled); std::set<uint256> lateEnabled;
lateEnabled.insert(amendmentId(supported_[0]));
lateEnabled.insert(amendmentId(enabled_[0]));
lateEnabled.insert(amendmentId(vetoed_[0]));
// Enable the subset and verify // Do the late enabling.
for (auto const& a : enabled) for (uint256 const& a : lateEnabled)
table->enable(a); table->enable(a);
for (auto const& a : enabled) // So far all enabled amendments are supported.
BEAST_EXPECT(table->isEnabled(a)); BEAST_EXPECT(!table->hasUnsupportedEnabled());
// Disable the subset and verify // Verify all pre- and late-enables are enabled and nothing else.
for (auto const& a : enabled) allEnabled.insert(lateEnabled.begin(), lateEnabled.end());
table->disable(a); for (std::string const& a : supported_)
{
uint256 const supportedID = amendmentId(a);
BEAST_EXPECT(
table->isEnabled(supportedID) ==
(allEnabled.find(supportedID) != allEnabled.end()));
}
for (auto const& a : enabled) // All supported and unVetoed amendments should be returned as desired.
BEAST_EXPECT(!table->isEnabled(a)); {
std::set<uint256> vetoed;
for (std::string const& a : vetoed_)
vetoed.insert(amendmentId(a));
// Get the state after, excluding the items we changed: std::vector<uint256> const desired = table->getDesired();
auto const post_state = getState(table.get(), enabled); for (uint256 const& a : desired)
BEAST_EXPECT(vetoed.count(a) == 0);
// Ensure the states are identical // Unveto an amendment that is already not vetoed. Shouldn't
auto ret = std::mismatch( // hurt anything, but the values returned by getDesired()
pre_state.begin(), // shouldn't change.
pre_state.end(), table->unVeto(amendmentId(supported_[1]));
post_state.begin(), BEAST_EXPECT(desired == table->getDesired());
post_state.end()); }
BEAST_EXPECT(ret.first == pre_state.end()); // UnVeto one of the vetoed amendments. It should now be desired.
BEAST_EXPECT(ret.second == post_state.end()); {
uint256 const unvetoedID = amendmentId(vetoed_[0]);
table->unVeto(unvetoedID);
std::vector<uint256> const desired = table->getDesired();
BEAST_EXPECT(
std::find(desired.begin(), desired.end(), unvetoedID) !=
desired.end());
}
// Veto all supported amendments. Now desired should be empty.
for (std::string const& a : supported_)
{
table->veto(amendmentId(a));
}
BEAST_EXPECT(table->getDesired().empty());
// Enable an unsupported amendment.
{
BEAST_EXPECT(!table->hasUnsupportedEnabled());
table->enable(amendmentId(unsupported_[0]));
BEAST_EXPECT(table->hasUnsupportedEnabled());
}
} }
std::vector<std::pair<PublicKey, SecretKey>> std::vector<std::pair<PublicKey, SecretKey>>
@@ -456,7 +478,8 @@ public:
auto const testAmendment = amendmentId("TestAmendment"); auto const testAmendment = amendmentId("TestAmendment");
auto const validators = makeValidators(10); auto const validators = makeValidators(10);
auto table = makeTable(2, emptySection, emptySection, emptySection); auto table =
makeTable(weeks(2), emptySection, emptySection, emptySection);
std::vector<std::pair<uint256, int>> votes; std::vector<std::pair<uint256, int>> votes;
std::vector<uint256> ourVotes; std::vector<uint256> ourVotes;
@@ -495,7 +518,7 @@ public:
auto const testAmendment = amendmentId("vetoedAmendment"); auto const testAmendment = amendmentId("vetoedAmendment");
auto table = makeTable( auto table = makeTable(
2, emptySection, emptySection, makeSection(testAmendment)); weeks(2), emptySection, emptySection, makeSection(testAmendment));
auto const validators = makeValidators(10); auto const validators = makeValidators(10);
@@ -531,8 +554,8 @@ public:
{ {
testcase("voteEnable"); testcase("voteEnable");
auto table = auto table = makeTable(
makeTable(2, makeSection(m_set1), emptySection, emptySection); weeks(2), makeSection(supported_), 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;
@@ -543,35 +566,35 @@ public:
// Week 1: We should vote for all known amendments not enabled // Week 1: We should vote for all known amendments not enabled
doRound( doRound(
*table, weeks{1}, validators, votes, ourVotes, enabled, majority); *table, weeks{1}, validators, votes, ourVotes, enabled, majority);
BEAST_EXPECT(ourVotes.size() == m_set1.size()); BEAST_EXPECT(ourVotes.size() == supported_.size());
BEAST_EXPECT(enabled.empty()); BEAST_EXPECT(enabled.empty());
for (auto const& i : m_set1) for (auto const& i : supported_)
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 : m_set1) for (auto const& i : supported_)
votes.emplace_back(amendmentId(i), 256); votes.emplace_back(amendmentId(i), 256);
// Week 2: We should recognize a majority // Week 2: We should recognize a majority
doRound( doRound(
*table, weeks{2}, validators, votes, ourVotes, enabled, majority); *table, weeks{2}, validators, votes, ourVotes, enabled, majority);
BEAST_EXPECT(ourVotes.size() == m_set1.size()); BEAST_EXPECT(ourVotes.size() == supported_.size());
BEAST_EXPECT(enabled.empty()); BEAST_EXPECT(enabled.empty());
for (auto const& i : m_set1) for (auto const& i : supported_)
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
doRound( doRound(
*table, weeks{5}, validators, votes, ourVotes, enabled, majority); *table, weeks{5}, validators, votes, ourVotes, enabled, majority);
BEAST_EXPECT(enabled.size() == m_set1.size()); BEAST_EXPECT(enabled.size() == supported_.size());
// Week 6: We should remove it from our votes and from having a majority // Week 6: We should remove it from our votes and from having a majority
doRound( doRound(
*table, weeks{6}, validators, votes, ourVotes, enabled, majority); *table, weeks{6}, validators, votes, ourVotes, enabled, majority);
BEAST_EXPECT(enabled.size() == m_set1.size()); BEAST_EXPECT(enabled.size() == supported_.size());
BEAST_EXPECT(ourVotes.empty()); BEAST_EXPECT(ourVotes.empty());
for (auto const& i : m_set1) for (auto const& i : supported_)
BEAST_EXPECT(majority.find(amendmentId(i)) == majority.end()); BEAST_EXPECT(majority.find(amendmentId(i)) == majority.end());
} }
@@ -583,7 +606,7 @@ public:
auto const testAmendment = amendmentId("detectMajority"); auto const testAmendment = amendmentId("detectMajority");
auto table = makeTable( auto table = makeTable(
2, makeSection(testAmendment), emptySection, emptySection); weeks(2), makeSection(testAmendment), emptySection, emptySection);
auto const validators = makeValidators(16); auto const validators = makeValidators(16);
@@ -648,7 +671,7 @@ public:
auto const validators = makeValidators(16); auto const validators = makeValidators(16);
auto table = makeTable( auto table = makeTable(
8, makeSection(testAmendment), emptySection, emptySection); weeks(8), makeSection(testAmendment), emptySection, emptySection);
std::set<uint256> enabled; std::set<uint256> enabled;
majorityAmendments_t majority; majorityAmendments_t majority;
@@ -712,32 +735,44 @@ public:
{ {
testcase("hasUnsupportedEnabled"); testcase("hasUnsupportedEnabled");
using namespace std::chrono_literals; test::jtx::Env env(*this); // Used only for its Rules
env.close();
int constexpr w = 1; using namespace std::chrono_literals;
weeks constexpr w(1);
auto table = makeTable(w); auto table = makeTable(w);
BEAST_EXPECT(!table->hasUnsupportedEnabled()); BEAST_EXPECT(!table->hasUnsupportedEnabled());
BEAST_EXPECT(!table->firstUnsupportedExpected()); BEAST_EXPECT(!table->firstUnsupportedExpected());
BEAST_EXPECT(table->needValidatedLedger(1));
std::set<uint256> enabled; std::set<uint256> enabled;
std::for_each(
unsupported_.begin(),
unsupported_.end(),
[&enabled](auto const& s) { enabled.insert(amendmentId(s)); });
majorityAmendments_t majority; majorityAmendments_t majority;
std::for_each(m_set4.begin(), m_set4.end(), [&enabled](auto const& s) {
enabled.insert(amendmentId(s));
});
table->doValidatedLedger(1, enabled, majority); table->doValidatedLedger(1, enabled, majority);
BEAST_EXPECT(table->hasUnsupportedEnabled()); BEAST_EXPECT(table->hasUnsupportedEnabled());
BEAST_EXPECT(!table->firstUnsupportedExpected()); BEAST_EXPECT(!table->firstUnsupportedExpected());
NetClock::duration t{1000s}; NetClock::duration t{1000s};
std::for_each( std::for_each(
m_set5.begin(), m_set5.end(), [&majority, &t](auto const& s) { unsupportedMajority_.begin(),
unsupportedMajority_.end(),
[&majority, &t](auto const& s) {
majority[amendmentId(s)] = NetClock::time_point{--t}; majority[amendmentId(s)] = NetClock::time_point{--t};
}); });
table->doValidatedLedger(1, enabled, majority); table->doValidatedLedger(1, enabled, majority);
BEAST_EXPECT(table->hasUnsupportedEnabled()); BEAST_EXPECT(table->hasUnsupportedEnabled());
BEAST_EXPECT( BEAST_EXPECT(
table->firstUnsupportedExpected() && table->firstUnsupportedExpected() &&
*table->firstUnsupportedExpected() == *table->firstUnsupportedExpected() == NetClock::time_point{t} + w);
NetClock::time_point{t} + weeks{w});
// Make sure the table knows when it needs an update.
BEAST_EXPECT(!table->needValidatedLedger(256));
BEAST_EXPECT(table->needValidatedLedger(257));
} }
void void
@@ -746,7 +781,7 @@ public:
testConstruct(); testConstruct();
testGet(); testGet();
testBadConfig(); testBadConfig();
testEnableDisable(); testEnableVeto();
testNoOnUnknown(); testNoOnUnknown();
testNoOnVetoed(); testNoOnVetoed();
testVoteEnable(); testVoteEnable();