Compare commits

...

7 Commits

Author SHA1 Message Date
Mayukha Vadari
ec7039c0e7 update changelog accordingly 2026-03-24 11:55:11 -07:00
Mayukha Vadari
f170f4c2c2 fix tests, improve code 2026-03-24 11:54:02 -07:00
Mayukha Vadari
8719bc19bc fix clang-tidy 2026-03-24 10:19:16 -07:00
Mayukha Vadari
137de6f8a8 add obsolete field to non-admin 2026-03-24 10:18:19 -07:00
copilot-swe-agent[bot]
5bf2674944 Add isBool() checks for obsolete field and clarify admin-mode in changelog
Co-authored-by: mvadari <8029314+mvadari@users.noreply.github.com>
2026-03-24 10:17:59 -07:00
copilot-swe-agent[bot]
3e2f6658c6 Update all test assertions to validate new boolean vetoed and obsolete fields
Co-authored-by: mvadari <8029314+mvadari@users.noreply.github.com>
2026-03-24 10:17:58 -07:00
copilot-swe-agent[bot]
ee368f7096 Fix amendment vetoed field type - use boolean and add obsolete field
Co-authored-by: mvadari <8029314+mvadari@users.noreply.github.com>
2026-03-24 10:16:50 -07:00
4 changed files with 49 additions and 35 deletions

View File

