Add error code extension mechanism and use malformed currency code (#396)

Fixes #275
This commit is contained in:
Alex Kremer
2022-11-15 18:08:09 +01:00
committed by GitHub
parent 6015faa0d3
commit 0a5bf911c1
31 changed files with 808 additions and 434 deletions

View File

@@ -6,7 +6,7 @@ exec 1>&2
find src unittests -type f \( -name '*.cpp' -o -name '*.h' -o -name '*.ipp' \) -print0 | xargs -0 clang-format -i find src unittests -type f \( -name '*.cpp' -o -name '*.h' -o -name '*.ipp' \) -print0 | xargs -0 clang-format -i
# check how many lines differ # check how many lines differ
lines=$(git diff | wc -l) lines=$(git diff src unittests | wc -l)
# check if there is any updated files # check if there is any updated files
if [ "$lines" != "0" ]; then if [ "$lines" != "0" ]; then

View File

@@ -61,6 +61,7 @@ target_sources(clio PRIVATE
## Subscriptions ## Subscriptions
src/subscriptions/SubscriptionManager.cpp src/subscriptions/SubscriptionManager.cpp
## RPC ## RPC
src/rpc/Errors.cpp
src/rpc/RPC.cpp src/rpc/RPC.cpp
src/rpc/RPCHelpers.cpp src/rpc/RPCHelpers.cpp
src/rpc/Counters.cpp src/rpc/Counters.cpp
@@ -107,7 +108,8 @@ add_executable(clio_server src/main/main.cpp)
target_link_libraries(clio_server PUBLIC clio) target_link_libraries(clio_server PUBLIC clio)
if(BUILD_TESTS) if(BUILD_TESTS)
add_executable(clio_tests add_executable(clio_tests
unittests/RPCErrors.cpp
unittests/config.cpp unittests/config.cpp
unittests/main.cpp) unittests/main.cpp)
include(CMake/deps/gtest.cmake) include(CMake/deps/gtest.cmake)

135
src/rpc/Errors.cpp Normal file
View File

@@ -0,0 +1,135 @@
#include <rpc/Errors.h>
#include <algorithm>
using namespace std;
namespace {
template <class... Ts>
struct overloadSet : Ts...
{
using Ts::operator()...;
};
// explicit deduction guide (not needed as of C++20, but clang be clang)
template <class... Ts>
overloadSet(Ts...) -> overloadSet<Ts...>;
} // namespace
namespace RPC {
WarningInfo const&
getWarningInfo(WarningCode code)
{
constexpr static WarningInfo infos[]{
{warnUNKNOWN, "Unknown warning"},
{warnRPC_CLIO,
"This is a clio server. clio only serves validated data. If you "
"want to talk to rippled, include 'ledger_index':'current' in your "
"request"},
{warnRPC_OUTDATED, "This server may be out of date"},
{warnRPC_RATE_LIMIT, "You are about to be rate limited"}};
auto matchByCode = [code](auto const& info) { return info.code == code; };
if (auto it = find_if(begin(infos), end(infos), matchByCode);
it != end(infos))
return *it;
throw(out_of_range("Invalid WarningCode"));
}
boost::json::object
makeWarning(WarningCode code)
{
boost::json::object json;
auto const& info = getWarningInfo(code);
json["id"] = code;
json["message"] = static_cast<string>(info.message);
return json;
}
ClioErrorInfo const&
getErrorInfo(ClioError code)
{
constexpr static ClioErrorInfo infos[]{
{ClioError::rpcMALFORMED_CURRENCY,
"malformedCurrency",
"Malformed currency."},
};
auto matchByCode = [code](auto const& info) { return info.code == code; };
if (auto it = find_if(begin(infos), end(infos), matchByCode);
it != end(infos))
return *it;
throw(out_of_range("Invalid error code"));
}
boost::json::object
makeError(
RippledError err,
optional<string_view> customError,
optional<string_view> customMessage)
{
boost::json::object json;
auto const& info = ripple::RPC::get_error_info(err);
json["error"] = customError.value_or(info.token.c_str()).data();
json["error_code"] = static_cast<uint32_t>(err);
json["error_message"] = customMessage.value_or(info.message.c_str()).data();
json["status"] = "error";
json["type"] = "response";
return json;
}
boost::json::object
makeError(
ClioError err,
optional<string_view> customError,
optional<string_view> customMessage)
{
boost::json::object json;
auto const& info = getErrorInfo(err);
json["error"] = customError.value_or(info.error).data();
json["error_code"] = static_cast<uint32_t>(info.code);
json["error_message"] = customMessage.value_or(info.message).data();
json["status"] = "error";
json["type"] = "response";
return json;
}
boost::json::object
makeError(Status const& status)
{
auto wrapOptional = [](string_view const& str) {
return str.empty() ? nullopt : make_optional(str);
};
return visit(
overloadSet{
[&status, &wrapOptional](RippledError err) {
if (err == ripple::rpcUNKNOWN)
{
return boost::json::object{
{"error", status.message},
{"type", "response"},
{"status", "error"}};
}
return makeError(
err,
wrapOptional(status.error),
wrapOptional(status.message));
},
[&status, &wrapOptional](ClioError err) {
return makeError(
err,
wrapOptional(status.error),
wrapOptional(status.message));
},
},
status.code);
}
} // namespace RPC

206
src/rpc/Errors.h Normal file
View File

@@ -0,0 +1,206 @@
#ifndef REPORTING_RPC_ERRORS_H_INCLUDED
#define REPORTING_RPC_ERRORS_H_INCLUDED
#include <ripple/protocol/ErrorCodes.h>
#include <boost/json.hpp>
#include <optional>
#include <string>
#include <string_view>
#include <variant>
namespace RPC {
/**
* @brief Custom clio RPC Errors.
*/
enum class ClioError {
rpcMALFORMED_CURRENCY = 5000,
};
/**
* @brief Holds info about a particular @ref ClioError.
*/
struct ClioErrorInfo
{
ClioError const code;
std::string_view const error;
std::string_view const message;
};
/**
* @brief Clio uses compatible Rippled error codes for most RPC errors.
*/
using RippledError = ripple::error_code_i;
/**
* @brief Clio operates on a combination of Rippled and Custom Clio error codes.
*
* @see RippledError For rippled error codes
* @see ClioError For custom clio error codes
*/
using CombinedError = std::variant<RippledError, ClioError>;
/**
* @brief A status returned from any RPC handler.
*/
struct Status
{
CombinedError code = RippledError::rpcSUCCESS;
std::string error = "";
std::string message = "";
Status() = default;
/* implicit */ Status(CombinedError code) : code(code){};
// HACK. Some rippled handlers explicitly specify errors.
// This means that we have to be able to duplicate this
// functionality.
explicit Status(std::string const& message)
: code(ripple::rpcUNKNOWN), message(message)
{
}
Status(CombinedError code, std::string message)
: code(code), message(message)
{
}
Status(CombinedError code, std::string error, std::string message)
: code(code), error(error), message(message)
{
}
/**
* @brief Returns true if the Status is *not* OK.
*/
operator bool() const
{
if (auto err = std::get_if<RippledError>(&code))
return *err != RippledError::rpcSUCCESS;
return true;
}
};
/**
* @brief Warning codes that can be returned by clio.
*/
enum WarningCode {
warnUNKNOWN = -1,
warnRPC_CLIO = 2001,
warnRPC_OUTDATED = 2002,
warnRPC_RATE_LIMIT = 2003
};
/**
* @brief Holds information about a clio warning.
*/
struct WarningInfo
{
constexpr WarningInfo() = default;
constexpr WarningInfo(WarningCode code, char const* message)
: code(code), message(message)
{
}
WarningCode code = warnUNKNOWN;
std::string_view const message = "unknown warning";
};
/**
* @brief Invalid parameters error.
*/
class InvalidParamsError : public std::exception
{
std::string msg;
public:
explicit InvalidParamsError(std::string const& msg) : msg(msg)
{
}
const char*
what() const throw() override
{
return msg.c_str();
}
};
/**
* @brief Account not found error.
*/
class AccountNotFoundError : public std::exception
{
std::string account;
public:
explicit AccountNotFoundError(std::string const& acct) : account(acct)
{
}
const char*
what() const throw() override
{
return account.c_str();
}
};
/**
* @brief A globally available @ref Status that represents a successful state
*/
static Status OK;
/**
* @brief Get the warning info object from a warning code.
*
* @param code The warning code
* @return WarningInfo const& A reference to the static warning info
*/
WarningInfo const&
getWarningInfo(WarningCode code);
/**
* @brief Generate JSON from a warning code.
*
* @param code The @ref WarningCode
* @return boost::json::object The JSON output
*/
boost::json::object
makeWarning(WarningCode code);
/**
* @brief Generate JSON from a @ref Status.
*
* @param status The @ref Status
* @return boost::json::object The JSON output
*/
boost::json::object
makeError(Status const& status);
/**
* @brief Generate JSON from a @ref RippledError.
*
* @param status The rippled @ref RippledError
* @return boost::json::object The JSON output
*/
boost::json::object
makeError(
RippledError err,
std::optional<std::string_view> customError = std::nullopt,
std::optional<std::string_view> customMessage = std::nullopt);
/**
* @brief Generate JSON from a @ref ClioError.
*
* @param status The clio's custom @ref ClioError
* @return boost::json::object The JSON output
*/
boost::json::object
makeError(
ClioError err,
std::optional<std::string_view> customError = std::nullopt,
std::optional<std::string_view> customMessage = std::nullopt);
} // namespace RPC
#endif // REPORTING_RPC_ERRORS_H_INCLUDED

View File

@@ -8,22 +8,24 @@
#include <unordered_map> #include <unordered_map>
using namespace std;
namespace RPC { namespace RPC {
Context::Context( Context::Context(
boost::asio::yield_context& yield_, boost::asio::yield_context& yield_,
std::string const& command_, string const& command_,
std::uint32_t version_, uint32_t version_,
boost::json::object const& params_, boost::json::object const& params_,
std::shared_ptr<BackendInterface const> const& backend_, shared_ptr<BackendInterface const> const& backend_,
std::shared_ptr<SubscriptionManager> const& subscriptions_, shared_ptr<SubscriptionManager> const& subscriptions_,
std::shared_ptr<ETLLoadBalancer> const& balancer_, shared_ptr<ETLLoadBalancer> const& balancer_,
std::shared_ptr<ReportingETL const> const& etl_, shared_ptr<ReportingETL const> const& etl_,
std::shared_ptr<WsBase> const& session_, shared_ptr<WsBase> const& session_,
util::TagDecoratorFactory const& tagFactory_, util::TagDecoratorFactory const& tagFactory_,
Backend::LedgerRange const& range_, Backend::LedgerRange const& range_,
Counters& counters_, Counters& counters_,
std::string const& clientIp_) string const& clientIp_)
: Taggable(tagFactory_) : Taggable(tagFactory_)
, yield(yield_) , yield(yield_)
, method(command_) , method(command_)
@@ -41,19 +43,19 @@ Context::Context(
BOOST_LOG_TRIVIAL(debug) << tag() << "new Context created"; BOOST_LOG_TRIVIAL(debug) << tag() << "new Context created";
} }
std::optional<Context> optional<Context>
make_WsContext( make_WsContext(
boost::asio::yield_context& yc, boost::asio::yield_context& yc,
boost::json::object const& request, boost::json::object const& request,
std::shared_ptr<BackendInterface const> const& backend, shared_ptr<BackendInterface const> const& backend,
std::shared_ptr<SubscriptionManager> const& subscriptions, shared_ptr<SubscriptionManager> const& subscriptions,
std::shared_ptr<ETLLoadBalancer> const& balancer, shared_ptr<ETLLoadBalancer> const& balancer,
std::shared_ptr<ReportingETL const> const& etl, shared_ptr<ReportingETL const> const& etl,
std::shared_ptr<WsBase> const& session, shared_ptr<WsBase> const& session,
util::TagDecoratorFactory const& tagFactory, util::TagDecoratorFactory const& tagFactory,
Backend::LedgerRange const& range, Backend::LedgerRange const& range,
Counters& counters, Counters& counters,
std::string const& clientIp) string const& clientIp)
{ {
boost::json::value commandValue = nullptr; boost::json::value commandValue = nullptr;
if (!request.contains("command") && request.contains("method")) if (!request.contains("command") && request.contains("method"))
@@ -64,9 +66,9 @@ make_WsContext(
if (!commandValue.is_string()) if (!commandValue.is_string())
return {}; return {};
std::string command = commandValue.as_string().c_str(); string command = commandValue.as_string().c_str();
return std::make_optional<Context>( return make_optional<Context>(
yc, yc,
command, command,
1, 1,
@@ -82,23 +84,23 @@ make_WsContext(
clientIp); clientIp);
} }
std::optional<Context> optional<Context>
make_HttpContext( make_HttpContext(
boost::asio::yield_context& yc, boost::asio::yield_context& yc,
boost::json::object const& request, boost::json::object const& request,
std::shared_ptr<BackendInterface const> const& backend, shared_ptr<BackendInterface const> const& backend,
std::shared_ptr<SubscriptionManager> const& subscriptions, shared_ptr<SubscriptionManager> const& subscriptions,
std::shared_ptr<ETLLoadBalancer> const& balancer, shared_ptr<ETLLoadBalancer> const& balancer,
std::shared_ptr<ReportingETL const> const& etl, shared_ptr<ReportingETL const> const& etl,
util::TagDecoratorFactory const& tagFactory, util::TagDecoratorFactory const& tagFactory,
Backend::LedgerRange const& range, Backend::LedgerRange const& range,
RPC::Counters& counters, RPC::Counters& counters,
std::string const& clientIp) string const& clientIp)
{ {
if (!request.contains("method") || !request.at("method").is_string()) if (!request.contains("method") || !request.at("method").is_string())
return {}; return {};
std::string const& command = request.at("method").as_string().c_str(); string const& command = request.at("method").as_string().c_str();
if (command == "subscribe" || command == "unsubscribe") if (command == "subscribe" || command == "unsubscribe")
return {}; return {};
@@ -114,7 +116,7 @@ make_HttpContext(
if (!array.at(0).is_object()) if (!array.at(0).is_object())
return {}; return {};
return std::make_optional<Context>( return make_optional<Context>(
yc, yc,
command, command,
1, 1,
@@ -130,107 +132,38 @@ make_HttpContext(
clientIp); clientIp);
} }
constexpr static WarningInfo warningInfos[]{ using LimitRange = tuple<uint32_t, uint32_t, uint32_t>;
{warnUNKNOWN, "Unknown warning"}, using HandlerFunction = function<Result(Context const&)>;
{warnRPC_CLIO,
"This is a clio server. clio only serves validated data. If you "
"want to talk to rippled, include 'ledger_index':'current' in your "
"request"},
{warnRPC_OUTDATED, "This server may be out of date"},
{warnRPC_RATE_LIMIT, "You are about to be rate limited"}};
WarningInfo const&
get_warning_info(warning_code code)
{
for (WarningInfo const& info : warningInfos)
{
if (info.code == code)
{
return info;
}
}
throw(std::out_of_range("Invalid warning_code"));
}
boost::json::object
make_warning(warning_code code)
{
boost::json::object json;
WarningInfo const& info(get_warning_info(code));
json["id"] = code;
json["message"] = static_cast<std::string>(info.message);
return json;
}
boost::json::object
make_error(Error err)
{
boost::json::object json;
ripple::RPC::ErrorInfo const& info(ripple::RPC::get_error_info(err));
json["error"] = info.token;
json["error_code"] = static_cast<std::uint32_t>(err);
json["error_message"] = info.message;
json["status"] = "error";
json["type"] = "response";
return json;
}
boost::json::object
make_error(Status const& status)
{
if (status.error == ripple::rpcUNKNOWN)
{
return {
{"error", status.message},
{"type", "response"},
{"status", "error"}};
}
boost::json::object json;
ripple::RPC::ErrorInfo const& info(
ripple::RPC::get_error_info(status.error));
json["error"] =
status.strCode.size() ? status.strCode.c_str() : info.token.c_str();
json["error_code"] = static_cast<std::uint32_t>(status.error);
json["error_message"] =
status.message.size() ? status.message.c_str() : info.message.c_str();
json["status"] = "error";
json["type"] = "response";
return json;
}
using LimitRange = std::tuple<std::uint32_t, std::uint32_t, std::uint32_t>;
using HandlerFunction = std::function<Result(Context const&)>;
struct Handler struct Handler
{ {
std::string method; string method;
std::function<Result(Context const&)> handler; function<Result(Context const&)> handler;
std::optional<LimitRange> limit; optional<LimitRange> limit;
bool isClioOnly = false; bool isClioOnly = false;
}; };
class HandlerTable class HandlerTable
{ {
std::unordered_map<std::string, Handler> handlerMap_; unordered_map<string, Handler> handlerMap_;
public: public:
HandlerTable(std::initializer_list<Handler> handlers) HandlerTable(initializer_list<Handler> handlers)
{ {
for (auto const& handler : handlers) for (auto const& handler : handlers)
{ {
handlerMap_[handler.method] = std::move(handler); handlerMap_[handler.method] = move(handler);
} }
} }
bool bool
contains(std::string const& method) contains(string const& method)
{ {
return handlerMap_.contains(method); return handlerMap_.contains(method);
} }
std::optional<LimitRange> optional<LimitRange>
getLimitRange(std::string const& command) getLimitRange(string const& command)
{ {
if (!handlerMap_.contains(command)) if (!handlerMap_.contains(command))
return {}; return {};
@@ -238,8 +171,8 @@ public:
return handlerMap_[command].limit; return handlerMap_[command].limit;
} }
std::optional<HandlerFunction> optional<HandlerFunction>
getHandler(std::string const& command) getHandler(string const& command)
{ {
if (!handlerMap_.contains(command)) if (!handlerMap_.contains(command))
return {}; return {};
@@ -248,7 +181,7 @@ public:
} }
bool bool
isClioOnly(std::string const& command) isClioOnly(string const& command)
{ {
return handlerMap_.contains(command) && handlerMap_[command].isClioOnly; return handlerMap_.contains(command) && handlerMap_[command].isClioOnly;
} }
@@ -282,7 +215,7 @@ static HandlerTable handlerTable{
{"transaction_entry", &doTransactionEntry, {}}, {"transaction_entry", &doTransactionEntry, {}},
{"random", &doRandom, {}}}; {"random", &doRandom, {}}};
static std::unordered_set<std::string> forwardCommands{ static unordered_set<string> forwardCommands{
"submit", "submit",
"submit_multisigned", "submit_multisigned",
"fee", "fee",
@@ -294,13 +227,13 @@ static std::unordered_set<std::string> forwardCommands{
"channel_verify"}; "channel_verify"};
bool bool
validHandler(std::string const& method) validHandler(string const& method)
{ {
return handlerTable.contains(method) || forwardCommands.contains(method); return handlerTable.contains(method) || forwardCommands.contains(method);
} }
bool bool
isClioOnly(std::string const& method) isClioOnly(string const& method)
{ {
return handlerTable.isClioOnly(method); return handlerTable.isClioOnly(method);
} }
@@ -313,27 +246,28 @@ shouldSuppressValidatedFlag(RPC::Context const& context)
} }
Status Status
getLimit(RPC::Context const& context, std::uint32_t& limit) getLimit(RPC::Context const& context, uint32_t& limit)
{ {
if (!handlerTable.getHandler(context.method)) if (!handlerTable.getHandler(context.method))
return Status{Error::rpcUNKNOWN_COMMAND}; return Status{RippledError::rpcUNKNOWN_COMMAND};
if (!handlerTable.getLimitRange(context.method)) if (!handlerTable.getLimitRange(context.method))
return Status{Error::rpcINVALID_PARAMS, "rpcDoesNotRequireLimit"}; return Status{
RippledError::rpcINVALID_PARAMS, "rpcDoesNotRequireLimit"};
auto [lo, def, hi] = *handlerTable.getLimitRange(context.method); auto [lo, def, hi] = *handlerTable.getLimitRange(context.method);
if (context.params.contains(JS(limit))) if (context.params.contains(JS(limit)))
{ {
std::string errMsg = "Invalid field 'limit', not unsigned integer."; string errMsg = "Invalid field 'limit', not unsigned integer.";
if (!context.params.at(JS(limit)).is_int64()) if (!context.params.at(JS(limit)).is_int64())
return Status{Error::rpcINVALID_PARAMS, errMsg}; return Status{RippledError::rpcINVALID_PARAMS, errMsg};
int input = context.params.at(JS(limit)).as_int64(); int input = context.params.at(JS(limit)).as_int64();
if (input <= 0) if (input <= 0)
return Status{Error::rpcINVALID_PARAMS, errMsg}; return Status{RippledError::rpcINVALID_PARAMS, errMsg};
limit = std::clamp(static_cast<std::uint32_t>(input), lo, hi); limit = clamp(static_cast<uint32_t>(input), lo, hi);
} }
else else
{ {
@@ -378,7 +312,7 @@ buildResponse(Context const& ctx)
ctx.counters.rpcForwarded(ctx.method); ctx.counters.rpcForwarded(ctx.method);
if (!res) if (!res)
return Status{Error::rpcFAILED_TO_FORWARD}; return Status{RippledError::rpcFAILED_TO_FORWARD};
if (res->contains("result") && res->at("result").is_object()) if (res->contains("result") && res->at("result").is_object())
return res->at("result").as_object(); return res->at("result").as_object();
@@ -393,13 +327,13 @@ buildResponse(Context const& ctx)
{ {
BOOST_LOG_TRIVIAL(error) BOOST_LOG_TRIVIAL(error)
<< __func__ << " Database is too busy. Rejecting request"; << __func__ << " Database is too busy. Rejecting request";
return Status{Error::rpcTOO_BUSY}; return Status{RippledError::rpcTOO_BUSY};
} }
auto method = handlerTable.getHandler(ctx.method); auto method = handlerTable.getHandler(ctx.method);
if (!method) if (!method)
return Status{Error::rpcUNKNOWN_COMMAND}; return Status{RippledError::rpcUNKNOWN_COMMAND};
try try
{ {
@@ -411,7 +345,7 @@ buildResponse(Context const& ctx)
<< ctx.tag() << __func__ << " finish executing rpc `" << ctx.method << ctx.tag() << __func__ << " finish executing rpc `" << ctx.method
<< '`'; << '`';
if (auto object = std::get_if<boost::json::object>(&v); if (auto object = get_if<boost::json::object>(&v);
object && not shouldSuppressValidatedFlag(ctx)) object && not shouldSuppressValidatedFlag(ctx))
{ {
(*object)["validated"] = true; (*object)["validated"] = true;
@@ -421,22 +355,22 @@ buildResponse(Context const& ctx)
} }
catch (InvalidParamsError const& err) catch (InvalidParamsError const& err)
{ {
return Status{Error::rpcINVALID_PARAMS, err.what()}; return Status{RippledError::rpcINVALID_PARAMS, err.what()};
} }
catch (AccountNotFoundError const& err) catch (AccountNotFoundError const& err)
{ {
return Status{Error::rpcACT_NOT_FOUND, err.what()}; return Status{RippledError::rpcACT_NOT_FOUND, err.what()};
} }
catch (Backend::DatabaseTimeout const& t) catch (Backend::DatabaseTimeout const& t)
{ {
BOOST_LOG_TRIVIAL(error) << __func__ << " Database timeout"; BOOST_LOG_TRIVIAL(error) << __func__ << " Database timeout";
return Status{Error::rpcTOO_BUSY}; return Status{RippledError::rpcTOO_BUSY};
} }
catch (std::exception const& err) catch (exception const& err)
{ {
BOOST_LOG_TRIVIAL(error) BOOST_LOG_TRIVIAL(error)
<< ctx.tag() << __func__ << " caught exception : " << err.what(); << ctx.tag() << __func__ << " caught exception : " << err.what();
return Status{Error::rpcINTERNAL}; return Status{RippledError::rpcINTERNAL};
} }
} }

View File

@@ -1,7 +1,7 @@
#ifndef REPORTING_RPC_H_INCLUDED #ifndef REPORTING_RPC_H_INCLUDED
#define REPORTING_RPC_H_INCLUDED #define REPORTING_RPC_H_INCLUDED
#include <ripple/protocol/ErrorCodes.h> #include <rpc/Errors.h>
#include <boost/asio/spawn.hpp> #include <boost/asio/spawn.hpp>
#include <boost/json.hpp> #include <boost/json.hpp>
@@ -65,7 +65,6 @@ struct Context : public util::Taggable
Counters& counters_, Counters& counters_,
std::string const& clientIp_); std::string const& clientIp_);
}; };
using Error = ripple::error_code_i;
struct AccountCursor struct AccountCursor
{ {
@@ -85,108 +84,8 @@ struct AccountCursor
} }
}; };
struct Status
{
Error error = Error::rpcSUCCESS;
std::string strCode = "";
std::string message = "";
Status(){};
Status(Error error_) : error(error_){};
// HACK. Some rippled handlers explicitly specify errors.
// This means that we have to be able to duplicate this
// functionality.
Status(std::string const& message_)
: error(ripple::rpcUNKNOWN), message(message_)
{
}
Status(Error error_, std::string message_)
: error(error_), message(message_)
{
}
Status(Error error_, std::string strCode_, std::string message_)
: error(error_), strCode(strCode_), message(message_)
{
}
/** Returns true if the Status is *not* OK. */
operator bool() const
{
return error != Error::rpcSUCCESS;
}
};
static Status OK;
using Result = std::variant<Status, boost::json::object>; using Result = std::variant<Status, boost::json::object>;
class InvalidParamsError : public std::exception
{
std::string msg;
public:
InvalidParamsError(std::string const& msg) : msg(msg)
{
}
const char*
what() const throw() override
{
return msg.c_str();
}
};
class AccountNotFoundError : public std::exception
{
std::string account;
public:
AccountNotFoundError(std::string const& acct) : account(acct)
{
}
const char*
what() const throw() override
{
return account.c_str();
}
};
enum warning_code {
warnUNKNOWN = -1,
warnRPC_CLIO = 2001,
warnRPC_OUTDATED = 2002,
warnRPC_RATE_LIMIT = 2003
};
struct WarningInfo
{
constexpr WarningInfo() : code(warnUNKNOWN), message("unknown warning")
{
}
constexpr WarningInfo(warning_code code_, char const* message_)
: code(code_), message(message_)
{
}
warning_code code;
std::string_view const message;
};
WarningInfo const&
get_warning_info(warning_code code);
boost::json::object
make_warning(warning_code code);
boost::json::object
make_error(Status const& status);
boost::json::object
make_error(Error err);
std::optional<Context> std::optional<Context>
make_WsContext( make_WsContext(
boost::asio::yield_context& yc, boost::asio::yield_context& yc,

View File

@@ -187,10 +187,10 @@ getHexMarker(boost::json::object const& request, ripple::uint256& marker)
if (request.contains(JS(marker))) if (request.contains(JS(marker)))
{ {
if (!request.at(JS(marker)).is_string()) if (!request.at(JS(marker)).is_string())
return Status{Error::rpcINVALID_PARAMS, "markerNotString"}; return Status{RippledError::rpcINVALID_PARAMS, "markerNotString"};
if (!marker.parseHex(request.at(JS(marker)).as_string().c_str())) if (!marker.parseHex(request.at(JS(marker)).as_string().c_str()))
return Status{Error::rpcINVALID_PARAMS, "malformedMarker"}; return Status{RippledError::rpcINVALID_PARAMS, "malformedMarker"};
} }
return {}; return {};
@@ -207,14 +207,14 @@ getAccount(
{ {
if (required) if (required)
return Status{ return Status{
Error::rpcINVALID_PARAMS, field.to_string() + "Missing"}; RippledError::rpcINVALID_PARAMS, field.to_string() + "Missing"};
return {}; return {};
} }
if (!request.at(field).is_string()) if (!request.at(field).is_string())
return Status{ return Status{
Error::rpcINVALID_PARAMS, field.to_string() + "NotString"}; RippledError::rpcINVALID_PARAMS, field.to_string() + "NotString"};
if (auto a = accountFromStringStrict(request.at(field).as_string().c_str()); if (auto a = accountFromStringStrict(request.at(field).as_string().c_str());
a) a)
@@ -223,7 +223,8 @@ getAccount(
return {}; return {};
} }
return Status{Error::rpcACT_MALFORMED, field.to_string() + "Malformed"}; return Status{
RippledError::rpcACT_MALFORMED, field.to_string() + "Malformed"};
} }
Status Status
@@ -240,7 +241,7 @@ getOptionalAccount(
if (!request.at(field).is_string()) if (!request.at(field).is_string())
return Status{ return Status{
Error::rpcINVALID_PARAMS, field.to_string() + "NotString"}; RippledError::rpcINVALID_PARAMS, field.to_string() + "NotString"};
if (auto a = accountFromStringStrict(request.at(field).as_string().c_str()); if (auto a = accountFromStringStrict(request.at(field).as_string().c_str());
a) a)
@@ -249,7 +250,8 @@ getOptionalAccount(
return {}; return {};
} }
return Status{Error::rpcINVALID_PARAMS, field.to_string() + "Malformed"}; return Status{
RippledError::rpcINVALID_PARAMS, field.to_string() + "Malformed"};
} }
Status Status
@@ -286,13 +288,13 @@ Status
getChannelId(boost::json::object const& request, ripple::uint256& channelId) getChannelId(boost::json::object const& request, ripple::uint256& channelId)
{ {
if (!request.contains(JS(channel_id))) if (!request.contains(JS(channel_id)))
return Status{Error::rpcINVALID_PARAMS, "missingChannelID"}; return Status{RippledError::rpcINVALID_PARAMS, "missingChannelID"};
if (!request.at(JS(channel_id)).is_string()) if (!request.at(JS(channel_id)).is_string())
return Status{Error::rpcINVALID_PARAMS, "channelIDNotString"}; return Status{RippledError::rpcINVALID_PARAMS, "channelIDNotString"};
if (!channelId.parseHex(request.at(JS(channel_id)).as_string().c_str())) if (!channelId.parseHex(request.at(JS(channel_id)).as_string().c_str()))
return Status{Error::rpcCHANNEL_MALFORMED, "malformedChannelID"}; return Status{RippledError::rpcCHANNEL_MALFORMED, "malformedChannelID"};
return {}; return {};
} }
@@ -554,16 +556,18 @@ ledgerInfoFromRequest(Context const& ctx)
if (!hashValue.is_null()) if (!hashValue.is_null())
{ {
if (!hashValue.is_string()) if (!hashValue.is_string())
return Status{Error::rpcINVALID_PARAMS, "ledgerHashNotString"}; return Status{
RippledError::rpcINVALID_PARAMS, "ledgerHashNotString"};
ripple::uint256 ledgerHash; ripple::uint256 ledgerHash;
if (!ledgerHash.parseHex(hashValue.as_string().c_str())) if (!ledgerHash.parseHex(hashValue.as_string().c_str()))
return Status{Error::rpcINVALID_PARAMS, "ledgerHashMalformed"}; return Status{
RippledError::rpcINVALID_PARAMS, "ledgerHashMalformed"};
auto lgrInfo = ctx.backend->fetchLedgerByHash(ledgerHash, ctx.yield); auto lgrInfo = ctx.backend->fetchLedgerByHash(ledgerHash, ctx.yield);
if (!lgrInfo || lgrInfo->seq > ctx.range.maxSequence) if (!lgrInfo || lgrInfo->seq > ctx.range.maxSequence)
return Status{Error::rpcLGR_NOT_FOUND, "ledgerNotFound"}; return Status{RippledError::rpcLGR_NOT_FOUND, "ledgerNotFound"};
return *lgrInfo; return *lgrInfo;
} }
@@ -592,13 +596,13 @@ ledgerInfoFromRequest(Context const& ctx)
} }
if (!ledgerSequence) if (!ledgerSequence)
return Status{Error::rpcINVALID_PARAMS, "ledgerIndexMalformed"}; return Status{RippledError::rpcINVALID_PARAMS, "ledgerIndexMalformed"};
auto lgrInfo = auto lgrInfo =
ctx.backend->fetchLedgerBySequence(*ledgerSequence, ctx.yield); ctx.backend->fetchLedgerBySequence(*ledgerSequence, ctx.yield);
if (!lgrInfo || lgrInfo->seq > ctx.range.maxSequence) if (!lgrInfo || lgrInfo->seq > ctx.range.maxSequence)
return Status{Error::rpcLGR_NOT_FOUND, "ledgerNotFound"}; return Status{RippledError::rpcLGR_NOT_FOUND, "ledgerNotFound"};
return *lgrInfo; return *lgrInfo;
} }
@@ -651,7 +655,7 @@ traverseOwnedNodes(
{ {
if (!backend.fetchLedgerObject( if (!backend.fetchLedgerObject(
ripple::keylet::account(accountID).key, sequence, yield)) ripple::keylet::account(accountID).key, sequence, yield))
return Status{Error::rpcACT_NOT_FOUND}; return Status{RippledError::rpcACT_NOT_FOUND};
auto parsedCursor = auto parsedCursor =
parseAccountCursor(backend, sequence, jsonCursor, accountID, yield); parseAccountCursor(backend, sequence, jsonCursor, accountID, yield);
@@ -891,12 +895,12 @@ keypairFromRequst(boost::json::object const& request)
} }
if (count == 0) if (count == 0)
return Status{Error::rpcINVALID_PARAMS, "missing field secret"}; return Status{RippledError::rpcINVALID_PARAMS, "missing field secret"};
if (count > 1) if (count > 1)
{ {
return Status{ return Status{
Error::rpcINVALID_PARAMS, RippledError::rpcINVALID_PARAMS,
"Exactly one of the following must be specified: " "Exactly one of the following must be specified: "
" passphrase, secret, seed, or seed_hex"}; " passphrase, secret, seed, or seed_hex"};
} }
@@ -907,17 +911,18 @@ keypairFromRequst(boost::json::object const& request)
if (has_key_type) if (has_key_type)
{ {
if (!request.at("key_type").is_string()) if (!request.at("key_type").is_string())
return Status{Error::rpcINVALID_PARAMS, "keyTypeNotString"}; return Status{RippledError::rpcINVALID_PARAMS, "keyTypeNotString"};
std::string key_type = request.at("key_type").as_string().c_str(); std::string key_type = request.at("key_type").as_string().c_str();
keyType = ripple::keyTypeFromString(key_type); keyType = ripple::keyTypeFromString(key_type);
if (!keyType) if (!keyType)
return Status{Error::rpcINVALID_PARAMS, "invalidFieldKeyType"}; return Status{
RippledError::rpcINVALID_PARAMS, "invalidFieldKeyType"};
if (secretType == "secret") if (secretType == "secret")
return Status{ return Status{
Error::rpcINVALID_PARAMS, RippledError::rpcINVALID_PARAMS,
"The secret field is not allowed if key_type is used."}; "The secret field is not allowed if key_type is used."};
} }
@@ -935,7 +940,7 @@ keypairFromRequst(boost::json::object const& request)
if (keyType.value_or(ripple::KeyType::ed25519) != if (keyType.value_or(ripple::KeyType::ed25519) !=
ripple::KeyType::ed25519) ripple::KeyType::ed25519)
return Status{ return Status{
Error::rpcINVALID_PARAMS, RippledError::rpcINVALID_PARAMS,
"Specified seed is for an Ed25519 wallet."}; "Specified seed is for an Ed25519 wallet."};
keyType = ripple::KeyType::ed25519; keyType = ripple::KeyType::ed25519;
@@ -951,7 +956,8 @@ keypairFromRequst(boost::json::object const& request)
{ {
if (!request.at(secretType).is_string()) if (!request.at(secretType).is_string())
return Status{ return Status{
Error::rpcINVALID_PARAMS, "secret value must be string"}; RippledError::rpcINVALID_PARAMS,
"secret value must be string"};
std::string key = request.at(secretType).as_string().c_str(); std::string key = request.at(secretType).as_string().c_str();
@@ -970,7 +976,7 @@ keypairFromRequst(boost::json::object const& request)
{ {
if (!request.at("secret").is_string()) if (!request.at("secret").is_string())
return Status{ return Status{
Error::rpcINVALID_PARAMS, RippledError::rpcINVALID_PARAMS,
"field secret should be a string"}; "field secret should be a string"};
std::string secret = request.at("secret").as_string().c_str(); std::string secret = request.at("secret").as_string().c_str();
@@ -980,12 +986,14 @@ keypairFromRequst(boost::json::object const& request)
if (!seed) if (!seed)
return Status{ return Status{
Error::rpcBAD_SEED, "Bad Seed: invalid field message secretType"}; RippledError::rpcBAD_SEED,
"Bad Seed: invalid field message secretType"};
if (keyType != ripple::KeyType::secp256k1 && if (keyType != ripple::KeyType::secp256k1 &&
keyType != ripple::KeyType::ed25519) keyType != ripple::KeyType::ed25519)
return Status{ return Status{
Error::rpcINVALID_PARAMS, "keypairForSignature: invalid key type"}; RippledError::rpcINVALID_PARAMS,
"keypairForSignature: invalid key type"};
return generateKeyPair(*keyType, *seed); return generateKeyPair(*keyType, *seed);
} }
@@ -1343,54 +1351,63 @@ std::variant<Status, ripple::Book>
parseBook(boost::json::object const& request) parseBook(boost::json::object const& request)
{ {
if (!request.contains("taker_pays")) if (!request.contains("taker_pays"))
return Status{Error::rpcBAD_MARKET, "missingTakerPays"}; return Status{RippledError::rpcBAD_MARKET, "missingTakerPays"};
if (!request.contains("taker_gets")) if (!request.contains("taker_gets"))
return Status{Error::rpcINVALID_PARAMS, "missingTakerGets"}; return Status{RippledError::rpcINVALID_PARAMS, "missingTakerGets"};
if (!request.at("taker_pays").is_object()) if (!request.at("taker_pays").is_object())
return Status{Error::rpcINVALID_PARAMS, "takerPaysNotObject"}; return Status{RippledError::rpcINVALID_PARAMS, "takerPaysNotObject"};
if (!request.at("taker_gets").is_object()) if (!request.at("taker_gets").is_object())
return Status{Error::rpcINVALID_PARAMS, "takerGetsNotObject"}; return Status{RippledError::rpcINVALID_PARAMS, "takerGetsNotObject"};
auto taker_pays = request.at("taker_pays").as_object(); auto taker_pays = request.at("taker_pays").as_object();
if (!taker_pays.contains("currency")) if (!taker_pays.contains("currency"))
return Status{Error::rpcINVALID_PARAMS, "missingTakerPaysCurrency"}; return Status{
RippledError::rpcINVALID_PARAMS, "missingTakerPaysCurrency"};
if (!taker_pays.at("currency").is_string()) if (!taker_pays.at("currency").is_string())
return Status{Error::rpcINVALID_PARAMS, "takerPaysCurrencyNotString"}; return Status{
RippledError::rpcINVALID_PARAMS, "takerPaysCurrencyNotString"};
auto taker_gets = request.at("taker_gets").as_object(); auto taker_gets = request.at("taker_gets").as_object();
if (!taker_gets.contains("currency")) if (!taker_gets.contains("currency"))
return Status{Error::rpcINVALID_PARAMS, "missingTakerGetsCurrency"}; return Status{
RippledError::rpcINVALID_PARAMS, "missingTakerGetsCurrency"};
if (!taker_gets.at("currency").is_string()) if (!taker_gets.at("currency").is_string())
return Status{Error::rpcINVALID_PARAMS, "takerGetsCurrencyNotString"}; return Status{
RippledError::rpcINVALID_PARAMS, "takerGetsCurrencyNotString"};
ripple::Currency pay_currency; ripple::Currency pay_currency;
if (!ripple::to_currency( if (!ripple::to_currency(
pay_currency, taker_pays.at("currency").as_string().c_str())) pay_currency, taker_pays.at("currency").as_string().c_str()))
return Status{Error::rpcSRC_CUR_MALFORMED, "badTakerPaysCurrency"}; return Status{
RippledError::rpcSRC_CUR_MALFORMED, "badTakerPaysCurrency"};
ripple::Currency get_currency; ripple::Currency get_currency;
if (!ripple::to_currency( if (!ripple::to_currency(
get_currency, taker_gets["currency"].as_string().c_str())) get_currency, taker_gets["currency"].as_string().c_str()))
return Status{Error::rpcDST_AMT_MALFORMED, "badTakerGetsCurrency"}; return Status{
RippledError::rpcDST_AMT_MALFORMED, "badTakerGetsCurrency"};
ripple::AccountID pay_issuer; ripple::AccountID pay_issuer;
if (taker_pays.contains("issuer")) if (taker_pays.contains("issuer"))
{ {
if (!taker_pays.at("issuer").is_string()) if (!taker_pays.at("issuer").is_string())
return Status{Error::rpcINVALID_PARAMS, "takerPaysIssuerNotString"}; return Status{
RippledError::rpcINVALID_PARAMS, "takerPaysIssuerNotString"};
if (!ripple::to_issuer( if (!ripple::to_issuer(
pay_issuer, taker_pays.at("issuer").as_string().c_str())) pay_issuer, taker_pays.at("issuer").as_string().c_str()))
return Status{Error::rpcSRC_ISR_MALFORMED, "badTakerPaysIssuer"}; return Status{
RippledError::rpcSRC_ISR_MALFORMED, "badTakerPaysIssuer"};
if (pay_issuer == ripple::noAccount()) if (pay_issuer == ripple::noAccount())
return Status{ return Status{
Error::rpcSRC_ISR_MALFORMED, "badTakerPaysIssuerAccountOne"}; RippledError::rpcSRC_ISR_MALFORMED,
"badTakerPaysIssuerAccountOne"};
} }
else else
{ {
@@ -1399,18 +1416,19 @@ parseBook(boost::json::object const& request)
if (isXRP(pay_currency) && !isXRP(pay_issuer)) if (isXRP(pay_currency) && !isXRP(pay_issuer))
return Status{ return Status{
Error::rpcSRC_ISR_MALFORMED, RippledError::rpcSRC_ISR_MALFORMED,
"Unneeded field 'taker_pays.issuer' for XRP currency " "Unneeded field 'taker_pays.issuer' for XRP currency "
"specification."}; "specification."};
if (!isXRP(pay_currency) && isXRP(pay_issuer)) if (!isXRP(pay_currency) && isXRP(pay_issuer))
return Status{ return Status{
Error::rpcSRC_ISR_MALFORMED, RippledError::rpcSRC_ISR_MALFORMED,
"Invalid field 'taker_pays.issuer', expected non-XRP " "Invalid field 'taker_pays.issuer', expected non-XRP "
"issuer."}; "issuer."};
if ((!isXRP(pay_currency)) && (!taker_pays.contains("issuer"))) if ((!isXRP(pay_currency)) && (!taker_pays.contains("issuer")))
return Status{Error::rpcSRC_ISR_MALFORMED, "Missing non-XRP issuer."}; return Status{
RippledError::rpcSRC_ISR_MALFORMED, "Missing non-XRP issuer."};
ripple::AccountID get_issuer; ripple::AccountID get_issuer;
@@ -1418,17 +1436,18 @@ parseBook(boost::json::object const& request)
{ {
if (!taker_gets["issuer"].is_string()) if (!taker_gets["issuer"].is_string())
return Status{ return Status{
Error::rpcINVALID_PARAMS, "taker_gets.issuer should be string"}; RippledError::rpcINVALID_PARAMS,
"taker_gets.issuer should be string"};
if (!ripple::to_issuer( if (!ripple::to_issuer(
get_issuer, taker_gets.at("issuer").as_string().c_str())) get_issuer, taker_gets.at("issuer").as_string().c_str()))
return Status{ return Status{
Error::rpcDST_ISR_MALFORMED, RippledError::rpcDST_ISR_MALFORMED,
"Invalid field 'taker_gets.issuer', bad issuer."}; "Invalid field 'taker_gets.issuer', bad issuer."};
if (get_issuer == ripple::noAccount()) if (get_issuer == ripple::noAccount())
return Status{ return Status{
Error::rpcDST_ISR_MALFORMED, RippledError::rpcDST_ISR_MALFORMED,
"Invalid field 'taker_gets.issuer', bad issuer account " "Invalid field 'taker_gets.issuer', bad issuer account "
"one."}; "one."};
} }
@@ -1439,17 +1458,17 @@ parseBook(boost::json::object const& request)
if (ripple::isXRP(get_currency) && !ripple::isXRP(get_issuer)) if (ripple::isXRP(get_currency) && !ripple::isXRP(get_issuer))
return Status{ return Status{
Error::rpcDST_ISR_MALFORMED, RippledError::rpcDST_ISR_MALFORMED,
"Unneeded field 'taker_gets.issuer' for XRP currency " "Unneeded field 'taker_gets.issuer' for XRP currency "
"specification."}; "specification."};
if (!ripple::isXRP(get_currency) && ripple::isXRP(get_issuer)) if (!ripple::isXRP(get_currency) && ripple::isXRP(get_issuer))
return Status{ return Status{
Error::rpcDST_ISR_MALFORMED, RippledError::rpcDST_ISR_MALFORMED,
"Invalid field 'taker_gets.issuer', expected non-XRP issuer."}; "Invalid field 'taker_gets.issuer', expected non-XRP issuer."};
if (pay_currency == get_currency && pay_issuer == get_issuer) if (pay_currency == get_currency && pay_issuer == get_issuer)
return Status{Error::rpcBAD_MARKET, "badMarket"}; return Status{RippledError::rpcBAD_MARKET, "badMarket"};
return ripple::Book{{pay_currency, pay_issuer}, {get_currency, get_issuer}}; return ripple::Book{{pay_currency, pay_issuer}, {get_currency, get_issuer}};
} }
@@ -1459,12 +1478,12 @@ parseTaker(boost::json::value const& taker)
{ {
std::optional<ripple::AccountID> takerID = {}; std::optional<ripple::AccountID> takerID = {};
if (!taker.is_string()) if (!taker.is_string())
return {Status{Error::rpcINVALID_PARAMS, "takerNotString"}}; return {Status{RippledError::rpcINVALID_PARAMS, "takerNotString"}};
takerID = accountFromStringStrict(taker.as_string().c_str()); takerID = accountFromStringStrict(taker.as_string().c_str());
if (!takerID) if (!takerID)
return Status{Error::rpcBAD_ISSUER, "invalidTakerAccount"}; return Status{RippledError::rpcBAD_ISSUER, "invalidTakerAccount"};
return *takerID; return *takerID;
} }
bool bool
@@ -1486,14 +1505,14 @@ std::variant<ripple::uint256, Status>
getNFTID(boost::json::object const& request) getNFTID(boost::json::object const& request)
{ {
if (!request.contains(JS(nft_id))) if (!request.contains(JS(nft_id)))
return Status{Error::rpcINVALID_PARAMS, "missingTokenID"}; return Status{RippledError::rpcINVALID_PARAMS, "missingTokenID"};
if (!request.at(JS(nft_id)).is_string()) if (!request.at(JS(nft_id)).is_string())
return Status{Error::rpcINVALID_PARAMS, "tokenIDNotString"}; return Status{RippledError::rpcINVALID_PARAMS, "tokenIDNotString"};
ripple::uint256 tokenid; ripple::uint256 tokenid;
if (!tokenid.parseHex(request.at(JS(nft_id)).as_string().c_str())) if (!tokenid.parseHex(request.at(JS(nft_id)).as_string().c_str()))
return Status{Error::rpcINVALID_PARAMS, "malformedTokenID"}; return Status{RippledError::rpcINVALID_PARAMS, "malformedTokenID"};
return tokenid; return tokenid;
} }
@@ -1521,7 +1540,7 @@ traverseTransactions(
if (request.contains(JS(marker))) if (request.contains(JS(marker)))
{ {
if (!request.at(JS(marker)).is_object()) if (!request.at(JS(marker)).is_object())
return Status{Error::rpcINVALID_PARAMS, "invalidMarker"}; return Status{RippledError::rpcINVALID_PARAMS, "invalidMarker"};
auto const& obj = request.at(JS(marker)).as_object(); auto const& obj = request.at(JS(marker)).as_object();
std::optional<std::uint32_t> transactionIndex = {}; std::optional<std::uint32_t> transactionIndex = {};
@@ -1529,7 +1548,7 @@ traverseTransactions(
{ {
if (!obj.at(JS(seq)).is_int64()) if (!obj.at(JS(seq)).is_int64())
return Status{ return Status{
Error::rpcINVALID_PARAMS, "transactionIndexNotInt"}; RippledError::rpcINVALID_PARAMS, "transactionIndexNotInt"};
transactionIndex = transactionIndex =
boost::json::value_to<std::uint32_t>(obj.at(JS(seq))); boost::json::value_to<std::uint32_t>(obj.at(JS(seq)));
@@ -1539,14 +1558,16 @@ traverseTransactions(
if (obj.contains(JS(ledger))) if (obj.contains(JS(ledger)))
{ {
if (!obj.at(JS(ledger)).is_int64()) if (!obj.at(JS(ledger)).is_int64())
return Status{Error::rpcINVALID_PARAMS, "ledgerIndexNotInt"}; return Status{
RippledError::rpcINVALID_PARAMS, "ledgerIndexNotInt"};
ledgerIndex = ledgerIndex =
boost::json::value_to<std::uint32_t>(obj.at(JS(ledger))); boost::json::value_to<std::uint32_t>(obj.at(JS(ledger)));
} }
if (!transactionIndex || !ledgerIndex) if (!transactionIndex || !ledgerIndex)
return Status{Error::rpcINVALID_PARAMS, "missingLedgerOrSeq"}; return Status{
RippledError::rpcINVALID_PARAMS, "missingLedgerOrSeq"};
cursor = {*ledgerIndex, *transactionIndex}; cursor = {*ledgerIndex, *transactionIndex};
} }
@@ -1557,14 +1578,16 @@ traverseTransactions(
auto& min = request.at(JS(ledger_index_min)); auto& min = request.at(JS(ledger_index_min));
if (!min.is_int64()) if (!min.is_int64())
return Status{Error::rpcINVALID_PARAMS, "ledgerSeqMinNotNumber"}; return Status{
RippledError::rpcINVALID_PARAMS, "ledgerSeqMinNotNumber"};
if (min.as_int64() != -1) if (min.as_int64() != -1)
{ {
if (context.range.maxSequence < min.as_int64() || if (context.range.maxSequence < min.as_int64() ||
context.range.minSequence > min.as_int64()) context.range.minSequence > min.as_int64())
return Status{ return Status{
Error::rpcLGR_IDX_MALFORMED, "ledgerSeqMinOutOfRange"}; RippledError::rpcLGR_IDX_MALFORMED,
"ledgerSeqMinOutOfRange"};
else else
minIndex = boost::json::value_to<std::uint32_t>(min); minIndex = boost::json::value_to<std::uint32_t>(min);
} }
@@ -1579,20 +1602,22 @@ traverseTransactions(
auto& max = request.at(JS(ledger_index_max)); auto& max = request.at(JS(ledger_index_max));
if (!max.is_int64()) if (!max.is_int64())
return Status{Error::rpcINVALID_PARAMS, "ledgerSeqMaxNotNumber"}; return Status{
RippledError::rpcINVALID_PARAMS, "ledgerSeqMaxNotNumber"};
if (max.as_int64() != -1) if (max.as_int64() != -1)
{ {
if (context.range.maxSequence < max.as_int64() || if (context.range.maxSequence < max.as_int64() ||
context.range.minSequence > max.as_int64()) context.range.minSequence > max.as_int64())
return Status{ return Status{
Error::rpcLGR_IDX_MALFORMED, "ledgerSeqMaxOutOfRange"}; RippledError::rpcLGR_IDX_MALFORMED,
"ledgerSeqMaxOutOfRange"};
else else
maxIndex = boost::json::value_to<std::uint32_t>(max); maxIndex = boost::json::value_to<std::uint32_t>(max);
} }
if (minIndex > maxIndex) if (minIndex > maxIndex)
return Status{Error::rpcINVALID_PARAMS, "invalidIndex"}; return Status{RippledError::rpcINVALID_PARAMS, "invalidIndex"};
if (!forward && !cursor) if (!forward && !cursor)
cursor = {maxIndex, INT32_MAX}; cursor = {maxIndex, INT32_MAX};
@@ -1603,7 +1628,8 @@ traverseTransactions(
if (request.contains(JS(ledger_index_max)) || if (request.contains(JS(ledger_index_max)) ||
request.contains(JS(ledger_index_min))) request.contains(JS(ledger_index_min)))
return Status{ return Status{
Error::rpcINVALID_PARAMS, "containsLedgerSpecifierAndRange"}; RippledError::rpcINVALID_PARAMS,
"containsLedgerSpecifierAndRange"};
auto v = ledgerInfoFromRequest(context); auto v = ledgerInfoFromRequest(context);
if (auto status = std::get_if<Status>(&v); status) if (auto status = std::get_if<Status>(&v); status)

View File

@@ -62,7 +62,7 @@ doAccountChannels(Context const& context)
ripple::keylet::account(accountID).key, lgrInfo.seq, context.yield); ripple::keylet::account(accountID).key, lgrInfo.seq, context.yield);
if (!rawAcct) if (!rawAcct)
return Status{Error::rpcACT_NOT_FOUND, "accountNotFound"}; return Status{RippledError::rpcACT_NOT_FOUND, "accountNotFound"};
ripple::AccountID destAccount; ripple::AccountID destAccount;
if (auto const status = if (auto const status =
@@ -78,7 +78,7 @@ doAccountChannels(Context const& context)
if (request.contains(JS(marker))) if (request.contains(JS(marker)))
{ {
if (!request.at(JS(marker)).is_string()) if (!request.at(JS(marker)).is_string())
return Status{Error::rpcINVALID_PARAMS, "markerNotString"}; return Status{RippledError::rpcINVALID_PARAMS, "markerNotString"};
marker = request.at(JS(marker)).as_string().c_str(); marker = request.at(JS(marker)).as_string().c_str();
} }

View File

@@ -32,7 +32,7 @@ doAccountCurrencies(Context const& context)
ripple::keylet::account(accountID).key, lgrInfo.seq, context.yield); ripple::keylet::account(accountID).key, lgrInfo.seq, context.yield);
if (!rawAcct) if (!rawAcct)
return Status{Error::rpcACT_NOT_FOUND, "accountNotFound"}; return Status{RippledError::rpcACT_NOT_FOUND, "accountNotFound"};
std::set<std::string> send, receive; std::set<std::string> send, receive;
auto const addToResponse = [&](ripple::SLE&& sle) { auto const addToResponse = [&](ripple::SLE&& sle) {

View File

@@ -34,7 +34,7 @@ doAccountInfo(Context const& context)
else if (request.contains(JS(ident))) else if (request.contains(JS(ident)))
strIdent = request.at(JS(ident)).as_string().c_str(); strIdent = request.at(JS(ident)).as_string().c_str();
else else
return Status{Error::rpcACT_MALFORMED}; return Status{RippledError::rpcACT_MALFORMED};
// We only need to fetch the ledger header because the ledger hash is // We only need to fetch the ledger header because the ledger hash is
// supposed to be included in the response. The ledger sequence is specified // supposed to be included in the response. The ledger sequence is specified
@@ -48,7 +48,7 @@ doAccountInfo(Context const& context)
// Get info on account. // Get info on account.
auto accountID = accountFromStringStrict(strIdent); auto accountID = accountFromStringStrict(strIdent);
if (!accountID) if (!accountID)
return Status{Error::rpcACT_MALFORMED}; return Status{RippledError::rpcACT_MALFORMED};
assert(accountID.has_value()); assert(accountID.has_value());
@@ -59,14 +59,14 @@ doAccountInfo(Context const& context)
if (!dbResponse) if (!dbResponse)
{ {
return Status{Error::rpcACT_NOT_FOUND}; return Status{RippledError::rpcACT_NOT_FOUND};
} }
ripple::STLedgerEntry sle{ ripple::STLedgerEntry sle{
ripple::SerialIter{dbResponse->data(), dbResponse->size()}, key.key}; ripple::SerialIter{dbResponse->data(), dbResponse->size()}, key.key};
if (!key.check(sle)) if (!key.check(sle))
return Status{Error::rpcDB_DESERIALIZATION}; return Status{RippledError::rpcDB_DESERIALIZATION};
// if (!binary) // if (!binary)
// response[JS(account_data)] = getJson(sle); // response[JS(account_data)] = getJson(sle);
@@ -97,7 +97,7 @@ doAccountInfo(Context const& context)
ripple::SerialIter{signers->data(), signers->size()}, ripple::SerialIter{signers->data(), signers->size()},
signersKey.key}; signersKey.key};
if (!signersKey.check(sleSigners)) if (!signersKey.check(sleSigners))
return Status{Error::rpcDB_DESERIALIZATION}; return Status{RippledError::rpcDB_DESERIALIZATION};
signerList.push_back(toJson(sleSigners)); signerList.push_back(toJson(sleSigners));
} }

View File

@@ -107,7 +107,7 @@ doAccountLines(Context const& context)
ripple::keylet::account(accountID).key, lgrInfo.seq, context.yield); ripple::keylet::account(accountID).key, lgrInfo.seq, context.yield);
if (!rawAcct) if (!rawAcct)
return Status{Error::rpcACT_NOT_FOUND, "accountNotFound"}; return Status{RippledError::rpcACT_NOT_FOUND, "accountNotFound"};
std::optional<ripple::AccountID> peerAccount; std::optional<ripple::AccountID> peerAccount;
if (auto const status = getOptionalAccount(request, peerAccount, JS(peer)); if (auto const status = getOptionalAccount(request, peerAccount, JS(peer));
@@ -122,7 +122,7 @@ doAccountLines(Context const& context)
if (request.contains(JS(marker))) if (request.contains(JS(marker)))
{ {
if (not request.at(JS(marker)).is_string()) if (not request.at(JS(marker)).is_string())
return Status{Error::rpcINVALID_PARAMS, "markerNotString"}; return Status{RippledError::rpcINVALID_PARAMS, "markerNotString"};
marker = request.at(JS(marker)).as_string().c_str(); marker = request.at(JS(marker)).as_string().c_str();
} }
@@ -131,7 +131,8 @@ doAccountLines(Context const& context)
if (request.contains(JS(ignore_default))) if (request.contains(JS(ignore_default)))
{ {
if (not request.at(JS(ignore_default)).is_bool()) if (not request.at(JS(ignore_default)).is_bool())
return Status{Error::rpcINVALID_PARAMS, "ignoreDefaultNotBool"}; return Status{
RippledError::rpcINVALID_PARAMS, "ignoreDefaultNotBool"};
ignoreDefault = request.at(JS(ignore_default)).as_bool(); ignoreDefault = request.at(JS(ignore_default)).as_bool();
} }

View File

@@ -45,13 +45,13 @@ doAccountNFTs(Context const& context)
return status; return status;
if (!accountID) if (!accountID)
return Status{Error::rpcINVALID_PARAMS, "malformedAccount"}; return Status{RippledError::rpcINVALID_PARAMS, "malformedAccount"};
auto rawAcct = context.backend->fetchLedgerObject( auto rawAcct = context.backend->fetchLedgerObject(
ripple::keylet::account(accountID).key, lgrInfo.seq, context.yield); ripple::keylet::account(accountID).key, lgrInfo.seq, context.yield);
if (!rawAcct) if (!rawAcct)
return Status{Error::rpcACT_NOT_FOUND, "accountNotFound"}; return Status{RippledError::rpcACT_NOT_FOUND, "accountNotFound"};
std::uint32_t limit; std::uint32_t limit;
if (auto const status = getLimit(context, limit); status) if (auto const status = getLimit(context, limit); status)
@@ -157,7 +157,7 @@ doAccountObjects(Context const& context)
if (request.contains("marker")) if (request.contains("marker"))
{ {
if (!request.at("marker").is_string()) if (!request.at("marker").is_string())
return Status{Error::rpcINVALID_PARAMS, "markerNotString"}; return Status{RippledError::rpcINVALID_PARAMS, "markerNotString"};
marker = request.at("marker").as_string().c_str(); marker = request.at("marker").as_string().c_str();
} }
@@ -166,11 +166,11 @@ doAccountObjects(Context const& context)
if (request.contains(JS(type))) if (request.contains(JS(type)))
{ {
if (!request.at(JS(type)).is_string()) if (!request.at(JS(type)).is_string())
return Status{Error::rpcINVALID_PARAMS, "typeNotString"}; return Status{RippledError::rpcINVALID_PARAMS, "typeNotString"};
std::string typeAsString = request.at(JS(type)).as_string().c_str(); std::string typeAsString = request.at(JS(type)).as_string().c_str();
if (types.find(typeAsString) == types.end()) if (types.find(typeAsString) == types.end())
return Status{Error::rpcINVALID_PARAMS, "typeInvalid"}; return Status{RippledError::rpcINVALID_PARAMS, "typeInvalid"};
objectType = types[typeAsString]; objectType = types[typeAsString];
} }

View File

@@ -84,7 +84,7 @@ doAccountOffers(Context const& context)
ripple::keylet::account(accountID).key, lgrInfo.seq, context.yield); ripple::keylet::account(accountID).key, lgrInfo.seq, context.yield);
if (!rawAcct) if (!rawAcct)
return Status{Error::rpcACT_NOT_FOUND, "accountNotFound"}; return Status{RippledError::rpcACT_NOT_FOUND, "accountNotFound"};
std::uint32_t limit; std::uint32_t limit;
if (auto const status = getLimit(context, limit); status) if (auto const status = getLimit(context, limit); status)
@@ -94,7 +94,7 @@ doAccountOffers(Context const& context)
if (request.contains(JS(marker))) if (request.contains(JS(marker)))
{ {
if (!request.at(JS(marker)).is_string()) if (!request.at(JS(marker)).is_string())
return Status{Error::rpcINVALID_PARAMS, "markerNotString"}; return Status{RippledError::rpcINVALID_PARAMS, "markerNotString"};
marker = request.at(JS(marker)).as_string().c_str(); marker = request.at(JS(marker)).as_string().c_str();
} }

View File

@@ -30,10 +30,10 @@ doBookOffers(Context const& context)
if (request.contains("book")) if (request.contains("book"))
{ {
if (!request.at("book").is_string()) if (!request.at("book").is_string())
return Status{Error::rpcINVALID_PARAMS, "bookNotString"}; return Status{RippledError::rpcINVALID_PARAMS, "bookNotString"};
if (!bookBase.parseHex(request.at("book").as_string().c_str())) if (!bookBase.parseHex(request.at("book").as_string().c_str()))
return Status{Error::rpcINVALID_PARAMS, "invalidBook"}; return Status{RippledError::rpcINVALID_PARAMS, "invalidBook"};
} }
else else
{ {

View File

@@ -28,13 +28,14 @@ doChannelAuthorize(Context const& context)
boost::json::object response = {}; boost::json::object response = {};
if (!request.contains(JS(amount))) if (!request.contains(JS(amount)))
return Status{Error::rpcINVALID_PARAMS, "missingAmount"}; return Status{RippledError::rpcINVALID_PARAMS, "missingAmount"};
if (!request.at(JS(amount)).is_string()) if (!request.at(JS(amount)).is_string())
return Status{Error::rpcINVALID_PARAMS, "amountNotString"}; return Status{RippledError::rpcINVALID_PARAMS, "amountNotString"};
if (!request.contains(JS(key_type)) && !request.contains(JS(secret))) if (!request.contains(JS(key_type)) && !request.contains(JS(secret)))
return Status{Error::rpcINVALID_PARAMS, "missingKeyTypeOrSecret"}; return Status{
RippledError::rpcINVALID_PARAMS, "missingKeyTypeOrSecret"};
auto v = keypairFromRequst(request); auto v = keypairFromRequst(request);
if (auto status = std::get_if<Status>(&v)) if (auto status = std::get_if<Status>(&v))
@@ -51,7 +52,8 @@ doChannelAuthorize(Context const& context)
ripple::to_uint64(request.at(JS(amount)).as_string().c_str()); ripple::to_uint64(request.at(JS(amount)).as_string().c_str());
if (!optDrops) if (!optDrops)
return Status{Error::rpcCHANNEL_AMT_MALFORMED, "couldNotParseAmount"}; return Status{
RippledError::rpcCHANNEL_AMT_MALFORMED, "couldNotParseAmount"};
std::uint64_t drops = *optDrops; std::uint64_t drops = *optDrops;
@@ -66,7 +68,7 @@ doChannelAuthorize(Context const& context)
} }
catch (std::exception&) catch (std::exception&)
{ {
return Status{Error::rpcINTERNAL}; return Status{RippledError::rpcINTERNAL};
} }
return response; return response;

View File

@@ -17,22 +17,22 @@ doChannelVerify(Context const& context)
boost::json::object response = {}; boost::json::object response = {};
if (!request.contains(JS(amount))) if (!request.contains(JS(amount)))
return Status{Error::rpcINVALID_PARAMS, "missingAmount"}; return Status{RippledError::rpcINVALID_PARAMS, "missingAmount"};
if (!request.at(JS(amount)).is_string()) if (!request.at(JS(amount)).is_string())
return Status{Error::rpcINVALID_PARAMS, "amountNotString"}; return Status{RippledError::rpcINVALID_PARAMS, "amountNotString"};
if (!request.contains(JS(signature))) if (!request.contains(JS(signature)))
return Status{Error::rpcINVALID_PARAMS, "missingSignature"}; return Status{RippledError::rpcINVALID_PARAMS, "missingSignature"};
if (!request.at(JS(signature)).is_string()) if (!request.at(JS(signature)).is_string())
return Status{Error::rpcINVALID_PARAMS, "signatureNotString"}; return Status{RippledError::rpcINVALID_PARAMS, "signatureNotString"};
if (!request.contains(JS(public_key))) if (!request.contains(JS(public_key)))
return Status{Error::rpcINVALID_PARAMS, "missingPublicKey"}; return Status{RippledError::rpcINVALID_PARAMS, "missingPublicKey"};
if (!request.at(JS(public_key)).is_string()) if (!request.at(JS(public_key)).is_string())
return Status{Error::rpcINVALID_PARAMS, "publicKeyNotString"}; return Status{RippledError::rpcINVALID_PARAMS, "publicKeyNotString"};
std::optional<ripple::PublicKey> pk; std::optional<ripple::PublicKey> pk;
{ {
@@ -45,12 +45,14 @@ doChannelVerify(Context const& context)
{ {
auto pkHex = ripple::strUnHex(strPk); auto pkHex = ripple::strUnHex(strPk);
if (!pkHex) if (!pkHex)
return Status{Error::rpcPUBLIC_MALFORMED, "malformedPublicKey"}; return Status{
RippledError::rpcPUBLIC_MALFORMED, "malformedPublicKey"};
auto const pkType = auto const pkType =
ripple::publicKeyType(ripple::makeSlice(*pkHex)); ripple::publicKeyType(ripple::makeSlice(*pkHex));
if (!pkType) if (!pkType)
return Status{Error::rpcPUBLIC_MALFORMED, "invalidKeyType"}; return Status{
RippledError::rpcPUBLIC_MALFORMED, "invalidKeyType"};
pk.emplace(ripple::makeSlice(*pkHex)); pk.emplace(ripple::makeSlice(*pkHex));
} }
@@ -64,14 +66,15 @@ doChannelVerify(Context const& context)
ripple::to_uint64(request.at(JS(amount)).as_string().c_str()); ripple::to_uint64(request.at(JS(amount)).as_string().c_str());
if (!optDrops) if (!optDrops)
return Status{Error::rpcCHANNEL_AMT_MALFORMED, "couldNotParseAmount"}; return Status{
RippledError::rpcCHANNEL_AMT_MALFORMED, "couldNotParseAmount"};
std::uint64_t drops = *optDrops; std::uint64_t drops = *optDrops;
auto sig = ripple::strUnHex(request.at(JS(signature)).as_string().c_str()); auto sig = ripple::strUnHex(request.at(JS(signature)).as_string().c_str());
if (!sig || !sig->size()) if (!sig || !sig->size())
return Status{Error::rpcINVALID_PARAMS, "invalidSignature"}; return Status{RippledError::rpcINVALID_PARAMS, "invalidSignature"};
ripple::Serializer msg; ripple::Serializer msg;
ripple::serializePayChanAuthorization( ripple::serializePayChanAuthorization(

View File

@@ -187,7 +187,7 @@ doGatewayBalances(Context const& context)
}; };
if (not std::all_of( if (not std::all_of(
hotWallets.begin(), hotWallets.end(), containsHotWallet)) hotWallets.begin(), hotWallets.end(), containsHotWallet))
return Status{Error::rpcINVALID_PARAMS, "invalidHotWallet"}; return Status{RippledError::rpcINVALID_PARAMS, "invalidHotWallet"};
if (auto balances = toJson(hotBalances); balances.size()) if (auto balances = toJson(hotBalances); balances.size())
response[JS(balances)] = balances; response[JS(balances)] = balances;

View File

@@ -13,7 +13,7 @@ doLedger(Context const& context)
if (params.contains(JS(binary))) if (params.contains(JS(binary)))
{ {
if (!params.at(JS(binary)).is_bool()) if (!params.at(JS(binary)).is_bool())
return Status{Error::rpcINVALID_PARAMS, "binaryFlagNotBool"}; return Status{RippledError::rpcINVALID_PARAMS, "binaryFlagNotBool"};
binary = params.at(JS(binary)).as_bool(); binary = params.at(JS(binary)).as_bool();
} }
@@ -22,7 +22,8 @@ doLedger(Context const& context)
if (params.contains(JS(transactions))) if (params.contains(JS(transactions)))
{ {
if (!params.at(JS(transactions)).is_bool()) if (!params.at(JS(transactions)).is_bool())
return Status{Error::rpcINVALID_PARAMS, "transactionsFlagNotBool"}; return Status{
RippledError::rpcINVALID_PARAMS, "transactionsFlagNotBool"};
transactions = params.at(JS(transactions)).as_bool(); transactions = params.at(JS(transactions)).as_bool();
} }
@@ -31,7 +32,7 @@ doLedger(Context const& context)
if (params.contains(JS(expand))) if (params.contains(JS(expand)))
{ {
if (!params.at(JS(expand)).is_bool()) if (!params.at(JS(expand)).is_bool())
return Status{Error::rpcINVALID_PARAMS, "expandFlagNotBool"}; return Status{RippledError::rpcINVALID_PARAMS, "expandFlagNotBool"};
expand = params.at(JS(expand)).as_bool(); expand = params.at(JS(expand)).as_bool();
} }
@@ -40,7 +41,7 @@ doLedger(Context const& context)
if (params.contains("diff")) if (params.contains("diff"))
{ {
if (!params.at("diff").is_bool()) if (!params.at("diff").is_bool())
return Status{Error::rpcINVALID_PARAMS, "diffFlagNotBool"}; return Status{RippledError::rpcINVALID_PARAMS, "diffFlagNotBool"};
diff = params.at("diff").as_bool(); diff = params.at("diff").as_bool();
} }

View File

@@ -41,7 +41,7 @@ doLedgerData(Context const& context)
if (request.contains("out_of_order")) if (request.contains("out_of_order"))
{ {
if (!request.at("out_of_order").is_bool()) if (!request.at("out_of_order").is_bool())
return Status{Error::rpcINVALID_PARAMS, "binaryFlagNotBool"}; return Status{RippledError::rpcINVALID_PARAMS, "binaryFlagNotBool"};
outOfOrder = request.at("out_of_order").as_bool(); outOfOrder = request.at("out_of_order").as_bool();
} }
@@ -55,11 +55,13 @@ doLedgerData(Context const& context)
{ {
if (!request.at(JS(marker)).is_int64()) if (!request.at(JS(marker)).is_int64())
return Status{ return Status{
Error::rpcINVALID_PARAMS, "markerNotStringOrInt"}; RippledError::rpcINVALID_PARAMS,
"markerNotStringOrInt"};
diffMarker = value_to<uint32_t>(request.at(JS(marker))); diffMarker = value_to<uint32_t>(request.at(JS(marker)));
} }
else else
return Status{Error::rpcINVALID_PARAMS, "markerNotString"}; return Status{
RippledError::rpcINVALID_PARAMS, "markerNotString"};
} }
else else
{ {
@@ -67,7 +69,8 @@ doLedgerData(Context const& context)
marker = ripple::uint256{}; marker = ripple::uint256{};
if (!marker->parseHex(request.at(JS(marker)).as_string().c_str())) if (!marker->parseHex(request.at(JS(marker)).as_string().c_str()))
return Status{Error::rpcINVALID_PARAMS, "markerMalformed"}; return Status{
RippledError::rpcINVALID_PARAMS, "markerMalformed"};
} }
} }
@@ -115,7 +118,8 @@ doLedgerData(Context const& context)
if (!outOfOrder && if (!outOfOrder &&
!context.backend->fetchLedgerObject( !context.backend->fetchLedgerObject(
*marker, lgrInfo.seq, context.yield)) *marker, lgrInfo.seq, context.yield))
return Status{Error::rpcINVALID_PARAMS, "markerDoesNotExist"}; return Status{
RippledError::rpcINVALID_PARAMS, "markerDoesNotExist"};
} }
response[JS(ledger_hash)] = ripple::strHex(lgrInfo.hash); response[JS(ledger_hash)] = ripple::strHex(lgrInfo.hash);

View File

@@ -32,31 +32,32 @@ doLedgerEntry(Context const& context)
if (request.contains(JS(index))) if (request.contains(JS(index)))
{ {
if (!request.at(JS(index)).is_string()) if (!request.at(JS(index)).is_string())
return Status{Error::rpcINVALID_PARAMS, "indexNotString"}; return Status{RippledError::rpcINVALID_PARAMS, "indexNotString"};
if (!key.parseHex(request.at(JS(index)).as_string().c_str())) if (!key.parseHex(request.at(JS(index)).as_string().c_str()))
return Status{Error::rpcINVALID_PARAMS, "malformedIndex"}; return Status{RippledError::rpcINVALID_PARAMS, "malformedIndex"};
} }
else if (request.contains(JS(account_root))) else if (request.contains(JS(account_root)))
{ {
if (!request.at(JS(account_root)).is_string()) if (!request.at(JS(account_root)).is_string())
return Status{Error::rpcINVALID_PARAMS, "account_rootNotString"}; return Status{
RippledError::rpcINVALID_PARAMS, "account_rootNotString"};
auto const account = ripple::parseBase58<ripple::AccountID>( auto const account = ripple::parseBase58<ripple::AccountID>(
request.at(JS(account_root)).as_string().c_str()); request.at(JS(account_root)).as_string().c_str());
if (!account || account->isZero()) if (!account || account->isZero())
return Status{Error::rpcINVALID_PARAMS, "malformedAddress"}; return Status{RippledError::rpcINVALID_PARAMS, "malformedAddress"};
else else
key = ripple::keylet::account(*account).key; key = ripple::keylet::account(*account).key;
} }
else if (request.contains(JS(check))) else if (request.contains(JS(check)))
{ {
if (!request.at(JS(check)).is_string()) if (!request.at(JS(check)).is_string())
return Status{Error::rpcINVALID_PARAMS, "checkNotString"}; return Status{RippledError::rpcINVALID_PARAMS, "checkNotString"};
if (!key.parseHex(request.at(JS(check)).as_string().c_str())) if (!key.parseHex(request.at(JS(check)).as_string().c_str()))
{ {
return Status{Error::rpcINVALID_PARAMS, "checkMalformed"}; return Status{RippledError::rpcINVALID_PARAMS, "checkMalformed"};
} }
} }
else if (request.contains(JS(deposit_preauth))) else if (request.contains(JS(deposit_preauth)))
@@ -68,7 +69,8 @@ doLedgerEntry(Context const& context)
request.at(JS(deposit_preauth)).as_string().c_str())) request.at(JS(deposit_preauth)).as_string().c_str()))
{ {
return Status{ return Status{
Error::rpcINVALID_PARAMS, "deposit_preauthMalformed"}; RippledError::rpcINVALID_PARAMS,
"deposit_preauthMalformed"};
} }
} }
else if ( else if (
@@ -78,7 +80,7 @@ doLedgerEntry(Context const& context)
.at(JS(owner)) .at(JS(owner))
.is_string()) .is_string())
{ {
return Status{Error::rpcINVALID_PARAMS, "malformedOwner"}; return Status{RippledError::rpcINVALID_PARAMS, "malformedOwner"};
} }
else if ( else if (
!request.at(JS(deposit_preauth)) !request.at(JS(deposit_preauth))
@@ -89,7 +91,8 @@ doLedgerEntry(Context const& context)
.at(JS(authorized)) .at(JS(authorized))
.is_string()) .is_string())
{ {
return Status{Error::rpcINVALID_PARAMS, "authorizedNotString"}; return Status{
RippledError::rpcINVALID_PARAMS, "authorizedNotString"};
} }
else else
{ {
@@ -103,9 +106,11 @@ doLedgerEntry(Context const& context)
deposit_preauth.at(JS(authorized)).as_string().c_str()); deposit_preauth.at(JS(authorized)).as_string().c_str());
if (!owner) if (!owner)
return Status{Error::rpcINVALID_PARAMS, "malformedOwner"}; return Status{
RippledError::rpcINVALID_PARAMS, "malformedOwner"};
else if (!authorized) else if (!authorized)
return Status{Error::rpcINVALID_PARAMS, "malformedAuthorized"}; return Status{
RippledError::rpcINVALID_PARAMS, "malformedAuthorized"};
else else
key = ripple::keylet::depositPreauth(*owner, *authorized).key; key = ripple::keylet::depositPreauth(*owner, *authorized).key;
} }
@@ -115,18 +120,20 @@ doLedgerEntry(Context const& context)
if (!request.at(JS(directory)).is_object()) if (!request.at(JS(directory)).is_object())
{ {
if (!request.at(JS(directory)).is_string()) if (!request.at(JS(directory)).is_string())
return Status{Error::rpcINVALID_PARAMS, "directoryNotString"}; return Status{
RippledError::rpcINVALID_PARAMS, "directoryNotString"};
if (!key.parseHex(request.at(JS(directory)).as_string().c_str())) if (!key.parseHex(request.at(JS(directory)).as_string().c_str()))
{ {
return Status{Error::rpcINVALID_PARAMS, "malformedDirectory"}; return Status{
RippledError::rpcINVALID_PARAMS, "malformedDirectory"};
} }
} }
else if ( else if (
request.at(JS(directory)).as_object().contains(JS(sub_index)) && request.at(JS(directory)).as_object().contains(JS(sub_index)) &&
!request.at(JS(directory)).as_object().at(JS(sub_index)).is_int64()) !request.at(JS(directory)).as_object().at(JS(sub_index)).is_int64())
{ {
return Status{Error::rpcINVALID_PARAMS, "sub_indexNotInt"}; return Status{RippledError::rpcINVALID_PARAMS, "sub_indexNotInt"};
} }
else else
{ {
@@ -144,13 +151,14 @@ doLedgerEntry(Context const& context)
{ {
// May not specify both dir_root and owner. // May not specify both dir_root and owner.
return Status{ return Status{
Error::rpcINVALID_PARAMS, RippledError::rpcINVALID_PARAMS,
"mayNotSpecifyBothDirRootAndOwner"}; "mayNotSpecifyBothDirRootAndOwner"};
} }
else if (!uDirRoot.parseHex( else if (!uDirRoot.parseHex(
directory.at(JS(dir_root)).as_string().c_str())) directory.at(JS(dir_root)).as_string().c_str()))
{ {
return Status{Error::rpcINVALID_PARAMS, "malformedDirRoot"}; return Status{
RippledError::rpcINVALID_PARAMS, "malformedDirRoot"};
} }
else else
{ {
@@ -164,7 +172,8 @@ doLedgerEntry(Context const& context)
if (!ownerID) if (!ownerID)
{ {
return Status{Error::rpcINVALID_PARAMS, "malformedAddress"}; return Status{
RippledError::rpcINVALID_PARAMS, "malformedAddress"};
} }
else else
{ {
@@ -176,7 +185,7 @@ doLedgerEntry(Context const& context)
else else
{ {
return Status{ return Status{
Error::rpcINVALID_PARAMS, "missingOwnerOrDirRoot"}; RippledError::rpcINVALID_PARAMS, "missingOwnerOrDirRoot"};
} }
} }
} }
@@ -185,19 +194,20 @@ doLedgerEntry(Context const& context)
if (!request.at(JS(escrow)).is_object()) if (!request.at(JS(escrow)).is_object())
{ {
if (!key.parseHex(request.at(JS(escrow)).as_string().c_str())) if (!key.parseHex(request.at(JS(escrow)).as_string().c_str()))
return Status{Error::rpcINVALID_PARAMS, "malformedEscrow"}; return Status{
RippledError::rpcINVALID_PARAMS, "malformedEscrow"};
} }
else if ( else if (
!request.at(JS(escrow)).as_object().contains(JS(owner)) || !request.at(JS(escrow)).as_object().contains(JS(owner)) ||
!request.at(JS(escrow)).as_object().at(JS(owner)).is_string()) !request.at(JS(escrow)).as_object().at(JS(owner)).is_string())
{ {
return Status{Error::rpcINVALID_PARAMS, "malformedOwner"}; return Status{RippledError::rpcINVALID_PARAMS, "malformedOwner"};
} }
else if ( else if (
!request.at(JS(escrow)).as_object().contains(JS(seq)) || !request.at(JS(escrow)).as_object().contains(JS(seq)) ||
!request.at(JS(escrow)).as_object().at(JS(seq)).is_int64()) !request.at(JS(escrow)).as_object().at(JS(seq)).is_int64())
{ {
return Status{Error::rpcINVALID_PARAMS, "malformedSeq"}; return Status{RippledError::rpcINVALID_PARAMS, "malformedSeq"};
} }
else else
{ {
@@ -209,7 +219,8 @@ doLedgerEntry(Context const& context)
.c_str()); .c_str());
if (!id) if (!id)
return Status{Error::rpcINVALID_PARAMS, "malformedAddress"}; return Status{
RippledError::rpcINVALID_PARAMS, "malformedAddress"};
else else
{ {
std::uint32_t seq = std::uint32_t seq =
@@ -223,19 +234,20 @@ doLedgerEntry(Context const& context)
if (!request.at(JS(offer)).is_object()) if (!request.at(JS(offer)).is_object())
{ {
if (!key.parseHex(request.at(JS(offer)).as_string().c_str())) if (!key.parseHex(request.at(JS(offer)).as_string().c_str()))
return Status{Error::rpcINVALID_PARAMS, "malformedOffer"}; return Status{
RippledError::rpcINVALID_PARAMS, "malformedOffer"};
} }
else if ( else if (
!request.at(JS(offer)).as_object().contains(JS(account)) || !request.at(JS(offer)).as_object().contains(JS(account)) ||
!request.at(JS(offer)).as_object().at(JS(account)).is_string()) !request.at(JS(offer)).as_object().at(JS(account)).is_string())
{ {
return Status{Error::rpcINVALID_PARAMS, "malformedAccount"}; return Status{RippledError::rpcINVALID_PARAMS, "malformedAccount"};
} }
else if ( else if (
!request.at(JS(offer)).as_object().contains(JS(seq)) || !request.at(JS(offer)).as_object().contains(JS(seq)) ||
!request.at(JS(offer)).as_object().at(JS(seq)).is_int64()) !request.at(JS(offer)).as_object().at(JS(seq)).is_int64())
{ {
return Status{Error::rpcINVALID_PARAMS, "malformedSeq"}; return Status{RippledError::rpcINVALID_PARAMS, "malformedSeq"};
} }
else else
{ {
@@ -244,7 +256,8 @@ doLedgerEntry(Context const& context)
offer.at(JS(account)).as_string().c_str()); offer.at(JS(account)).as_string().c_str());
if (!id) if (!id)
return Status{Error::rpcINVALID_PARAMS, "malformedAddress"}; return Status{
RippledError::rpcINVALID_PARAMS, "malformedAddress"};
else else
{ {
std::uint32_t seq = std::uint32_t seq =
@@ -256,15 +269,18 @@ doLedgerEntry(Context const& context)
else if (request.contains(JS(payment_channel))) else if (request.contains(JS(payment_channel)))
{ {
if (!request.at(JS(payment_channel)).is_string()) if (!request.at(JS(payment_channel)).is_string())
return Status{Error::rpcINVALID_PARAMS, "paymentChannelNotString"}; return Status{
RippledError::rpcINVALID_PARAMS, "paymentChannelNotString"};
if (!key.parseHex(request.at(JS(payment_channel)).as_string().c_str())) if (!key.parseHex(request.at(JS(payment_channel)).as_string().c_str()))
return Status{Error::rpcINVALID_PARAMS, "malformedPaymentChannel"}; return Status{
RippledError::rpcINVALID_PARAMS, "malformedPaymentChannel"};
} }
else if (request.contains(JS(ripple_state))) else if (request.contains(JS(ripple_state)))
{ {
if (!request.at(JS(ripple_state)).is_object()) if (!request.at(JS(ripple_state)).is_object())
return Status{Error::rpcINVALID_PARAMS, "rippleStateNotObject"}; return Status{
RippledError::rpcINVALID_PARAMS, "rippleStateNotObject"};
ripple::Currency currency; ripple::Currency currency;
boost::json::object const& state = boost::json::object const& state =
@@ -273,7 +289,7 @@ doLedgerEntry(Context const& context)
if (!state.contains(JS(currency)) || if (!state.contains(JS(currency)) ||
!state.at(JS(currency)).is_string()) !state.at(JS(currency)).is_string())
{ {
return Status{Error::rpcINVALID_PARAMS, "malformedCurrency"}; return Status{RippledError::rpcINVALID_PARAMS, "currencyNotString"};
} }
if (!state.contains(JS(accounts)) || if (!state.contains(JS(accounts)) ||
@@ -284,7 +300,7 @@ doLedgerEntry(Context const& context)
(state.at(JS(accounts)).as_array().at(0).as_string() == (state.at(JS(accounts)).as_array().at(0).as_string() ==
state.at(JS(accounts)).as_array().at(1).as_string())) state.at(JS(accounts)).as_array().at(1).as_string()))
{ {
return Status{Error::rpcINVALID_PARAMS, "malformedAccounts"}; return Status{RippledError::rpcINVALID_PARAMS, "malformedAccounts"};
} }
auto const id1 = ripple::parseBase58<ripple::AccountID>( auto const id1 = ripple::parseBase58<ripple::AccountID>(
@@ -293,11 +309,13 @@ doLedgerEntry(Context const& context)
state.at(JS(accounts)).as_array().at(1).as_string().c_str()); state.at(JS(accounts)).as_array().at(1).as_string().c_str());
if (!id1 || !id2) if (!id1 || !id2)
return Status{Error::rpcINVALID_PARAMS, "malformedAddresses"}; return Status{
RippledError::rpcINVALID_PARAMS, "malformedAddresses"};
else if (!ripple::to_currency( else if (!ripple::to_currency(
currency, state.at(JS(currency)).as_string().c_str())) currency, state.at(JS(currency)).as_string().c_str()))
return Status{Error::rpcINVALID_PARAMS, "malformedCurrency"}; return Status{
ClioError::rpcMALFORMED_CURRENCY, "malformedCurrency"};
key = ripple::keylet::line(*id1, *id2, currency).key; key = ripple::keylet::line(*id1, *id2, currency).key;
} }
@@ -306,22 +324,25 @@ doLedgerEntry(Context const& context)
if (!request.at(JS(ticket)).is_object()) if (!request.at(JS(ticket)).is_object())
{ {
if (!request.at(JS(ticket)).is_string()) if (!request.at(JS(ticket)).is_string())
return Status{Error::rpcINVALID_PARAMS, "ticketNotString"}; return Status{
RippledError::rpcINVALID_PARAMS, "ticketNotString"};
if (!key.parseHex(request.at(JS(ticket)).as_string().c_str())) if (!key.parseHex(request.at(JS(ticket)).as_string().c_str()))
return Status{Error::rpcINVALID_PARAMS, "malformedTicket"}; return Status{
RippledError::rpcINVALID_PARAMS, "malformedTicket"};
} }
else if ( else if (
!request.at(JS(ticket)).as_object().contains(JS(owner)) || !request.at(JS(ticket)).as_object().contains(JS(owner)) ||
!request.at(JS(ticket)).as_object().at(JS(owner)).is_string()) !request.at(JS(ticket)).as_object().at(JS(owner)).is_string())
{ {
return Status{Error::rpcINVALID_PARAMS, "malformedOwner"}; return Status{RippledError::rpcINVALID_PARAMS, "malformedOwner"};
} }
else if ( else if (
!request.at(JS(ticket)).as_object().contains(JS(ticket_seq)) || !request.at(JS(ticket)).as_object().contains(JS(ticket_seq)) ||
!request.at(JS(ticket)).as_object().at(JS(ticket_seq)).is_int64()) !request.at(JS(ticket)).as_object().at(JS(ticket_seq)).is_int64())
{ {
return Status{Error::rpcINVALID_PARAMS, "malformedTicketSeq"}; return Status{
RippledError::rpcINVALID_PARAMS, "malformedTicketSeq"};
} }
else else
{ {
@@ -333,7 +354,8 @@ doLedgerEntry(Context const& context)
.c_str()); .c_str());
if (!id) if (!id)
return Status{Error::rpcINVALID_PARAMS, "malformedOwner"}; return Status{
RippledError::rpcINVALID_PARAMS, "malformedOwner"};
else else
{ {
std::uint32_t seq = request.at(JS(offer)) std::uint32_t seq = request.at(JS(offer))
@@ -347,7 +369,7 @@ doLedgerEntry(Context const& context)
} }
else else
{ {
return Status{Error::rpcINVALID_PARAMS, "unknownOption"}; return Status{RippledError::rpcINVALID_PARAMS, "unknownOption"};
} }
auto dbResponse = auto dbResponse =

View File

@@ -12,7 +12,7 @@ doLedgerRange(Context const& context)
auto range = context.backend->fetchLedgerRange(); auto range = context.backend->fetchLedgerRange();
if (!range) if (!range)
{ {
return Status{Error::rpcNOT_READY, "rangeNotFound"}; return Status{RippledError::rpcNOT_READY, "rangeNotFound"};
} }
else else
{ {

View File

@@ -45,7 +45,8 @@ getURI(Backend::NFT const& dbResponse, Context const& context)
if (!blob || blob->size() == 0) if (!blob || blob->size() == 0)
return Status{ return Status{
Error::rpcINTERNAL, "Cannot find NFTokenPage for this NFT"}; RippledError::rpcINTERNAL,
"Cannot find NFTokenPage for this NFT"};
sle = ripple::STLedgerEntry( sle = ripple::STLedgerEntry(
ripple::SerialIter{blob->data(), blob->size()}, nextKey); ripple::SerialIter{blob->data(), blob->size()}, nextKey);
@@ -57,7 +58,7 @@ getURI(Backend::NFT const& dbResponse, Context const& context)
if (!sle) if (!sle)
return Status{ return Status{
Error::rpcINTERNAL, "Cannot find NFTokenPage for this NFT"}; RippledError::rpcINTERNAL, "Cannot find NFTokenPage for this NFT"};
auto const nfts = sle->getFieldArray(ripple::sfNFTokens); auto const nfts = sle->getFieldArray(ripple::sfNFTokens);
auto const nft = std::find_if( auto const nft = std::find_if(
@@ -70,7 +71,7 @@ getURI(Backend::NFT const& dbResponse, Context const& context)
if (nft == nfts.end()) if (nft == nfts.end())
return Status{ return Status{
Error::rpcINTERNAL, "Cannot find NFTokenPage for this NFT"}; RippledError::rpcINTERNAL, "Cannot find NFTokenPage for this NFT"};
ripple::Blob const uriField = nft->getFieldVL(ripple::sfURI); ripple::Blob const uriField = nft->getFieldVL(ripple::sfURI);
@@ -102,7 +103,7 @@ doNFTInfo(Context const& context)
std::optional<Backend::NFT> dbResponse = std::optional<Backend::NFT> dbResponse =
context.backend->fetchNFT(tokenID, lgrInfo.seq, context.yield); context.backend->fetchNFT(tokenID, lgrInfo.seq, context.yield);
if (!dbResponse) if (!dbResponse)
return Status{Error::rpcOBJECT_NOT_FOUND, "NFT not found"}; return Status{RippledError::rpcOBJECT_NOT_FOUND, "NFT not found"};
response["nft_id"] = ripple::strHex(dbResponse->tokenID); response["nft_id"] = ripple::strHex(dbResponse->tokenID);
response["ledger_index"] = dbResponse->ledgerSequence; response["ledger_index"] = dbResponse->ledgerSequence;

View File

@@ -56,7 +56,7 @@ enumerateNFTOffers(
// TODO: just check for existence without pulling // TODO: just check for existence without pulling
if (!context.backend->fetchLedgerObject( if (!context.backend->fetchLedgerObject(
directory.key, lgrInfo.seq, context.yield)) directory.key, lgrInfo.seq, context.yield))
return Status{Error::rpcOBJECT_NOT_FOUND, "notFound"}; return Status{RippledError::rpcOBJECT_NOT_FOUND, "notFound"};
std::uint32_t limit; std::uint32_t limit;
if (auto const status = getLimit(context, limit); status) if (auto const status = getLimit(context, limit); status)
@@ -78,10 +78,10 @@ enumerateNFTOffers(
auto const& marker(request.at(JS(marker))); auto const& marker(request.at(JS(marker)));
if (!marker.is_string()) if (!marker.is_string())
return Status{Error::rpcINVALID_PARAMS, "markerNotString"}; return Status{RippledError::rpcINVALID_PARAMS, "markerNotString"};
if (!cursor.parseHex(marker.as_string().c_str())) if (!cursor.parseHex(marker.as_string().c_str()))
return Status{Error::rpcINVALID_PARAMS, "malformedCursor"}; return Status{RippledError::rpcINVALID_PARAMS, "malformedCursor"};
auto const sle = auto const sle =
read(ripple::keylet::nftoffer(cursor), lgrInfo, context); read(ripple::keylet::nftoffer(cursor), lgrInfo, context);
@@ -90,7 +90,7 @@ enumerateNFTOffers(
sle->getFieldU16(ripple::sfLedgerEntryType) != sle->getFieldU16(ripple::sfLedgerEntryType) !=
ripple::ltNFTOKEN_OFFER || ripple::ltNFTOKEN_OFFER ||
tokenid != sle->getFieldH256(ripple::sfNFTokenID)) tokenid != sle->getFieldH256(ripple::sfNFTokenID))
return Status{Error::rpcINVALID_PARAMS}; return Status{RippledError::rpcINVALID_PARAMS};
startHint = sle->getFieldU64(ripple::sfNFTokenOfferNode); startHint = sle->getFieldU64(ripple::sfNFTokenOfferNode);
jsonOffers.push_back(json::value_from(*sle)); jsonOffers.push_back(json::value_from(*sle));

View File

@@ -31,7 +31,8 @@ doNoRippleCheck(Context const& context)
if (role == "gateway") if (role == "gateway")
roleGateway = true; roleGateway = true;
else if (role != "user") else if (role != "user")
return Status{Error::rpcINVALID_PARAMS, "role field is invalid"}; return Status{
RippledError::rpcINVALID_PARAMS, "role field is invalid"};
} }
std::uint32_t limit = 300; std::uint32_t limit = 300;

View File

@@ -16,7 +16,7 @@ doServerInfo(Context const& context)
if (!range) if (!range)
{ {
return Status{ return Status{
Error::rpcNOT_READY, RippledError::rpcNOT_READY,
"emptyDatabase", "emptyDatabase",
"The server has no data in the database"}; "The server has no data in the database"};
} }
@@ -27,7 +27,7 @@ doServerInfo(Context const& context)
auto fees = context.backend->fetchFees(lgrInfo->seq, context.yield); auto fees = context.backend->fetchFees(lgrInfo->seq, context.yield);
if (!lgrInfo || !fees) if (!lgrInfo || !fees)
return Status{Error::rpcINTERNAL}; return Status{RippledError::rpcINTERNAL};
auto age = std::chrono::duration_cast<std::chrono::seconds>( auto age = std::chrono::duration_cast<std::chrono::seconds>(
std::chrono::system_clock::now().time_since_epoch()) std::chrono::system_clock::now().time_since_epoch())

View File

@@ -22,10 +22,10 @@ validateStreams(boost::json::object const& request)
auto const& stream : streams) auto const& stream : streams)
{ {
if (!stream.is_string()) if (!stream.is_string())
return Status{Error::rpcINVALID_PARAMS, "streamNotString"}; return Status{RippledError::rpcINVALID_PARAMS, "streamNotString"};
if (!validCommonStreams.contains(stream.as_string().c_str())) if (!validCommonStreams.contains(stream.as_string().c_str()))
return Status{Error::rpcSTREAM_MALFORMED}; return Status{RippledError::rpcSTREAM_MALFORMED};
} }
return OK; return OK;
@@ -98,10 +98,10 @@ validateAccounts(boost::json::array const& accounts)
for (auto const& account : accounts) for (auto const& account : accounts)
{ {
if (!account.is_string()) if (!account.is_string())
return Status{Error::rpcINVALID_PARAMS, "accountNotString"}; return Status{RippledError::rpcINVALID_PARAMS, "accountNotString"};
if (!accountFromStringStrict(account.as_string().c_str())) if (!accountFromStringStrict(account.as_string().c_str()))
return Status{Error::rpcACT_MALFORMED, "Account malformed."}; return Status{RippledError::rpcACT_MALFORMED, "Account malformed."};
} }
return OK; return OK;
@@ -212,7 +212,7 @@ validateAndGetBooks(
std::shared_ptr<Backend::BackendInterface const> const& backend) std::shared_ptr<Backend::BackendInterface const> const& backend)
{ {
if (!request.at(JS(books)).is_array()) if (!request.at(JS(books)).is_array())
return Status{Error::rpcINVALID_PARAMS, "booksNotArray"}; return Status{RippledError::rpcINVALID_PARAMS, "booksNotArray"};
boost::json::array const& books = request.at(JS(books)).as_array(); boost::json::array const& books = request.at(JS(books)).as_array();
std::vector<ripple::Book> booksToSub; std::vector<ripple::Book> booksToSub;
@@ -294,7 +294,7 @@ doSubscribe(Context const& context)
if (request.contains(JS(streams))) if (request.contains(JS(streams)))
{ {
if (!request.at(JS(streams)).is_array()) if (!request.at(JS(streams)).is_array())
return Status{Error::rpcINVALID_PARAMS, "streamsNotArray"}; return Status{RippledError::rpcINVALID_PARAMS, "streamsNotArray"};
auto status = validateStreams(request); auto status = validateStreams(request);
@@ -306,11 +306,11 @@ doSubscribe(Context const& context)
{ {
auto const& jsonAccounts = request.at(JS(accounts)); auto const& jsonAccounts = request.at(JS(accounts));
if (!jsonAccounts.is_array()) if (!jsonAccounts.is_array())
return Status{Error::rpcINVALID_PARAMS, "accountsNotArray"}; return Status{RippledError::rpcINVALID_PARAMS, "accountsNotArray"};
auto const& accounts = jsonAccounts.as_array(); auto const& accounts = jsonAccounts.as_array();
if (accounts.empty()) if (accounts.empty())
return Status{Error::rpcACT_MALFORMED, "Account malformed."}; return Status{RippledError::rpcACT_MALFORMED, "Account malformed."};
auto const status = validateAccounts(accounts); auto const status = validateAccounts(accounts);
if (status) if (status)
@@ -321,11 +321,12 @@ doSubscribe(Context const& context)
{ {
auto const& jsonAccounts = request.at(JS(accounts_proposed)); auto const& jsonAccounts = request.at(JS(accounts_proposed));
if (!jsonAccounts.is_array()) if (!jsonAccounts.is_array())
return Status{Error::rpcINVALID_PARAMS, "accountsProposedNotArray"}; return Status{
RippledError::rpcINVALID_PARAMS, "accountsProposedNotArray"};
auto const& accounts = jsonAccounts.as_array(); auto const& accounts = jsonAccounts.as_array();
if (accounts.empty()) if (accounts.empty())
return Status{Error::rpcACT_MALFORMED, "Account malformed."}; return Status{RippledError::rpcACT_MALFORMED, "Account malformed."};
auto const status = validateAccounts(accounts); auto const status = validateAccounts(accounts);
if (status) if (status)
@@ -373,7 +374,7 @@ doUnsubscribe(Context const& context)
if (request.contains(JS(streams))) if (request.contains(JS(streams)))
{ {
if (!request.at(JS(streams)).is_array()) if (!request.at(JS(streams)).is_array())
return Status{Error::rpcINVALID_PARAMS, "streamsNotArray"}; return Status{RippledError::rpcINVALID_PARAMS, "streamsNotArray"};
auto status = validateStreams(request); auto status = validateStreams(request);
@@ -385,11 +386,11 @@ doUnsubscribe(Context const& context)
{ {
auto const& jsonAccounts = request.at(JS(accounts)); auto const& jsonAccounts = request.at(JS(accounts));
if (!jsonAccounts.is_array()) if (!jsonAccounts.is_array())
return Status{Error::rpcINVALID_PARAMS, "accountsNotArray"}; return Status{RippledError::rpcINVALID_PARAMS, "accountsNotArray"};
auto const& accounts = jsonAccounts.as_array(); auto const& accounts = jsonAccounts.as_array();
if (accounts.empty()) if (accounts.empty())
return Status{Error::rpcACT_MALFORMED, "Account malformed."}; return Status{RippledError::rpcACT_MALFORMED, "Account malformed."};
auto const status = validateAccounts(accounts); auto const status = validateAccounts(accounts);
if (status) if (status)
@@ -400,11 +401,12 @@ doUnsubscribe(Context const& context)
{ {
auto const& jsonAccounts = request.at(JS(accounts_proposed)); auto const& jsonAccounts = request.at(JS(accounts_proposed));
if (!jsonAccounts.is_array()) if (!jsonAccounts.is_array())
return Status{Error::rpcINVALID_PARAMS, "accountsProposedNotArray"}; return Status{
RippledError::rpcINVALID_PARAMS, "accountsProposedNotArray"};
auto const& accounts = jsonAccounts.as_array(); auto const& accounts = jsonAccounts.as_array();
if (accounts.empty()) if (accounts.empty())
return Status{Error::rpcACT_MALFORMED, "Account malformed."}; return Status{RippledError::rpcACT_MALFORMED, "Account malformed."};
auto const status = validateAccounts(accounts); auto const status = validateAccounts(accounts);
if (status) if (status)

View File

@@ -14,7 +14,7 @@ doTransactionEntry(Context const& context)
ripple::uint256 hash; ripple::uint256 hash;
if (!hash.parseHex(getRequiredString(context.params, JS(tx_hash)))) if (!hash.parseHex(getRequiredString(context.params, JS(tx_hash))))
return Status{Error::rpcINVALID_PARAMS, "malformedTransaction"}; return Status{RippledError::rpcINVALID_PARAMS, "malformedTransaction"};
auto dbResponse = context.backend->fetchTransaction(hash, context.yield); auto dbResponse = context.backend->fetchTransaction(hash, context.yield);
// Note: transaction_entry is meant to only search a specified ledger for // Note: transaction_entry is meant to only search a specified ledger for
@@ -28,7 +28,7 @@ doTransactionEntry(Context const& context)
// is in a different ledger than the one specified. // is in a different ledger than the one specified.
if (!dbResponse || dbResponse->ledgerSequence != lgrInfo.seq) if (!dbResponse || dbResponse->ledgerSequence != lgrInfo.seq)
return Status{ return Status{
Error::rpcTXN_NOT_FOUND, RippledError::rpcTXN_NOT_FOUND,
"transactionNotFound", "transactionNotFound",
"Transaction not found."}; "Transaction not found."};

View File

@@ -14,31 +14,31 @@ doTx(Context const& context)
boost::json::object response = {}; boost::json::object response = {};
if (!request.contains(JS(transaction))) if (!request.contains(JS(transaction)))
return Status{Error::rpcINVALID_PARAMS, "specifyTransaction"}; return Status{RippledError::rpcINVALID_PARAMS, "specifyTransaction"};
if (!request.at(JS(transaction)).is_string()) if (!request.at(JS(transaction)).is_string())
return Status{Error::rpcINVALID_PARAMS, "transactionNotString"}; return Status{RippledError::rpcINVALID_PARAMS, "transactionNotString"};
ripple::uint256 hash; ripple::uint256 hash;
if (!hash.parseHex(request.at(JS(transaction)).as_string().c_str())) if (!hash.parseHex(request.at(JS(transaction)).as_string().c_str()))
return Status{Error::rpcINVALID_PARAMS, "malformedTransaction"}; return Status{RippledError::rpcINVALID_PARAMS, "malformedTransaction"};
bool binary = false; bool binary = false;
if (request.contains(JS(binary))) if (request.contains(JS(binary)))
{ {
if (!request.at(JS(binary)).is_bool()) if (!request.at(JS(binary)).is_bool())
return Status{Error::rpcINVALID_PARAMS, "binaryFlagNotBool"}; return Status{RippledError::rpcINVALID_PARAMS, "binaryFlagNotBool"};
binary = request.at(JS(binary)).as_bool(); binary = request.at(JS(binary)).as_bool();
} }
auto range = context.backend->fetchLedgerRange(); auto range = context.backend->fetchLedgerRange();
if (!range) if (!range)
return Status{Error::rpcNOT_READY}; return Status{RippledError::rpcNOT_READY};
auto dbResponse = context.backend->fetchTransaction(hash, context.yield); auto dbResponse = context.backend->fetchTransaction(hash, context.yield);
if (!dbResponse) if (!dbResponse)
return Status{Error::rpcTXN_NOT_FOUND}; return Status{RippledError::rpcTXN_NOT_FOUND};
if (!binary) if (!binary)
{ {

View File

@@ -270,7 +270,7 @@ public:
res.set(http::field::content_type, "application/json"); res.set(http::field::content_type, "application/json");
res.keep_alive(req_.keep_alive()); res.keep_alive(req_.keep_alive());
res.body() = boost::json::serialize( res.body() = boost::json::serialize(
RPC::make_error(RPC::Error::rpcTOO_BUSY)); RPC::makeError(RPC::RippledError::rpcTOO_BUSY));
res.prepare_payload(); res.prepare_payload();
lambda_(std::move(res)); lambda_(std::move(res));
} }
@@ -375,7 +375,7 @@ handle_request(
http::status::ok, http::status::ok,
"application/json", "application/json",
boost::json::serialize( boost::json::serialize(
RPC::make_error(RPC::Error::rpcBAD_SYNTAX)))); RPC::makeError(RPC::RippledError::rpcBAD_SYNTAX))));
} }
auto range = backend->fetchLedgerRange(); auto range = backend->fetchLedgerRange();
@@ -384,7 +384,7 @@ handle_request(
http::status::ok, http::status::ok,
"application/json", "application/json",
boost::json::serialize( boost::json::serialize(
RPC::make_error(RPC::Error::rpcNOT_READY)))); RPC::makeError(RPC::RippledError::rpcNOT_READY))));
std::optional<RPC::Context> context = RPC::make_HttpContext( std::optional<RPC::Context> context = RPC::make_HttpContext(
yc, yc,
@@ -403,7 +403,7 @@ handle_request(
http::status::ok, http::status::ok,
"application/json", "application/json",
boost::json::serialize( boost::json::serialize(
RPC::make_error(RPC::Error::rpcBAD_SYNTAX)))); RPC::makeError(RPC::RippledError::rpcBAD_SYNTAX))));
boost::json::object response{{"result", boost::json::object{}}}; boost::json::object response{{"result", boost::json::object{}}};
boost::json::object& result = response["result"].as_object(); boost::json::object& result = response["result"].as_object();
@@ -418,7 +418,7 @@ handle_request(
if (auto status = std::get_if<RPC::Status>(&v)) if (auto status = std::get_if<RPC::Status>(&v))
{ {
counters.rpcErrored(context->method); counters.rpcErrored(context->method);
auto error = RPC::make_error(*status); auto error = RPC::makeError(*status);
error["request"] = request; error["request"] = request;
result = error; result = error;
@@ -438,16 +438,16 @@ handle_request(
} }
boost::json::array warnings; boost::json::array warnings;
warnings.emplace_back(RPC::make_warning(RPC::warnRPC_CLIO)); warnings.emplace_back(RPC::makeWarning(RPC::warnRPC_CLIO));
auto lastCloseAge = context->etl->lastCloseAgeSeconds(); auto lastCloseAge = context->etl->lastCloseAgeSeconds();
if (lastCloseAge >= 60) if (lastCloseAge >= 60)
warnings.emplace_back(RPC::make_warning(RPC::warnRPC_OUTDATED)); warnings.emplace_back(RPC::makeWarning(RPC::warnRPC_OUTDATED));
response["warnings"] = warnings; response["warnings"] = warnings;
responseStr = boost::json::serialize(response); responseStr = boost::json::serialize(response);
if (!dosGuard.add(ip, responseStr.size())) if (!dosGuard.add(ip, responseStr.size()))
{ {
response["warning"] = "load"; response["warning"] = "load";
warnings.emplace_back(RPC::make_warning(RPC::warnRPC_RATE_LIMIT)); warnings.emplace_back(RPC::makeWarning(RPC::warnRPC_RATE_LIMIT));
response["warnings"] = warnings; response["warnings"] = warnings;
// reserialize when we need to include this warning // reserialize when we need to include this warning
responseStr = boost::json::serialize(response); responseStr = boost::json::serialize(response);
@@ -462,7 +462,8 @@ handle_request(
return send(httpResponse( return send(httpResponse(
http::status::internal_server_error, http::status::internal_server_error,
"application/json", "application/json",
boost::json::serialize(RPC::make_error(RPC::Error::rpcINTERNAL)))); boost::json::serialize(
RPC::makeError(RPC::RippledError::rpcINTERNAL))));
} }
} }

View File

@@ -278,7 +278,7 @@ public:
boost::json::object response = {}; boost::json::object response = {};
auto sendError = [this, &request, id](auto error) { auto sendError = [this, &request, id](auto error) {
auto e = RPC::make_error(error); auto e = RPC::makeError(error);
if (!id.is_null()) if (!id.is_null())
e["id"] = id; e["id"] = id;
e["request"] = request; e["request"] = request;
@@ -292,7 +292,7 @@ public:
auto range = backend_->fetchLedgerRange(); auto range = backend_->fetchLedgerRange();
if (!range) if (!range)
return sendError(RPC::Error::rpcNOT_READY); return sendError(RPC::RippledError::rpcNOT_READY);
std::optional<RPC::Context> context = RPC::make_WsContext( std::optional<RPC::Context> context = RPC::make_WsContext(
yield, yield,
@@ -311,7 +311,7 @@ public:
{ {
BOOST_LOG_TRIVIAL(warning) BOOST_LOG_TRIVIAL(warning)
<< tag() << " could not create RPC context"; << tag() << " could not create RPC context";
return sendError(RPC::Error::rpcBAD_SYNTAX); return sendError(RPC::RippledError::rpcBAD_SYNTAX);
} }
response = getDefaultWsResponse(id); response = getDefaultWsResponse(id);
@@ -327,7 +327,7 @@ public:
{ {
counters_.rpcErrored(context->method); counters_.rpcErrored(context->method);
auto error = RPC::make_error(*status); auto error = RPC::makeError(*status);
if (!id.is_null()) if (!id.is_null())
error["id"] = id; error["id"] = id;
@@ -347,22 +347,22 @@ public:
BOOST_LOG_TRIVIAL(error) BOOST_LOG_TRIVIAL(error)
<< tag() << __func__ << " caught exception : " << e.what(); << tag() << __func__ << " caught exception : " << e.what();
return sendError(RPC::Error::rpcINTERNAL); return sendError(RPC::RippledError::rpcINTERNAL);
} }
boost::json::array warnings; boost::json::array warnings;
warnings.emplace_back(RPC::make_warning(RPC::warnRPC_CLIO)); warnings.emplace_back(RPC::makeWarning(RPC::warnRPC_CLIO));
auto lastCloseAge = etl_->lastCloseAgeSeconds(); auto lastCloseAge = etl_->lastCloseAgeSeconds();
if (lastCloseAge >= 60) if (lastCloseAge >= 60)
warnings.emplace_back(RPC::make_warning(RPC::warnRPC_OUTDATED)); warnings.emplace_back(RPC::makeWarning(RPC::warnRPC_OUTDATED));
response["warnings"] = warnings; response["warnings"] = warnings;
std::string responseStr = boost::json::serialize(response); std::string responseStr = boost::json::serialize(response);
if (!dosGuard_.add(*ip, responseStr.size())) if (!dosGuard_.add(*ip, responseStr.size()))
{ {
response["warning"] = "load"; response["warning"] = "load";
warnings.emplace_back(RPC::make_warning(RPC::warnRPC_RATE_LIMIT)); warnings.emplace_back(RPC::makeWarning(RPC::warnRPC_RATE_LIMIT));
response["warnings"] = warnings; response["warnings"] = warnings;
// reserialize if we need to include this warning // reserialize if we need to include this warning
responseStr = boost::json::serialize(response); responseStr = boost::json::serialize(response);
@@ -392,7 +392,7 @@ public:
auto error, auto error,
boost::json::value const& id, boost::json::value const& id,
boost::json::object const& request) { boost::json::object const& request) {
auto e = RPC::make_error(error); auto e = RPC::makeError(error);
if (!id.is_null()) if (!id.is_null())
e["id"] = id; e["id"] = id;
@@ -417,14 +417,15 @@ public:
boost::json::object request; boost::json::object request;
if (!raw.is_object()) if (!raw.is_object())
return sendError(RPC::Error::rpcINVALID_PARAMS, nullptr, request); return sendError(
RPC::RippledError::rpcINVALID_PARAMS, nullptr, request);
request = raw.as_object(); request = raw.as_object();
auto id = request.contains("id") ? request.at("id") : nullptr; auto id = request.contains("id") ? request.at("id") : nullptr;
if (!dosGuard_.isOk(*ip)) if (!dosGuard_.isOk(*ip))
{ {
sendError(RPC::Error::rpcSLOW_DOWN, id, request); sendError(RPC::RippledError::rpcSLOW_DOWN, id, request);
} }
else else
{ {
@@ -438,7 +439,7 @@ public:
shared_this->handle_request(std::move(r), id, yield); shared_this->handle_request(std::move(r), id, yield);
}, },
dosGuard_.isWhiteListed(*ip))) dosGuard_.isWhiteListed(*ip)))
sendError(RPC::Error::rpcTOO_BUSY, id, request); sendError(RPC::RippledError::rpcTOO_BUSY, id, request);
} }
do_read(); do_read();

133
unittests/RPCErrors.cpp Normal file
View File

@@ -0,0 +1,133 @@
#include <rpc/RPC.h>
#include <boost/json.hpp>
#include <gtest/gtest.h>
using namespace RPC;
using namespace std;
namespace {
void
check(
boost::json::object const& j,
std::string_view error,
uint32_t errorCode,
std::string_view errorMessage)
{
EXPECT_TRUE(j.contains("error"));
EXPECT_TRUE(j.contains("error_code"));
EXPECT_TRUE(j.contains("error_message"));
EXPECT_TRUE(j.contains("status"));
EXPECT_TRUE(j.contains("type"));
EXPECT_TRUE(j.at("error").is_string());
EXPECT_TRUE(j.at("error_code").is_uint64());
EXPECT_TRUE(j.at("error_message").is_string());
EXPECT_TRUE(j.at("status").is_string());
EXPECT_TRUE(j.at("type").is_string());
EXPECT_STREQ(j.at("status").as_string().c_str(), "error");
EXPECT_STREQ(j.at("type").as_string().c_str(), "response");
EXPECT_STREQ(j.at("error").as_string().c_str(), error.data());
EXPECT_EQ(j.at("error_code").as_uint64(), errorCode);
EXPECT_STREQ(
j.at("error_message").as_string().c_str(), errorMessage.data());
}
} // namespace
TEST(RPCErrorsTest, StatusAsBool)
{
// Only rpcSUCCESS status should return false
EXPECT_FALSE(Status{RippledError::rpcSUCCESS});
// true should be returned for any error state, we just test a few
CombinedError const errors[]{
RippledError::rpcINVALID_PARAMS,
RippledError::rpcUNKNOWN_COMMAND,
RippledError::rpcTOO_BUSY,
RippledError::rpcNO_NETWORK,
RippledError::rpcACT_MALFORMED,
RippledError::rpcBAD_MARKET,
ClioError::rpcMALFORMED_CURRENCY,
};
for (auto const& ec : errors)
EXPECT_TRUE(Status{ec});
}
TEST(RPCErrorsTest, SuccessToJSON)
{
auto const status = Status{RippledError::rpcSUCCESS};
check(makeError(status), "unknown", 0, "An unknown error code.");
}
TEST(RPCErrorsTest, RippledErrorToJSON)
{
auto const status = Status{RippledError::rpcINVALID_PARAMS};
check(makeError(status), "invalidParams", 31, "Invalid parameters.");
}
TEST(RPCErrorsTest, RippledErrorFromStringToJSON)
{
auto const j = makeError(Status{"veryCustomError"});
EXPECT_STREQ(j.at("error").as_string().c_str(), "veryCustomError");
}
TEST(RPCErrorsTest, RippledErrorToJSONCustomMessage)
{
auto const status = Status{RippledError::rpcINVALID_PARAMS, "custom"};
check(makeError(status), "invalidParams", 31, "custom");
}
TEST(RPCErrorsTest, RippledErrorToJSONCustomStrCodeAndMessage)
{
auto const status =
Status{RippledError::rpcINVALID_PARAMS, "customCode", "customMessage"};
check(makeError(status), "customCode", 31, "customMessage");
}
TEST(RPCErrorsTest, ClioErrorToJSON)
{
auto const status = Status{ClioError::rpcMALFORMED_CURRENCY};
check(makeError(status), "malformedCurrency", 5000, "Malformed currency.");
}
TEST(RPCErrorsTest, ClioErrorToJSONCustomMessage)
{
auto const status = Status{ClioError::rpcMALFORMED_CURRENCY, "custom"};
check(makeError(status), "malformedCurrency", 5000, "custom");
}
TEST(RPCErrorsTest, ClioErrorToJSONCustomStrCodeAndMessage)
{
auto const status =
Status{ClioError::rpcMALFORMED_CURRENCY, "customCode", "customMessage"};
check(makeError(status), "customCode", 5000, "customMessage");
}
TEST(RPCErrorsTest, InvalidClioErrorToJSON)
{
EXPECT_ANY_THROW((void)makeError(static_cast<ClioError>(999999)));
}
TEST(RPCErrorsTest, WarningToJSON)
{
auto j = makeWarning(WarningCode::warnRPC_OUTDATED);
EXPECT_TRUE(j.contains("id"));
EXPECT_TRUE(j.contains("message"));
EXPECT_TRUE(j.at("id").is_int64());
EXPECT_TRUE(j.at("message").is_string());
EXPECT_EQ(
j.at("id").as_int64(),
static_cast<uint32_t>(WarningCode::warnRPC_OUTDATED));
EXPECT_STREQ(
j.at("message").as_string().c_str(), "This server may be out of date");
}
TEST(RPCErrorsTest, InvalidWarningToJSON)
{
EXPECT_ANY_THROW((void)makeWarning(static_cast<WarningCode>(999999)));
}