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
# check how many lines differ
lines=$(git diff | wc -l)
lines=$(git diff src unittests | wc -l)
# check if there is any updated files
if [ "$lines" != "0" ]; then

View File

@@ -61,6 +61,7 @@ target_sources(clio PRIVATE
## Subscriptions
src/subscriptions/SubscriptionManager.cpp
## RPC
src/rpc/Errors.cpp
src/rpc/RPC.cpp
src/rpc/RPCHelpers.cpp
src/rpc/Counters.cpp
@@ -107,7 +108,8 @@ add_executable(clio_server src/main/main.cpp)
target_link_libraries(clio_server PUBLIC clio)
if(BUILD_TESTS)
add_executable(clio_tests
add_executable(clio_tests
unittests/RPCErrors.cpp
unittests/config.cpp
unittests/main.cpp)
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>
using namespace std;
namespace RPC {
Context::Context(
boost::asio::yield_context& yield_,
std::string const& command_,
std::uint32_t version_,
string const& command_,
uint32_t version_,
boost::json::object const& params_,
std::shared_ptr<BackendInterface const> const& backend_,
std::shared_ptr<SubscriptionManager> const& subscriptions_,
std::shared_ptr<ETLLoadBalancer> const& balancer_,
std::shared_ptr<ReportingETL const> const& etl_,
std::shared_ptr<WsBase> const& session_,
shared_ptr<BackendInterface const> const& backend_,
shared_ptr<SubscriptionManager> const& subscriptions_,
shared_ptr<ETLLoadBalancer> const& balancer_,
shared_ptr<ReportingETL const> const& etl_,
shared_ptr<WsBase> const& session_,
util::TagDecoratorFactory const& tagFactory_,
Backend::LedgerRange const& range_,
Counters& counters_,
std::string const& clientIp_)
string const& clientIp_)
: Taggable(tagFactory_)
, yield(yield_)
, method(command_)
@@ -41,19 +43,19 @@ Context::Context(
BOOST_LOG_TRIVIAL(debug) << tag() << "new Context created";
}
std::optional<Context>
optional<Context>
make_WsContext(
boost::asio::yield_context& yc,
boost::json::object const& request,
std::shared_ptr<BackendInterface const> const& backend,
std::shared_ptr<SubscriptionManager> const& subscriptions,
std::shared_ptr<ETLLoadBalancer> const& balancer,
std::shared_ptr<ReportingETL const> const& etl,
std::shared_ptr<WsBase> const& session,
shared_ptr<BackendInterface const> const& backend,
shared_ptr<SubscriptionManager> const& subscriptions,
shared_ptr<ETLLoadBalancer> const& balancer,
shared_ptr<ReportingETL const> const& etl,
shared_ptr<WsBase> const& session,
util::TagDecoratorFactory const& tagFactory,
Backend::LedgerRange const& range,
Counters& counters,
std::string const& clientIp)
string const& clientIp)
{
boost::json::value commandValue = nullptr;
if (!request.contains("command") && request.contains("method"))
@@ -64,9 +66,9 @@ make_WsContext(
if (!commandValue.is_string())
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,
command,
1,
@@ -82,23 +84,23 @@ make_WsContext(
clientIp);
}
std::optional<Context>
optional<Context>
make_HttpContext(
boost::asio::yield_context& yc,
boost::json::object const& request,
std::shared_ptr<BackendInterface const> const& backend,
std::shared_ptr<SubscriptionManager> const& subscriptions,
std::shared_ptr<ETLLoadBalancer> const& balancer,
std::shared_ptr<ReportingETL const> const& etl,
shared_ptr<BackendInterface const> const& backend,
shared_ptr<SubscriptionManager> const& subscriptions,
shared_ptr<ETLLoadBalancer> const& balancer,
shared_ptr<ReportingETL const> const& etl,
util::TagDecoratorFactory const& tagFactory,
Backend::LedgerRange const& range,
RPC::Counters& counters,
std::string const& clientIp)
string const& clientIp)
{
if (!request.contains("method") || !request.at("method").is_string())
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")
return {};
@@ -114,7 +116,7 @@ make_HttpContext(
if (!array.at(0).is_object())
return {};
return std::make_optional<Context>(
return make_optional<Context>(
yc,
command,
1,
@@ -130,107 +132,38 @@ make_HttpContext(
clientIp);
}
constexpr static WarningInfo warningInfos[]{
{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"}};
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&)>;
using LimitRange = tuple<uint32_t, uint32_t, uint32_t>;
using HandlerFunction = function<Result(Context const&)>;
struct Handler
{
std::string method;
std::function<Result(Context const&)> handler;
std::optional<LimitRange> limit;
string method;
function<Result(Context const&)> handler;
optional<LimitRange> limit;
bool isClioOnly = false;
};
class HandlerTable
{
std::unordered_map<std::string, Handler> handlerMap_;
unordered_map<string, Handler> handlerMap_;
public:
HandlerTable(std::initializer_list<Handler> handlers)
HandlerTable(initializer_list<Handler> handlers)
{
for (auto const& handler : handlers)
{
handlerMap_[handler.method] = std::move(handler);
handlerMap_[handler.method] = move(handler);
}
}
bool
contains(std::string const& method)
contains(string const& method)
{
return handlerMap_.contains(method);
}
std::optional<LimitRange>
getLimitRange(std::string const& command)
optional<LimitRange>
getLimitRange(string const& command)
{
if (!handlerMap_.contains(command))
return {};
@@ -238,8 +171,8 @@ public:
return handlerMap_[command].limit;
}
std::optional<HandlerFunction>
getHandler(std::string const& command)
optional<HandlerFunction>
getHandler(string const& command)
{
if (!handlerMap_.contains(command))
return {};
@@ -248,7 +181,7 @@ public:
}
bool
isClioOnly(std::string const& command)
isClioOnly(string const& command)
{
return handlerMap_.contains(command) && handlerMap_[command].isClioOnly;
}
@@ -282,7 +215,7 @@ static HandlerTable handlerTable{
{"transaction_entry", &doTransactionEntry, {}},
{"random", &doRandom, {}}};
static std::unordered_set<std::string> forwardCommands{
static unordered_set<string> forwardCommands{
"submit",
"submit_multisigned",
"fee",
@@ -294,13 +227,13 @@ static std::unordered_set<std::string> forwardCommands{
"channel_verify"};
bool
validHandler(std::string const& method)
validHandler(string const& method)
{
return handlerTable.contains(method) || forwardCommands.contains(method);
}
bool
isClioOnly(std::string const& method)
isClioOnly(string const& method)
{
return handlerTable.isClioOnly(method);
}
@@ -313,27 +246,28 @@ shouldSuppressValidatedFlag(RPC::Context const& context)
}
Status
getLimit(RPC::Context const& context, std::uint32_t& limit)
getLimit(RPC::Context const& context, uint32_t& limit)
{
if (!handlerTable.getHandler(context.method))
return Status{Error::rpcUNKNOWN_COMMAND};
return Status{RippledError::rpcUNKNOWN_COMMAND};
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);
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())
return Status{Error::rpcINVALID_PARAMS, errMsg};
return Status{RippledError::rpcINVALID_PARAMS, errMsg};
int input = context.params.at(JS(limit)).as_int64();
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
{
@@ -378,7 +312,7 @@ buildResponse(Context const& ctx)
ctx.counters.rpcForwarded(ctx.method);
if (!res)
return Status{Error::rpcFAILED_TO_FORWARD};
return Status{RippledError::rpcFAILED_TO_FORWARD};
if (res->contains("result") && res->at("result").is_object())
return res->at("result").as_object();
@@ -393,13 +327,13 @@ buildResponse(Context const& ctx)
{
BOOST_LOG_TRIVIAL(error)
<< __func__ << " Database is too busy. Rejecting request";
return Status{Error::rpcTOO_BUSY};
return Status{RippledError::rpcTOO_BUSY};
}
auto method = handlerTable.getHandler(ctx.method);
if (!method)
return Status{Error::rpcUNKNOWN_COMMAND};
return Status{RippledError::rpcUNKNOWN_COMMAND};
try
{
@@ -411,7 +345,7 @@ buildResponse(Context const& ctx)
<< 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)["validated"] = true;
@@ -421,22 +355,22 @@ buildResponse(Context const& ctx)
}
catch (InvalidParamsError const& err)
{
return Status{Error::rpcINVALID_PARAMS, err.what()};
return Status{RippledError::rpcINVALID_PARAMS, err.what()};
}
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)
{
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)
<< 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
#define REPORTING_RPC_H_INCLUDED
#include <ripple/protocol/ErrorCodes.h>
#include <rpc/Errors.h>
#include <boost/asio/spawn.hpp>
#include <boost/json.hpp>
@@ -65,7 +65,6 @@ struct Context : public util::Taggable
Counters& counters_,
std::string const& clientIp_);
};
using Error = ripple::error_code_i;
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>;
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>
make_WsContext(
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.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()))
return Status{Error::rpcINVALID_PARAMS, "malformedMarker"};
return Status{RippledError::rpcINVALID_PARAMS, "malformedMarker"};
}
return {};
@@ -207,14 +207,14 @@ getAccount(
{
if (required)
return Status{
Error::rpcINVALID_PARAMS, field.to_string() + "Missing"};
RippledError::rpcINVALID_PARAMS, field.to_string() + "Missing"};
return {};
}
if (!request.at(field).is_string())
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());
a)
@@ -223,7 +223,8 @@ getAccount(
return {};
}
return Status{Error::rpcACT_MALFORMED, field.to_string() + "Malformed"};
return Status{
RippledError::rpcACT_MALFORMED, field.to_string() + "Malformed"};
}
Status
@@ -240,7 +241,7 @@ getOptionalAccount(
if (!request.at(field).is_string())
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());
a)
@@ -249,7 +250,8 @@ getOptionalAccount(
return {};
}
return Status{Error::rpcINVALID_PARAMS, field.to_string() + "Malformed"};
return Status{
RippledError::rpcINVALID_PARAMS, field.to_string() + "Malformed"};
}
Status
@@ -286,13 +288,13 @@ Status
getChannelId(boost::json::object const& request, ripple::uint256& channelId)
{
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())
return Status{Error::rpcINVALID_PARAMS, "channelIDNotString"};
return Status{RippledError::rpcINVALID_PARAMS, "channelIDNotString"};
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 {};
}
@@ -554,16 +556,18 @@ ledgerInfoFromRequest(Context const& ctx)
if (!hashValue.is_null())
{
if (!hashValue.is_string())
return Status{Error::rpcINVALID_PARAMS, "ledgerHashNotString"};
return Status{
RippledError::rpcINVALID_PARAMS, "ledgerHashNotString"};
ripple::uint256 ledgerHash;
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);
if (!lgrInfo || lgrInfo->seq > ctx.range.maxSequence)
return Status{Error::rpcLGR_NOT_FOUND, "ledgerNotFound"};
return Status{RippledError::rpcLGR_NOT_FOUND, "ledgerNotFound"};
return *lgrInfo;
}
@@ -592,13 +596,13 @@ ledgerInfoFromRequest(Context const& ctx)
}
if (!ledgerSequence)
return Status{Error::rpcINVALID_PARAMS, "ledgerIndexMalformed"};
return Status{RippledError::rpcINVALID_PARAMS, "ledgerIndexMalformed"};
auto lgrInfo =
ctx.backend->fetchLedgerBySequence(*ledgerSequence, ctx.yield);
if (!lgrInfo || lgrInfo->seq > ctx.range.maxSequence)
return Status{Error::rpcLGR_NOT_FOUND, "ledgerNotFound"};
return Status{RippledError::rpcLGR_NOT_FOUND, "ledgerNotFound"};
return *lgrInfo;
}
@@ -651,7 +655,7 @@ traverseOwnedNodes(
{
if (!backend.fetchLedgerObject(
ripple::keylet::account(accountID).key, sequence, yield))
return Status{Error::rpcACT_NOT_FOUND};
return Status{RippledError::rpcACT_NOT_FOUND};
auto parsedCursor =
parseAccountCursor(backend, sequence, jsonCursor, accountID, yield);
@@ -891,12 +895,12 @@ keypairFromRequst(boost::json::object const& request)
}
if (count == 0)
return Status{Error::rpcINVALID_PARAMS, "missing field secret"};
return Status{RippledError::rpcINVALID_PARAMS, "missing field secret"};
if (count > 1)
{
return Status{
Error::rpcINVALID_PARAMS,
RippledError::rpcINVALID_PARAMS,
"Exactly one of the following must be specified: "
" passphrase, secret, seed, or seed_hex"};
}
@@ -907,17 +911,18 @@ keypairFromRequst(boost::json::object const& request)
if (has_key_type)
{
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();
keyType = ripple::keyTypeFromString(key_type);
if (!keyType)
return Status{Error::rpcINVALID_PARAMS, "invalidFieldKeyType"};
return Status{
RippledError::rpcINVALID_PARAMS, "invalidFieldKeyType"};
if (secretType == "secret")
return Status{
Error::rpcINVALID_PARAMS,
RippledError::rpcINVALID_PARAMS,
"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) !=
ripple::KeyType::ed25519)
return Status{
Error::rpcINVALID_PARAMS,
RippledError::rpcINVALID_PARAMS,
"Specified seed is for an Ed25519 wallet."};
keyType = ripple::KeyType::ed25519;
@@ -951,7 +956,8 @@ keypairFromRequst(boost::json::object const& request)
{
if (!request.at(secretType).is_string())
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();
@@ -970,7 +976,7 @@ keypairFromRequst(boost::json::object const& request)
{
if (!request.at("secret").is_string())
return Status{
Error::rpcINVALID_PARAMS,
RippledError::rpcINVALID_PARAMS,
"field secret should be a string"};
std::string secret = request.at("secret").as_string().c_str();
@@ -980,12 +986,14 @@ keypairFromRequst(boost::json::object const& request)
if (!seed)
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 &&
keyType != ripple::KeyType::ed25519)
return Status{
Error::rpcINVALID_PARAMS, "keypairForSignature: invalid key type"};
RippledError::rpcINVALID_PARAMS,
"keypairForSignature: invalid key type"};
return generateKeyPair(*keyType, *seed);
}
@@ -1343,54 +1351,63 @@ std::variant<Status, ripple::Book>
parseBook(boost::json::object const& request)
{
if (!request.contains("taker_pays"))
return Status{Error::rpcBAD_MARKET, "missingTakerPays"};
return Status{RippledError::rpcBAD_MARKET, "missingTakerPays"};
if (!request.contains("taker_gets"))
return Status{Error::rpcINVALID_PARAMS, "missingTakerGets"};
return Status{RippledError::rpcINVALID_PARAMS, "missingTakerGets"};
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())
return Status{Error::rpcINVALID_PARAMS, "takerGetsNotObject"};
return Status{RippledError::rpcINVALID_PARAMS, "takerGetsNotObject"};
auto taker_pays = request.at("taker_pays").as_object();
if (!taker_pays.contains("currency"))
return Status{Error::rpcINVALID_PARAMS, "missingTakerPaysCurrency"};
return Status{
RippledError::rpcINVALID_PARAMS, "missingTakerPaysCurrency"};
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();
if (!taker_gets.contains("currency"))
return Status{Error::rpcINVALID_PARAMS, "missingTakerGetsCurrency"};
return Status{
RippledError::rpcINVALID_PARAMS, "missingTakerGetsCurrency"};
if (!taker_gets.at("currency").is_string())
return Status{Error::rpcINVALID_PARAMS, "takerGetsCurrencyNotString"};
return Status{
RippledError::rpcINVALID_PARAMS, "takerGetsCurrencyNotString"};
ripple::Currency pay_currency;
if (!ripple::to_currency(
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;
if (!ripple::to_currency(
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;
if (taker_pays.contains("issuer"))
{
if (!taker_pays.at("issuer").is_string())
return Status{Error::rpcINVALID_PARAMS, "takerPaysIssuerNotString"};
return Status{
RippledError::rpcINVALID_PARAMS, "takerPaysIssuerNotString"};
if (!ripple::to_issuer(
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())
return Status{
Error::rpcSRC_ISR_MALFORMED, "badTakerPaysIssuerAccountOne"};
RippledError::rpcSRC_ISR_MALFORMED,
"badTakerPaysIssuerAccountOne"};
}
else
{
@@ -1399,18 +1416,19 @@ parseBook(boost::json::object const& request)
if (isXRP(pay_currency) && !isXRP(pay_issuer))
return Status{
Error::rpcSRC_ISR_MALFORMED,
RippledError::rpcSRC_ISR_MALFORMED,
"Unneeded field 'taker_pays.issuer' for XRP currency "
"specification."};
if (!isXRP(pay_currency) && isXRP(pay_issuer))
return Status{
Error::rpcSRC_ISR_MALFORMED,
RippledError::rpcSRC_ISR_MALFORMED,
"Invalid field 'taker_pays.issuer', expected non-XRP "
"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;
@@ -1418,17 +1436,18 @@ parseBook(boost::json::object const& request)
{
if (!taker_gets["issuer"].is_string())
return Status{
Error::rpcINVALID_PARAMS, "taker_gets.issuer should be string"};
RippledError::rpcINVALID_PARAMS,
"taker_gets.issuer should be string"};
if (!ripple::to_issuer(
get_issuer, taker_gets.at("issuer").as_string().c_str()))
return Status{
Error::rpcDST_ISR_MALFORMED,
RippledError::rpcDST_ISR_MALFORMED,
"Invalid field 'taker_gets.issuer', bad issuer."};
if (get_issuer == ripple::noAccount())
return Status{
Error::rpcDST_ISR_MALFORMED,
RippledError::rpcDST_ISR_MALFORMED,
"Invalid field 'taker_gets.issuer', bad issuer account "
"one."};
}
@@ -1439,17 +1458,17 @@ parseBook(boost::json::object const& request)
if (ripple::isXRP(get_currency) && !ripple::isXRP(get_issuer))
return Status{
Error::rpcDST_ISR_MALFORMED,
RippledError::rpcDST_ISR_MALFORMED,
"Unneeded field 'taker_gets.issuer' for XRP currency "
"specification."};
if (!ripple::isXRP(get_currency) && ripple::isXRP(get_issuer))
return Status{
Error::rpcDST_ISR_MALFORMED,
RippledError::rpcDST_ISR_MALFORMED,
"Invalid field 'taker_gets.issuer', expected non-XRP 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}};
}
@@ -1459,12 +1478,12 @@ parseTaker(boost::json::value const& taker)
{
std::optional<ripple::AccountID> takerID = {};
if (!taker.is_string())
return {Status{Error::rpcINVALID_PARAMS, "takerNotString"}};
return {Status{RippledError::rpcINVALID_PARAMS, "takerNotString"}};
takerID = accountFromStringStrict(taker.as_string().c_str());
if (!takerID)
return Status{Error::rpcBAD_ISSUER, "invalidTakerAccount"};
return Status{RippledError::rpcBAD_ISSUER, "invalidTakerAccount"};
return *takerID;
}
bool
@@ -1486,14 +1505,14 @@ std::variant<ripple::uint256, Status>
getNFTID(boost::json::object const& request)
{
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())
return Status{Error::rpcINVALID_PARAMS, "tokenIDNotString"};
return Status{RippledError::rpcINVALID_PARAMS, "tokenIDNotString"};
ripple::uint256 tokenid;
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;
}
@@ -1521,7 +1540,7 @@ traverseTransactions(
if (request.contains(JS(marker)))
{
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();
std::optional<std::uint32_t> transactionIndex = {};
@@ -1529,7 +1548,7 @@ traverseTransactions(
{
if (!obj.at(JS(seq)).is_int64())
return Status{
Error::rpcINVALID_PARAMS, "transactionIndexNotInt"};
RippledError::rpcINVALID_PARAMS, "transactionIndexNotInt"};
transactionIndex =
boost::json::value_to<std::uint32_t>(obj.at(JS(seq)));
@@ -1539,14 +1558,16 @@ traverseTransactions(
if (obj.contains(JS(ledger)))
{
if (!obj.at(JS(ledger)).is_int64())
return Status{Error::rpcINVALID_PARAMS, "ledgerIndexNotInt"};
return Status{
RippledError::rpcINVALID_PARAMS, "ledgerIndexNotInt"};
ledgerIndex =
boost::json::value_to<std::uint32_t>(obj.at(JS(ledger)));
}
if (!transactionIndex || !ledgerIndex)
return Status{Error::rpcINVALID_PARAMS, "missingLedgerOrSeq"};
return Status{
RippledError::rpcINVALID_PARAMS, "missingLedgerOrSeq"};
cursor = {*ledgerIndex, *transactionIndex};
}
@@ -1557,14 +1578,16 @@ traverseTransactions(
auto& min = request.at(JS(ledger_index_min));
if (!min.is_int64())
return Status{Error::rpcINVALID_PARAMS, "ledgerSeqMinNotNumber"};
return Status{
RippledError::rpcINVALID_PARAMS, "ledgerSeqMinNotNumber"};
if (min.as_int64() != -1)
{
if (context.range.maxSequence < min.as_int64() ||
context.range.minSequence > min.as_int64())
return Status{
Error::rpcLGR_IDX_MALFORMED, "ledgerSeqMinOutOfRange"};
RippledError::rpcLGR_IDX_MALFORMED,
"ledgerSeqMinOutOfRange"};
else
minIndex = boost::json::value_to<std::uint32_t>(min);
}
@@ -1579,20 +1602,22 @@ traverseTransactions(
auto& max = request.at(JS(ledger_index_max));
if (!max.is_int64())
return Status{Error::rpcINVALID_PARAMS, "ledgerSeqMaxNotNumber"};
return Status{
RippledError::rpcINVALID_PARAMS, "ledgerSeqMaxNotNumber"};
if (max.as_int64() != -1)
{
if (context.range.maxSequence < max.as_int64() ||
context.range.minSequence > max.as_int64())
return Status{
Error::rpcLGR_IDX_MALFORMED, "ledgerSeqMaxOutOfRange"};
RippledError::rpcLGR_IDX_MALFORMED,
"ledgerSeqMaxOutOfRange"};
else
maxIndex = boost::json::value_to<std::uint32_t>(max);
}
if (minIndex > maxIndex)
return Status{Error::rpcINVALID_PARAMS, "invalidIndex"};
return Status{RippledError::rpcINVALID_PARAMS, "invalidIndex"};
if (!forward && !cursor)
cursor = {maxIndex, INT32_MAX};
@@ -1603,7 +1628,8 @@ traverseTransactions(
if (request.contains(JS(ledger_index_max)) ||
request.contains(JS(ledger_index_min)))
return Status{
Error::rpcINVALID_PARAMS, "containsLedgerSpecifierAndRange"};
RippledError::rpcINVALID_PARAMS,
"containsLedgerSpecifierAndRange"};
auto v = ledgerInfoFromRequest(context);
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);
if (!rawAcct)
return Status{Error::rpcACT_NOT_FOUND, "accountNotFound"};
return Status{RippledError::rpcACT_NOT_FOUND, "accountNotFound"};
ripple::AccountID destAccount;
if (auto const status =
@@ -78,7 +78,7 @@ doAccountChannels(Context const& context)
if (request.contains(JS(marker)))
{
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();
}

View File

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

View File

@@ -34,7 +34,7 @@ doAccountInfo(Context const& context)
else if (request.contains(JS(ident)))
strIdent = request.at(JS(ident)).as_string().c_str();
else
return Status{Error::rpcACT_MALFORMED};
return Status{RippledError::rpcACT_MALFORMED};
// 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
@@ -48,7 +48,7 @@ doAccountInfo(Context const& context)
// Get info on account.
auto accountID = accountFromStringStrict(strIdent);
if (!accountID)
return Status{Error::rpcACT_MALFORMED};
return Status{RippledError::rpcACT_MALFORMED};
assert(accountID.has_value());
@@ -59,14 +59,14 @@ doAccountInfo(Context const& context)
if (!dbResponse)
{
return Status{Error::rpcACT_NOT_FOUND};
return Status{RippledError::rpcACT_NOT_FOUND};
}
ripple::STLedgerEntry sle{
ripple::SerialIter{dbResponse->data(), dbResponse->size()}, key.key};
if (!key.check(sle))
return Status{Error::rpcDB_DESERIALIZATION};
return Status{RippledError::rpcDB_DESERIALIZATION};
// if (!binary)
// response[JS(account_data)] = getJson(sle);
@@ -97,7 +97,7 @@ doAccountInfo(Context const& context)
ripple::SerialIter{signers->data(), signers->size()},
signersKey.key};
if (!signersKey.check(sleSigners))
return Status{Error::rpcDB_DESERIALIZATION};
return Status{RippledError::rpcDB_DESERIALIZATION};
signerList.push_back(toJson(sleSigners));
}

View File

@@ -107,7 +107,7 @@ doAccountLines(Context const& context)
ripple::keylet::account(accountID).key, lgrInfo.seq, context.yield);
if (!rawAcct)
return Status{Error::rpcACT_NOT_FOUND, "accountNotFound"};
return Status{RippledError::rpcACT_NOT_FOUND, "accountNotFound"};
std::optional<ripple::AccountID> peerAccount;
if (auto const status = getOptionalAccount(request, peerAccount, JS(peer));
@@ -122,7 +122,7 @@ doAccountLines(Context const& context)
if (request.contains(JS(marker)))
{
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();
}
@@ -131,7 +131,8 @@ doAccountLines(Context const& context)
if (request.contains(JS(ignore_default)))
{
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();
}

View File

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

View File

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

View File

@@ -30,10 +30,10 @@ doBookOffers(Context const& context)
if (request.contains("book"))
{
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()))
return Status{Error::rpcINVALID_PARAMS, "invalidBook"};
return Status{RippledError::rpcINVALID_PARAMS, "invalidBook"};
}
else
{

View File

@@ -28,13 +28,14 @@ doChannelAuthorize(Context const& context)
boost::json::object response = {};
if (!request.contains(JS(amount)))
return Status{Error::rpcINVALID_PARAMS, "missingAmount"};
return Status{RippledError::rpcINVALID_PARAMS, "missingAmount"};
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)))
return Status{Error::rpcINVALID_PARAMS, "missingKeyTypeOrSecret"};
return Status{
RippledError::rpcINVALID_PARAMS, "missingKeyTypeOrSecret"};
auto v = keypairFromRequst(request);
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());
if (!optDrops)
return Status{Error::rpcCHANNEL_AMT_MALFORMED, "couldNotParseAmount"};
return Status{
RippledError::rpcCHANNEL_AMT_MALFORMED, "couldNotParseAmount"};
std::uint64_t drops = *optDrops;
@@ -66,7 +68,7 @@ doChannelAuthorize(Context const& context)
}
catch (std::exception&)
{
return Status{Error::rpcINTERNAL};
return Status{RippledError::rpcINTERNAL};
}
return response;