@@ -92,6 +92,10 @@ This release contains bug fixes only and no API changes.
[Version 2.5.0](https://github.com/XRPLF/rippled/releases/tag/2.5.0) was released on Jun 24, 2025.
### Breaking changes in 2.5.0
- `feature`: The `vetoed` field in admin-mode responses is now always a boolean. Disabled obsolete amendments now have `"vetoed": true` instead of the previous `"vetoed": "Obsolete"` string value, which was not type-safe. A new `"obsolete"` boolean field is now included in all responses (both admin and non-admin), indicating whether an amendment is obsolete.
### Additions and bugfixes in 2.5.0
- `tx`: Added `ctid` field to the response and improved error handling. ([#4738](https://github.com/XRPLF/rippled/pull/4738))

View File

@@ -455,6 +455,7 @@ JSS(node_write_retries); // out: GetCounts
JSS(node_writes_delayed); // out::GetCounts
JSS(nth); // out: RPC server_definitions
JSS(obligations); // out: GatewayBalances
JSS(obsolete); // out: AmendmentTableImpl
JSS(offers); // out: NetworkOPs, AccountOffers, Subscribe
JSS(offer_id); // out: insertNFTokenOfferID
JSS(offline); // in: TransactionSign

View File

@@ -150,12 +150,15 @@ class Feature_test : public beast::unit_test::suite
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");
feature.isMember(jss::vetoed) && feature[jss::vetoed].isBool(),
feature[jss::name].asString() + " vetoed is bool");
BEAST_EXPECTS(
feature[jss::vetoed].asBool() == (expectVeto || expectObsolete),
feature[jss::name].asString() + " vetoed value");
BEAST_EXPECTS(
feature.isMember(jss::obsolete) && feature[jss::obsolete].isBool() &&
feature[jss::obsolete].asBool() == expectObsolete,
feature[jss::name].asString() + " obsolete");
BEAST_EXPECTS(
feature.isMember(jss::supported) && feature[jss::supported].asBool(),
feature[jss::name].asString() + " supported");
@@ -287,6 +290,9 @@ class Feature_test : public beast::unit_test::suite
(*it).isMember(jss::supported) &&
(*it)[jss::supported].asBool() == expectSupported,
(*it)[jss::name].asString() + " supported");
BEAST_EXPECTS(
(*it).isMember(jss::obsolete) && (*it)[jss::obsolete].isBool(),
(*it)[jss::name].asString() + " unknown obsolete");
BEAST_EXPECT(!(*it).isMember(jss::vetoed));
BEAST_EXPECT(!(*it).isMember(jss::majority));
BEAST_EXPECT(!(*it).isMember(jss::count));
@@ -354,12 +360,15 @@ class Feature_test : public beast::unit_test::suite
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");
(*it).isMember(jss::vetoed) && (*it)[jss::vetoed].isBool(),
(*it)[jss::name].asString() + " vetoed is bool");
BEAST_EXPECTS(
(*it)[jss::vetoed].asBool() == (expectVeto || expectObsolete),
(*it)[jss::name].asString() + " vetoed value");
BEAST_EXPECTS(
(*it).isMember(jss::obsolete) && (*it)[jss::obsolete].isBool() &&
(*it)[jss::obsolete].asBool() == expectObsolete,
(*it)[jss::name].asString() + " obsolete");
}
BEAST_EXPECTS(
(*it).isMember(jss::supported) && (*it)[jss::supported].asBool() == expectSupported,
@@ -428,12 +437,15 @@ class Feature_test : public beast::unit_test::suite
(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");
feature.isMember(jss::vetoed) && feature[jss::vetoed].isBool(),
feature[jss::name].asString() + " vetoed is bool");
BEAST_EXPECTS(
feature[jss::vetoed].asBool() == (expectVeto || expectObsolete),
feature[jss::name].asString() + " vetoed value");
BEAST_EXPECTS(
feature.isMember(jss::obsolete) && feature[jss::obsolete].isBool() &&
feature[jss::obsolete].asBool() == expectObsolete,
feature[jss::name].asString() + " obsolete");
BEAST_EXPECTS(feature.isMember(jss::count), feature[jss::name].asString() + " count");
BEAST_EXPECTS(
feature.isMember(jss::threshold), feature[jss::name].asString() + " threshold");
@@ -524,8 +536,9 @@ class Feature_test : public beast::unit_test::suite
auto feature = *(jrr.begin());
BEAST_EXPECTS(feature[jss::name] == featureName, "name");
BEAST_EXPECTS(
feature[jss::vetoed].isString() && feature[jss::vetoed].asString() == "Obsolete",
"vetoed");
feature[jss::vetoed].isBool() && feature[jss::vetoed].asBool() == true, "vetoed");
BEAST_EXPECTS(
feature[jss::obsolete].isBool() && feature[jss::obsolete].asBool() == true, "obsolete");
jrr = env.rpc("feature", featureName, "reject")[jss::result];
if (!BEAST_EXPECTS(jrr[jss::status] == jss::success, "status"))
@@ -536,8 +549,9 @@ class Feature_test : public beast::unit_test::suite
feature = *(jrr.begin());
BEAST_EXPECTS(feature[jss::name] == featureName, "name");
BEAST_EXPECTS(
feature[jss::vetoed].isString() && feature[jss::vetoed].asString() == "Obsolete",
"vetoed");
feature[jss::vetoed].isBool() && feature[jss::vetoed].asBool() == true, "vetoed");
BEAST_EXPECTS(
feature[jss::obsolete].isBool() && feature[jss::obsolete].asBool() == true, "obsolete");
jrr = env.rpc("feature", featureName, "accept")[jss::result];
if (!BEAST_EXPECTS(jrr[jss::status] == jss::success, "status"))
@@ -548,8 +562,9 @@ class Feature_test : public beast::unit_test::suite
feature = *(jrr.begin());
BEAST_EXPECTS(feature[jss::name] == featureName, "name");
BEAST_EXPECTS(
feature[jss::vetoed].isString() && feature[jss::vetoed].asString() == "Obsolete",
"vetoed");
feature[jss::vetoed].isBool() && feature[jss::vetoed].asBool() == true, "vetoed");
BEAST_EXPECTS(
feature[jss::obsolete].isBool() && feature[jss::obsolete].asBool() == true, "obsolete");
// anything other than accept or reject is an error
jrr = env.rpc("feature", featureName, "maybe");

View File

@@ -924,18 +924,12 @@ AmendmentTableImpl::injectJson(
v[jss::name] = fs.name;
v[jss::supported] = fs.supported;
if (!fs.enabled && isAdmin)
{
if (fs.vote == AmendmentVote::obsolete)
{
v[jss::vetoed] = "Obsolete";
}
else
{
v[jss::vetoed] = fs.vote == AmendmentVote::down;
}
}
v[jss::enabled] = fs.enabled;
v[jss::obsolete] = fs.vote == AmendmentVote::obsolete;
if (isAdmin)
{
v[jss::vetoed] = fs.vote == AmendmentVote::down || fs.vote == AmendmentVote::obsolete;
}
if (!fs.enabled && lastVote_ && isAdmin)
{