From 856af53b470df8a3daee81cec8b0dbc93b950291 Mon Sep 17 00:00:00 2001 From: Scott Schurr Date: Tue, 5 Apr 2022 12:16:45 -0700 Subject: [PATCH] RPC tooBusy response now has 503 HTTP status code: Fixes #4005 Makes it possible for internal RPC Error Codes to associate themselves with a non-OK (200) HTTP status code. There are a few RPC responses in addition to tooBusy that now have non-OK HTTP status codes. --- src/ripple/protocol/ErrorCodes.h | 24 ++- src/ripple/protocol/impl/ErrorCodes.cpp | 192 +++++++++-------------- src/ripple/rpc/impl/ServerHandlerImp.cpp | 30 +++- 3 files changed, 129 insertions(+), 117 deletions(-) diff --git a/src/ripple/protocol/ErrorCodes.h b/src/ripple/protocol/ErrorCodes.h index 45fa7da2911..3d4abd90967 100644 --- a/src/ripple/protocol/ErrorCodes.h +++ b/src/ripple/protocol/ErrorCodes.h @@ -165,7 +165,10 @@ struct ErrorInfo { // Default ctor needed to produce an empty std::array during constexpr eval. constexpr ErrorInfo() - : code(rpcUNKNOWN), token("unknown"), message("An unknown error code.") + : code(rpcUNKNOWN) + , token("unknown") + , message("An unknown error code.") + , http_status(200) { } @@ -173,13 +176,26 @@ struct ErrorInfo error_code_i code_, char const* token_, char const* message_) - : code(code_), token(token_), message(message_) + : code(code_), token(token_), message(message_), http_status(200) + { + } + + constexpr ErrorInfo( + error_code_i code_, + char const* token_, + char const* message_, + int http_status_) + : code(code_) + , token(token_) + , message(message_) + , http_status(http_status_) { } error_code_i code; Json::StaticString token; Json::StaticString message; + int http_status; }; /** Returns an ErrorInfo that reflects the error code. */ @@ -329,6 +345,10 @@ not_validator_error() bool contains_error(Json::Value const& json); +/** Returns http status that corresponds to the error code. */ +int +error_code_http_status(error_code_i code); + } // namespace RPC /** Returns a single string with the contents of an RPC error. */ diff --git a/src/ripple/protocol/impl/ErrorCodes.cpp b/src/ripple/protocol/impl/ErrorCodes.cpp index 87fb2da2af3..300710e9a10 100644 --- a/src/ripple/protocol/impl/ErrorCodes.cpp +++ b/src/ripple/protocol/impl/ErrorCodes.cpp @@ -18,6 +18,7 @@ //============================================================================== #include +#include #include #include @@ -31,121 +32,80 @@ namespace detail { // // This array will be omitted from the object file; only the sorted version // will remain in the object file. But the string literals will remain. -constexpr static ErrorInfo unorderedErrorInfos[]{ - {rpcACT_MALFORMED, "actMalformed", "Account malformed."}, - {rpcACT_NOT_FOUND, "actNotFound", "Account not found."}, - {rpcALREADY_MULTISIG, "alreadyMultisig", "Already multisigned."}, - {rpcALREADY_SINGLE_SIG, "alreadySingleSig", "Already single-signed."}, - {rpcAMENDMENT_BLOCKED, - "amendmentBlocked", - "Amendment blocked, need upgrade."}, - {rpcEXPIRED_VALIDATOR_LIST, "unlBlocked", "Validator list expired."}, - {rpcATX_DEPRECATED, - "deprecated", - "Use the new API or specify a ledger range."}, - {rpcBAD_KEY_TYPE, "badKeyType", "Bad key type."}, - {rpcBAD_FEATURE, "badFeature", "Feature unknown or invalid."}, - {rpcBAD_ISSUER, "badIssuer", "Issuer account malformed."}, - {rpcBAD_MARKET, "badMarket", "No such market."}, - {rpcBAD_SECRET, "badSecret", "Secret does not match account."}, - {rpcBAD_SEED, "badSeed", "Disallowed seed."}, - {rpcBAD_SYNTAX, "badSyntax", "Syntax error."}, - {rpcCHANNEL_MALFORMED, "channelMalformed", "Payment channel is malformed."}, - {rpcCHANNEL_AMT_MALFORMED, - "channelAmtMalformed", - "Payment channel amount is malformed."}, - {rpcCOMMAND_MISSING, "commandMissing", "Missing command entry."}, - {rpcDB_DESERIALIZATION, - "dbDeserialization", - "Database deserialization error."}, - {rpcDST_ACT_MALFORMED, - "dstActMalformed", - "Destination account is malformed."}, - {rpcDST_ACT_MISSING, "dstActMissing", "Destination account not provided."}, - {rpcDST_ACT_NOT_FOUND, "dstActNotFound", "Destination account not found."}, - {rpcDST_AMT_MALFORMED, - "dstAmtMalformed", - "Destination amount/currency/issuer is malformed."}, - {rpcDST_AMT_MISSING, - "dstAmtMissing", - "Destination amount/currency/issuer is missing."}, - {rpcDST_ISR_MALFORMED, - "dstIsrMalformed", - "Destination issuer is malformed."}, - {rpcEXCESSIVE_LGR_RANGE, "excessiveLgrRange", "Ledger range exceeds 1000."}, - {rpcFORBIDDEN, "forbidden", "Bad credentials."}, - {rpcFAILED_TO_FORWARD, - "failedToForward", - "Failed to forward request to p2p node"}, - {rpcHIGH_FEE, "highFee", "Current transaction fee exceeds your limit."}, - {rpcINTERNAL, "internal", "Internal error."}, - {rpcINVALID_LGR_RANGE, "invalidLgrRange", "Ledger range is invalid."}, - {rpcINVALID_PARAMS, "invalidParams", "Invalid parameters."}, - {rpcJSON_RPC, "json_rpc", "JSON-RPC transport error."}, - {rpcLGR_IDXS_INVALID, "lgrIdxsInvalid", "Ledger indexes invalid."}, - {rpcLGR_IDX_MALFORMED, "lgrIdxMalformed", "Ledger index malformed."}, - {rpcLGR_NOT_FOUND, "lgrNotFound", "Ledger not found."}, - {rpcLGR_NOT_VALIDATED, "lgrNotValidated", "Ledger not validated."}, - {rpcMASTER_DISABLED, "masterDisabled", "Master key is disabled."}, - {rpcNOT_ENABLED, "notEnabled", "Not enabled in configuration."}, - {rpcNOT_IMPL, "notImpl", "Not implemented."}, - {rpcNOT_READY, "notReady", "Not ready to handle this request."}, - {rpcNOT_SUPPORTED, "notSupported", "Operation not supported."}, - {rpcNO_CLOSED, "noClosed", "Closed ledger is unavailable."}, - {rpcNO_CURRENT, "noCurrent", "Current ledger is unavailable."}, - {rpcNOT_SYNCED, "notSynced", "Not synced to the network."}, - {rpcNO_EVENTS, "noEvents", "Current transport does not support events."}, - {rpcNO_NETWORK, "noNetwork", "Not synced to the network."}, - {rpcNO_PERMISSION, - "noPermission", - "You don't have permission for this command."}, - {rpcNO_PF_REQUEST, "noPathRequest", "No pathfinding request in progress."}, - {rpcPUBLIC_MALFORMED, "publicMalformed", "Public key is malformed."}, - {rpcREPORTING_UNSUPPORTED, - "reportingUnsupported", - "Requested operation not supported by reporting mode server"}, - {rpcSIGNING_MALFORMED, - "signingMalformed", - "Signing of transaction is malformed."}, - {rpcSLOW_DOWN, "slowDown", "You are placing too much load on the server."}, - {rpcSRC_ACT_MALFORMED, "srcActMalformed", "Source account is malformed."}, - {rpcSRC_ACT_MISSING, "srcActMissing", "Source account not provided."}, - {rpcSRC_ACT_NOT_FOUND, "srcActNotFound", "Source account not found."}, - {rpcSRC_CUR_MALFORMED, "srcCurMalformed", "Source currency is malformed."}, - {rpcSRC_ISR_MALFORMED, "srcIsrMalformed", "Source issuer is malformed."}, - {rpcSTREAM_MALFORMED, "malformedStream", "Stream malformed."}, - {rpcTOO_BUSY, "tooBusy", "The server is too busy to help you now."}, - {rpcTXN_NOT_FOUND, "txnNotFound", "Transaction not found."}, - {rpcUNKNOWN_COMMAND, "unknownCmd", "Unknown method."}, - {rpcSENDMAX_MALFORMED, "sendMaxMalformed", "SendMax amount malformed."}}; - -// C++ does not allow you to return an array from a function. You must -// return an object which may in turn contain an array. The following -// struct is simply defined so the enclosed array can be returned from a -// constexpr function. -// -// In C++17 this struct can be replaced by a std::array. But in C++14 -// the constexpr methods of a std::array are not sufficient to perform the -// necessary work at compile time. -template -struct ErrorInfoArray -{ - // Visual Studio doesn't treat a templated aggregate as an aggregate. - // So, for Visual Studio, we define a constexpr default constructor. - constexpr ErrorInfoArray() : infos{} - { - } - ErrorInfo infos[N]; -}; +// clang-format off +constexpr static ErrorInfo unorderedErrorInfos[]{ + {rpcACT_MALFORMED, "actMalformed", "Account malformed."}, + {rpcACT_NOT_FOUND, "actNotFound", "Account not found."}, + {rpcALREADY_MULTISIG, "alreadyMultisig", "Already multisigned."}, + {rpcALREADY_SINGLE_SIG, "alreadySingleSig", "Already single-signed."}, + {rpcAMENDMENT_BLOCKED, "amendmentBlocked", "Amendment blocked, need upgrade."}, + {rpcEXPIRED_VALIDATOR_LIST, "unlBlocked", "Validator list expired."}, + {rpcATX_DEPRECATED, "deprecated", "Use the new API or specify a ledger range."}, + {rpcBAD_KEY_TYPE, "badKeyType", "Bad key type."}, + {rpcBAD_FEATURE, "badFeature", "Feature unknown or invalid."}, + {rpcBAD_ISSUER, "badIssuer", "Issuer account malformed."}, + {rpcBAD_MARKET, "badMarket", "No such market."}, + {rpcBAD_SECRET, "badSecret", "Secret does not match account."}, + {rpcBAD_SEED, "badSeed", "Disallowed seed."}, + {rpcBAD_SYNTAX, "badSyntax", "Syntax error.", 400}, + {rpcCHANNEL_MALFORMED, "channelMalformed", "Payment channel is malformed."}, + {rpcCHANNEL_AMT_MALFORMED, "channelAmtMalformed", "Payment channel amount is malformed."}, + {rpcCOMMAND_MISSING, "commandMissing", "Missing command entry."}, + {rpcDB_DESERIALIZATION, "dbDeserialization", "Database deserialization error."}, + {rpcDST_ACT_MALFORMED, "dstActMalformed", "Destination account is malformed."}, + {rpcDST_ACT_MISSING, "dstActMissing", "Destination account not provided."}, + {rpcDST_ACT_NOT_FOUND, "dstActNotFound", "Destination account not found."}, + {rpcDST_AMT_MALFORMED, "dstAmtMalformed", "Destination amount/currency/issuer is malformed."}, + {rpcDST_AMT_MISSING, "dstAmtMissing", "Destination amount/currency/issuer is missing."}, + {rpcDST_ISR_MALFORMED, "dstIsrMalformed", "Destination issuer is malformed."}, + {rpcEXCESSIVE_LGR_RANGE, "excessiveLgrRange", "Ledger range exceeds 1000."}, + {rpcFORBIDDEN, "forbidden", "Bad credentials."}, + {rpcFAILED_TO_FORWARD, "failedToForward", "Failed to forward request to p2p node"}, + {rpcHIGH_FEE, "highFee", "Current transaction fee exceeds your limit."}, + {rpcINTERNAL, "internal", "Internal error.", 500}, + {rpcINVALID_LGR_RANGE, "invalidLgrRange", "Ledger range is invalid."}, + {rpcINVALID_PARAMS, "invalidParams", "Invalid parameters.", 400}, + {rpcJSON_RPC, "json_rpc", "JSON-RPC transport error."}, + {rpcLGR_IDXS_INVALID, "lgrIdxsInvalid", "Ledger indexes invalid."}, + {rpcLGR_IDX_MALFORMED, "lgrIdxMalformed", "Ledger index malformed."}, + {rpcLGR_NOT_FOUND, "lgrNotFound", "Ledger not found."}, + {rpcLGR_NOT_VALIDATED, "lgrNotValidated", "Ledger not validated."}, + {rpcMASTER_DISABLED, "masterDisabled", "Master key is disabled."}, + {rpcNOT_ENABLED, "notEnabled", "Not enabled in configuration."}, + {rpcNOT_IMPL, "notImpl", "Not implemented.", 501}, + {rpcNOT_READY, "notReady", "Not ready to handle this request."}, + {rpcNOT_SUPPORTED, "notSupported", "Operation not supported.", 501}, + {rpcNO_CLOSED, "noClosed", "Closed ledger is unavailable."}, + {rpcNO_CURRENT, "noCurrent", "Current ledger is unavailable."}, + {rpcNOT_SYNCED, "notSynced", "Not synced to the network."}, + {rpcNO_EVENTS, "noEvents", "Current transport does not support events."}, + {rpcNO_NETWORK, "noNetwork", "Not synced to the network."}, + {rpcNO_PERMISSION, "noPermission", "You don't have permission for this command."}, + {rpcNO_PF_REQUEST, "noPathRequest", "No pathfinding request in progress."}, + {rpcPUBLIC_MALFORMED, "publicMalformed", "Public key is malformed."}, + {rpcREPORTING_UNSUPPORTED, "reportingUnsupported", "Requested operation not supported by reporting mode server"}, + {rpcSENDMAX_MALFORMED, "sendMaxMalformed", "SendMax amount malformed."}, + {rpcSIGNING_MALFORMED, "signingMalformed", "Signing of transaction is malformed."}, + {rpcSLOW_DOWN, "slowDown", "You are placing too much load on the server.", 429}, + {rpcSRC_ACT_MALFORMED, "srcActMalformed", "Source account is malformed."}, + {rpcSRC_ACT_MISSING, "srcActMissing", "Source account not provided."}, + {rpcSRC_ACT_NOT_FOUND, "srcActNotFound", "Source account not found."}, + {rpcSRC_CUR_MALFORMED, "srcCurMalformed", "Source currency is malformed."}, + {rpcSRC_ISR_MALFORMED, "srcIsrMalformed", "Source issuer is malformed."}, + {rpcSTREAM_MALFORMED, "malformedStream", "Stream malformed."}, + {rpcTOO_BUSY, "tooBusy", "The server is too busy to help you now.", 503}, + {rpcTXN_NOT_FOUND, "txnNotFound", "Transaction not found."}, + {rpcUNKNOWN_COMMAND, "unknownCmd", "Unknown method."}}; +// clang-format on // Sort and validate unorderedErrorInfos at compile time. Should be // converted to consteval when get to C++20. template constexpr auto -sortErrorInfos(ErrorInfo const (&unordered)[N]) -> ErrorInfoArray +sortErrorInfos(ErrorInfo const (&unordered)[N]) -> std::array { - ErrorInfoArray ret; + std::array ret = {}; for (ErrorInfo const& info : unordered) { @@ -156,12 +116,10 @@ sortErrorInfos(ErrorInfo const (&unordered)[N]) -> ErrorInfoArray static_assert(rpcSUCCESS == 0, "Unexpected error_code_i layout."); int const index{info.code - 1}; - if (ret.infos[index].code != rpcUNKNOWN) + if (ret[index].code != rpcUNKNOWN) throw(std::invalid_argument("Duplicate error_code_i in list")); - ret.infos[index].code = info.code; - ret.infos[index].token = info.token; - ret.infos[index].message = info.message; + ret[index] = info; } // Verify that all entries are filled in starting with 1 and proceeding @@ -171,7 +129,7 @@ sortErrorInfos(ErrorInfo const (&unordered)[N]) -> ErrorInfoArray // rpcUNKNOWN. But other than that all entries should match their index. int codeCount{0}; int expect{rpcBAD_SYNTAX - 1}; - for (ErrorInfo const& info : ret.infos) + for (ErrorInfo const& info : ret) { ++expect; if (info.code == rpcUNKNOWN) @@ -202,7 +160,7 @@ get_error_info(error_code_i code) { if (code <= rpcSUCCESS || code > rpcLAST) return detail::unknownError; - return detail::sortedErrorInfos.infos[code - 1]; + return detail::sortedErrorInfos[code - 1]; } Json::Value @@ -229,6 +187,12 @@ contains_error(Json::Value const& json) return false; } +int +error_code_http_status(error_code_i code) +{ + return get_error_info(code).http_status; +} + } // namespace RPC std::string diff --git a/src/ripple/rpc/impl/ServerHandlerImp.cpp b/src/ripple/rpc/impl/ServerHandlerImp.cpp index 3ac5f04a9d6..ff54563cc85 100644 --- a/src/ripple/rpc/impl/ServerHandlerImp.cpp +++ b/src/ripple/rpc/impl/ServerHandlerImp.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -956,6 +957,33 @@ ServerHandlerImp::processRequest( } } } + + // If we're returning an error_code, use that to determine the HTTP status. + int const httpStatus = [&reply]() { + // Handle ripplerpc 1.0. + if (reply.isMember(jss::result) && + reply[jss::result].isMember(jss::error_code) && + reply[jss::result][jss::error_code].isInt()) + { + int const errCode = reply[jss::result][jss::error_code].asInt(); + return RPC::error_code_http_status( + static_cast(errCode)); + } + + // Handle ripplerpc 2.0 and greater. + if (reply.isMember(jss::error) && + reply[jss::error].isMember(jss::error_code) && + reply[jss::error][jss::error_code].isInt()) + { + int const errCode = reply[jss::error][jss::error_code].asInt(); + return RPC::error_code_http_status( + static_cast(errCode)); + } + + // No error code found. Return OK. + return 200; + }(); + auto response = to_string(reply); rpc_time_.notify(std::chrono::duration_cast( @@ -974,7 +1002,7 @@ ServerHandlerImp::processRequest( stream << "Reply: " << response.substr(0, maxSize); } - HTTPReply(200, response, output, rpcJ); + HTTPReply(httpStatus, response, output, rpcJ); } //------------------------------------------------------------------------------