View File

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

View File

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

View File

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

View File

@@ -41,7 +41,7 @@ doLedgerData(Context const& context)
if (request.contains("out_of_order"))
{
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();
}
@@ -55,11 +55,13 @@ doLedgerData(Context const& context)
{
if (!request.at(JS(marker)).is_int64())
return Status{
Error::rpcINVALID_PARAMS, "markerNotStringOrInt"};
RippledError::rpcINVALID_PARAMS,
"markerNotStringOrInt"};
diffMarker = value_to<uint32_t>(request.at(JS(marker)));
}
else
return Status{Error::rpcINVALID_PARAMS, "markerNotString"};
return Status{
RippledError::rpcINVALID_PARAMS, "markerNotString"};
}
else
{
@@ -67,7 +69,8 @@ doLedgerData(Context const& context)
marker = ripple::uint256{};
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 &&
!context.backend->fetchLedgerObject(
*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);

View File

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

View File

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

View File

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

View File

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

View File

@@ -31,7 +31,8 @@ doNoRippleCheck(Context const& context)
if (role == "gateway")
roleGateway = true;
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;

View File

@@ -16,7 +16,7 @@ doServerInfo(Context const& context)
if (!range)
{
return Status{
Error::rpcNOT_READY,
RippledError::rpcNOT_READY,
"emptyDatabase",
"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);
if (!lgrInfo || !fees)
return Status{Error::rpcINTERNAL};
return Status{RippledError::rpcINTERNAL};
auto age = std::chrono::duration_cast<std::chrono::seconds>(
std::chrono::system_clock::now().time_since_epoch())

View File

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

View File

@@ -14,7 +14,7 @@ doTransactionEntry(Context const& context)
ripple::uint256 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);
// 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.
if (!dbResponse || dbResponse->ledgerSequence != lgrInfo.seq)
return Status{
Error::rpcTXN_NOT_FOUND,
RippledError::rpcTXN_NOT_FOUND,
"transactionNotFound",
"Transaction not found."};

View File

@@ -14,31 +14,31 @@ doTx(Context const& context)
boost::json::object response = {};
if (!request.contains(JS(transaction)))
return Status{Error::rpcINVALID_PARAMS, "specifyTransaction"};
return Status{RippledError::rpcINVALID_PARAMS, "specifyTransaction"};
if (!request.at(JS(transaction)).is_string())
return Status{Error::rpcINVALID_PARAMS, "transactionNotString"};
return Status{RippledError::rpcINVALID_PARAMS, "transactionNotString"};
ripple::uint256 hash;
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;
if (request.contains(JS(binary)))
{
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();
}
auto range = context.backend->fetchLedgerRange();
if (!range)
return Status{Error::rpcNOT_READY};
return Status{RippledError::rpcNOT_READY};
auto dbResponse = context.backend->fetchTransaction(hash, context.yield);
if (!dbResponse)
return Status{Error::rpcTXN_NOT_FOUND};
return Status{RippledError::rpcTXN_NOT_FOUND};
if (!binary)
{

View File

@@ -270,7 +270,7 @@ public:
res.set(http::field::content_type, "application/json");
res.keep_alive(req_.keep_alive());
res.body() = boost::json::serialize(
RPC::make_error(RPC::Error::rpcTOO_BUSY));
RPC::makeError(RPC::RippledError::rpcTOO_BUSY));
res.prepare_payload();
lambda_(std::move(res));
}
@@ -375,7 +375,7 @@ handle_request(
http::status::ok,
"application/json",
boost::json::serialize(
RPC::make_error(RPC::Error::rpcBAD_SYNTAX))));
RPC::makeError(RPC::RippledError::rpcBAD_SYNTAX))));
}
auto range = backend->fetchLedgerRange();
@@ -384,7 +384,7 @@ handle_request(
http::status::ok,
"application/json",
boost::json::serialize(
RPC::make_error(RPC::Error::rpcNOT_READY))));
RPC::makeError(RPC::RippledError::rpcNOT_READY))));
std::optional<RPC::Context> context = RPC::make_HttpContext(
yc,
@@ -403,7 +403,7 @@ handle_request(
http::status::ok,
"application/json",
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& result = response["result"].as_object();
@@ -418,7 +418,7 @@ handle_request(
if (auto status = std::get_if<RPC::Status>(&v))
{
counters.rpcErrored(context->method);
auto error = RPC::make_error(*status);
auto error = RPC::makeError(*status);
error["request"] = request;
result = error;
@@ -438,16 +438,16 @@ handle_request(
}
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();
if (lastCloseAge >= 60)
warnings.emplace_back(RPC::make_warning(RPC::warnRPC_OUTDATED));
warnings.emplace_back(RPC::makeWarning(RPC::warnRPC_OUTDATED));
response["warnings"] = warnings;
responseStr = boost::json::serialize(response);
if (!dosGuard.add(ip, responseStr.size()))
{
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;
// reserialize when we need to include this warning
responseStr = boost::json::serialize(response);
@@ -462,7 +462,8 @@ handle_request(
return send(httpResponse(
http::status::internal_server_error,
"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 = {};
auto sendError = [this, &request, id](auto error) {
auto e = RPC::make_error(error);
auto e = RPC::makeError(error);
if (!id.is_null())
e["id"] = id;
e["request"] = request;
@@ -292,7 +292,7 @@ public:
auto range = backend_->fetchLedgerRange();
if (!range)
return sendError(RPC::Error::rpcNOT_READY);
return sendError(RPC::RippledError::rpcNOT_READY);
std::optional<RPC::Context> context = RPC::make_WsContext(
yield,
@@ -311,7 +311,7 @@ public:
{
BOOST_LOG_TRIVIAL(warning)
<< tag() << " could not create RPC context";
return sendError(RPC::Error::rpcBAD_SYNTAX);
return sendError(RPC::RippledError::rpcBAD_SYNTAX);
}
response = getDefaultWsResponse(id);
@@ -327,7 +327,7 @@ public:
{
counters_.rpcErrored(context->method);
auto error = RPC::make_error(*status);
auto error = RPC::makeError(*status);
if (!id.is_null())
error["id"] = id;
@@ -347,22 +347,22 @@ public:
BOOST_LOG_TRIVIAL(error)
<< tag() << __func__ << " caught exception : " << e.what();
return sendError(RPC::Error::rpcINTERNAL);
return sendError(RPC::RippledError::rpcINTERNAL);
}
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();
if (lastCloseAge >= 60)
warnings.emplace_back(RPC::make_warning(RPC::warnRPC_OUTDATED));
warnings.emplace_back(RPC::makeWarning(RPC::warnRPC_OUTDATED));
response["warnings"] = warnings;
std::string responseStr = boost::json::serialize(response);
if (!dosGuard_.add(*ip, responseStr.size()))
{
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;
// reserialize if we need to include this warning
responseStr = boost::json::serialize(response);
@@ -392,7 +392,7 @@ public:
auto error,
boost::json::value const& id,
boost::json::object const& request) {
auto e = RPC::make_error(error);
auto e = RPC::makeError(error);
if (!id.is_null())
e["id"] = id;
@@ -417,14 +417,15 @@ public:
boost::json::object request;
if (!raw.is_object())
return sendError(RPC::Error::rpcINVALID_PARAMS, nullptr, request);
return sendError(
RPC::RippledError::rpcINVALID_PARAMS, nullptr, request);
request = raw.as_object();
auto id = request.contains("id") ? request.at("id") : nullptr;
if (!dosGuard_.isOk(*ip))
{
sendError(RPC::Error::rpcSLOW_DOWN, id, request);
sendError(RPC::RippledError::rpcSLOW_DOWN, id, request);
}
else
{
@@ -438,7 +439,7 @@ public:
shared_this->handle_request(std::move(r), id, yield);
},
dosGuard_.isWhiteListed(*ip)))
sendError(RPC::Error::rpcTOO_BUSY, id, request);
sendError(RPC::RippledError::rpcTOO_BUSY, id, request);
}
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)));
}