Compare commits

..

3 Commits

Author SHA1 Message Date
Valentin Balaschenko
20766dc505 tests 2026-05-29 13:58:08 +01:00
Valentin Balaschenko
532cac0446 fix 2026-05-29 13:44:39 +01:00
Bart
2f3558c610 ci: Run PR title and description checks on staging and release branches (#7331)
Co-authored-by: Bart <11445373+bthomee@users.noreply.github.com>
2026-05-28 14:57:29 +00:00
13 changed files with 97 additions and 29 deletions

View File

@@ -5,8 +5,17 @@ on:
types:
- checks_requested
pull_request:
types: [opened, edited, reopened, synchronize, ready_for_review]
branches: [develop]
types:
- opened
- edited
- reopened
- synchronize
- ready_for_review
branches:
- develop
- "release-*"
- "release/*"
- "staging/*"
jobs:
check_description:

View File

@@ -5,8 +5,17 @@ on:
types:
- checks_requested
pull_request:
types: [opened, edited, reopened, synchronize, ready_for_review]
branches: [develop]
types:
- opened
- edited
- reopened
- synchronize
- ready_for_review
branches:
- develop
- "release-*"
- "release/*"
- "staging/*"
jobs:
check_title:

View File

@@ -10,7 +10,6 @@
#include <cctype>
#include <iterator>
#include <string>
#include <string_view>
#include <vector>
namespace beast::rfc2616 {
@@ -182,7 +181,7 @@ splitCommas(FwdIt first, FwdIt last)
template <class Result = std::vector<std::string>>
Result
splitCommas(std::string_view const& s)
splitCommas(boost::beast::string_view const& s)
{
return splitCommas(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

@@ -18,7 +18,6 @@
#include <algorithm>
#include <functional>
#include <list>
#include <string_view>
namespace xrpl {
@@ -50,7 +49,7 @@ private:
bool pingActive_ = false;
boost::beast::websocket::ping_data payload_;
error_code ec_;
std::function<void(boost::beast::websocket::frame_type, std::string_view)>
std::function<void(boost::beast::websocket::frame_type, boost::beast::string_view)>
controlCallback_;
public:
@@ -139,7 +138,7 @@ protected:
onPing(error_code const& ec);
void
onPingPong(boost::beast::websocket::frame_type kind, std::string_view payload);
onPingPong(boost::beast::websocket::frame_type kind, boost::beast::string_view payload);
void
onTimer(error_code ec);
@@ -420,11 +419,11 @@ template <class Handler, class Impl>
void
BaseWSPeer<Handler, Impl>::onPingPong(
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)
{
closeOnTimer_ = false;

View File

@@ -9,7 +9,6 @@
#include <set> // IWYU pragma: keep
#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

@@ -19,6 +19,14 @@ namespace xrpl::permissioned_dex {
bool
accountInDomain(ReadView const& view, AccountID const& account, Domain const& domainID)
{
// A zero domainID is malformed: it would build a keylet with a zero key
// and violate Ledger::read's invariant. Defense in depth: callers should
// reject this at preflight, but guard here too so any future caller and
// the order-book sweep path (offerInDomain -> here) cannot trip the
// invariant.
if (domainID.isZero())
return false;
auto const sleDomain = view.read(keylet::permissionedDomain(domainID));
if (!sleDomain)
return false;

View File

@@ -94,6 +94,16 @@ OfferCreate::preflight(PreflightContext const& ctx)
if (tx.isFlag(tfHybrid) && !tx.isFieldPresent(sfDomainID))
return temINVALID_FLAG;
// A present but zero DomainID is malformed: it would build a keylet with
// a zero key and violate Ledger::read's invariant. Reject at preflight
// (temMALFORMED) instead of letting it slip to preclaim and be
// misclassified as tecNO_PERMISSION.
if (tx.isFieldPresent(sfDomainID) && tx.getFieldH256(sfDomainID).isZero())
{
JLOG(j.debug()) << "Malformed offer: zero DomainID";
return temMALFORMED;
}
bool const bImmediateOrCancel(tx.isFlag(tfImmediateOrCancel));
bool const bFillOrKill(tx.isFlag(tfFillOrKill));

View File

@@ -125,6 +125,16 @@ Payment::preflight(PreflightContext const& ctx)
if (!mpTokensV2 && isDstMPT && ctx.tx.isFieldPresent(sfPaths))
return temMALFORMED;
// A present but zero DomainID is malformed: it would build a keylet with
// a zero key and violate Ledger::read's invariant. Reject at preflight
// (temMALFORMED) instead of letting it slip to preclaim and be
// misclassified as tecNO_PERMISSION.
if (tx.isFieldPresent(sfDomainID) && tx.getFieldH256(sfDomainID).isZero())
{
JLOG(j.debug()) << "Malformed payment: zero DomainID";
return temMALFORMED;
}
bool const partialPaymentAllowed = tx.isFlag(tfPartialPayment);
bool const limitQuality = tx.isFlag(tfLimitQuality);
bool const defaultPathsAllowed = !tx.isFlag(tfNoRippleDirect);

View File

@@ -278,6 +278,17 @@ class PermissionedDEX_test : public beast::unit_test::Suite
env.close();
}
// preflight - a present but zero DomainID is malformed.
// Regression test for "Ledger::read : zero key" assertion.
{
Env env(*this, features);
auto const& [gw_, domainOwner, alice_, bob_, carol_, USD, domainID, credType] =
PermissionedDEX(env);
env(offer(bob_, XRP(10), USD(10)), Domain(uint256{}), Ter(temMALFORMED));
env.close();
}
// apply - offer can be created even if takergets issuer is not in
// domain
{
@@ -413,6 +424,21 @@ class PermissionedDEX_test : public beast::unit_test::Suite
env.close();
}
// preflight - a present but zero DomainID is malformed.
// Regression test for "Ledger::read : zero key" assertion.
{
Env env(*this, features);
auto const& [gw_, domainOwner, alice_, bob_, carol_, USD, domainID, credType] =
PermissionedDEX(env);
env(pay(bob_, alice_, USD(10)),
Path(~USD),
Sendmax(XRP(10)),
Domain(uint256{}),
Ter(temMALFORMED));
env.close();
}
// preclaim - payment with non-domain destination fails
{
Env env(*this, features);

View File

@@ -64,7 +64,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 kRE(
"^" // start of line
@@ -131,7 +131,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

@@ -264,7 +264,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>
@@ -564,14 +564,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::isKeepAlive(session->request()))