Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions API-CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ This section contains changes targeting a future version.
- `submit`: The `fail_hard` field now returns an error if the value is not a boolean. [#6529](https://github.com/XRPLF/rippled/pull/6529)
- `subscribe`: The `taker` field in the `books` array now returns `actMalformed` instead of `badIssuer` if the value is not a valid account. [#6529](https://github.com/XRPLF/rippled/pull/6529)
- Fixed a bug in `Forwarded` HTTP header parsing where the extracted IP address could be incorrect when no comma or semicolon delimiter follows the address. This could cause the server to misidentify a client's IP address when operating behind a reverse proxy. [#6529](https://github.com/XRPLF/rippled/pull/6529)
- `book_offers`: The `domain` field now returns `objectNotFound` if the specified permissioned domain does not exist in the ledger, instead of silently returning an empty `offers` array. [#7963](https://github.com/XRPLF/rippled/issues/7963)

## XRP Ledger server version 3.1.0

Expand Down
48 changes: 48 additions & 0 deletions src/test/rpc/Book_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1532,6 +1532,38 @@ class Book_test : public beast::unit_test::Suite
BEAST_EXPECT(jrr[jss::error] == "domainMalformed");
BEAST_EXPECT(jrr[jss::error_message] == "Unable to parse domain.");
}

// Well-formed domain that does not exist in the ledger.
{
json::Value jvParams;
jvParams[jss::ledger_index] = "validated";
jvParams[jss::taker_pays][jss::currency] = "USD";
jvParams[jss::taker_pays][jss::issuer] = gw.human();
jvParams[jss::taker_gets][jss::currency] = "EUR";
jvParams[jss::taker_gets][jss::issuer] = gw.human();
jvParams[jss::domain] = std::string(64, 'A');
auto const jrr = env.rpc("json", "book_offers", to_string(jvParams))[jss::result];
BEAST_EXPECT(jrr[jss::error] == "objectNotFound");
BEAST_EXPECT(jrr[jss::error_message] == "Domain not found.");
}

// An all-zero domain is well-formed hex but can never name an object.
// Both ledger views are covered: a validated ledger reaches
// Ledger::read, which treats a zero key as UNREACHABLE, so dropping
// the isZero() guard in doBookOffers aborts on this case.
for (auto const* ledgerIndex : {"validated", "current"})
{
json::Value jvParams;
jvParams[jss::ledger_index] = ledgerIndex;
jvParams[jss::taker_pays][jss::currency] = "USD";
jvParams[jss::taker_pays][jss::issuer] = gw.human();
jvParams[jss::taker_gets][jss::currency] = "EUR";
jvParams[jss::taker_gets][jss::issuer] = gw.human();
jvParams[jss::domain] = std::string(64, '0');
auto const jrr = env.rpc("json", "book_offers", to_string(jvParams))[jss::result];
BEAST_EXPECT(jrr[jss::error] == "objectNotFound");
BEAST_EXPECT(jrr[jss::error_message] == "Domain not found.");
}
}

void
Expand Down Expand Up @@ -1662,6 +1694,22 @@ class Book_test : public beast::unit_test::Suite
checkBookOffers(jrr);
}

// book_offers: an unknown domain is rejected rather than silently
// returning an empty book
{
json::Value jvParams;
jvParams[jss::taker] = env.master.human();
jvParams[jss::taker_pays][jss::currency] = "XRP";
jvParams[jss::ledger_index] = "validated";
jvParams[jss::taker_gets][jss::currency] = "USD";
jvParams[jss::taker_gets][jss::issuer] = gw.human();
jvParams[jss::domain] = std::string(64, 'A');

auto const jrr = env.rpc("json", "book_offers", to_string(jvParams))[jss::result];
BEAST_EXPECT(jrr[jss::error] == "objectNotFound");
BEAST_EXPECT(jrr[jss::error_message] == "Domain not found.");
}

// subscribe to domain book should return domain offer
{
json::Value books;
Expand Down
11 changes: 11 additions & 0 deletions src/xrpld/rpc/handlers/orderbook/BookOffers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include <xrpl/protocol/Asset.h>
#include <xrpl/protocol/Book.h>
#include <xrpl/protocol/ErrorCodes.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Issue.h>
#include <xrpl/protocol/RPCErr.h>
#include <xrpl/protocol/UintTypes.h>
Expand Down Expand Up @@ -232,6 +233,16 @@ doBookOffers(RPC::JsonContext& context)
return RPC::makeError(RpcDomainMalformed, "Unable to parse domain.");
}

// A PermissionedDomain's DomainID is its ledger key, so the domain
// exists only if that entry is present. `read` is used rather than
// `exists` because the caller supplies the raw key: `exists` does not
// check the entry type, so the index of any unrelated object would
// pass. The all-zero key is well-formed hex but can never name a real
// object, and `Ledger::read` treats it as UNREACHABLE, so it must be
// rejected before reaching the view.
if (num.isZero() || !lpLedger->read(keylet::permissionedDomain(num)))
return RPC::makeError(RpcObjectNotFound, "Domain not found.");

domain = num;
}

Expand Down