Compare commits

..
Author SHA1 Message Date
tequ ac1e2356e5 Refactor ledger_entry RPC source code and tests (#5237)
This is a major refactor of LedgerEntry.cpp. It adds a number of helper functions to make the code easier to maintain.

It also splits up the ledger and ledger_entry tests into different files, and cleans up the ledger_entry tests to make them easier to write and maintain.

This refactor also caught a few bugs in some of the other RPC processing, so those are fixed along the way.

Xahau: the Xahau ledger entry parsers (app_loader, cron, emitted_txn, hook, hook_definition, hook_state, import_vlseq, manifest_entry, random, uri_token) are ported to the new helpers; rpcENTRY_NOT_FOUND and rpcUNEXPECTED_LEDGER_TYPE take upstream's codes 98 and 99. A non-string permissioned_domain account now returns malformedAddress, matching upstream after #5344. `maxURITokenURILength` is introduced in Protocol.h and used in URIToken.cpp and the tests (taken from Xahau sync commit 9ff0d3c7c3; no behaviour change). requiredUInt256 reports "hex string" instead of upstream's "Hash256" (sync-branch choice; it only affects the hook_state key/namespace_id error text). The Vault_test.cpp hunk and the Delegate test are dropped because Xahau has neither feature.
2026-10-22 16:33:31 +09:00
tequ 5de98a6775 refactor(trivial): reorganize ledger entry tests and helper functions (#5376)
This PR splits out `ledger_entry` tests into its own file (`LedgerEntry_test.cpp`) and alphabetizes the helper functions in `LedgerEntry.cpp`. These commits were split out of #5237 to make that PR a little more manageable, since these basic trivial changes are most of the diff. There is no code change, just moving code around.

Xahau: the Xahau-specific parsers (app_loader, random) and tests (emitted_txn, import_vlseq/uri_token/hook invalid-param cases) move along with the rest.
2026-10-22 16:10:19 +09:00
16 changed files with 4347 additions and 4498 deletions

No files matched your search

+6 -1
View File
@@ -157,7 +157,12 @@ enum error_code_i {
// Simulate
rpcTX_SIGNED = 97,
rpcLAST = rpcTX_SIGNED // rpcLAST should always equal the last code.
// ledger_entry
rpcENTRY_NOT_FOUND = 98,
rpcUNEXPECTED_LEDGER_TYPE = 99,
rpcLAST =
rpcUNEXPECTED_LEDGER_TYPE // rpcLAST should always equal the last code.
};
/** Codes returned in the `warnings` array of certain RPC commands.
+3
View File
@@ -84,6 +84,9 @@ std::uint16_t constexpr maxTransferFee = 50000;
/** The maximum length of a URI inside an NFT */
std::size_t constexpr maxTokenURILength = 256;
/** The maximum length of a URI inside an URIToken */
std::size_t constexpr maxURITokenURILength = 256;
/** The maximum length of a Data element inside a DID */
std::size_t constexpr maxDIDDocumentLength = 256;
+4
View File
@@ -100,11 +100,15 @@ JSS(isVLEncoded); // out: RPC server_definitions
// matches definitions.json format
JSS(Invalid); //
JSS(Issuer); // in: Credential transactions
JSS(IssuingChainDoor); // field.
JSS(IssuingChainIssue); // field.
JSS(InvoiceID); // field
JSS(LastLedgerSequence); // in: TransactionSign; field
JSS(FirstLedgerSequence); // in: TransactionSign; field
JSS(LastUpdateTime); // field.
JSS(LimitAmount); // field.
JSS(LockingChainDoor); // field.
JSS(LockingChainIssue); // field.
JSS(NetworkID); // field.
JSS(LPTokenOut); // in: AMM Liquidity Provider deposit tokens
JSS(LPTokenIn); // in: AMM Liquidity Provider withdraw tokens
+53 -83
View File
@@ -29,7 +29,6 @@
#include <boost/asio/buffer.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/post.hpp>
#include <boost/asio/spawn.hpp>
#include <boost/beast/core/detect_ssl.hpp>
#include <boost/beast/core/multi_buffer.hpp>
@@ -61,9 +60,6 @@ private:
std::vector<char> recv_buffer_;
endpoint_type local_endpoint_; // Store TCP-style endpoint
// Large enough for one maximum-size datagram (see do_send).
static constexpr int minSendBufferSize = 256 * 1024;
public:
UDPDoor(
Handler& handler,
@@ -101,27 +97,6 @@ public:
return;
}
// Some platforms (macOS: net.inet.udp.maxdgram = 9216) cap outgoing
// datagrams at SO_SNDBUF, so a full-size fragment fails with
// EMSGSIZE unless the send buffer is at least that large.
{
boost::asio::socket_base::send_buffer_size current;
socket_.get_option(current, ec);
if (!ec && current.value() < minSendBufferSize)
{
socket_.set_option(
boost::asio::socket_base::send_buffer_size(
minSendBufferSize),
ec);
if (ec)
{
JLOG(j_.warn())
<< "UDP set send_buffer_size failed: " << ec.message();
}
}
ec.clear();
}
socket_.bind(udp_endpoint, ec);
if (ec)
{
@@ -199,50 +174,22 @@ private:
std::string(recv_buffer_.data(), bytes_transferred),
tcp_endpoint,
port_,
[self = this->shared_from_this(),
tcp_endpoint](std::string const& response) {
self->do_send(response, tcp_endpoint);
[this, tcp_endpoint](std::string const& response) {
do_send(response, tcp_endpoint);
});
do_receive();
}
// The payload must stay alive until the send completes, and the socket
// is only touched on the strand.
void
send_datagram(
std::shared_ptr<std::string const> payload,
boost::asio::ip::udp::endpoint const& udp_endpoint)
{
boost::asio::post(
strand_,
[this,
self = this->shared_from_this(),
payload = std::move(payload),
udp_endpoint]() {
if (!socket_.is_open())
return;
socket_.async_send_to(
boost::asio::buffer(*payload),
udp_endpoint,
boost::asio::bind_executor(
strand_,
[self, payload](
error_code ec, std::size_t bytes_transferred) {
if (ec &&
ec != boost::asio::error::operation_aborted)
{
JLOG(self->j_.error())
<< "UDP send failed: " << ec.message();
}
}));
});
}
void
do_send(std::string const& response, endpoint_type const& tcp_endpoint)
{
if (!socket_.is_open())
{
std::cout << "UDP SOCKET NOT OPEN WHEN SENDING\n\n";
return;
}
const size_t HEADER_SIZE = 16;
const size_t MAX_DATAGRAM_SIZE =
65487; // Allow for ipv6 header 40 bytes + 8 bytes of udp header
@@ -255,8 +202,19 @@ private:
// If message fits in single datagram, send normally
if (response.length() <= MAX_DATAGRAM_SIZE)
{
send_datagram(
std::make_shared<std::string const>(response), udp_endpoint);
socket_.async_send_to(
boost::asio::buffer(response),
udp_endpoint,
boost::asio::bind_executor(
strand_,
[this, self = this->shared_from_this()](
error_code ec, std::size_t bytes_transferred) {
if (ec && ec != boost::asio::error::operation_aborted)
{
JLOG(j_.error())
<< "UDP send failed: " << ec.message();
}
}));
return;
}
@@ -275,39 +233,51 @@ private:
// Send fragmented packets
for (uint16_t packet_num = 0; packet_num < total_packets; packet_num++)
{
auto fragment = std::make_shared<std::string>();
fragment->reserve(MAX_DATAGRAM_SIZE);
std::string fragment;
fragment.reserve(MAX_DATAGRAM_SIZE);
// Add header - 4 bytes of zeros
fragment->push_back(0);
fragment->push_back(0);
fragment->push_back(0);
fragment->push_back(0);
fragment.push_back(0);
fragment.push_back(0);
fragment.push_back(0);
fragment.push_back(0);
// Add packet number (little endian)
fragment->push_back(packet_num & 0xFF);
fragment->push_back((packet_num >> 8) & 0xFF);
fragment.push_back(packet_num & 0xFF);
fragment.push_back((packet_num >> 8) & 0xFF);
// Add total packets (little endian)
fragment->push_back(total_packets & 0xFF);
fragment->push_back((total_packets >> 8) & 0xFF);
fragment.push_back(total_packets & 0xFF);
fragment.push_back((total_packets >> 8) & 0xFF);
// Add timestamp (8 bytes, little endian)
fragment->push_back(timestamp & 0xFF);
fragment->push_back((timestamp >> 8) & 0xFF);
fragment->push_back((timestamp >> 16) & 0xFF);
fragment->push_back((timestamp >> 24) & 0xFF);
fragment->push_back((timestamp >> 32) & 0xFF);
fragment->push_back((timestamp >> 40) & 0xFF);
fragment->push_back((timestamp >> 48) & 0xFF);
fragment->push_back((timestamp >> 56) & 0xFF);
fragment.push_back(timestamp & 0xFF);
fragment.push_back((timestamp >> 8) & 0xFF);
fragment.push_back((timestamp >> 16) & 0xFF);
fragment.push_back((timestamp >> 24) & 0xFF);
fragment.push_back((timestamp >> 32) & 0xFF);
fragment.push_back((timestamp >> 40) & 0xFF);
fragment.push_back((timestamp >> 48) & 0xFF);
fragment.push_back((timestamp >> 56) & 0xFF);
// Calculate payload slice
size_t start = packet_num * payload_size;
size_t length = std::min(payload_size, response.length() - start);
fragment->append(response.substr(start, length));
fragment.append(response.substr(start, length));
send_datagram(std::move(fragment), udp_endpoint);
socket_.async_send_to(
boost::asio::buffer(fragment),
udp_endpoint,
boost::asio::bind_executor(
strand_,
[this, self = this->shared_from_this()](
error_code ec, std::size_t bytes_transferred) {
if (ec && ec != boost::asio::error::operation_aborted)
{
JLOG(j_.error())
<< "UDP send failed: " << ec.message();
}
}));
}
}
+5 -1
View File
@@ -23,6 +23,8 @@
#include <xrpl/json/json_writer.h>
#include <xrpl/json/to_string.h>
#include <cmath>
namespace Json {
const Value Value::null;
@@ -672,7 +674,9 @@ Value::isConvertibleTo(ValueType other) const
(other == intValue && value_.real_ >= minInt &&
value_.real_ <= maxInt) ||
(other == uintValue && value_.real_ >= 0 &&
value_.real_ <= maxUInt) ||
value_.real_ <= maxUInt &&
std::fabs(round(value_.real_) - value_.real_) <
std::numeric_limits<double>::epsilon()) ||
other == realValue || other == stringValue ||
other == booleanValue;
+4 -1
View File
@@ -113,7 +113,10 @@ constexpr static ErrorInfo unorderedErrorInfos[]{
{rpcLEDGER_MISSING, "ledgerMissing", "One or more ledgers in the specified range is missing", 406},
{rpcORACLE_MALFORMED, "oracleMalformed", "Oracle request is malformed.", 400},
{rpcBAD_CREDENTIALS, "badCredentials", "Credentials do not exist, are not accepted, or have expired.", 400},
{rpcTX_SIGNED, "transactionSigned", "Transaction should not be signed.", 400}};
{rpcTX_SIGNED, "transactionSigned", "Transaction should not be signed.", 400},
{rpcENTRY_NOT_FOUND, "entryNotFound", "Entry not found.", 400},
{rpcUNEXPECTED_LEDGER_TYPE, "unexpectedLedgerType", "Unexpected ledger type.", 400},
};
// clang-format on
// Sort and validate unorderedErrorInfos at compile time. Should be
+8 -10
View File
@@ -95,12 +95,10 @@ STXChainBridge::STXChainBridge(SField const& name, Json::Value const& v)
};
checkExtra(v);
Json::Value const& lockingChainDoorStr =
v[sfLockingChainDoor.getJsonName()];
Json::Value const& lockingChainIssue = v[sfLockingChainIssue.getJsonName()];
Json::Value const& issuingChainDoorStr =
v[sfIssuingChainDoor.getJsonName()];
Json::Value const& issuingChainIssue = v[sfIssuingChainIssue.getJsonName()];
Json::Value const& lockingChainDoorStr = v[jss::LockingChainDoor];
Json::Value const& lockingChainIssue = v[jss::LockingChainIssue];
Json::Value const& issuingChainDoorStr = v[jss::IssuingChainDoor];
Json::Value const& issuingChainIssue = v[jss::IssuingChainIssue];
if (!lockingChainDoorStr.isString())
{
@@ -158,10 +156,10 @@ Json::Value
STXChainBridge::getJson(JsonOptions jo) const
{
Json::Value v;
v[sfLockingChainDoor.getJsonName()] = lockingChainDoor_.getJson(jo);
v[sfLockingChainIssue.getJsonName()] = lockingChainIssue_.getJson(jo);
v[sfIssuingChainDoor.getJsonName()] = issuingChainDoor_.getJson(jo);
v[sfIssuingChainIssue.getJsonName()] = issuingChainIssue_.getJson(jo);
v[jss::LockingChainDoor] = lockingChainDoor_.getJson(jo);
v[jss::LockingChainIssue] = lockingChainIssue_.getJson(jo);
v[jss::IssuingChainDoor] = issuingChainDoor_.getJson(jo);
v[jss::IssuingChainIssue] = issuingChainIssue_.getJson(jo);
return v;
}
+8 -8
View File
@@ -44,10 +44,10 @@ bridge(
Issue const& issuingChainIssue)
{
Json::Value jv;
jv[sfLockingChainDoor.getJsonName()] = lockingChainDoor.human();
jv[sfLockingChainIssue.getJsonName()] = to_json(lockingChainIssue);
jv[sfIssuingChainDoor.getJsonName()] = issuingChainDoor.human();
jv[sfIssuingChainIssue.getJsonName()] = to_json(issuingChainIssue);
jv[jss::LockingChainDoor] = lockingChainDoor.human();
jv[jss::LockingChainIssue] = to_json(lockingChainIssue);
jv[jss::IssuingChainDoor] = issuingChainDoor.human();
jv[jss::IssuingChainIssue] = to_json(issuingChainIssue);
return jv;
}
@@ -60,10 +60,10 @@ bridge_rpc(
Issue const& issuingChainIssue)
{
Json::Value jv;
jv[sfLockingChainDoor.getJsonName()] = lockingChainDoor.human();
jv[sfLockingChainIssue.getJsonName()] = to_json(lockingChainIssue);
jv[sfIssuingChainDoor.getJsonName()] = issuingChainDoor.human();
jv[sfIssuingChainIssue.getJsonName()] = to_json(issuingChainIssue);
jv[jss::LockingChainDoor] = lockingChainDoor.human();
jv[jss::LockingChainIssue] = to_json(lockingChainIssue);
jv[jss::IssuingChainDoor] = issuingChainDoor.human();
jv[jss::IssuingChainIssue] = to_json(issuingChainIssue);
return jv;
}
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
-148
View File
@@ -37,8 +37,6 @@
#include <chrono>
#include <condition_variable>
#include <cstdint>
#include <map>
#include <mutex>
#include <optional>
#include <stdexcept>
@@ -500,151 +498,6 @@ public:
run(false);
}
// Answers every datagram through sendResponse: "big" gets a reply too
// large for one datagram, anything else gets "pong".
struct UDPEchoHandler : TestHandler
{
// Two fragments: small enough not to overflow a default socket buffer.
static constexpr std::size_t bigSize = 100000;
void
onUDPMessage(
std::string const& message,
boost::asio::ip::tcp::endpoint const& remoteEndpoint,
Port const& port,
std::function<void(std::string const&)> sendResponse)
{
if (message == "big")
{
std::string big(bigSize, '\0');
for (std::size_t i = 0; i < big.size(); ++i)
big[i] = static_cast<char>('a' + (i % 26));
sendResponse(big);
}
else
{
sendResponse("pong");
}
}
static std::string
expectedBig()
{
std::string big(bigSize, '\0');
for (std::size_t i = 0; i < big.size(); ++i)
big[i] = static_cast<char>('a' + (i % 26));
return big;
}
};
// Exercises UDPDoor::do_send / send_datagram: a short reply goes out as
// one datagram, a long one is split into headered fragments.
void
udpResponseTests()
{
testcase("UDP RPC door responses");
using namespace std::chrono_literals;
using udp = boost::asio::ip::udp;
TestSink sink{*this};
TestThread thread;
sink.threshold(beast::severities::Severity::kAll);
beast::Journal journal{sink};
auto const addr = boost::asio::ip::make_address(getEnvLocalhostAddr());
// UDPDoor reports the configured port, not the bound one, so pick a
// free port up front.
std::uint16_t port = 0;
{
boost::asio::io_context io;
udp::socket probe(io, udp::endpoint(addr, 0));
port = probe.local_endpoint().port();
}
UDPEchoHandler handler;
auto s = make_Server(handler, thread.get_io_context(), journal);
std::vector<Port> ports(1);
ports.back().name = "udp_rpc";
ports.back().ip = addr;
ports.back().port = port;
ports.back().protocol.insert("udp");
s->ports(ports);
boost::asio::io_context io;
udp::socket client(io, udp::endpoint(addr, 0));
// Room for the full fragmented reply on platforms with small
// default socket buffers.
client.set_option(udp::socket::receive_buffer_size(512 * 1024));
udp::endpoint const server(addr, port);
// Receive one datagram, or nothing if the timeout expires.
auto receive = [&](std::chrono::milliseconds timeout)
-> std::optional<std::string> {
std::vector<char> buf(70000);
udp::endpoint from;
std::optional<std::string> result;
client.async_receive_from(
boost::asio::buffer(buf),
from,
[&](boost::system::error_code ec, std::size_t n) {
if (!ec)
result.emplace(buf.data(), n);
});
io.restart();
io.run_for(timeout);
if (!result)
{
client.cancel();
io.restart();
io.run();
}
return result;
};
// Single datagram reply
client.send_to(boost::asio::buffer(std::string("ping")), server);
if (auto const r = receive(5s); BEAST_EXPECT(r))
BEAST_EXPECT(*r == "pong");
// Fragmented reply
client.send_to(boost::asio::buffer(std::string("big")), server);
std::size_t constexpr headerSize = 16;
std::size_t constexpr payloadSize = 65487 - headerSize;
std::size_t const expectedPackets =
(UDPEchoHandler::bigSize + payloadSize - 1) / payloadSize;
std::map<std::uint16_t, std::string> fragments;
std::uint16_t total = 0;
while (fragments.size() < expectedPackets)
{
auto const r = receive(5s);
if (!BEAST_EXPECT(r) || !BEAST_EXPECT(r->size() > headerSize))
break;
auto const byte = [&](std::size_t i) {
return static_cast<std::uint16_t>(
static_cast<unsigned char>((*r)[i]));
};
BEAST_EXPECT(byte(0) == 0 && byte(1) == 0);
BEAST_EXPECT(byte(2) == 0 && byte(3) == 0);
std::uint16_t const num = byte(4) | (byte(5) << 8);
total = byte(6) | (byte(7) << 8);
fragments[num] = r->substr(headerSize);
}
BEAST_EXPECT(total == expectedPackets);
if (BEAST_EXPECT(fragments.size() == expectedPackets))
{
std::string reassembled;
for (auto const& [num, data] : fragments)
reassembled += data;
BEAST_EXPECT(reassembled == UDPEchoHandler::expectedBig());
}
s = nullptr;
}
void
testBadConfig()
{
@@ -789,7 +642,6 @@ public:
basicTests();
stressTest();
udpHighwayTests();
udpResponseTests();
testBadConfig();
}
};
+1 -1
View File
@@ -91,7 +91,7 @@ URIToken::preflight(PreflightContext const& ctx)
{
auto const uri = ctx.tx.getFieldVL(sfURI);
if (uri.size() < 1 || uri.size() > 256)
if (uri.size() < 1 || uri.size() > maxURITokenURILength)
{
JLOG(ctx.j.warn())
<< "Malformed transaction. URI must be at least 1 "
+10 -7
View File
@@ -193,7 +193,7 @@ getAccountObjects(
auto& jvObjects = (jvResult[jss::account_objects] = Json::arrayValue);
// this is a mutable version of limit, used to seemlessly switch
// this is a mutable version of limit, used to seamlessly switch
// to iterating directory entries when nftokenpages are exhausted
uint32_t mlimit = limit;
@@ -459,7 +459,7 @@ ledgerFromRequest(T& ledger, JsonContext& context)
indexValue = legacyLedger;
}
if (hashValue)
if (!hashValue.isNull())
{
if (!hashValue.isString())
return {rpcINVALID_PARAMS, "ledgerHashNotString"};
@@ -470,6 +470,9 @@ ledgerFromRequest(T& ledger, JsonContext& context)
return getLedger(ledger, ledgerHash, context);
}
if (!indexValue.isConvertibleTo(Json::stringValue))
return {rpcINVALID_PARAMS, "ledgerIndexMalformed"};
auto const index = indexValue.asString();
if (index == "current" || index.empty())
@@ -481,11 +484,11 @@ ledgerFromRequest(T& ledger, JsonContext& context)
if (index == "closed")
return getLedger(ledger, LedgerShortcut::CLOSED, context);
std::uint32_t iVal;
if (beast::lexicalCastChecked(iVal, index))
return getLedger(ledger, iVal, context);
std::uint32_t val;
if (!beast::lexicalCastChecked(val, index))
return {rpcINVALID_PARAMS, "ledgerIndexMalformed"};
return {rpcINVALID_PARAMS, "ledgerIndexMalformed"};
return getLedger(ledger, val, context);
}
} // namespace
@@ -701,7 +704,7 @@ getLedger(T& ledger, LedgerShortcut shortcut, Context& context)
return Status::OK;
}
// Explicit instantiaion of above three functions
// Explicit instantiation of above three functions
template Status
getLedger<>(std::shared_ptr<ReadView const>&, uint32_t, Context&);
-4
View File
@@ -1126,10 +1126,6 @@ ServerHandler::onUDPMessage(
if (port.has_peer())
return app_.overlay().processXUSH(message, remoteEndpoint);
if (!ipAllowed(
remoteEndpoint.address(), port.admin_nets_v4, port.admin_nets_v6))
return;
Json::Value jv;
if (message.size() > RPC::Tuning::maxRequestSize ||
!Json::Reader{}.parse(message, jv) || !jv.isObject())
File diff suppressed because it is too large. Load diff
+299
View File
@@ -0,0 +1,299 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012-2025 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include <xrpld/rpc/detail/RPCHelpers.h>
#include <xrpl/basics/StringUtilities.h>
#include <xrpl/basics/strHex.h>
#include <xrpl/beast/core/LexicalCast.h>
#include <xrpl/json/json_errors.h>
#include <xrpl/protocol/ErrorCodes.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/RPCErr.h>
#include <xrpl/protocol/STXChainBridge.h>
#include <xrpl/protocol/jss.h>
#include <functional>
namespace ripple {
namespace LedgerEntryHelpers {
Unexpected<Json::Value>
missingFieldError(
Json::StaticString const field,
std::optional<std::string> err = std::nullopt)
{
Json::Value json = Json::objectValue;
auto error = RPC::missing_field_message(std::string(field.c_str()));
json[jss::error] = err.value_or("malformedRequest");
json[jss::error_code] = rpcINVALID_PARAMS;
json[jss::error_message] = std::move(error);
return Unexpected(json);
}
Unexpected<Json::Value>
invalidFieldError(
std::string const& err,
Json::StaticString const field,
std::string const& type)
{
Json::Value json = Json::objectValue;
auto error = RPC::expected_field_message(field, type);
json[jss::error] = err;
json[jss::error_code] = rpcINVALID_PARAMS;
json[jss::error_message] = std::move(error);
return Unexpected(json);
}
Unexpected<Json::Value>
malformedError(std::string const& err, std::string const& message)
{
Json::Value json = Json::objectValue;
json[jss::error] = err;
json[jss::error_code] = rpcINVALID_PARAMS;
json[jss::error_message] = message;
return Unexpected(json);
}
Expected<bool, Json::Value>
hasRequired(
Json::Value const& params,
std::initializer_list<Json::StaticString> fields,
std::optional<std::string> err = std::nullopt)
{
for (auto const field : fields)
{
if (!params.isMember(field) || params[field].isNull())
{
return missingFieldError(field, err);
}
}
return true;
}
template <class T>
std::optional<T>
parse(Json::Value const& param);
template <class T>
Expected<T, Json::Value>
required(
Json::Value const& params,
Json::StaticString const fieldName,
std::string const& err,
std::string const& expectedType)
{
if (!params.isMember(fieldName) || params[fieldName].isNull())
{
return missingFieldError(fieldName);
}
if (auto obj = parse<T>(params[fieldName]))
{
return *obj;
}
return invalidFieldError(err, fieldName, expectedType);
}
template <>
std::optional<AccountID>
parse(Json::Value const& param)
{
if (!param.isString())
return std::nullopt;
auto const account = parseBase58<AccountID>(param.asString());
if (!account || account->isZero())
{
return std::nullopt;
}
return account;
}
Expected<AccountID, Json::Value>
requiredAccountID(
Json::Value const& params,
Json::StaticString const fieldName,
std::string const& err)
{
return required<AccountID>(params, fieldName, err, "AccountID");
}
std::optional<Blob>
parseHexBlob(Json::Value const& param, std::size_t maxLength)
{
if (!param.isString())
return std::nullopt;
auto const blob = strUnHex(param.asString());
if (!blob || blob->empty() || blob->size() > maxLength)
return std::nullopt;
return blob;
}
Expected<Blob, Json::Value>
requiredHexBlob(
Json::Value const& params,
Json::StaticString const fieldName,
std::size_t maxLength,
std::string const& err)
{
if (!params.isMember(fieldName) || params[fieldName].isNull())
{
return missingFieldError(fieldName);
}
if (auto blob = parseHexBlob(params[fieldName], maxLength))
{
return *blob;
}
return invalidFieldError(err, fieldName, "hex string");
}
template <>
std::optional<std::uint32_t>
parse(Json::Value const& param)
{
if (param.isUInt() || (param.isInt() && param.asInt() >= 0))
return param.asUInt();
if (param.isString())
{
std::uint32_t v;
if (beast::lexicalCastChecked(v, param.asString()))
return v;
}
return std::nullopt;
}
Expected<std::uint32_t, Json::Value>
requiredUInt32(
Json::Value const& params,
Json::StaticString const fieldName,
std::string const& err)
{
return required<std::uint32_t>(params, fieldName, err, "number");
}
template <>
std::optional<uint256>
parse(Json::Value const& param)
{
uint256 uNodeIndex;
if (!param.isString() || !uNodeIndex.parseHex(param.asString()))
{
return std::nullopt;
}
return uNodeIndex;
}
Expected<uint256, Json::Value>
requiredUInt256(
Json::Value const& params,
Json::StaticString const fieldName,
std::string const& err)
{
return required<uint256>(params, fieldName, err, "hex string");
}
template <>
std::optional<uint192>
parse(Json::Value const& param)
{
uint192 field;
if (!param.isString() || !field.parseHex(param.asString()))
{
return std::nullopt;
}
return field;
}
Expected<uint192, Json::Value>
requiredUInt192(
Json::Value const& params,
Json::StaticString const fieldName,
std::string const& err)
{
return required<uint192>(params, fieldName, err, "Hash192");
}
Expected<STXChainBridge, Json::Value>
parseBridgeFields(Json::Value const& params)
{
if (auto const value = hasRequired(
params,
{jss::LockingChainDoor,
jss::LockingChainIssue,
jss::IssuingChainDoor,
jss::IssuingChainIssue});
!value)
{
return Unexpected(value.error());
}
auto const lockingChainDoor = requiredAccountID(
params, jss::LockingChainDoor, "malformedLockingChainDoor");
if (!lockingChainDoor)
{
return Unexpected(lockingChainDoor.error());
}
auto const issuingChainDoor = requiredAccountID(
params, jss::IssuingChainDoor, "malformedIssuingChainDoor");
if (!issuingChainDoor)
{
return Unexpected(issuingChainDoor.error());
}
Issue lockingChainIssue;
try
{
lockingChainIssue = issueFromJson(params[jss::LockingChainIssue]);
}
catch (std::runtime_error const& ex)
{
return invalidFieldError(
"malformedIssue", jss::LockingChainIssue, "Issue");
}
Issue issuingChainIssue;
try
{
issuingChainIssue = issueFromJson(params[jss::IssuingChainIssue]);
}
catch (std::runtime_error const& ex)
{
return invalidFieldError(
"malformedIssue", jss::IssuingChainIssue, "Issue");
}
return STXChainBridge(
*lockingChainDoor,
lockingChainIssue,
*issuingChainDoor,
issuingChainIssue);
}
} // namespace LedgerEntryHelpers
} // namespace ripple