mirror of
https://github.com/XRPLF/rippled.git
synced 2025-11-19 02:25:52 +00:00
Amendments activated for more than 2 years can be retired. This change retires the MultiSignReserve and ExpandedSignerList amendments.
598 lines
22 KiB
C++
598 lines
22 KiB
C++
#include <test/jtx.h>
|
|
|
|
#include <xrpld/app/misc/AmendmentTable.h>
|
|
|
|
#include <xrpl/protocol/Feature.h>
|
|
#include <xrpl/protocol/jss.h>
|
|
|
|
namespace ripple {
|
|
|
|
class Feature_test : public beast::unit_test::suite
|
|
{
|
|
void
|
|
testInternals()
|
|
{
|
|
testcase("internals");
|
|
|
|
auto const& supportedAmendments = ripple::detail::supportedAmendments();
|
|
auto const& allAmendments = ripple::allAmendments();
|
|
|
|
BEAST_EXPECT(
|
|
supportedAmendments.size() ==
|
|
ripple::detail::numDownVotedAmendments() +
|
|
ripple::detail::numUpVotedAmendments());
|
|
{
|
|
std::size_t up = 0, down = 0, obsolete = 0;
|
|
for (auto const& [name, vote] : supportedAmendments)
|
|
{
|
|
switch (vote)
|
|
{
|
|
case VoteBehavior::DefaultYes:
|
|
++up;
|
|
break;
|
|
case VoteBehavior::DefaultNo:
|
|
++down;
|
|
break;
|
|
case VoteBehavior::Obsolete:
|
|
++obsolete;
|
|
break;
|
|
default:
|
|
fail("Unknown VoteBehavior", __FILE__, __LINE__);
|
|
}
|
|
|
|
if (vote == VoteBehavior::Obsolete)
|
|
{
|
|
BEAST_EXPECT(
|
|
allAmendments.contains(name) &&
|
|
allAmendments.at(name) == AmendmentSupport::Retired);
|
|
}
|
|
else
|
|
{
|
|
BEAST_EXPECT(
|
|
allAmendments.contains(name) &&
|
|
allAmendments.at(name) == AmendmentSupport::Supported);
|
|
}
|
|
}
|
|
BEAST_EXPECT(
|
|
down + obsolete == ripple::detail::numDownVotedAmendments());
|
|
BEAST_EXPECT(up == ripple::detail::numUpVotedAmendments());
|
|
}
|
|
{
|
|
std::size_t supported = 0, unsupported = 0, retired = 0;
|
|
for (auto const& [name, support] : allAmendments)
|
|
{
|
|
switch (support)
|
|
{
|
|
case AmendmentSupport::Supported:
|
|
++supported;
|
|
BEAST_EXPECT(supportedAmendments.contains(name));
|
|
break;
|
|
case AmendmentSupport::Unsupported:
|
|
++unsupported;
|
|
break;
|
|
case AmendmentSupport::Retired:
|
|
++retired;
|
|
break;
|
|
default:
|
|
fail("Unknown AmendmentSupport", __FILE__, __LINE__);
|
|
}
|
|
}
|
|
|
|
BEAST_EXPECT(supported + retired == supportedAmendments.size());
|
|
BEAST_EXPECT(
|
|
allAmendments.size() - unsupported ==
|
|
supportedAmendments.size());
|
|
}
|
|
}
|
|
|
|
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(fixRemoveNFTokenAutoTrustLine) ==
|
|
"fixRemoveNFTokenAutoTrustLine");
|
|
BEAST_EXPECT(featureToName(featureFlow) == "Flow");
|
|
BEAST_EXPECT(featureToName(featureNegativeUNL) == "NegativeUNL");
|
|
BEAST_EXPECT(
|
|
featureToName(fixIncludeKeyletFields) == "fixIncludeKeyletFields");
|
|
BEAST_EXPECT(featureToName(featureTokenEscrow) == "TokenEscrow");
|
|
}
|
|
|
|
void
|
|
testNoParams()
|
|
{
|
|
testcase("No Params, None Enabled");
|
|
|
|
using namespace test::jtx;
|
|
Env env{*this};
|
|
|
|
std::map<std::string, VoteBehavior> const& votes =
|
|
ripple::detail::supportedAmendments();
|
|
|
|
auto jrr = env.rpc("feature")[jss::result];
|
|
if (!BEAST_EXPECT(jrr.isMember(jss::features)))
|
|
return;
|
|
for (auto const& feature : jrr[jss::features])
|
|
{
|
|
if (!BEAST_EXPECT(feature.isMember(jss::name)))
|
|
return;
|
|
// default config - so all should be disabled, and
|
|
// supported. Some may be vetoed.
|
|
bool expectVeto =
|
|
(votes.at(feature[jss::name].asString()) ==
|
|
VoteBehavior::DefaultNo);
|
|
bool expectObsolete =
|
|
(votes.at(feature[jss::name].asString()) ==
|
|
VoteBehavior::Obsolete);
|
|
BEAST_EXPECTS(
|
|
feature.isMember(jss::enabled) &&
|
|
!feature[jss::enabled].asBool(),
|
|
feature[jss::name].asString() + " enabled");
|
|
BEAST_EXPECTS(
|
|
feature.isMember(jss::vetoed) &&
|
|
feature[jss::vetoed].isBool() == !expectObsolete &&
|
|
(!feature[jss::vetoed].isBool() ||
|
|
feature[jss::vetoed].asBool() == expectVeto) &&
|
|
(feature[jss::vetoed].isBool() ||
|
|
feature[jss::vetoed].asString() == "Obsolete"),
|
|
feature[jss::name].asString() + " vetoed");
|
|
BEAST_EXPECTS(
|
|
feature.isMember(jss::supported) &&
|
|
feature[jss::supported].asBool(),
|
|
feature[jss::name].asString() + " supported");
|
|
}
|
|
}
|
|
|
|
void
|
|
testSingleFeature()
|
|
{
|
|
testcase("Feature Param");
|
|
|
|
using namespace test::jtx;
|
|
Env env{*this};
|
|
|
|
auto jrr = env.rpc("feature", "RequireFullyCanonicalSig")[jss::result];
|
|
BEAST_EXPECTS(jrr[jss::status] == jss::success, "status");
|
|
jrr.removeMember(jss::status);
|
|
BEAST_EXPECT(jrr.size() == 1);
|
|
BEAST_EXPECT(
|
|
jrr.isMember("00C1FC4A53E60AB02C864641002B3172F38677E29C26C54066851"
|
|
"79B37E1EDAC"));
|
|
auto feature = *(jrr.begin());
|
|
|
|
BEAST_EXPECTS(feature[jss::name] == "RequireFullyCanonicalSig", "name");
|
|
BEAST_EXPECTS(!feature[jss::enabled].asBool(), "enabled");
|
|
BEAST_EXPECTS(
|
|
feature[jss::vetoed].isBool() && !feature[jss::vetoed].asBool(),
|
|
"vetoed");
|
|
BEAST_EXPECTS(feature[jss::supported].asBool(), "supported");
|
|
|
|
// feature names are case-sensitive - expect error here
|
|
jrr = env.rpc("feature", "requireFullyCanonicalSig")[jss::result];
|
|
BEAST_EXPECT(jrr[jss::error] == "badFeature");
|
|
BEAST_EXPECT(jrr[jss::error_message] == "Feature unknown or invalid.");
|
|
}
|
|
|
|
void
|
|
testInvalidFeature()
|
|
{
|
|
testcase("Invalid Feature");
|
|
|
|
using namespace test::jtx;
|
|
Env env{*this};
|
|
|
|
auto testInvalidParam = [&](auto const& param) {
|
|
Json::Value params;
|
|
params[jss::feature] = param;
|
|
auto jrr =
|
|
env.rpc("json", "feature", to_string(params))[jss::result];
|
|
BEAST_EXPECT(jrr[jss::error] == "invalidParams");
|
|
BEAST_EXPECT(jrr[jss::error_message] == "Invalid parameters.");
|
|
};
|
|
|
|
testInvalidParam(1);
|
|
testInvalidParam(1.1);
|
|
testInvalidParam(true);
|
|
testInvalidParam(Json::Value(Json::nullValue));
|
|
testInvalidParam(Json::Value(Json::objectValue));
|
|
testInvalidParam(Json::Value(Json::arrayValue));
|
|
|
|
{
|
|
auto jrr = env.rpc("feature", "AllTheThings")[jss::result];
|
|
BEAST_EXPECT(jrr[jss::error] == "badFeature");
|
|
BEAST_EXPECT(
|
|
jrr[jss::error_message] == "Feature unknown or invalid.");
|
|
}
|
|
}
|
|
|
|
void
|
|
testNonAdmin()
|
|
{
|
|
testcase("Feature Without Admin");
|
|
|
|
using namespace test::jtx;
|
|
Env env{*this, envconfig([](std::unique_ptr<Config> cfg) {
|
|
(*cfg)["port_rpc"].set("admin", "");
|
|
(*cfg)["port_ws"].set("admin", "");
|
|
return cfg;
|
|
})};
|
|
|
|
{
|
|
auto result = env.rpc("feature")[jss::result];
|
|
BEAST_EXPECT(result.isMember(jss::features));
|
|
// There should be at least 50 amendments. Don't do exact
|
|
// comparison to avoid maintenance as more amendments are added in
|
|
// the future.
|
|
BEAST_EXPECT(result[jss::features].size() >= 50);
|
|
for (auto it = result[jss::features].begin();
|
|
it != result[jss::features].end();
|
|
++it)
|
|
{
|
|
uint256 id;
|
|
(void)id.parseHex(it.key().asString().c_str());
|
|
if (!BEAST_EXPECT((*it).isMember(jss::name)))
|
|
return;
|
|
bool expectEnabled =
|
|
env.app().getAmendmentTable().isEnabled(id);
|
|
bool expectSupported =
|
|
env.app().getAmendmentTable().isSupported(id);
|
|
BEAST_EXPECTS(
|
|
(*it).isMember(jss::enabled) &&
|
|
(*it)[jss::enabled].asBool() == expectEnabled,
|
|
(*it)[jss::name].asString() + " enabled");
|
|
BEAST_EXPECTS(
|
|
(*it).isMember(jss::supported) &&
|
|
(*it)[jss::supported].asBool() == expectSupported,
|
|
(*it)[jss::name].asString() + " supported");
|
|
BEAST_EXPECT(!(*it).isMember(jss::vetoed));
|
|
BEAST_EXPECT(!(*it).isMember(jss::majority));
|
|
BEAST_EXPECT(!(*it).isMember(jss::count));
|
|
BEAST_EXPECT(!(*it).isMember(jss::validations));
|
|
BEAST_EXPECT(!(*it).isMember(jss::threshold));
|
|
}
|
|
}
|
|
|
|
{
|
|
Json::Value params;
|
|
// invalid feature
|
|
params[jss::feature] =
|
|
"1234567890ABCDEF1234567890ABCDEF1234567890ABCDEF1234567890ABCD"
|
|
"EF";
|
|
auto const result =
|
|
env.rpc("json", "feature", to_string(params))[jss::result];
|
|
BEAST_EXPECTS(
|
|
result[jss::error] == "badFeature", result.toStyledString());
|
|
BEAST_EXPECT(
|
|
result[jss::error_message] == "Feature unknown or invalid.");
|
|
}
|
|
|
|
{
|
|
Json::Value params;
|
|
params[jss::feature] =
|
|
"93E516234E35E08CA689FA33A6D38E103881F8DCB53023F728C307AA89D515"
|
|
"A7";
|
|
// invalid param
|
|
params[jss::vetoed] = true;
|
|
auto const result =
|
|
env.rpc("json", "feature", to_string(params))[jss::result];
|
|
BEAST_EXPECTS(
|
|
result[jss::error] == "noPermission",
|
|
result[jss::error].asString());
|
|
BEAST_EXPECT(
|
|
result[jss::error_message] ==
|
|
"You don't have permission for this command.");
|
|
}
|
|
}
|
|
|
|
void
|
|
testSomeEnabled()
|
|
{
|
|
testcase("No Params, Some Enabled");
|
|
|
|
using namespace test::jtx;
|
|
Env env{*this, FeatureBitset{}};
|
|
|
|
std::map<std::string, VoteBehavior> const& votes =
|
|
ripple::detail::supportedAmendments();
|
|
|
|
auto jrr = env.rpc("feature")[jss::result];
|
|
if (!BEAST_EXPECT(jrr.isMember(jss::features)))
|
|
return;
|
|
for (auto it = jrr[jss::features].begin();
|
|
it != jrr[jss::features].end();
|
|
++it)
|
|
{
|
|
uint256 id;
|
|
(void)id.parseHex(it.key().asString().c_str());
|
|
if (!BEAST_EXPECT((*it).isMember(jss::name)))
|
|
return;
|
|
bool expectEnabled = env.app().getAmendmentTable().isEnabled(id);
|
|
bool expectSupported =
|
|
env.app().getAmendmentTable().isSupported(id);
|
|
bool expectVeto =
|
|
(votes.at((*it)[jss::name].asString()) ==
|
|
VoteBehavior::DefaultNo);
|
|
bool expectObsolete =
|
|
(votes.at((*it)[jss::name].asString()) ==
|
|
VoteBehavior::Obsolete);
|
|
BEAST_EXPECTS(
|
|
(*it).isMember(jss::enabled) &&
|
|
(*it)[jss::enabled].asBool() == expectEnabled,
|
|
(*it)[jss::name].asString() + " enabled");
|
|
if (expectEnabled)
|
|
BEAST_EXPECTS(
|
|
!(*it).isMember(jss::vetoed),
|
|
(*it)[jss::name].asString() + " vetoed");
|
|
else
|
|
BEAST_EXPECTS(
|
|
(*it).isMember(jss::vetoed) &&
|
|
(*it)[jss::vetoed].isBool() == !expectObsolete &&
|
|
(!(*it)[jss::vetoed].isBool() ||
|
|
(*it)[jss::vetoed].asBool() == expectVeto) &&
|
|
((*it)[jss::vetoed].isBool() ||
|
|
(*it)[jss::vetoed].asString() == "Obsolete"),
|
|
(*it)[jss::name].asString() + " vetoed");
|
|
BEAST_EXPECTS(
|
|
(*it).isMember(jss::supported) &&
|
|
(*it)[jss::supported].asBool() == expectSupported,
|
|
(*it)[jss::name].asString() + " supported");
|
|
}
|
|
}
|
|
|
|
void
|
|
testWithMajorities()
|
|
{
|
|
testcase("With Majorities");
|
|
|
|
using namespace test::jtx;
|
|
Env env{*this, envconfig(validator, "")};
|
|
|
|
auto jrr = env.rpc("feature")[jss::result];
|
|
if (!BEAST_EXPECT(jrr.isMember(jss::features)))
|
|
return;
|
|
|
|
// at this point, there are no majorities so no fields related to
|
|
// amendment voting
|
|
for (auto const& feature : jrr[jss::features])
|
|
{
|
|
if (!BEAST_EXPECT(feature.isMember(jss::name)))
|
|
return;
|
|
BEAST_EXPECTS(
|
|
!feature.isMember(jss::majority),
|
|
feature[jss::name].asString() + " majority");
|
|
BEAST_EXPECTS(
|
|
!feature.isMember(jss::count),
|
|
feature[jss::name].asString() + " count");
|
|
BEAST_EXPECTS(
|
|
!feature.isMember(jss::threshold),
|
|
feature[jss::name].asString() + " threshold");
|
|
BEAST_EXPECTS(
|
|
!feature.isMember(jss::validations),
|
|
feature[jss::name].asString() + " validations");
|
|
BEAST_EXPECTS(
|
|
!feature.isMember(jss::vote),
|
|
feature[jss::name].asString() + " vote");
|
|
}
|
|
|
|
auto majorities = getMajorityAmendments(*env.closed());
|
|
if (!BEAST_EXPECT(majorities.empty()))
|
|
return;
|
|
|
|
// close ledgers until the amendments show up.
|
|
for (auto i = 0; i <= 256; ++i)
|
|
{
|
|
env.close();
|
|
majorities = getMajorityAmendments(*env.closed());
|
|
if (!majorities.empty())
|
|
break;
|
|
}
|
|
|
|
// 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, VoteBehavior> const& votes =
|
|
ripple::detail::supportedAmendments();
|
|
|
|
jrr = env.rpc("feature")[jss::result];
|
|
if (!BEAST_EXPECT(jrr.isMember(jss::features)))
|
|
return;
|
|
for (auto const& feature : jrr[jss::features])
|
|
{
|
|
if (!BEAST_EXPECT(feature.isMember(jss::name)))
|
|
return;
|
|
bool expectVeto =
|
|
(votes.at(feature[jss::name].asString()) ==
|
|
VoteBehavior::DefaultNo);
|
|
bool expectObsolete =
|
|
(votes.at(feature[jss::name].asString()) ==
|
|
VoteBehavior::Obsolete);
|
|
BEAST_EXPECTS(
|
|
(expectVeto || expectObsolete) ^
|
|
feature.isMember(jss::majority),
|
|
feature[jss::name].asString() + " majority");
|
|
BEAST_EXPECTS(
|
|
feature.isMember(jss::vetoed) &&
|
|
feature[jss::vetoed].isBool() == !expectObsolete &&
|
|
(!feature[jss::vetoed].isBool() ||
|
|
feature[jss::vetoed].asBool() == expectVeto) &&
|
|
(feature[jss::vetoed].isBool() ||
|
|
feature[jss::vetoed].asString() == "Obsolete"),
|
|
feature[jss::name].asString() + " vetoed");
|
|
BEAST_EXPECTS(
|
|
feature.isMember(jss::count),
|
|
feature[jss::name].asString() + " count");
|
|
BEAST_EXPECTS(
|
|
feature.isMember(jss::threshold),
|
|
feature[jss::name].asString() + " threshold");
|
|
BEAST_EXPECTS(
|
|
feature.isMember(jss::validations),
|
|
feature[jss::name].asString() + " validations");
|
|
BEAST_EXPECT(
|
|
feature[jss::count] ==
|
|
((expectVeto || expectObsolete) ? 0 : 1));
|
|
BEAST_EXPECT(feature[jss::threshold] == 1);
|
|
BEAST_EXPECT(feature[jss::validations] == 1);
|
|
BEAST_EXPECTS(
|
|
expectVeto || expectObsolete || feature[jss::majority] == 2540,
|
|
"Majority: " + feature[jss::majority].asString());
|
|
}
|
|
}
|
|
|
|
void
|
|
testVeto()
|
|
{
|
|
testcase("Veto");
|
|
|
|
using namespace test::jtx;
|
|
Env env{*this, FeatureBitset{featureRequireFullyCanonicalSig}};
|
|
constexpr char const* featureName = "RequireFullyCanonicalSig";
|
|
|
|
auto jrr = env.rpc("feature", featureName)[jss::result];
|
|
if (!BEAST_EXPECTS(jrr[jss::status] == jss::success, "status"))
|
|
return;
|
|
jrr.removeMember(jss::status);
|
|
if (!BEAST_EXPECT(jrr.size() == 1))
|
|
return;
|
|
auto feature = *(jrr.begin());
|
|
BEAST_EXPECTS(feature[jss::name] == featureName, "name");
|
|
BEAST_EXPECTS(
|
|
feature[jss::vetoed].isBool() && !feature[jss::vetoed].asBool(),
|
|
"vetoed");
|
|
|
|
jrr = env.rpc("feature", featureName, "reject")[jss::result];
|
|
if (!BEAST_EXPECTS(jrr[jss::status] == jss::success, "status"))
|
|
return;
|
|
jrr.removeMember(jss::status);
|
|
if (!BEAST_EXPECT(jrr.size() == 1))
|
|
return;
|
|
feature = *(jrr.begin());
|
|
BEAST_EXPECTS(feature[jss::name] == featureName, "name");
|
|
BEAST_EXPECTS(
|
|
feature[jss::vetoed].isBool() && feature[jss::vetoed].asBool(),
|
|
"vetoed");
|
|
|
|
jrr = env.rpc("feature", featureName, "accept")[jss::result];
|
|
if (!BEAST_EXPECTS(jrr[jss::status] == jss::success, "status"))
|
|
return;
|
|
jrr.removeMember(jss::status);
|
|
if (!BEAST_EXPECT(jrr.size() == 1))
|
|
return;
|
|
feature = *(jrr.begin());
|
|
BEAST_EXPECTS(feature[jss::name] == featureName, "name");
|
|
BEAST_EXPECTS(
|
|
feature[jss::vetoed].isBool() && !feature[jss::vetoed].asBool(),
|
|
"vetoed");
|
|
|
|
// anything other than accept or reject is an error
|
|
jrr = env.rpc("feature", featureName, "maybe");
|
|
BEAST_EXPECT(jrr[jss::error] == "invalidParams");
|
|
BEAST_EXPECT(jrr[jss::error_message] == "Invalid parameters.");
|
|
}
|
|
|
|
void
|
|
testObsolete()
|
|
{
|
|
testcase("Obsolete");
|
|
|
|
using namespace test::jtx;
|
|
Env env{*this};
|
|
constexpr char const* featureName = "CryptoConditionsSuite";
|
|
|
|
auto jrr = env.rpc("feature", featureName)[jss::result];
|
|
if (!BEAST_EXPECTS(jrr[jss::status] == jss::success, "status"))
|
|
return;
|
|
jrr.removeMember(jss::status);
|
|
if (!BEAST_EXPECT(jrr.size() == 1))
|
|
return;
|
|
auto feature = *(jrr.begin());
|
|
BEAST_EXPECTS(feature[jss::name] == featureName, "name");
|
|
BEAST_EXPECTS(
|
|
feature[jss::vetoed].isString() &&
|
|
feature[jss::vetoed].asString() == "Obsolete",
|
|
"vetoed");
|
|
|
|
jrr = env.rpc("feature", featureName, "reject")[jss::result];
|
|
if (!BEAST_EXPECTS(jrr[jss::status] == jss::success, "status"))
|
|
return;
|
|
jrr.removeMember(jss::status);
|
|
if (!BEAST_EXPECT(jrr.size() == 1))
|
|
return;
|
|
feature = *(jrr.begin());
|
|
BEAST_EXPECTS(feature[jss::name] == featureName, "name");
|
|
BEAST_EXPECTS(
|
|
feature[jss::vetoed].isString() &&
|
|
feature[jss::vetoed].asString() == "Obsolete",
|
|
"vetoed");
|
|
|
|
jrr = env.rpc("feature", featureName, "accept")[jss::result];
|
|
if (!BEAST_EXPECTS(jrr[jss::status] == jss::success, "status"))
|
|
return;
|
|
jrr.removeMember(jss::status);
|
|
if (!BEAST_EXPECT(jrr.size() == 1))
|
|
return;
|
|
feature = *(jrr.begin());
|
|
BEAST_EXPECTS(feature[jss::name] == featureName, "name");
|
|
BEAST_EXPECTS(
|
|
feature[jss::vetoed].isString() &&
|
|
feature[jss::vetoed].asString() == "Obsolete",
|
|
"vetoed");
|
|
|
|
// anything other than accept or reject is an error
|
|
jrr = env.rpc("feature", featureName, "maybe");
|
|
BEAST_EXPECT(jrr[jss::error] == "invalidParams");
|
|
BEAST_EXPECT(jrr[jss::error_message] == "Invalid parameters.");
|
|
}
|
|
|
|
public:
|
|
void
|
|
run() override
|
|
{
|
|
testInternals();
|
|
testFeatureLookups();
|
|
testNoParams();
|
|
testSingleFeature();
|
|
testInvalidFeature();
|
|
testNonAdmin();
|
|
testSomeEnabled();
|
|
testWithMajorities();
|
|
testVeto();
|
|
testObsolete();
|
|
}
|
|
};
|
|
|
|
BEAST_DEFINE_TESTSUITE(Feature, rpc, ripple);
|
|
|
|
} // namespace ripple
|