Compare commits

..

8 Commits

Author SHA1 Message Date
Mayukha Vadari
c5037b0dc6 Merge branch 'develop' into copilot/fix-vetoed-type-error 2026-04-01 16:19:18 -04:00
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
11 changed files with 71 additions and 61 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

@@ -10,7 +10,6 @@
#include <cctype>
#include <iterator>
#include <string>
#include <string_view>
#include <vector>
namespace beast {
@@ -182,7 +181,7 @@ split_commas(FwdIt first, FwdIt last)
template <class Result = std::vector<std::string>>
Result
split_commas(std::string_view s)
split_commas(boost::beast::string_view const& s)
{
return split_commas(s.begin(), s.end());
}

View File

@@ -1,19 +1,20 @@
#pragma once
#include <boost/beast/core/string.hpp>
#include <functional>
#include <string>
#include <string_view>
namespace Json {
class Value;
using Output = std::function<void(std::string_view)>;
using Output = std::function<void(boost::beast::string_view const&)>;
inline Output
stringOutput(std::string& s)
{
return [&](std::string_view b) { s.append(b.data(), b.size()); };
return [&](boost::beast::string_view const& b) { s.append(b.data(), b.size()); };
}
/** Writes a minimal representation of a Json value to an Output in O(n) time.

View File

@@ -433,6 +433,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

@@ -17,7 +17,6 @@
#include <functional>
#include <list>
#include <string_view>
namespace xrpl {
@@ -49,7 +48,8 @@ private:
bool ping_active_ = false;
boost::beast::websocket::ping_data payload_;
error_code ec_;
std::function<void(boost::beast::websocket::frame_type, std::string_view)> control_callback_;
std::function<void(boost::beast::websocket::frame_type, boost::beast::string_view)>
control_callback_;
public:
template <class Body, class Headers>
@@ -137,7 +137,7 @@ protected:
on_ping(error_code const& ec);
void
on_ping_pong(boost::beast::websocket::frame_type kind, std::string_view payload);
on_ping_pong(boost::beast::websocket::frame_type kind, boost::beast::string_view payload);
void
on_timer(error_code ec);
@@ -414,11 +414,11 @@ template <class Handler, class Impl>
void
BaseWSPeer<Handler, Impl>::on_ping_pong(
boost::beast::websocket::frame_type kind,
std::string_view payload)
boost::beast::string_view payload)
{
if (kind == boost::beast::websocket::frame_type::pong)
{
std::string_view const p(payload_.begin(), payload_.size());
boost::beast::string_view const p(payload_.begin());
if (payload == p)
{
close_on_timer_ = false;

View File

@@ -8,7 +8,6 @@
#include <set>
#include <stack>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
@@ -88,14 +87,14 @@ public:
}
void
output(std::string_view bytes)
output(boost::beast::string_view const& bytes)
{
markStarted();
output_(bytes);
}
void
stringOutput(std::string_view bytes)
stringOutput(boost::beast::string_view const& bytes)
{
markStarted();
std::size_t position = 0, writtenUntil = 0;

View File

@@ -151,12 +151,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");
@@ -288,6 +291,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));
@@ -357,12 +363,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,
@@ -432,12 +441,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");
@@ -528,8 +540,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"))
@@ -540,8 +553,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"))
@@ -552,8 +566,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)
{

View File

@@ -56,7 +56,7 @@ to_string(ProtocolVersion const& p)
}
std::vector<ProtocolVersion>
parseProtocolVersions(std::string_view value)
parseProtocolVersions(boost::beast::string_view const& value)
{
static boost::regex const re(
"^" // start of line
@@ -126,7 +126,7 @@ negotiateProtocolVersion(std::vector<ProtocolVersion> const& versions)
}
std::optional<ProtocolVersion>
negotiateProtocolVersion(std::string_view versions)
negotiateProtocolVersion(boost::beast::string_view const& versions)
{
auto const them = parseProtocolVersions(versions);

View File

@@ -1,9 +1,10 @@
#pragma once
#include <boost/beast/core/string.hpp>
#include <cstdint>
#include <optional>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
@@ -38,7 +39,7 @@ to_string(ProtocolVersion const& p);
no duplicates and will be sorted in ascending protocol order.
*/
std::vector<ProtocolVersion>
parseProtocolVersions(std::string_view s);
parseProtocolVersions(boost::beast::string_view const& s);
/** Given a list of supported protocol versions, choose the one we prefer. */
std::optional<ProtocolVersion>
@@ -46,7 +47,7 @@ negotiateProtocolVersion(std::vector<ProtocolVersion> const& versions);
/** Given a list of supported protocol versions, choose the one we prefer. */
std::optional<ProtocolVersion>
negotiateProtocolVersion(std::string_view versions);
negotiateProtocolVersion(boost::beast::string_view const& versions);
/** The list of all the protocol versions we support. */
std::string const&

View File

@@ -33,7 +33,6 @@
#include <algorithm>
#include <memory>
#include <stdexcept>
#include <string_view>
namespace xrpl {
@@ -230,7 +229,7 @@ ServerHandler::onHandoff(
static inline Json::Output
makeOutput(Session& session)
{
return [&](std::string_view b) { session.write(b.data(), b.size()); };
return [&](boost::beast::string_view const& b) { session.write(b.data(), b.size()); };
}
static std::map<std::string, std::string>
@@ -531,14 +530,11 @@ ServerHandler::processSession(
makeOutput(*session),
coro,
forwardedFor(session->request()),
[&]() -> std::string_view {
[&] {
auto const iter = session->request().find("X-User");
if (iter != session->request().end())
{
auto const& val = iter->value();
return std::string_view(val.data(), val.size());
}
return std::string_view{};
return iter->value();
return boost::beast::string_view{};
}());
if (beast::rfc2616::is_keep_alive(session->request()))