From 9d07ddeae106a4768288bad7f2602e30eb6778e7 Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Thu, 12 Dec 2013 21:17:54 -0500 Subject: [PATCH] Improved human readable JSON-RPC error messages --- Builds/VisualStudio2012/RippleD.vcxproj | 14 + .../VisualStudio2012/RippleD.vcxproj.filters | 12 + src/ripple/rpc/api/ErrorCodes.h | 208 +++++ src/ripple/rpc/impl/ErrorCodes.cpp | 169 +++++ src/ripple/rpc/ripple_rpc.cpp | 1 + src/ripple/rpc/ripple_rpc.h | 1 + src/ripple_app/misc/SerializedTransaction.cpp | 8 +- src/ripple_app/ripple_app_pt8.cpp | 2 + src/ripple_app/rpc/RPCHandler.cpp | 587 ++++++++------ src/ripple_data/protocol/STParsedJSON.cpp | 717 ++++++++++++++++++ src/ripple_data/protocol/STParsedJSON.h | 84 ++ src/ripple_data/protocol/SerializedObject.cpp | 399 +--------- src/ripple_data/protocol/SerializedObject.h | 39 +- .../protocol/SerializedObjectTemplate.cpp | 2 +- .../protocol/SerializedObjectTemplate.h | 16 +- src/ripple_data/protocol/SerializedTypes.h | 55 +- src/ripple_data/ripple_data.cpp | 3 + src/ripple_data/ripple_data.h | 2 + src/ripple_net/ripple_net.cpp | 6 + src/ripple_net/rpc/RPCCall.cpp | 3 +- src/ripple_net/rpc/RPCErr.cpp | 104 +-- src/ripple_net/rpc/RPCErr.h | 89 +-- 22 files changed, 1682 insertions(+), 839 deletions(-) create mode 100644 src/ripple/rpc/api/ErrorCodes.h create mode 100644 src/ripple/rpc/impl/ErrorCodes.cpp create mode 100644 src/ripple_data/protocol/STParsedJSON.cpp create mode 100644 src/ripple_data/protocol/STParsedJSON.h diff --git a/Builds/VisualStudio2012/RippleD.vcxproj b/Builds/VisualStudio2012/RippleD.vcxproj index 8982619cab..1c8697aae5 100644 --- a/Builds/VisualStudio2012/RippleD.vcxproj +++ b/Builds/VisualStudio2012/RippleD.vcxproj @@ -604,6 +604,12 @@ true true + + true + true + true + true + true true @@ -1732,6 +1738,12 @@ true true + + true + true + true + true + true true @@ -2326,6 +2338,7 @@ + @@ -2561,6 +2574,7 @@ + diff --git a/Builds/VisualStudio2012/RippleD.vcxproj.filters b/Builds/VisualStudio2012/RippleD.vcxproj.filters index d4143c7127..83a6202470 100644 --- a/Builds/VisualStudio2012/RippleD.vcxproj.filters +++ b/Builds/VisualStudio2012/RippleD.vcxproj.filters @@ -1437,6 +1437,12 @@ [2] Old Ripple\ripple_app\peers + + [2] Old Ripple\ripple_data\protocol + + + [1] Ripple\rpc\impl + @@ -2916,6 +2922,12 @@ [2] Old Ripple\ripple_app\peers + + [2] Old Ripple\ripple_data\protocol + + + [1] Ripple\rpc\api + diff --git a/src/ripple/rpc/api/ErrorCodes.h b/src/ripple/rpc/api/ErrorCodes.h new file mode 100644 index 0000000000..ddca504d1a --- /dev/null +++ b/src/ripple/rpc/api/ErrorCodes.h @@ -0,0 +1,208 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2012, 2013 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef RIPPLE_RPC_ERRORCODES_H_INCLUDED +#define RIPPLE_RPC_ERRORCODES_H_INCLUDED + +namespace ripple { + +// VFALCO NOTE These are outside the RPC namespace + +enum error_code_i +{ + rpcUNKNOWN = -1, // Represents codes not listed in this enumeration + + rpcSUCCESS = 0, + + rpcBAD_SYNTAX, // Must be 1 to print usage to command line. + rpcJSON_RPC, + rpcFORBIDDEN, + + // Error numbers beyond this line are not stable between versions. + // Programs should use error tokens. + + // Misc failure + rpcLOAD_FAILED, + rpcNO_PERMISSION, + rpcNO_EVENTS, + rpcNOT_STANDALONE, + rpcTOO_BUSY, + rpcSLOW_DOWN, + + // Networking + rpcNO_CLOSED, + rpcNO_CURRENT, + rpcNO_NETWORK, + + // Ledger state + rpcACT_EXISTS, + rpcACT_NOT_FOUND, + rpcINSUF_FUNDS, + rpcLGR_NOT_FOUND, + rpcNICKNAME_MISSING, + rpcNO_ACCOUNT, + rpcNO_PATH, + rpcPASSWD_CHANGED, + rpcSRC_MISSING, + rpcSRC_UNCLAIMED, + rpcTXN_NOT_FOUND, + rpcWRONG_SEED, + + // Malformed command + rpcINVALID_PARAMS, + rpcUNKNOWN_COMMAND, + rpcNO_PF_REQUEST, + + // Bad parameter + rpcACT_BITCOIN, + rpcACT_MALFORMED, + rpcQUALITY_MALFORMED, + rpcBAD_BLOB, + rpcBAD_FEATURE, + rpcBAD_ISSUER, + rpcBAD_MARKET, + rpcBAD_SECRET, + rpcBAD_SEED, + rpcCOMMAND_MISSING, + rpcDST_ACT_MALFORMED, + rpcDST_ACT_MISSING, + rpcDST_AMT_MALFORMED, + rpcDST_ISR_MALFORMED, + rpcGETS_ACT_MALFORMED, + rpcGETS_AMT_MALFORMED, + rpcHOST_IP_MALFORMED, + rpcLGR_IDXS_INVALID, + rpcLGR_IDX_MALFORMED, + rpcNICKNAME_MALFORMED, + rpcNICKNAME_PERM, + rpcPAYS_ACT_MALFORMED, + rpcPAYS_AMT_MALFORMED, + rpcPORT_MALFORMED, + rpcPUBLIC_MALFORMED, + rpcSRC_ACT_MALFORMED, + rpcSRC_ACT_MISSING, + rpcSRC_ACT_NOT_FOUND, + rpcSRC_AMT_MALFORMED, + rpcSRC_CUR_MALFORMED, + rpcSRC_ISR_MALFORMED, + rpcATX_DEPRECATED, + + // Internal error (should never happen) + rpcINTERNAL, // Generic internal error. + rpcFAIL_GEN_DECRPYT, + rpcNOT_IMPL, + rpcNOT_SUPPORTED, + rpcNO_GEN_DECRPYT, +}; + +//------------------------------------------------------------------------------ + +namespace RPC { + +/** Maps an rpc error code to its token and default message. */ +struct ErrorInfo +{ + ErrorInfo (error_code_i code_, std::string const& token_, + std::string const& message_) + : code (code_) + , token (token_) + , message (message_) + { } + + error_code_i code; + std::string token; + std::string message; +}; + +/** Returns an ErrorInfo that reflects the error code. */ +ErrorInfo const& get_error_info (error_code_i code); + +/** Add or update the json update to reflect the error code. */ +/** @{ */ +void inject_error (error_code_i code, Json::Value& json); +inline void inject_error (int code, Json::Value& json) + { inject_error (error_code_i (code), json); } +void inject_error (error_code_i code, std::string const& message, Json::Value& json); +/** @} */ + +/** Returns a new json object that reflects the error code. */ +/** @{ */ +Json::Value make_error (error_code_i code); +Json::Value make_error (error_code_i code, std::string const& message); +/** @} */ + +/** Returns a new json object that indicates invalid parameters. */ +/** @{ */ +inline Json::Value make_param_error (std::string const& message) +{ + return make_error (rpcINVALID_PARAMS, message); +} + +inline std::string missing_field_message (std::string const& name) +{ + return "Missing field '" + name + "'."; +} + +inline Json::Value missing_field_error (std::string const& name) +{ + return make_param_error (missing_field_message (name)); +} + +inline std::string object_field_message (std::string const& name) +{ + return "Invalid field '" + name + "', not object."; +} + +inline Json::Value object_field_error (std::string const& name) +{ + return make_param_error (object_field_message (name)); +} + +inline std::string invalid_field_message (std::string const& name) +{ + return "Invalid field '" + name + "'."; +} + +inline Json::Value invalid_field_error (std::string const& name) +{ + return make_param_error (object_field_message (name)); +} + +inline std::string expected_field_message ( + std::string const& name, std::string const& type) +{ + return "Invalid field '" + name + "', not " + type + "."; +} + +inline Json::Value expected_field_error ( + std::string const& name, std::string const& type) +{ + return make_param_error (expected_field_message (name, type)); +} + +/** @} */ + +/** Returns `true` if the json contains an rpc error specification. */ +bool contains_error (Json::Value const& json); + +} + +} + +#endif diff --git a/src/ripple/rpc/impl/ErrorCodes.cpp b/src/ripple/rpc/impl/ErrorCodes.cpp new file mode 100644 index 0000000000..05ecf03ee7 --- /dev/null +++ b/src/ripple/rpc/impl/ErrorCodes.cpp @@ -0,0 +1,169 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2012, 2013 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +namespace ripple { +namespace RPC { + +namespace detail { + +class ErrorCategory +{ +public: + typedef boost::unordered_map Map; + + ErrorCategory () + : m_unknown (rpcUNKNOWN, "unknown", "An unknown error code.") + { + add (rpcACT_BITCOIN, "actBitcoin", "Account is bitcoin address."); + add (rpcACT_EXISTS, "actExists", "Account already exists."); + add (rpcACT_MALFORMED, "actMalformed", "Account malformed."); + add (rpcACT_NOT_FOUND, "actNotFound", "Account not found."); + add (rpcBAD_BLOB, "badBlob", "Blob must be a non-empty hex string."); + add (rpcBAD_FEATURE, "badFeature", "Feature unknown or invalid."); + add (rpcBAD_ISSUER, "badIssuer", "Issuer account malformed."); + add (rpcBAD_MARKET, "badMarket", "No such market."); + add (rpcBAD_SECRET, "badSecret", "Secret does not match account."); + add (rpcBAD_SEED, "badSeed", "Disallowed seed."); + add (rpcBAD_SYNTAX, "badSyntax", "Syntax error."); + add (rpcCOMMAND_MISSING, "commandMissing", "Missing command entry."); + add (rpcDST_ACT_MALFORMED, "dstActMalformed", "Destination account is malformed."); + add (rpcDST_ACT_MISSING, "dstActMissing", "Destination account does not exist."); + add (rpcDST_AMT_MALFORMED, "dstAmtMalformed", "Destination amount/currency/issuer is malformed."); + add (rpcDST_ISR_MALFORMED, "dstIsrMalformed", "Destination issuer is malformed."); + add (rpcFORBIDDEN, "forbidden", "Bad credentials."); + add (rpcFAIL_GEN_DECRPYT, "failGenDecrypt", "Failed to decrypt generator."); + add (rpcGETS_ACT_MALFORMED, "getsActMalformed", "Gets account malformed."); + add (rpcGETS_AMT_MALFORMED, "getsAmtMalformed", "Gets amount malformed."); + add (rpcHOST_IP_MALFORMED, "hostIpMalformed", "Host IP is malformed."); + add (rpcINSUF_FUNDS, "insufFunds", "Insufficient funds."); + add (rpcINTERNAL, "internal", "Internal error."); + add (rpcINVALID_PARAMS, "invalidParams", "Invalid parameters."); + add (rpcJSON_RPC, "json_rpc", "JSON-RPC transport error."); + add (rpcLGR_IDXS_INVALID, "lgrIdxsInvalid", "Ledger indexes invalid."); + add (rpcLGR_IDX_MALFORMED, "lgrIdxMalformed", "Ledger index malformed."); + add (rpcLGR_NOT_FOUND, "lgrNotFound", "Ledger not found."); + add (rpcNICKNAME_MALFORMED, "nicknameMalformed", "Nickname is malformed."); + add (rpcNICKNAME_MISSING, "nicknameMissing", "Nickname does not exist."); + add (rpcNICKNAME_PERM, "nicknamePerm", "Account does not control nickname."); + add (rpcNOT_IMPL, "notImpl", "Not implemented."); + add (rpcNO_ACCOUNT, "noAccount", "No such account."); + add (rpcNO_CLOSED, "noClosed", "Closed ledger is unavailable."); + add (rpcNO_CURRENT, "noCurrent", "Current ledger is unavailable."); + add (rpcNO_EVENTS, "noEvents", "Current transport does not support events."); + add (rpcNO_GEN_DECRPYT, "noGenDectypt", "Password failed to decrypt master public generator."); + add (rpcNO_NETWORK, "noNetwork", "Network not available."); + add (rpcNO_PATH, "noPath", "Unable to find a ripple path."); + add (rpcNO_PERMISSION, "noPermission", "You don't have permission for this command."); + add (rpcNO_PF_REQUEST, "noPathRequest", "No pathfinding request in progress."); + add (rpcNOT_STANDALONE, "notStandAlone", "Operation valid in debug mode only."); + add (rpcNOT_SUPPORTED, "notSupported", "Operation not supported."); + add (rpcPASSWD_CHANGED, "passwdChanged", "Wrong key, password changed."); + add (rpcPAYS_ACT_MALFORMED, "paysActMalformed", "Pays account malformed."); + add (rpcPAYS_AMT_MALFORMED, "paysAmtMalformed", "Pays amount malformed."); + add (rpcPORT_MALFORMED, "portMalformed", "Port is malformed."); + add (rpcPUBLIC_MALFORMED, "publicMalformed", "Public key is malformed."); + add (rpcQUALITY_MALFORMED, "qualityMalformed", "Quality malformed."); + add (rpcSRC_ACT_MALFORMED, "srcActMalformed", "Source account is malformed."); + add (rpcSRC_ACT_MISSING, "srcActMissing", "Source account not provided."); + add (rpcSRC_ACT_NOT_FOUND, "srcActNotFound", "Source account not found."); + add (rpcSRC_AMT_MALFORMED, "srcAmtMalformed", "Source amount/currency/issuer is malformed."); + add (rpcSRC_CUR_MALFORMED, "srcCurMalformed", "Source currency is malformed."); + add (rpcSRC_ISR_MALFORMED, "srcIsrMalformed", "Source issuer is malformed."); + add (rpcSRC_UNCLAIMED, "srcUnclaimed", "Source account is not claimed."); + add (rpcTXN_NOT_FOUND, "txnNotFound", "Transaction not found."); + add (rpcUNKNOWN_COMMAND, "unknownCmd", "Unknown method."); + add (rpcWRONG_SEED, "wrongSeed", "The regular key does not point as the master key."); + add (rpcTOO_BUSY, "tooBusy", "The server is too busy to help you now."); + add (rpcSLOW_DOWN, "slowDown", "You are placing too much load on the server."); + add (rpcATX_DEPRECATED, "deprecated", "Use the new API or specify a ledger range."); + } + + ErrorInfo const& get (error_code_i code) const + { + Map::const_iterator const iter (m_map.find (code)); + if (iter != m_map.end()) + return iter->second; + return m_unknown; + } + +private: + void add (error_code_i code, std::string const& token, + std::string const& message) + { + std::pair result ( + m_map.emplace (boost::unordered::piecewise_construct, + boost::make_tuple (code), boost::make_tuple ( + code, token, message))); + check_postcondition (result.second); + } + +private: + Map m_map; + ErrorInfo m_unknown; +}; + +} + +//------------------------------------------------------------------------------ + +ErrorInfo const& get_error_info (error_code_i code) +{ + static detail::ErrorCategory category; + return category.get (code); +} + +void inject_error (error_code_i code, Json::Value& json) +{ + ErrorInfo const& info (get_error_info (code)); + json ["error"] = info.token; + json ["error_code"] = info.code; + json ["error_message"] = info.message; +} + +void inject_error (error_code_i code, std::string const& message, Json::Value& json) +{ + ErrorInfo const& info (get_error_info (code)); + json ["error"] = info.token; + json ["error_code"] = info.code; + json ["error_message"] = message; +} + +Json::Value make_error (error_code_i code) +{ + Json::Value json; + inject_error (code, json); + return json; +} + +Json::Value make_error (error_code_i code, std::string const& message) +{ + Json::Value json; + inject_error (code, message, json); + return json; +} + +bool contains_error (Json::Value const& json) +{ + if (json.isObject() && json.isMember ("error")) + return true; + return false; +} + +} +} diff --git a/src/ripple/rpc/ripple_rpc.cpp b/src/ripple/rpc/ripple_rpc.cpp index 291ac130e2..f6195d4a87 100644 --- a/src/ripple/rpc/ripple_rpc.cpp +++ b/src/ripple/rpc/ripple_rpc.cpp @@ -24,6 +24,7 @@ #include "beast/modules/beast_core/system/BeforeBoost.h" #include +#include "impl/ErrorCodes.cpp" # include "impl/ManagerImpl.h" #include "impl/Manager.cpp" #include "impl/Handler.cpp" diff --git a/src/ripple/rpc/ripple_rpc.h b/src/ripple/rpc/ripple_rpc.h index 7b58e3f704..12aeabc69d 100644 --- a/src/ripple/rpc/ripple_rpc.h +++ b/src/ripple/rpc/ripple_rpc.h @@ -27,5 +27,6 @@ # include "api/Handler.h" # include "api/Service.h" #include "api/Manager.h" +#include "api/ErrorCodes.h" #endif diff --git a/src/ripple_app/misc/SerializedTransaction.cpp b/src/ripple_app/misc/SerializedTransaction.cpp index 3f4dfc95d4..de056793e9 100644 --- a/src/ripple_app/misc/SerializedTransaction.cpp +++ b/src/ripple_app/misc/SerializedTransaction.cpp @@ -344,9 +344,11 @@ public: pass (); } - std::unique_ptr new_obj = STObject::parseJson (j.getJson (0), sfGeneric); + STParsedJSON parsed ("test", j.getJson (0)); + std::unique_ptr new_obj (std::move (parsed.object)); - if (new_obj.get () == NULL) fail ("Unable to build object from json"); + if (new_obj.get () == nullptr) + fail ("Unable to build object from json"); if (STObject (j) != *new_obj) { @@ -361,4 +363,4 @@ public: } }; -static SerializedTransactionTests serializedTransactionTests; \ No newline at end of file +static SerializedTransactionTests serializedTransactionTests; diff --git a/src/ripple_app/ripple_app_pt8.cpp b/src/ripple_app/ripple_app_pt8.cpp index 01b20f958e..42814d546a 100644 --- a/src/ripple_app/ripple_app_pt8.cpp +++ b/src/ripple_app/ripple_app_pt8.cpp @@ -26,6 +26,8 @@ #pragma warning (disable: 4309) // truncation of constant value #endif +#include "../ripple/rpc/api/ErrorCodes.h" + namespace ripple { diff --git a/src/ripple_app/rpc/RPCHandler.cpp b/src/ripple_app/rpc/RPCHandler.cpp index 40c8446ff3..c1c84b1c8f 100644 --- a/src/ripple_app/rpc/RPCHandler.cpp +++ b/src/ripple_app/rpc/RPCHandler.cpp @@ -90,53 +90,56 @@ int LegacyPathFind::maxInProgress (2); Json::Value RPCHandler::transactionSign (Json::Value params, bool bSubmit, bool bFailHard, Application::ScopedLockType& mlh) { - if (getApp().getFeeTrack().isLoadedCluster() && (mRole != Config::ADMIN)) - return rpcError(rpcTOO_BUSY); - - Json::Value jvResult; - RippleAddress naSeed; - RippleAddress raSrcAddressID; - bool bOffline = params.isMember ("offline") && params["offline"].asBool (); + Json::Value jvResult; WriteLog (lsDEBUG, RPCHandler) << boost::str (boost::format ("transactionSign: %s") % params); - if (!bOffline && !getConfig ().RUN_STANDALONE && (getApp().getLedgerMaster().getValidatedLedgerAge() > 120)) - { + if (! params.isMember ("secret")) + return RPC::missing_field_error ("secret"); + + if (! params.isMember ("tx_json")) + return RPC::missing_field_error ("tx_json"); + + RippleAddress naSeed; + + if (! naSeed.setSeedGeneric (params["secret"].asString ())) + return RPC::make_error (rpcBAD_SEED, + RPC::invalid_field_message ("secret")); + + Json::Value tx_json (params ["tx_json"]); + + if (! tx_json.isObject ()) + return RPC::object_field_error ("tx_json"); + + if (! tx_json.isMember ("TransactionType")) + return RPC::missing_field_error ("tx_json.TransactionType"); + + std::string const sType = tx_json ["TransactionType"].asString (); + + if (! tx_json.isMember ("Account")) + return RPC::make_error (rpcSRC_ACT_MISSING, + RPC::missing_field_message ("tx_json.Account")); + + RippleAddress raSrcAddressID; + + if (! raSrcAddressID.setAccountID (tx_json["Account"].asString ())) + return RPC::make_error (rpcSRC_ACT_MALFORMED, + RPC::invalid_field_message ("tx_json.Account")); + + bool const bOffline ( + params.isMember ("offline") && params["offline"].asBool ()); + + if (! tx_json.isMember ("Sequence") && bOffline) + return RPC::missing_field_error ("tx_json.Sequence"); + + // Check for current ledger + if (!bOffline && !getConfig ().RUN_STANDALONE && + (getApp().getLedgerMaster().getValidatedLedgerAge() > 120)) return rpcError (rpcNO_CURRENT); - } - if (!params.isMember ("secret") || !params.isMember ("tx_json")) - { - return rpcError (rpcINVALID_PARAMS); - } - - Json::Value txJSON = params["tx_json"]; - - if (!txJSON.isObject ()) - { - return rpcError (rpcINVALID_PARAMS); - } - - if (!naSeed.setSeedGeneric (params["secret"].asString ())) - { - return rpcError (rpcBAD_SEED); - } - - if (!txJSON.isMember ("Account")) - { - return rpcError (rpcSRC_ACT_MISSING); - } - - if (!raSrcAddressID.setAccountID (txJSON["Account"].asString ())) - { - return rpcError (rpcSRC_ACT_MALFORMED); - } - - if (!txJSON.isMember ("TransactionType")) - { - return rpcError (rpcINVALID_PARAMS); - } - std::string sType = txJSON["TransactionType"].asString (); + // Check for load + if (getApp().getFeeTrack().isLoadedCluster() && (mRole != Config::ADMIN)) + return rpcError(rpcTOO_BUSY); Ledger::pointer lSnapshot = mNetOps->getCurrentLedger (); AccountState::pointer asSrc = bOffline @@ -153,40 +156,41 @@ Json::Value RPCHandler::transactionSign (Json::Value params, bool bSubmit, bool return rpcError (rpcSRC_ACT_NOT_FOUND); } - if (!txJSON.isMember ("Fee") - && ( - "AccountSet" == sType - || "Payment" == sType - || "OfferCreate" == sType - || "OfferCancel" == sType - || "TrustSet" == sType)) + if (! tx_json.isMember ("Fee") && ( + "AccountSet" == sType + || "Payment" == sType + || "OfferCreate" == sType + || "OfferCancel" == sType + || "TrustSet" == sType)) { + // VFALCO TODO This needs to be fixed // feeReq = lSnapshot->scaleFeeLoad(, - txJSON["Fee"] = (int) getConfig ().FEE_DEFAULT; + tx_json["Fee"] = (int) getConfig ().FEE_DEFAULT; } if ("Payment" == sType) { - RippleAddress dstAccountID; - if (!txJSON.isMember ("Destination")) - { - return rpcError (rpcDST_ACT_MISSING); - } + if (! tx_json.isMember ("Amount")) + return RPC::missing_field_error ("tx_json.Amount"); - if (!dstAccountID.setAccountID (txJSON["Destination"].asString ())) - { - return rpcError (rpcDST_ACT_MALFORMED); - } + STAmount amount; - if (txJSON.isMember ("Paths") && params.isMember ("build_path")) - { - // Asking to build a path when providing one is an error. - return rpcError (rpcINVALID_PARAMS); - } + if (! amount.bSetJson (tx_json ["Amount"])) + return RPC::invalid_field_error ("tx_json.Amount"); - if (!txJSON.isMember ("Paths") && txJSON.isMember ("Amount") && params.isMember ("build_path")) + if (!tx_json.isMember ("Destination")) + return RPC::missing_field_error ("tx_json.Destination"); + + if (!dstAccountID.setAccountID (tx_json["Destination"].asString ())) + return RPC::invalid_field_error ("tx_json.Destination"); + + if (tx_json.isMember ("Paths") && params.isMember ("build_path")) + return RPC::make_error (rpcINVALID_PARAMS, + "Cannot specify both 'tx_json.Paths' and 'tx_json.build_path'"); + + if (!tx_json.isMember ("Paths") && tx_json.isMember ("Amount") && params.isMember ("build_path")) { // Need a ripple path. STPathSet spsPaths; @@ -194,29 +198,22 @@ Json::Value RPCHandler::transactionSign (Json::Value params, bool bSubmit, bool uint160 uSrcIssuerID; STAmount saSendMax; - STAmount saSend; - if (!txJSON.isMember ("Amount") // Amount required. - || !saSend.bSetJson (txJSON["Amount"])) // Must be valid. - return rpcError (rpcDST_AMT_MALFORMED); - - if (txJSON.isMember ("SendMax")) + if (tx_json.isMember ("SendMax")) { - if (!saSendMax.bSetJson (txJSON["SendMax"])) - return rpcError (rpcINVALID_PARAMS); + if (!saSendMax.bSetJson (tx_json ["SendMax"])) + return RPC::invalid_field_error ("tx_json.SendMax"); } else { // If no SendMax, default to Amount with sender as issuer. - saSendMax = saSend; + saSendMax = amount; saSendMax.setIssuer (raSrcAddressID.getAccountID ()); } - if (saSendMax.isNative () && saSend.isNative ()) - { - // Asking to build a path for XRP to XRP is an error. - return rpcError (rpcINVALID_PARAMS); - } + if (saSendMax.isNative () && amount.isNative ()) + return RPC::make_error (rpcINVALID_PARAMS, + "Cannot build XRP to XRP paths."); { LegacyPathFind lpf (mRole == Config::ADMIN); @@ -226,7 +223,7 @@ Json::Value RPCHandler::transactionSign (Json::Value params, bool bSubmit, bool bool bValid; RippleLineCache::pointer cache = boost::make_shared (lSnapshot); Pathfinder pf (cache, raSrcAddressID, dstAccountID, - saSendMax.getCurrency (), saSendMax.getIssuer (), saSend, bValid); + saSendMax.getCurrency (), saSendMax.getIssuer (), amount, bValid); STPath extraPath; if (!bValid || !pf.findPaths (getConfig ().PATH_SEARCH_OLD, 4, spsPaths, extraPath)) @@ -242,23 +239,23 @@ Json::Value RPCHandler::transactionSign (Json::Value params, bool bSubmit, bool if (!spsPaths.isEmpty ()) { - txJSON["Paths"] = spsPaths.getJson (0); + tx_json["Paths"] = spsPaths.getJson (0); } } } } - if (!txJSON.isMember ("Fee") + if (!tx_json.isMember ("Fee") && ( - "AccountSet" == txJSON["TransactionType"].asString () - || "OfferCreate" == txJSON["TransactionType"].asString () - || "OfferCancel" == txJSON["TransactionType"].asString () - || "TrustSet" == txJSON["TransactionType"].asString ())) + "AccountSet" == tx_json["TransactionType"].asString () + || "OfferCreate" == tx_json["TransactionType"].asString () + || "OfferCancel" == tx_json["TransactionType"].asString () + || "TrustSet" == tx_json["TransactionType"].asString ())) { - txJSON["Fee"] = (int) getConfig ().FEE_DEFAULT; + tx_json["Fee"] = (int) getConfig ().FEE_DEFAULT; } - if (!txJSON.isMember ("Sequence")) + if (!tx_json.isMember ("Sequence")) { if (bOffline) { @@ -267,11 +264,11 @@ Json::Value RPCHandler::transactionSign (Json::Value params, bool bSubmit, bool } else { - txJSON["Sequence"] = asSrc->getSeq (); + tx_json["Sequence"] = asSrc->getSeq (); } } - if (!txJSON.isMember ("Flags")) txJSON["Flags"] = 0; + if (!tx_json.isMember ("Flags")) tx_json["Flags"] = 0; if (!bOffline) { @@ -334,16 +331,21 @@ Json::Value RPCHandler::transactionSign (Json::Value params, bool bSubmit, bool std::unique_ptr sopTrans; - try { - sopTrans = STObject::parseJson (txJSON); - } - catch (std::exception& e) - { - jvResult["error"] = "malformedTransaction"; - jvResult["error_exception"] = e.what (); - - return jvResult; + STParsedJSON parsed ("tx_json", tx_json); + if (parsed.object.get() != nullptr) + { + // VFALCO NOTE No idea why this doesn't compile. + //sopTrans = parsed.object; + sopTrans.reset (parsed.object.release()); + } + else + { + jvResult ["error"] = parsed.error ["error"]; + jvResult ["error_code"] = parsed.error ["error_code"]; + jvResult ["error_message"] = parsed.error ["error_message"]; + return jvResult; + } } sopTrans->setFieldVL (sfSigningPubKey, naAccountPublic.getAccountPublic ()); @@ -634,24 +636,29 @@ Json::Value RPCHandler::accountFromString (Ledger::ref lrLedger, RippleAddress& Json::Value RPCHandler::doAccountCurrencies (Json::Value params, Resource::Charge& loadType, Application::ScopedLockType& masterLockHolder) { masterLockHolder.unlock (); - Ledger::pointer lpLedger; - Json::Value jvResult = lookupLedger (params, lpLedger); - + // Get the current ledger + Ledger::pointer lpLedger; + Json::Value jvResult (lookupLedger (params, lpLedger)); if (!lpLedger) return jvResult; - if (!params.isMember ("account") && !params.isMember ("ident")) - return rpcError (rpcINVALID_PARAMS); + if (! params.isMember ("account") && ! params.isMember ("ident")) + return RPC::missing_field_error ("account"); - std::string strIdent = params.isMember ("account") ? params["account"].asString () : params["ident"].asString (); - bool bIndex; - int iIndex = params.isMember ("account_index") ? params["account_index"].asUInt () : 0; - bool bStrict = params.isMember ("strict") && params["strict"].asBool (); - RippleAddress naAccount; + std::string const strIdent (params.isMember ("account") + ? params["account"].asString () + : params["ident"].asString ()); + + int const iIndex (params.isMember ("account_index") + ? params["account_index"].asUInt () + : 0); + bool const bStrict (params.isMember ("strict") && params["strict"].asBool ()); // Get info on account. - - Json::Value jvAccepted = accountFromString (lpLedger, naAccount, bIndex, strIdent, iIndex, bStrict); + bool bIndex; // out param + RippleAddress naAccount; // out param + Json::Value jvAccepted (accountFromString ( + lpLedger, naAccount, bIndex, strIdent, iIndex, bStrict)); if (!jvAccepted.empty ()) return jvAccepted; @@ -707,7 +714,7 @@ Json::Value RPCHandler::doAccountInfo (Json::Value params, Resource::Charge& loa return jvResult; if (!params.isMember ("account") && !params.isMember ("ident")) - return rpcError (rpcINVALID_PARAMS); + return RPC::missing_field_error ("account"); std::string strIdent = params.isMember ("account") ? params["account"].asString () : params["ident"].asString (); bool bIndex; @@ -759,7 +766,7 @@ Json::Value RPCHandler::doConnect (Json::Value params, Resource::Charge& loadTyp return "cannot connect in standalone mode"; if (!params.isMember ("ip")) - return rpcError (rpcINVALID_PARAMS); + return RPC::missing_field_error ("ip"); std::string strIp = params["ip"].asString (); int iPort = params.isMember ("port") ? params["port"].asInt () : -1; @@ -777,7 +784,7 @@ Json::Value RPCHandler::doConnect (Json::Value params, Resource::Charge& loadTyp Json::Value RPCHandler::doDataDelete (Json::Value params, Resource::Charge& loadType, Application::ScopedLockType& masterLockHolder) { if (!params.isMember ("key")) - return rpcError (rpcINVALID_PARAMS); + return RPC::missing_field_error ("key"); std::string strKey = params["key"].asString (); @@ -803,7 +810,7 @@ Json::Value RPCHandler::doDataDelete (Json::Value params, Resource::Charge& load Json::Value RPCHandler::doDataFetch (Json::Value params, Resource::Charge& loadType, Application::ScopedLockType& masterLockHolder) { if (!params.isMember ("key")) - return rpcError (rpcINVALID_PARAMS); + return RPC::missing_field_error ("key"); std::string strKey = params["key"].asString (); std::string strValue; @@ -827,8 +834,9 @@ Json::Value RPCHandler::doDataFetch (Json::Value params, Resource::Charge& loadT Json::Value RPCHandler::doDataStore (Json::Value params, Resource::Charge& loadType, Application::ScopedLockType& masterLockHolder) { if (!params.isMember ("key") - || !params.isMember ("value")) - return rpcError (rpcINVALID_PARAMS); + return RPC::missing_field_error ("key"); + if (!params.isMember ("value") + return RPC::missing_field_error ("value"); std::string strKey = params["key"].asString (); std::string strValue = params["value"].asString (); @@ -888,7 +896,7 @@ Json::Value RPCHandler::doNicknameInfo (Json::Value params) Json::Value RPCHandler::doOwnerInfo (Json::Value params, Resource::Charge& loadType, Application::ScopedLockType& masterLockHolder) { if (!params.isMember ("account") && !params.isMember ("ident")) - return rpcError (rpcINVALID_PARAMS); + return RPC::missing_field_error ("account"); std::string strIdent = params.isMember ("account") ? params["account"].asString () : params["ident"].asString (); bool bIndex; @@ -1051,12 +1059,12 @@ Json::Value RPCHandler::doProofCreate (Json::Value params, Resource::Charge& loa if (params.isMember ("difficulty")) { if (!params["difficulty"].isIntegral ()) - return rpcError (rpcINVALID_PARAMS); + return RPC::invalid_field_error ("difficulty"); - int iDifficulty = params["difficulty"].asInt (); + int const iDifficulty (params["difficulty"].asInt ()); if (iDifficulty < 0 || iDifficulty > ProofOfWorkFactory::kMaxDifficulty) - return rpcError (rpcINVALID_PARAMS); + return RPC::invalid_field_error ("difficulty"); pgGen->setDifficulty (iDifficulty); } @@ -1088,12 +1096,12 @@ Json::Value RPCHandler::doProofSolve (Json::Value params, Resource::Charge& load Json::Value jvResult; if (!params.isMember ("token")) - return rpcError (rpcINVALID_PARAMS); + return RPC::missing_field_error ("token"); std::string strToken = params["token"].asString (); if (!ProofOfWork::validateToken (strToken)) - return rpcError (rpcINVALID_PARAMS); + return RPC::invalid_field_error ("token"); ProofOfWork powProof (strToken); uint256 uSolution = powProof.solve (); @@ -1119,10 +1127,10 @@ Json::Value RPCHandler::doProofVerify (Json::Value params, Resource::Charge& loa Json::Value jvResult; if (!params.isMember ("token")) - return rpcError (rpcINVALID_PARAMS); + return RPC::missing_field_error ("token"); if (!params.isMember ("solution")) - return rpcError (rpcINVALID_PARAMS); + return RPC::missing_field_error ("solution"); std::string strToken = params["token"].asString (); uint256 uSolution (params["solution"].asString ()); @@ -1137,12 +1145,12 @@ Json::Value RPCHandler::doProofVerify (Json::Value params, Resource::Charge& loa if (params.isMember ("difficulty")) { if (!params["difficulty"].isIntegral ()) - return rpcError (rpcINVALID_PARAMS); + return RPC::invalid_field_error ("difficulty"); int iDifficulty = params["difficulty"].asInt (); if (iDifficulty < 0 || iDifficulty > ProofOfWorkFactory::kMaxDifficulty) - return rpcError (rpcINVALID_PARAMS); + return RPC::missing_field_error ("difficulty"); pgGen->setDifficulty (iDifficulty); } @@ -1192,7 +1200,7 @@ Json::Value RPCHandler::doAccountLines (Json::Value params, Resource::Charge& lo return jvResult; if (!params.isMember ("account")) - return rpcError (rpcINVALID_PARAMS); + return RPC::missing_field_error ("account"); std::string strIdent = params["account"].asString (); bool bIndex = params.isMember ("account_index"); @@ -1303,7 +1311,7 @@ Json::Value RPCHandler::doAccountOffers (Json::Value params, Resource::Charge& l return jvResult; if (!params.isMember ("account")) - return rpcError (rpcINVALID_PARAMS); + return RPC::missing_field_error ("account"); std::string strIdent = params["account"].asString (); bool bIndex = params.isMember ("account_index"); @@ -1334,6 +1342,33 @@ Json::Value RPCHandler::doAccountOffers (Json::Value params, Resource::Charge& l return jvResult; } +template +inline bool is_xrp (UnsignedInteger const& value) +{ + return value.isZero(); +} + +template +inline bool is_not_xrp (UnsignedInteger const& value) +{ + return ! is_xrp (value); +} + +inline uint160 const& xrp_issuer () +{ + return ACCOUNT_XRP; +} + +inline uint160 const& xrp_currency () +{ + return CURRENCY_XRP; +} + +inline uint160 const& neutral_issuer () +{ + return ACCOUNT_ONE; +} + // { // "ledger_hash" : ledger, // Optional. // "ledger_index" : ledger_index, // Optional. @@ -1348,94 +1383,164 @@ Json::Value RPCHandler::doBookOffers (Json::Value params, Resource::Charge& load { masterLockHolder.unlock (); + // VFALCO TODO Here is a terrible place for this kind of business + // logic. It needs to be moved elsewhere and documented, + // and encapsulated into a function. if (getApp().getJobQueue ().getJobCountGE (jtCLIENT) > 200) - { return rpcError (rpcTOO_BUSY); - } - Ledger::pointer lpLedger; - Json::Value jvResult = lookupLedger (params, lpLedger); + Ledger::pointer lpLedger; + Json::Value jvResult (lookupLedger (params, lpLedger)); if (!lpLedger) return jvResult; - if (!params.isMember ("taker_pays") || !params.isMember ("taker_gets") || !params["taker_pays"].isObject () || !params["taker_gets"].isObject ()) - return rpcError (rpcINVALID_PARAMS); + if (!params.isMember ("taker_pays")) + return RPC::missing_field_error ("taker_pays"); - uint160 uTakerPaysCurrencyID; - uint160 uTakerPaysIssuerID; - const Json::Value& jvTakerPays = params["taker_pays"]; + if (!params.isMember ("taker_gets")) + return RPC::missing_field_error ("taker_gets"); - // Parse mandatory currency. - if (!jvTakerPays.isMember ("currency") - || !STAmount::currencyFromString (uTakerPaysCurrencyID, jvTakerPays["currency"].asString ())) + if (!params["taker_pays"].isObject ()) + return RPC::object_field_error ("taker_pays"); + + if (!params["taker_gets"].isObject ()) + return RPC::object_field_error ("taker_gets"); + + Json::Value const& taker_pays (params["taker_pays"]); + + if (!taker_pays.isMember ("currency")) + return RPC::missing_field_error ("taker_pays.currency"); + + if (! taker_pays ["currency"].isString ()) + return RPC::expected_field_error ("taker_pays.currency", "string"); + + Json::Value const& taker_gets = params["taker_gets"]; + + if (! taker_gets.isMember ("currency")) + return RPC::missing_field_error ("taker_gets.currency"); + + if (! taker_gets ["currency"].isString ()) + return RPC::expected_field_error ("taker_gets.currency", "string"); + + uint160 pay_currency; + + if (! STAmount::currencyFromString ( + pay_currency, taker_pays ["currency"].asString ())) { WriteLog (lsINFO, RPCHandler) << "Bad taker_pays currency."; - - return rpcError (rpcSRC_CUR_MALFORMED); + return RPC::make_error (rpcSRC_CUR_MALFORMED, + "Invalid field 'taker_pays.currency', bad currency."); } - // Parse optional issuer. - else if (((jvTakerPays.isMember ("issuer")) - && (!jvTakerPays["issuer"].isString () - || !STAmount::issuerFromString (uTakerPaysIssuerID, jvTakerPays["issuer"].asString ()))) - // Don't allow illegal issuers. - || (!uTakerPaysCurrencyID != !uTakerPaysIssuerID) - || ACCOUNT_ONE == uTakerPaysIssuerID) + + uint160 get_currency; + + if (! STAmount::currencyFromString ( + get_currency, taker_gets ["currency"].asString ())) { - WriteLog (lsINFO, RPCHandler) << "Bad taker_pays issuer."; - - return rpcError (rpcSRC_ISR_MALFORMED); + WriteLog (lsINFO, RPCHandler) << "Bad taker_gets currency."; + return RPC::make_error (rpcDST_AMT_MALFORMED, + "Invalid field 'taker_gets.currency', bad currency."); } - uint160 uTakerGetsCurrencyID; - uint160 uTakerGetsIssuerID; - const Json::Value& jvTakerGets = params["taker_gets"]; + uint160 pay_issuer; - // Parse mandatory currency. - if (!jvTakerGets.isMember ("currency") - || !STAmount::currencyFromString (uTakerGetsCurrencyID, jvTakerGets["currency"].asString ())) + if (taker_pays.isMember ("issuer")) { - WriteLog (lsINFO, RPCHandler) << "Bad taker_pays currency."; + if (! taker_pays ["issuer"].isString()) + return RPC::expected_field_error ("taker_pays.issuer", "string"); - return rpcError (rpcSRC_CUR_MALFORMED); + if (! STAmount::issuerFromString ( + pay_issuer, taker_pays ["issuer"].asString ())) + return RPC::make_error (rpcSRC_ISR_MALFORMED, + "Invalid field 'taker_pays.issuer', bad issuer."); + + if (pay_issuer == neutral_issuer ()) + return RPC::make_error (rpcSRC_ISR_MALFORMED, + "Invalid field 'taker_pays.issuer', bad issuer account one."); } - // Parse optional issuer. - else if (((jvTakerGets.isMember ("issuer")) - && (!jvTakerGets["issuer"].isString () - || !STAmount::issuerFromString (uTakerGetsIssuerID, jvTakerGets["issuer"].asString ()))) - // Don't allow illegal issuers. - || (!uTakerGetsCurrencyID != !uTakerGetsIssuerID) - || ACCOUNT_ONE == uTakerGetsIssuerID) + else { - WriteLog (lsINFO, RPCHandler) << "Bad taker_gets issuer."; - - return rpcError (rpcDST_ISR_MALFORMED); + pay_issuer = xrp_issuer (); } - if (uTakerPaysCurrencyID == uTakerGetsCurrencyID - && uTakerPaysIssuerID == uTakerGetsIssuerID) + if (is_xrp (pay_currency) && ! is_xrp (pay_issuer)) + return RPC::make_error (rpcSRC_ISR_MALFORMED, + "Unneeded field 'taker_pays.issuer' for XRP currency specification."); + + if (is_not_xrp (pay_currency) && is_xrp (pay_issuer)) + return RPC::make_error (rpcSRC_ISR_MALFORMED, + "Invalid field 'taker_pays.issuer', expected non-XRP issuer."); + + uint160 get_issuer; + + if (taker_gets.isMember ("issuer")) { - WriteLog (lsINFO, RPCHandler) << "taker_gets same as taker_pays."; + if (! taker_gets ["issuer"].isString()) + return RPC::expected_field_error ("taker_gets.issuer", "string"); - return rpcError (rpcBAD_MARKET); + if (! STAmount::issuerFromString ( + get_issuer, taker_gets ["issuer"].asString ())) + return RPC::make_error (rpcDST_ISR_MALFORMED, + "Invalid field 'taker_gets.issuer', bad issuer."); + + if (get_issuer == neutral_issuer ()) + return RPC::make_error (rpcDST_ISR_MALFORMED, + "Invalid field 'taker_gets.issuer', bad issuer account one."); + } + else + { + get_issuer = xrp_issuer (); } - RippleAddress raTakerID; - if (!params.isMember ("taker")) + if (is_xrp (get_currency) && ! is_xrp (get_issuer)) + return RPC::make_error (rpcDST_ISR_MALFORMED, + "Unneeded field 'taker_gets.issuer' for XRP currency specification."); + + if (is_not_xrp (get_currency) && is_xrp (get_issuer)) + return RPC::make_error (rpcDST_ISR_MALFORMED, + "Invalid field 'taker_gets.issuer', expected non-XRP issuer."); + + RippleAddress raTakerID; + + if (params.isMember ("taker")) + { + if (! params ["taker"].isString ()) + return RPC::expected_field_error ("taker", "string"); + + if (! raTakerID.setAccountID (params ["taker"].asString ())) + return RPC::invalid_field_error ("taker"); + } + else { raTakerID.setAccountID (ACCOUNT_ONE); } - else if (!raTakerID.setAccountID (params["taker"].asString ())) + + if (pay_currency == get_currency && pay_issuer == get_issuer) { - return rpcError (rpcBAD_ISSUER); + WriteLog (lsINFO, RPCHandler) << "taker_gets same as taker_pays."; + return RPC::make_error (rpcBAD_MARKET); } - const bool bProof = params.isMember ("proof"); - const unsigned int iLimit = params.isMember ("limit") ? params["limit"].asUInt () : 0; - const Json::Value jvMarker = params.isMember ("marker") ? params["marker"] : Json::Value (Json::nullValue); + if (params.isMember ("limit") && ! params ["limit"].isUInt()) + return RPC::expected_field_error ( + "taker_pays.currency", "unsigned integer"); + + unsigned int const iLimit (params.isMember ("limit") + ? params ["limit"].asUInt () + : 0); + + bool const bProof (params.isMember ("proof")); + + Json::Value const jvMarker (params.isMember ("marker") + ? params["marker"] + : Json::Value (Json::nullValue)); + + mNetOps->getBookPage (lpLedger, pay_currency, pay_issuer, + get_currency, get_issuer, raTakerID.getAccountID (), + bProof, iLimit, jvMarker, jvResult); - mNetOps->getBookPage (lpLedger, uTakerPaysCurrencyID, uTakerPaysIssuerID, uTakerGetsCurrencyID, uTakerGetsIssuerID, raTakerID.getAccountID (), bProof, iLimit, jvMarker, jvResult); loadType = Resource::feeMediumBurdenRPC; return jvResult; @@ -3573,33 +3678,35 @@ Json::Value RPCHandler::doSubscribe (Json::Value params, Resource::Charge& loadT || !jvSubRequest["taker_gets"].isObject ()) return rpcError (rpcINVALID_PARAMS); - RippleCurrency uTakerPaysCurrencyID; - RippleIssuer uTakerPaysIssuerID; - RippleCurrency uTakerGetsCurrencyID; - RippleIssuer uTakerGetsIssuerID; + // VFALCO TODO Use RippleAsset here + RippleCurrency pay_currency; + RippleIssuer pay_issuer; + RippleCurrency get_currency; + RippleIssuer get_issuer; + bool bBoth = (jvSubRequest.isMember ("both") && jvSubRequest["both"].asBool ()) || (jvSubRequest.isMember ("both_sides") && jvSubRequest["both_sides"].asBool ()); // DEPRECATED bool bSnapshot = (jvSubRequest.isMember ("snapshot") && jvSubRequest["snapshot"].asBool ()) || (jvSubRequest.isMember ("state_now") && jvSubRequest["state_now"].asBool ()); // DEPRECATED - Json::Value jvTakerPays = jvSubRequest["taker_pays"]; - Json::Value jvTakerGets = jvSubRequest["taker_gets"]; + Json::Value taker_pays = jvSubRequest["taker_pays"]; + Json::Value taker_gets = jvSubRequest["taker_gets"]; // Parse mandatory currency. - if (!jvTakerPays.isMember ("currency") - || !STAmount::currencyFromString (uTakerPaysCurrencyID, jvTakerPays["currency"].asString ())) + if (!taker_pays.isMember ("currency") + || !STAmount::currencyFromString (pay_currency, taker_pays["currency"].asString ())) { WriteLog (lsINFO, RPCHandler) << "Bad taker_pays currency."; return rpcError (rpcSRC_CUR_MALFORMED); } // Parse optional issuer. - else if (((jvTakerPays.isMember ("issuer")) - && (!jvTakerPays["issuer"].isString () - || !STAmount::issuerFromString (uTakerPaysIssuerID, jvTakerPays["issuer"].asString ()))) + else if (((taker_pays.isMember ("issuer")) + && (!taker_pays["issuer"].isString () + || !STAmount::issuerFromString (pay_issuer, taker_pays["issuer"].asString ()))) // Don't allow illegal issuers. - || (!uTakerPaysCurrencyID != !uTakerPaysIssuerID) - || ACCOUNT_ONE == uTakerPaysIssuerID) + || (!pay_currency != !pay_issuer) + || ACCOUNT_ONE == pay_issuer) { WriteLog (lsINFO, RPCHandler) << "Bad taker_pays issuer."; @@ -3607,28 +3714,28 @@ Json::Value RPCHandler::doSubscribe (Json::Value params, Resource::Charge& loadT } // Parse mandatory currency. - if (!jvTakerGets.isMember ("currency") - || !STAmount::currencyFromString (uTakerGetsCurrencyID, jvTakerGets["currency"].asString ())) + if (!taker_gets.isMember ("currency") + || !STAmount::currencyFromString (get_currency, taker_gets["currency"].asString ())) { WriteLog (lsINFO, RPCHandler) << "Bad taker_pays currency."; return rpcError (rpcSRC_CUR_MALFORMED); } // Parse optional issuer. - else if (((jvTakerGets.isMember ("issuer")) - && (!jvTakerGets["issuer"].isString () - || !STAmount::issuerFromString (uTakerGetsIssuerID, jvTakerGets["issuer"].asString ()))) + else if (((taker_gets.isMember ("issuer")) + && (!taker_gets["issuer"].isString () + || !STAmount::issuerFromString (get_issuer, taker_gets["issuer"].asString ()))) // Don't allow illegal issuers. - || (!uTakerGetsCurrencyID != !uTakerGetsIssuerID) - || ACCOUNT_ONE == uTakerGetsIssuerID) + || (!get_currency != !get_issuer) + || ACCOUNT_ONE == get_issuer) { WriteLog (lsINFO, RPCHandler) << "Bad taker_gets issuer."; return rpcError (rpcDST_ISR_MALFORMED); } - if (uTakerPaysCurrencyID == uTakerGetsCurrencyID - && uTakerPaysIssuerID == uTakerGetsIssuerID) + if (pay_currency == get_currency + && pay_issuer == get_issuer) { WriteLog (lsINFO, RPCHandler) << "taker_gets same as taker_pays."; @@ -3646,17 +3753,17 @@ Json::Value RPCHandler::doSubscribe (Json::Value params, Resource::Charge& loadT return rpcError (rpcBAD_ISSUER); } - if (!Ledger::isValidBook (uTakerPaysCurrencyID, uTakerPaysIssuerID, uTakerGetsCurrencyID, uTakerGetsIssuerID)) + if (!Ledger::isValidBook (pay_currency, pay_issuer, get_currency, get_issuer)) { WriteLog (lsWARNING, RPCHandler) << "Bad market: " << - uTakerPaysCurrencyID << ":" << uTakerPaysIssuerID << " -> " << - uTakerGetsCurrencyID << ":" << uTakerGetsIssuerID; + pay_currency << ":" << pay_issuer << " -> " << + get_currency << ":" << get_issuer; return rpcError (rpcBAD_MARKET); } - mNetOps->subBook (ispSub, uTakerPaysCurrencyID, uTakerGetsCurrencyID, uTakerPaysIssuerID, uTakerGetsIssuerID); + mNetOps->subBook (ispSub, pay_currency, get_currency, pay_issuer, get_issuer); - if (bBoth) mNetOps->subBook (ispSub, uTakerGetsCurrencyID, uTakerPaysCurrencyID, uTakerGetsIssuerID, uTakerPaysIssuerID); + if (bBoth) mNetOps->subBook (ispSub, get_currency, pay_currency, get_issuer, pay_issuer); if (bSnapshot) { @@ -3673,17 +3780,17 @@ Json::Value RPCHandler::doSubscribe (Json::Value params, Resource::Charge& loadT Json::Value jvBids (Json::objectValue); Json::Value jvAsks (Json::objectValue); - mNetOps->getBookPage (lpLedger, uTakerPaysCurrencyID, uTakerPaysIssuerID, uTakerGetsCurrencyID, uTakerGetsIssuerID, raTakerID.getAccountID (), false, 0, jvMarker, jvBids); + mNetOps->getBookPage (lpLedger, pay_currency, pay_issuer, get_currency, get_issuer, raTakerID.getAccountID (), false, 0, jvMarker, jvBids); if (jvBids.isMember ("offers")) jvResult["bids"] = jvBids["offers"]; - mNetOps->getBookPage (lpLedger, uTakerGetsCurrencyID, uTakerGetsIssuerID, uTakerPaysCurrencyID, uTakerPaysIssuerID, raTakerID.getAccountID (), false, 0, jvMarker, jvAsks); + mNetOps->getBookPage (lpLedger, get_currency, get_issuer, pay_currency, pay_issuer, raTakerID.getAccountID (), false, 0, jvMarker, jvAsks); if (jvAsks.isMember ("offers")) jvResult["asks"] = jvAsks["offers"]; } else { - mNetOps->getBookPage (lpLedger, uTakerPaysCurrencyID, uTakerPaysIssuerID, uTakerGetsCurrencyID, uTakerGetsIssuerID, raTakerID.getAccountID (), false, 0, jvMarker, jvResult); + mNetOps->getBookPage (lpLedger, pay_currency, pay_issuer, get_currency, get_issuer, raTakerID.getAccountID (), false, 0, jvMarker, jvResult); } } } @@ -3811,31 +3918,31 @@ Json::Value RPCHandler::doUnsubscribe (Json::Value params, Resource::Charge& loa || !jvSubRequest["taker_gets"].isObject ()) return rpcError (rpcINVALID_PARAMS); - uint160 uTakerPaysCurrencyID; - uint160 uTakerPaysIssuerID; - uint160 uTakerGetsCurrencyID; - uint160 uTakerGetsIssuerID; + uint160 pay_currency; + uint160 pay_issuer; + uint160 get_currency; + uint160 get_issuer; bool bBoth = (jvSubRequest.isMember ("both") && jvSubRequest["both"].asBool ()) || (jvSubRequest.isMember ("both_sides") && jvSubRequest["both_sides"].asBool ()); // DEPRECATED - Json::Value jvTakerPays = jvSubRequest["taker_pays"]; - Json::Value jvTakerGets = jvSubRequest["taker_gets"]; + Json::Value taker_pays = jvSubRequest["taker_pays"]; + Json::Value taker_gets = jvSubRequest["taker_gets"]; // Parse mandatory currency. - if (!jvTakerPays.isMember ("currency") - || !STAmount::currencyFromString (uTakerPaysCurrencyID, jvTakerPays["currency"].asString ())) + if (!taker_pays.isMember ("currency") + || !STAmount::currencyFromString (pay_currency, taker_pays["currency"].asString ())) { WriteLog (lsINFO, RPCHandler) << "Bad taker_pays currency."; return rpcError (rpcSRC_CUR_MALFORMED); } // Parse optional issuer. - else if (((jvTakerPays.isMember ("issuer")) - && (!jvTakerPays["issuer"].isString () - || !STAmount::issuerFromString (uTakerPaysIssuerID, jvTakerPays["issuer"].asString ()))) + else if (((taker_pays.isMember ("issuer")) + && (!taker_pays["issuer"].isString () + || !STAmount::issuerFromString (pay_issuer, taker_pays["issuer"].asString ()))) // Don't allow illegal issuers. - || (!uTakerPaysCurrencyID != !uTakerPaysIssuerID) - || ACCOUNT_ONE == uTakerPaysIssuerID) + || (!pay_currency != !pay_issuer) + || ACCOUNT_ONE == pay_issuer) { WriteLog (lsINFO, RPCHandler) << "Bad taker_pays issuer."; @@ -3843,37 +3950,37 @@ Json::Value RPCHandler::doUnsubscribe (Json::Value params, Resource::Charge& loa } // Parse mandatory currency. - if (!jvTakerGets.isMember ("currency") - || !STAmount::currencyFromString (uTakerGetsCurrencyID, jvTakerGets["currency"].asString ())) + if (!taker_gets.isMember ("currency") + || !STAmount::currencyFromString (get_currency, taker_gets["currency"].asString ())) { WriteLog (lsINFO, RPCHandler) << "Bad taker_pays currency."; return rpcError (rpcSRC_CUR_MALFORMED); } // Parse optional issuer. - else if (((jvTakerGets.isMember ("issuer")) - && (!jvTakerGets["issuer"].isString () - || !STAmount::issuerFromString (uTakerGetsIssuerID, jvTakerGets["issuer"].asString ()))) + else if (((taker_gets.isMember ("issuer")) + && (!taker_gets["issuer"].isString () + || !STAmount::issuerFromString (get_issuer, taker_gets["issuer"].asString ()))) // Don't allow illegal issuers. - || (!uTakerGetsCurrencyID != !uTakerGetsIssuerID) - || ACCOUNT_ONE == uTakerGetsIssuerID) + || (!get_currency != !get_issuer) + || ACCOUNT_ONE == get_issuer) { WriteLog (lsINFO, RPCHandler) << "Bad taker_gets issuer."; return rpcError (rpcDST_ISR_MALFORMED); } - if (uTakerPaysCurrencyID == uTakerGetsCurrencyID - && uTakerPaysIssuerID == uTakerGetsIssuerID) + if (pay_currency == get_currency + && pay_issuer == get_issuer) { WriteLog (lsINFO, RPCHandler) << "taker_gets same as taker_pays."; return rpcError (rpcBAD_MARKET); } - mNetOps->unsubBook (ispSub->getSeq (), uTakerPaysCurrencyID, uTakerGetsCurrencyID, uTakerPaysIssuerID, uTakerGetsIssuerID); + mNetOps->unsubBook (ispSub->getSeq (), pay_currency, get_currency, pay_issuer, get_issuer); - if (bBoth) mNetOps->unsubBook (ispSub->getSeq (), uTakerGetsCurrencyID, uTakerPaysCurrencyID, uTakerGetsIssuerID, uTakerPaysIssuerID); + if (bBoth) mNetOps->unsubBook (ispSub->getSeq (), get_currency, pay_currency, get_issuer, pay_issuer); } } @@ -3889,12 +3996,12 @@ Json::Value RPCHandler::doRpcCommand (const std::string& strMethod, Json::Value WriteLog (lsTRACE, RPCHandler) << "doRpcCommand:" << strMethod << ":" << jvParams; if (!jvParams.isArray () || jvParams.size () > 1) - return rpcError (rpcINVALID_PARAMS); + return logRPCError (rpcError (rpcINVALID_PARAMS)); Json::Value params = jvParams.size () ? jvParams[0u] : Json::Value (Json::objectValue); if (!params.isObject ()) - return rpcError (rpcINVALID_PARAMS); + return logRPCError (rpcError (rpcINVALID_PARAMS)); // Provide the JSON-RPC method as the field "command" in the request. params["command"] = strMethod; @@ -3922,7 +4029,7 @@ Json::Value RPCHandler::doRpcCommand (const std::string& strMethod, Json::Value jvResult["status"] = "success"; } - return jvResult; + return logRPCError (jvResult); } Json::Value RPCHandler::doInternal (Json::Value params, Resource::Charge& loadType, Application::ScopedLockType& masterLockHolder) diff --git a/src/ripple_data/protocol/STParsedJSON.cpp b/src/ripple_data/protocol/STParsedJSON.cpp new file mode 100644 index 0000000000..dab344f462 --- /dev/null +++ b/src/ripple_data/protocol/STParsedJSON.cpp @@ -0,0 +1,717 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2012, 2013 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +namespace ripple { + +STParsedJSON::STParsedJSON (std::string const& name, Json::Value const& json) +{ + parse (name, json, sfGeneric, 0, object); +} + +//------------------------------------------------------------------------------ + +std::string STParsedJSON::make_name (std::string const& object, + std::string const& field) +{ + if (field.empty ()) + return object; + + return object + "." + field; +} + +Json::Value STParsedJSON::not_an_object (std::string const& object, + std::string const& field) +{ + return RPC::make_error (rpcINVALID_PARAMS, + "Field '" + make_name (object, field) + "' is not a JSON object."); +} + +Json::Value STParsedJSON::unknown_field (std::string const& object, + std::string const& field) +{ + return RPC::make_error (rpcINVALID_PARAMS, + "Field '" + make_name (object, field) + "' is unknown."); +} + +Json::Value STParsedJSON::out_of_range (std::string const& object, + std::string const& field) +{ + return RPC::make_error (rpcINVALID_PARAMS, + "Field '" + make_name (object, field) + "' is out of range."); +} + +Json::Value STParsedJSON::bad_type (std::string const& object, + std::string const& field) +{ + return RPC::make_error (rpcINVALID_PARAMS, + "Field '" + make_name (object, field) + "' has bad type."); +} + +Json::Value STParsedJSON::invalid_data (std::string const& object, + std::string const& field) +{ + return RPC::make_error (rpcINVALID_PARAMS, + "Field '" + make_name (object, field) + "' has invalid data."); +} + +Json::Value STParsedJSON::array_expected (std::string const& object, + std::string const& field) +{ + return RPC::make_error (rpcINVALID_PARAMS, + "Field '" + make_name (object, field) + "' must be a JSON array."); +} + +Json::Value STParsedJSON::string_expected (std::string const& object, + std::string const& field) +{ + return RPC::make_error (rpcINVALID_PARAMS, + "Field '" + make_name (object, field) + "' must be a string."); +} + +Json::Value STParsedJSON::too_deep (std::string const& object, + std::string const& field) +{ + return RPC::make_error (rpcINVALID_PARAMS, + "Field '" + make_name (object, field) + "' exceeds nesting depth limit."); +} + +Json::Value STParsedJSON::singleton_expected (std::string const& object) +{ + return RPC::make_error (rpcINVALID_PARAMS, + "Field '" + object + + "' must be an object with a single key/object value."); +} + +//------------------------------------------------------------------------------ + +bool STParsedJSON::parse (std::string const& json_name, + Json::Value const& json, SField::ref inName, int depth, + std::unique_ptr & sub_object) +{ + if (! json.isObject ()) + { + error = not_an_object (json_name); + return false; + } + + SField::ptr name (&inName); + + boost::ptr_vector data; + Json::Value::Members members (json.getMemberNames ()); + + for (Json::Value::Members::iterator it (members.begin ()); + it != members.end (); ++it) + { + std::string const& fieldName = *it; + Json::Value const& value = json [fieldName]; + + SField::ref field = SField::getField (fieldName); + + if (field == sfInvalid) + { + error = unknown_field (json_name, fieldName); + return false; + } + + switch (field.fieldType) + { + case STI_UINT8: + try + { + if (value.isString ()) + { + // VFALCO TODO wtf? + } + else if (value.isInt ()) + { + if (value.asInt () < 0 || value.asInt () > 255) + { + error = out_of_range (json_name, fieldName); + return false; + } + + data.push_back (new STUInt8 (field, + range_check_cast ( + value.asInt (), 0, 255))); + } + else if (value.isUInt ()) + { + if (value.asUInt () > 255) + { + error = out_of_range (json_name, fieldName); + return false; + } + + data.push_back (new STUInt8 (field, + range_check_cast ( + value.asUInt (), 0, 255))); + } + else + { + error = bad_type (json_name, fieldName); + return false; + } + } + catch (...) + { + error = invalid_data (json_name, fieldName); + return false; + } + + break; + + case STI_UINT16: + try + { + if (value.isString ()) + { + std::string strValue = value.asString (); + + if (! strValue.empty () && + ((strValue[0] < '0') || (strValue[0] > '9'))) + { + if (field == sfTransactionType) + { + TxType const txType (TxFormats::getInstance()-> + findTypeByName (strValue)); + + data.push_back (new STUInt16 (field, + static_cast (txType))); + + if (*name == sfGeneric) + name = &sfTransaction; + } + else if (field == sfLedgerEntryType) + { + LedgerEntryType const type (LedgerFormats::getInstance()-> + findTypeByName (strValue)); + + data.push_back (new STUInt16 (field, + static_cast (type))); + + if (*name == sfGeneric) + name = &sfLedgerEntry; + } + else + { + error = invalid_data (json_name, fieldName); + return false; + } + } + else + { + data.push_back (new STUInt16 (field, + lexicalCastThrow (strValue))); + } + } + else if (value.isInt ()) + { + data.push_back (new STUInt16 (field, + range_check_cast ( + value.asInt (), 0, 65535))); + } + else if (value.isUInt ()) + { + data.push_back (new STUInt16 (field, + range_check_cast ( + value.asUInt (), 0, 65535))); + } + else + { + error = bad_type (json_name, fieldName); + return false; + } + } + catch (...) + { + error = invalid_data (json_name, fieldName); + return false; + } + + break; + + case STI_UINT32: + try + { + if (value.isString ()) + { + data.push_back (new STUInt32 (field, + lexicalCastThrow (value.asString ()))); + } + else if (value.isInt ()) + { + data.push_back (new STUInt32 (field, + range_check_cast (value.asInt (), 0u, 4294967295u))); + } + else if (value.isUInt ()) + { + data.push_back (new STUInt32 (field, + static_cast (value.asUInt ()))); + } + else + { + error = bad_type (json_name, fieldName); + return false; + } + } + catch (...) + { + error = invalid_data (json_name, fieldName); + return false; + } + + break; + + case STI_UINT64: + try + { + if (value.isString ()) + { + data.push_back (new STUInt64 (field, + uintFromHex (value.asString ()))); + } + else if (value.isInt ()) + { + data.push_back (new STUInt64 (field, + range_check_cast ( + value.asInt (), 0, 18446744073709551615ull))); + } + else if (value.isUInt ()) + { + data.push_back (new STUInt64 (field, + static_cast (value.asUInt ()))); + } + else + { + error = bad_type (json_name, fieldName); + return false; + } + } + catch (...) + { + error = invalid_data (json_name, fieldName); + return false; + } + + break; + + case STI_HASH128: + try + { + if (value.isString ()) + { + data.push_back (new STHash128 (field, value.asString ())); + } + else + { + error = bad_type (json_name, fieldName); + return false; + } + } + catch (...) + { + error = invalid_data (json_name, fieldName); + return false; + } + + break; + + case STI_HASH160: + try + { + if (value.isString ()) + { + data.push_back (new STHash160 (field, value.asString ())); + } + else + { + error = bad_type (json_name, fieldName); + return false; + } + } + catch (...) + { + error = invalid_data (json_name, fieldName); + return false; + } + + break; + + case STI_HASH256: + try + { + if (value.isString ()) + { + data.push_back (new STHash256 (field, value.asString ())); + } + else + { + error = bad_type (json_name, fieldName); + return false; + } + } + catch (...) + { + error = invalid_data (json_name, fieldName); + return false; + } + + break; + + case STI_VL: + if (! value.isString ()) + { + error = bad_type (json_name, fieldName); + return false; + } + + try + { + data.push_back (new STVariableLength (field, + strUnHex (value.asString ()))); + } + catch (...) + { + error = invalid_data (json_name, fieldName); + return false; + } + + break; + + case STI_AMOUNT: + try + { + data.push_back (new STAmount (field, value)); + } + catch (...) + { + error = invalid_data (json_name, fieldName); + return false; + } + + break; + + case STI_VECTOR256: + if (! value.isArray ()) + { + error = array_expected (json_name, fieldName); + return false; + } + + try + { + data.push_back (new STVector256 (field)); + STVector256* tail (dynamic_cast (&data.back ())); + check_precondition (tail); + + for (Json::UInt i = 0; !json.isValidIndex (i); ++i) + { + uint256 s; + s.SetHex (json[i].asString ()); + tail->addValue (s); + } + } + catch (...) + { + error = invalid_data (json_name, fieldName); + return false; + } + + break; + + case STI_PATHSET: + if (!value.isArray ()) + { + error = array_expected (json_name, fieldName); + return false; + } + + try + { + data.push_back (new STPathSet (field)); + STPathSet* tail = dynamic_cast (&data.back ()); + check_precondition (tail); + + for (Json::UInt i = 0; value.isValidIndex (i); ++i) + { + STPath p; + + if (!value[i].isArray ()) + { + std::stringstream ss; + ss << fieldName << "[" << i << "]"; + error = array_expected (json_name, ss.str ()); + return false; + } + + for (Json::UInt j = 0; value[i].isValidIndex (j); ++j) + { + std::stringstream ss; + ss << fieldName << "[" << i << "][" << j << "]"; + std::string const element_name ( + json_name + "." + ss.str()); + + // each element in this path has some combination of account, + // currency, or issuer + + Json::Value pathEl = value[i][j]; + + if (!pathEl.isObject ()) + { + error = not_an_object (element_name); + return false; + } + + const Json::Value& account = pathEl["account"]; + const Json::Value& currency = pathEl["currency"]; + const Json::Value& issuer = pathEl["issuer"]; + bool hasCurrency = false; + uint160 uAccount, uCurrency, uIssuer; + + if (! account.isNull ()) + { + // human account id + if (! account.isString ()) + { + error = string_expected (element_name, "account"); + return false; + } + + std::string const strValue (account.asString ()); + + if (value.size () == 40) // 160-bit hex account value + uAccount.SetHex (strValue); + + { + RippleAddress a; + + if (! a.setAccountID (strValue)) + { + error = invalid_data (element_name, "account"); + return false; + } + + uAccount = a.getAccountID (); + } + } + + if (!currency.isNull ()) + { + // human currency + if (!currency.isString ()) + { + error = string_expected (element_name, "currency"); + return false; + } + + hasCurrency = true; + + if (currency.asString ().size () == 40) + { + uCurrency.SetHex (currency.asString ()); + } + else if (!STAmount::currencyFromString ( + uCurrency, currency.asString ())) + { + error = invalid_data (element_name, "currency"); + return false; + } + } + + if (!issuer.isNull ()) + { + // human account id + if (!issuer.isString ()) + { + error = string_expected (element_name, "issuer"); + return false; + } + + if (issuer.asString ().size () == 40) + { + uIssuer.SetHex (issuer.asString ()); + } + else + { + RippleAddress a; + + if (!a.setAccountID (issuer.asString ())) + { + error = invalid_data (element_name, "issuer"); + return false; + } + + uIssuer = a.getAccountID (); + } + } + + p.addElement (STPathElement (uAccount, uCurrency, uIssuer, hasCurrency)); + } + + tail->addPath (p); + } + } + catch (...) + { + error = invalid_data (json_name, fieldName); + return false; + } + + break; + + case STI_ACCOUNT: + { + if (! value.isString ()) + { + error = bad_type (json_name, fieldName); + return false; + } + + std::string strValue = value.asString (); + + try + { + if (value.size () == 40) // 160-bit hex account value + { + uint160 v; + v.SetHex (strValue); + data.push_back (new STAccount (field, v)); + } + else + { + // ripple address + RippleAddress a; + + if (!a.setAccountID (strValue)) + { + error = invalid_data (json_name, fieldName); + return false; + } + + data.push_back (new STAccount (field, a.getAccountID ())); + } + } + catch (...) + { + error = invalid_data (json_name, fieldName); + return false; + } + } + break; + + case STI_OBJECT: + case STI_TRANSACTION: + case STI_LEDGERENTRY: + case STI_VALIDATION: + if (! value.isObject ()) + { + error = not_an_object (json_name, fieldName); + return false; + } + + if (depth > 64) + { + error = too_deep (json_name, fieldName); + return false; + } + + try + { + std::unique_ptr sub_object_; + bool const success (parse (json_name + "." + fieldName, + value, field, depth + 1, sub_object_)); + if (! success) + return false; + data.push_back (sub_object_.release ()); + } + catch (...) + { + error = invalid_data (json_name, fieldName); + return false; + } + + break; + + case STI_ARRAY: + if (! value.isArray ()) + { + error = array_expected (json_name, fieldName); + return false; + } + + try + { + data.push_back (new STArray (field)); + STArray* tail = dynamic_cast (&data.back ()); + check_precondition (tail); + + for (Json::UInt i = 0; value.isValidIndex (i); ++i) + { + bool const isObject (value[i].isObject()); + bool const singleKey (isObject + ? value [i].size() == 1 + : true); + + if (!isObject || !singleKey) + { + std::stringstream ss; + ss << json_name << "." << fieldName << "[" << i << "]"; + error = singleton_expected (ss.str ()); + return false; + } + + // TODO: There doesn't seem to be a nice way to get just the + // first/only key in an object without copying all keys into + // a vector + std::string const objectName (value[i].getMemberNames()[0]);; + SField::ref const nameField (SField::getField(objectName)); + Json::Value const objectFields (value[i][objectName]); + + std::unique_ptr sub_object_; + { + std::stringstream ss; + ss << json_name << "." << fieldName << + "[" << i << "]." << objectName; + bool const success (parse (ss.str (), objectFields, + nameField, depth + 1, sub_object_)); + if (! success) + return false; + } + tail->push_back (*sub_object_); + } + } + catch (...) + { + error = invalid_data (json_name, fieldName); + return false; + } + + break; + + default: + error = bad_type (json_name, fieldName); + return false; + } + } + + sub_object.reset (new STObject (*name, data)); + return true; +} + +} diff --git a/src/ripple_data/protocol/STParsedJSON.h b/src/ripple_data/protocol/STParsedJSON.h new file mode 100644 index 0000000000..218cede18a --- /dev/null +++ b/src/ripple_data/protocol/STParsedJSON.h @@ -0,0 +1,84 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2012, 2013 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef RIPPLE_DATA_STPARSEDJSON_H +#define RIPPLE_DATA_STPARSEDJSON_H + +namespace ripple { + +/** Holds the serialized result of parsing input JSON. + This does validation and checking on the provided JSON. +*/ +class STParsedJSON +{ +public: + /** Parses and creates an STParsedJSON object. + The result of the parsing is stored in object and error. + Exceptions: + Does not throw. + @param name The name of the JSON field, used in diagnostics. + @param json The JSON-RPC to parse. + */ + STParsedJSON (std::string const& name, + Json::Value const& json); + + /** The STObject if the parse was successful. */ + std::unique_ptr object; + + /** On failure, an appropriate set of error values. */ + Json::Value error; + +private: + static std::string make_name (std::string const& object, + std::string const& field); + + static Json::Value not_an_object (std::string const& object, + std::string const& field = std::string()); + + static Json::Value unknown_field (std::string const& object, + std::string const& field = std::string()); + + static Json::Value out_of_range (std::string const& object, + std::string const& field = std::string()); + + static Json::Value bad_type (std::string const& object, + std::string const& field = std::string()); + + static Json::Value invalid_data (std::string const& object, + std::string const& field = std::string()); + + static Json::Value array_expected (std::string const& object, + std::string const& field = std::string()); + + static Json::Value string_expected (std::string const& object, + std::string const& field = std::string()); + + static Json::Value too_deep (std::string const& object, + std::string const& field = std::string()); + + static Json::Value singleton_expected ( + std::string const& object); + + bool parse (std::string const& json_name, Json::Value const& json, + SField::ref inName, int depth, std::unique_ptr & sub_object); +}; + +} + +#endif diff --git a/src/ripple_data/protocol/SerializedObject.cpp b/src/ripple_data/protocol/SerializedObject.cpp index cc3504b6fe..cfe64dc062 100644 --- a/src/ripple_data/protocol/SerializedObject.cpp +++ b/src/ripple_data/protocol/SerializedObject.cpp @@ -138,7 +138,7 @@ void STObject::set (const SOTemplate& type) mData.clear (); mType = &type; - BOOST_FOREACH (const SOElement * elem, type.peek ()) + for (SOTemplate::value_type const& elem : type.peek ()) { if (elem->flags != SOE_REQUIRED) giveObject (makeNonPresentObject (elem->e_field)); @@ -154,7 +154,7 @@ bool STObject::setType (const SOTemplate& type) mType = &type; - BOOST_FOREACH (const SOElement * elem, type.peek ()) + for (SOTemplate::value_type const& elem : type.peek ()) { bool match = false; @@ -208,7 +208,7 @@ bool STObject::isValidForType () { boost::ptr_vector::iterator it = mData.begin (); - BOOST_FOREACH (const SOElement * elem, mType->peek ()) + for (SOTemplate::value_type const& elem : mType->peek ()) { if (it == mData.end ()) return false; @@ -1232,381 +1232,6 @@ void STArray::sort (bool (*compare) (const STObject&, const STObject&)) value.sort (compare); } -std::unique_ptr STObject::parseJson (const Json::Value& object, SField::ref inName, int depth) -{ - if (!object.isObject ()) - throw std::runtime_error ("Value is not an object"); - - SField::ptr name = &inName; - - boost::ptr_vector data; - Json::Value::Members members (object.getMemberNames ()); - - for (Json::Value::Members::iterator it = members.begin (), end = members.end (); it != end; ++it) - { - const std::string& fieldName = *it; - const Json::Value& value = object[fieldName]; - - SField::ref field = SField::getField (fieldName); - - if (field == sfInvalid) - throw std::runtime_error ("Unknown field: " + fieldName); - - switch (field.fieldType) - { - case STI_UINT8: - if (value.isString ()) - { -#if 0 - - if (field == sfTransactionResult) - { - TER terCode; - - if (FUNCTION_THAT_DOESNT_EXIST (value.asString (), terCode)) - value = static_cast (terCode); - else - data.push_back (new STUInt8 (field, lexicalCastThrow (value.asString ()))); - } - - data.push_back (new STUInt8 (field, lexicalCastThrow (value.asString ()))); -#endif - } - else if (value.isInt ()) - { - if (value.asInt () < 0 || value.asInt () > 255) - throw std::runtime_error ("value out of range"); - - data.push_back (new STUInt8 (field, range_check_cast (value.asInt (), 0, 255))); - } - else if (value.isUInt ()) - { - if (value.asUInt () > 255) - throw std::runtime_error ("value out of range"); - - data.push_back (new STUInt8 (field, range_check_cast (value.asUInt (), 0, 255))); - } - else - throw std::runtime_error ("Incorrect type"); - - break; - - case STI_UINT16: - if (value.isString ()) - { - std::string strValue = value.asString (); - - if (!strValue.empty () && ((strValue[0] < '0') || (strValue[0] > '9'))) - { - if (field == sfTransactionType) - { - // Retrieve type from name. Throws if not found. - TxType const txType = TxFormats::getInstance()->findTypeByName (strValue); - - data.push_back (new STUInt16 (field, static_cast (txType))); - - if (*name == sfGeneric) - name = &sfTransaction; - } - else if (field == sfLedgerEntryType) - { - LedgerEntryType const type = LedgerFormats::getInstance()->findTypeByName (strValue); - - data.push_back (new STUInt16 (field, static_cast (type))); - - if (*name == sfGeneric) - name = &sfLedgerEntry; - } - else - throw std::runtime_error ("Invalid field data"); - } - else - data.push_back (new STUInt16 (field, lexicalCastThrow (strValue))); - } - else if (value.isInt ()) - data.push_back (new STUInt16 (field, range_check_cast (value.asInt (), 0, 65535))); - else if (value.isUInt ()) - data.push_back (new STUInt16 (field, range_check_cast (value.asUInt (), 0, 65535))); - else - throw std::runtime_error ("Incorrect type"); - - break; - - case STI_UINT32: - if (value.isString ()) - { - data.push_back (new STUInt32 (field, lexicalCastThrow (value.asString ()))); - } - else if (value.isInt ()) - { - data.push_back (new STUInt32 (field, range_check_cast (value.asInt (), 0u, 4294967295u))); - } - else if (value.isUInt ()) - { - data.push_back (new STUInt32 (field, static_cast (value.asUInt ()))); - } - else - { - throw std::runtime_error ("Incorrect type"); - } - break; - - case STI_UINT64: - if (value.isString ()) - data.push_back (new STUInt64 (field, uintFromHex (value.asString ()))); - else if (value.isInt ()) - data.push_back (new STUInt64 (field, - range_check_cast (value.asInt (), 0, 18446744073709551615ull))); - else if (value.isUInt ()) - data.push_back (new STUInt64 (field, static_cast (value.asUInt ()))); - else - throw std::runtime_error ("Incorrect type"); - - break; - - - case STI_HASH128: - if (value.isString ()) - data.push_back (new STHash128 (field, value.asString ())); - else - throw std::runtime_error ("Incorrect type"); - - break; - - case STI_HASH160: - if (value.isString ()) - data.push_back (new STHash160 (field, value.asString ())); - else - throw std::runtime_error ("Incorrect type"); - - break; - - case STI_HASH256: - if (value.isString ()) - data.push_back (new STHash256 (field, value.asString ())); - else - throw std::runtime_error ("Incorrect type"); - - break; - - case STI_VL: - if (!value.isString ()) - throw std::runtime_error ("Incorrect type"); - - data.push_back (new STVariableLength (field, strUnHex (value.asString ()))); - break; - - case STI_AMOUNT: - data.push_back (new STAmount (field, value)); - break; - - case STI_VECTOR256: - if (!value.isArray ()) - throw std::runtime_error ("Incorrect type"); - - { - data.push_back (new STVector256 (field)); - STVector256* tail = dynamic_cast (&data.back ()); - assert (tail); - - for (Json::UInt i = 0; !object.isValidIndex (i); ++i) - { - uint256 s; - s.SetHex (object[i].asString ()); - tail->addValue (s); - } - } - break; - - case STI_PATHSET: - if (!value.isArray ()) - throw std::runtime_error ("Path set must be array"); - - { - data.push_back (new STPathSet (field)); - STPathSet* tail = dynamic_cast (&data.back ()); - assert (tail); - - for (Json::UInt i = 0; value.isValidIndex (i); ++i) - { - STPath p; - - if (!value[i].isArray ()) - throw std::runtime_error ("Path must be array"); - - for (Json::UInt j = 0; value[i].isValidIndex (j); ++j) - { - // each element in this path has some combination of account, currency, or issuer - - Json::Value pathEl = value[i][j]; - - if (!pathEl.isObject ()) - throw std::runtime_error ("Path elements must be objects"); - - const Json::Value& account = pathEl["account"]; - const Json::Value& currency = pathEl["currency"]; - const Json::Value& issuer = pathEl["issuer"]; - bool hasCurrency = false; - uint160 uAccount, uCurrency, uIssuer; - - if (!account.isNull ()) - { - // human account id - if (!account.isString ()) - throw std::runtime_error ("path element accounts must be strings"); - - std::string strValue = account.asString (); - - if (value.size () == 40) // 160-bit hex account value - uAccount.SetHex (strValue); - - { - RippleAddress a; - - if (!a.setAccountID (strValue)) - throw std::runtime_error ("Account in path element invalid"); - - uAccount = a.getAccountID (); - } - } - - if (!currency.isNull ()) - { - // human currency - if (!currency.isString ()) - throw std::runtime_error ("path element currencies must be strings"); - - hasCurrency = true; - - if (currency.asString ().size () == 40) - uCurrency.SetHex (currency.asString ()); - else if (!STAmount::currencyFromString (uCurrency, currency.asString ())) - throw std::runtime_error ("invalid currency"); - } - - if (!issuer.isNull ()) - { - // human account id - if (!issuer.isString ()) - throw std::runtime_error ("path element issuers must be strings"); - - if (issuer.asString ().size () == 40) - uIssuer.SetHex (issuer.asString ()); - else - { - RippleAddress a; - - if (!a.setAccountID (issuer.asString ())) - throw std::runtime_error ("path element issuer invalid"); - - uIssuer = a.getAccountID (); - } - } - - p.addElement (STPathElement (uAccount, uCurrency, uIssuer, hasCurrency)); - } - - tail->addPath (p); - } - } - break; - - case STI_ACCOUNT: - { - if (!value.isString ()) - throw std::runtime_error ("Incorrect type"); - - std::string strValue = value.asString (); - - if (value.size () == 40) // 160-bit hex account value - { - uint160 v; - v.SetHex (strValue); - data.push_back (new STAccount (field, v)); - } - else - { - // ripple address - RippleAddress a; - - if (!a.setAccountID (strValue)) - { - WriteLog (lsINFO, STObject) << "Invalid acccount JSON: " << fieldName << ": " << strValue; - throw std::runtime_error ("Account invalid"); - } - - data.push_back (new STAccount (field, a.getAccountID ())); - } - } - break; - - case STI_OBJECT: - case STI_TRANSACTION: - case STI_LEDGERENTRY: - case STI_VALIDATION: - if (!value.isObject ()) - throw std::runtime_error ("Inner value is not an object"); - - if (depth > 64) - throw std::runtime_error ("Json nest depth exceeded"); - - data.push_back (parseJson (value, field, depth + 1).release ()); - break; - - case STI_ARRAY: - if (!value.isArray ()) - throw std::runtime_error ("Inner value is not an array"); - - { - data.push_back (new STArray (field)); - STArray* tail = dynamic_cast (&data.back ()); - assert (tail); - - for (Json::UInt i = 0; value.isValidIndex (i); ++i) - { - - bool isObject (value[i].isObject()); - bool singleKey (true); - - if (isObject) - { - singleKey = value[i].size() == 1; - } - - if (!isObject || !singleKey) - { - std::stringstream err; - - err << "First level children of `" - << field.getName() - << "`must be objects containing a single key with" - << " an object value"; - - throw std::runtime_error (err.str()); - } - - // TODO: There doesn't seem to be a nice way to get just the - // first/only key in an object without copying all keys into - // a vector - std::string objectName (value[i].getMemberNames()[0]);; - SField::ref nameField (SField::getField(objectName)); - Json::Value objectFields (value[i][objectName]); - - tail->push_back (*STObject::parseJson (objectFields, - nameField, - depth + 1)); - } - } - break; - - default: - throw std::runtime_error ("Invalid field type"); - } - } - - return std::unique_ptr (new STObject (*name, data)); -} - //------------------------------------------------------------------------------ class SerializedObjectTests : public UnitTest @@ -1656,8 +1281,9 @@ public: Json::Value faultyJson; bool parsedOK (parseJSONString(faulty, faultyJson)); unexpected(!parsedOK, "failed to parse"); - so = STObject::parseJson (faultyJson); - fail ("It should have thrown. " + STParsedJSON parsed ("test", faultyJson); + expect (parsed.object.get() == nullptr, + "It should have thrown. " "Immediate children of STArray encoded as json must " "have one key only."); } @@ -1677,16 +1303,15 @@ public: bool parsedOK (parseJSONString(json, jsonObject)); if (parsedOK) { - std::unique_ptr so; - so = STObject::parseJson (jsonObject); - Json::FastWriter writer; - std::string const& serialized (writer.write(so->getJson(0))); - bool serializedOK = serialized == json; - unexpected (!serializedOK, serialized + " should equal: " + json); + STParsedJSON parsed ("test", jsonObject); + Json::FastWriter writer; + std::string const& serialized ( + writer.write (parsed.object->getJson(0))); + expect (serialized == json, serialized + " should equal: " + json); } else { - fail ("Couldn't parse json: " + json); + fail ("Couldn't parse json: " + json); } } diff --git a/src/ripple_data/protocol/SerializedObject.h b/src/ripple_data/protocol/SerializedObject.h index 1548c78467..66f4c5f639 100644 --- a/src/ripple_data/protocol/SerializedObject.h +++ b/src/ripple_data/protocol/SerializedObject.h @@ -48,17 +48,17 @@ public: setType (type); } - std::unique_ptr oClone () const + STObject (SField::ref name, boost::ptr_vector& data) : SerializedType (name), mType (NULL) + { + mData.swap (data); + } + + std::unique_ptr oClone () const { return std::unique_ptr (new STObject (*this)); } - static std::unique_ptr parseJson (const Json::Value & value, SField::ref name = sfGeneric, int depth = 0); - - virtual ~STObject () - { - ; - } + virtual ~STObject () { } static std::unique_ptr deserialize (SerializerIterator & sit, SField::ref name); @@ -295,26 +295,11 @@ private: } */ - // VFALCO TODO these parameters should not be const references. - template - static T range_check_cast (const U& value, const T& minimum, const T& maximum) - { - if ((value < minimum) || (value > maximum)) - throw std::runtime_error ("Value out of range"); - - return static_cast (value); - } - STObject* duplicate () const { return new STObject (*this); } - STObject (SField::ref name, boost::ptr_vector& data) : SerializedType (name), mType (NULL) - { - mData.swap (data); - } - private: boost::ptr_vector mData; const SOTemplate* mType; @@ -322,6 +307,16 @@ private: //------------------------------------------------------------------------------ +// VFALCO TODO these parameters should not be const references. +template +static T range_check_cast (const U& value, const T& minimum, const T& maximum) +{ + if ((value < minimum) || (value > maximum)) + throw std::runtime_error ("Value out of range"); + + return static_cast (value); +} + inline STObject::iterator range_begin (STObject& x) { return x.begin (); diff --git a/src/ripple_data/protocol/SerializedObjectTemplate.cpp b/src/ripple_data/protocol/SerializedObjectTemplate.cpp index affa61398b..9ee719763d 100644 --- a/src/ripple_data/protocol/SerializedObjectTemplate.cpp +++ b/src/ripple_data/protocol/SerializedObjectTemplate.cpp @@ -47,7 +47,7 @@ void SOTemplate::push_back (SOElement const& r) // Append the new element. // - mTypes.push_back (new SOElement (r)); + mTypes.push_back (value_type (new SOElement (r))); } int SOTemplate::getIndex (SField::ref f) const diff --git a/src/ripple_data/protocol/SerializedObjectTemplate.h b/src/ripple_data/protocol/SerializedObjectTemplate.h index 6c4e541b11..b1aff7833c 100644 --- a/src/ripple_data/protocol/SerializedObjectTemplate.h +++ b/src/ripple_data/protocol/SerializedObjectTemplate.h @@ -53,18 +53,18 @@ public: //------------------------------------------------------------------------------ /** Defines the fields and their attributes within a SerializedObject. - Each subclass of SerializedObject will provide its own template describing the available fields and their metadata attributes. */ class SOTemplate { public: - /** Create an empty template. + typedef std::unique_ptr value_type; + typedef std::vector list_type; + /** Create an empty template. After creating the template, call @ref push_back with the desired fields. - @see push_back */ SOTemplate (); @@ -72,21 +72,19 @@ public: // VFALCO NOTE Why do we even bother with the 'private' keyword if // this function is present? // - std::vector const& peek () const + list_type const& peek () const { return mTypes; } - /** Add an element to the template. - */ + /** Add an element to the template. */ void push_back (SOElement const& r); - /** Retrieve the position of a named field. - */ + /** Retrieve the position of a named field. */ int getIndex (SField::ref) const; private: - std::vector mTypes; + list_type mTypes; std::vector mIndex; // field num -> index }; diff --git a/src/ripple_data/protocol/SerializedTypes.h b/src/ripple_data/protocol/SerializedTypes.h index bee83323f6..41a8f16b99 100644 --- a/src/ripple_data/protocol/SerializedTypes.h +++ b/src/ripple_data/protocol/SerializedTypes.h @@ -65,6 +65,20 @@ static inline const uint160& get_u160_one () #define ACCOUNT_XRP get_u160_zero() #define ACCOUNT_ONE get_u160_one() // Used as a place holder. +//------------------------------------------------------------------------------ + +/** A type which can be exported to a well known binary format. + + A SerializedType: + - Always a field + - Can always go inside an eligible enclosing SerializedType + (such as STArray) + - Has a field name + + + Like JSON, a SerializedObject is a basket which has rules + on what it can hold. +*/ // VFALCO TODO Document this as it looks like a central class. // STObject is derived from it // @@ -88,6 +102,9 @@ public: return std::unique_ptr (new SerializedType (name)); } + /** A SerializeType is a field. + This sets the name. + */ void setFName (SField::ref n) { fName = &n; @@ -160,19 +177,27 @@ private: } }; +//------------------------------------------------------------------------------ + inline SerializedType* new_clone (const SerializedType& s) { - return s.clone ().release (); + SerializedType* const copy (s.clone ().release ()); + assert (typeid (*copy) == typeid (s)); + return copy; } + inline void delete_clone (const SerializedType* s) { boost::checked_delete (s); } + inline std::ostream& operator<< (std::ostream& out, const SerializedType& t) { return out << t.getFullText (); } +//------------------------------------------------------------------------------ + class STUInt8 : public SerializedType { public: @@ -230,6 +255,8 @@ private: static STUInt8* construct (SerializerIterator&, SField::ref f); }; +//------------------------------------------------------------------------------ + class STUInt16 : public SerializedType { public: @@ -287,6 +314,8 @@ private: static STUInt16* construct (SerializerIterator&, SField::ref name); }; +//------------------------------------------------------------------------------ + class STUInt32 : public SerializedType { public: @@ -344,10 +373,11 @@ private: static STUInt32* construct (SerializerIterator&, SField::ref name); }; +//------------------------------------------------------------------------------ + class STUInt64 : public SerializedType { public: - STUInt64 (uint64 v = 0) : value (v) { ; @@ -401,6 +431,8 @@ private: static STUInt64* construct (SerializerIterator&, SField::ref name); }; +//------------------------------------------------------------------------------ + // Internal form: // 1: If amount is zero, then value is zero and offset is -100 // 2: Otherwise: @@ -805,6 +837,8 @@ private: extern const STAmount saZero; extern const STAmount saOne; +//------------------------------------------------------------------------------ + class STHash128 : public SerializedType { public: @@ -876,6 +910,8 @@ private: static STHash128* construct (SerializerIterator&, SField::ref name); }; +//------------------------------------------------------------------------------ + class STHash160 : public SerializedType { public: @@ -947,6 +983,8 @@ private: static STHash160* construct (SerializerIterator&, SField::ref name); }; +//------------------------------------------------------------------------------ + class STHash256 : public SerializedType { public: @@ -1018,6 +1056,8 @@ private: static STHash256* construct (SerializerIterator&, SField::ref); }; +//------------------------------------------------------------------------------ + // variable length byte string class STVariableLength : public SerializedType { @@ -1091,6 +1131,8 @@ private: static STVariableLength* construct (SerializerIterator&, SField::ref); }; +//------------------------------------------------------------------------------ + class STAccount : public STVariableLength { public: @@ -1137,6 +1179,8 @@ private: static STAccount* construct (SerializerIterator&, SField::ref); }; +//------------------------------------------------------------------------------ + class STPathElement { private: @@ -1223,6 +1267,8 @@ private: uint160 mIssuerID; }; +//------------------------------------------------------------------------------ + class STPath { public: @@ -1322,6 +1368,8 @@ inline std::vector::const_iterator range_end (const STPath& x) return x.end (); } +//------------------------------------------------------------------------------ + // A set of zero or more payment paths class STPathSet : public SerializedType { @@ -1482,6 +1530,8 @@ inline std::vector::const_iterator range_end (const STPathSet& x) return x.end (); } +//------------------------------------------------------------------------------ + class STVector256 : public SerializedType { public: @@ -1582,4 +1632,3 @@ private: }; #endif -// vim:ts=4 diff --git a/src/ripple_data/ripple_data.cpp b/src/ripple_data/ripple_data.cpp index 6692adba8b..2c4ca57b7c 100644 --- a/src/ripple_data/ripple_data.cpp +++ b/src/ripple_data/ripple_data.cpp @@ -44,6 +44,7 @@ #include #include "../ripple/sslutil/ripple_sslutil.h" +#include "../ripple/rpc/api/ErrorCodes.h" // VFALCO TODO fix these warnings! #if BEAST_MSVC @@ -55,6 +56,8 @@ #undef min #endif +#include "protocol/STParsedJSON.cpp" + namespace ripple { diff --git a/src/ripple_data/ripple_data.h b/src/ripple_data/ripple_data.h index 9c4b1536d8..080334c19d 100644 --- a/src/ripple_data/ripple_data.h +++ b/src/ripple_data/ripple_data.h @@ -51,6 +51,8 @@ namespace ripple { } +#include "protocol/STParsedJSON.h" + //------------------------------------------------------------------------------ namespace boost diff --git a/src/ripple_net/ripple_net.cpp b/src/ripple_net/ripple_net.cpp index d2addab349..82114af828 100644 --- a/src/ripple_net/ripple_net.cpp +++ b/src/ripple_net/ripple_net.cpp @@ -42,6 +42,12 @@ #include "../ripple_websocket/ripple_websocket.h" // for HTTPClient, RPCDoor +// VFALCO NOTE This is the "new new new" where individual headers are included +// directly (instead of th emodule header). The corresponding .cpp +// still uses the unity style inclusion. +// +#include "../ripple/rpc/api/ErrorCodes.h" + namespace ripple { diff --git a/src/ripple_net/rpc/RPCCall.cpp b/src/ripple_net/rpc/RPCCall.cpp index 853af3a273..f8950df344 100644 --- a/src/ripple_net/rpc/RPCCall.cpp +++ b/src/ripple_net/rpc/RPCCall.cpp @@ -78,7 +78,8 @@ private: } else { - return rpcError (rpcINVALID_PARAMS); + return RPC::make_param_error (std::string ("Invalid currency/issuer '") + + strCurrencyIssuer + "'"); } } diff --git a/src/ripple_net/rpc/RPCErr.cpp b/src/ripple_net/rpc/RPCErr.cpp index 3d4e91f538..fa01f544df 100644 --- a/src/ripple_net/rpc/RPCErr.cpp +++ b/src/ripple_net/rpc/RPCErr.cpp @@ -17,104 +17,32 @@ */ //============================================================================== -struct RPCErr; // for Log +struct RPCErr; SETUP_LOG (RPCErr) +// VFALCO NOTE Deprecated function Json::Value rpcError (int iError, Json::Value jvResult) { - static struct - { - int iError; - const char* pToken; - const char* pMessage; - } errorInfoA[] = - { - { rpcACT_BITCOIN, "actBitcoin", "Account is bitcoin address." }, - { rpcACT_EXISTS, "actExists", "Account already exists." }, - { rpcACT_MALFORMED, "actMalformed", "Account malformed." }, - { rpcACT_NOT_FOUND, "actNotFound", "Account not found." }, - { rpcBAD_BLOB, "badBlob", "Blob must be a non-empty hex string." }, - { 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." }, - { rpcCOMMAND_MISSING, "commandMissing", "Missing command entry." }, - { rpcDST_ACT_MALFORMED, "dstActMalformed", "Destination account is malformed." }, - { rpcDST_ACT_MISSING, "dstActMissing", "Destination account does not exist." }, - { rpcDST_AMT_MALFORMED, "dstAmtMalformed", "Destination amount/currency/issuer is malformed." }, - { rpcDST_ISR_MALFORMED, "dstIsrMalformed", "Destination issuer is malformed." }, - { rpcFORBIDDEN, "forbidden", "Bad credentials." }, - { rpcFAIL_GEN_DECRPYT, "failGenDecrypt", "Failed to decrypt generator." }, - { rpcGETS_ACT_MALFORMED, "getsActMalformed", "Gets account malformed." }, - { rpcGETS_AMT_MALFORMED, "getsAmtMalformed", "Gets amount malformed." }, - { rpcHOST_IP_MALFORMED, "hostIpMalformed", "Host IP is malformed." }, - { rpcINSUF_FUNDS, "insufFunds", "Insufficient funds." }, - { rpcINTERNAL, "internal", "Internal error." }, - { 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." }, - { rpcNICKNAME_MALFORMED, "nicknameMalformed", "Nickname is malformed." }, - { rpcNICKNAME_MISSING, "nicknameMissing", "Nickname does not exist." }, - { rpcNICKNAME_PERM, "nicknamePerm", "Account does not control nickname." }, - { rpcNOT_IMPL, "notImpl", "Not implemented." }, - { rpcNO_ACCOUNT, "noAccount", "No such account." }, - { rpcNO_CLOSED, "noClosed", "Closed ledger is unavailable." }, - { rpcNO_CURRENT, "noCurrent", "Current ledger is unavailable." }, - { rpcNO_EVENTS, "noEvents", "Current transport does not support events." }, - { rpcNO_GEN_DECRPYT, "noGenDectypt", "Password failed to decrypt master public generator." }, - { rpcNO_NETWORK, "noNetwork", "Network not available." }, - { rpcNO_PATH, "noPath", "Unable to find a ripple path." }, - { rpcNO_PERMISSION, "noPermission", "You don't have permission for this command." }, - { rpcNO_PF_REQUEST, "noPathRequest", "No pathfinding request in progress." }, - { rpcNOT_STANDALONE, "notStandAlone", "Operation valid in debug mode only." }, - { rpcNOT_SUPPORTED, "notSupported", "Operation not supported." }, - { rpcPASSWD_CHANGED, "passwdChanged", "Wrong key, password changed." }, - { rpcPAYS_ACT_MALFORMED, "paysActMalformed", "Pays account malformed." }, - { rpcPAYS_AMT_MALFORMED, "paysAmtMalformed", "Pays amount malformed." }, - { rpcPORT_MALFORMED, "portMalformed", "Port is malformed." }, - { rpcPUBLIC_MALFORMED, "publicMalformed", "Public key is malformed." }, - { rpcQUALITY_MALFORMED, "qualityMalformed", "Quality malformed." }, - { 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_AMT_MALFORMED, "srcAmtMalformed", "Source amount/currency/issuer is malformed." }, - { rpcSRC_CUR_MALFORMED, "srcCurMalformed", "Source currency is malformed." }, - { rpcSRC_ISR_MALFORMED, "srcIsrMalformed", "Source issuer is malformed." }, - { rpcSRC_UNCLAIMED, "srcUnclaimed", "Source account is not claimed." }, - { rpcTXN_NOT_FOUND, "txnNotFound", "Transaction not found." }, - { rpcUNKNOWN_COMMAND, "unknownCmd", "Unknown method." }, - { rpcWRONG_SEED, "wrongSeed", "The regular key does not point as the master key." }, - { rpcTOO_BUSY, "tooBusy", "The server is too busy to help you now." }, - { rpcSLOW_DOWN, "slowDown", "You are placing too much load on the server." }, - { rpcATX_DEPRECATED, "deprecated", "Use the new API or specify a ledger range." }, - }; - - int i; - - for (i = NUMBER (errorInfoA); i-- && errorInfoA[i].iError != iError;) - ; - - jvResult["error"] = i >= 0 ? errorInfoA[i].pToken : lexicalCast (iError); - jvResult["error_message"] = i >= 0 ? errorInfoA[i].pMessage : lexicalCast (iError); - jvResult["error_code"] = iError; - - if (i >= 0) - { - WriteLog (lsDEBUG, RPCErr) << "rpcError: " - << errorInfoA[i].pToken << ": " << errorInfoA[i].pMessage << std::endl; - } - + RPC::inject_error (iError, jvResult); return jvResult; } +// VFALCO NOTE Deprecated function bool isRpcError (Json::Value jvResult) { return jvResult.isObject () && jvResult.isMember ("error"); } -// vim:ts=4 +Json::Value const& logRPCError (Json::Value const& json) +{ + if (RPC::contains_error (json)) + { + WriteLog (lsDEBUG, RPCErr) << + "rpcError: " << json ["error"] << + ": " << json ["error_message"]; + } + + return json; +} + diff --git a/src/ripple_net/rpc/RPCErr.h b/src/ripple_net/rpc/RPCErr.h index c08ea36323..59d6c4a87d 100644 --- a/src/ripple_net/rpc/RPCErr.h +++ b/src/ripple_net/rpc/RPCErr.h @@ -20,92 +20,11 @@ #ifndef RIPPLE_NET_RPC_RPCERR_H_INCLUDED #define RIPPLE_NET_RPC_RPCERR_H_INCLUDED -enum -{ - rpcSUCCESS = 0, - rpcBAD_SYNTAX, // Must be 1 to print usage to command line. - rpcJSON_RPC, - rpcFORBIDDEN, - - // Error numbers beyond this line are not stable between versions. - // Programs should use error tokens. - - // Misc failure - rpcLOAD_FAILED, - rpcNO_PERMISSION, - rpcNO_EVENTS, - rpcNOT_STANDALONE, - rpcTOO_BUSY, - rpcSLOW_DOWN, - - // Networking - rpcNO_CLOSED, - rpcNO_CURRENT, - rpcNO_NETWORK, - - // Ledger state - rpcACT_EXISTS, - rpcACT_NOT_FOUND, - rpcINSUF_FUNDS, - rpcLGR_NOT_FOUND, - rpcNICKNAME_MISSING, - rpcNO_ACCOUNT, - rpcNO_PATH, - rpcPASSWD_CHANGED, - rpcSRC_MISSING, - rpcSRC_UNCLAIMED, - rpcTXN_NOT_FOUND, - rpcWRONG_SEED, - - // Malformed command - rpcINVALID_PARAMS, - rpcUNKNOWN_COMMAND, - rpcNO_PF_REQUEST, - - // Bad parameter - rpcACT_BITCOIN, - rpcACT_MALFORMED, - rpcQUALITY_MALFORMED, - rpcBAD_BLOB, - rpcBAD_FEATURE, - rpcBAD_ISSUER, - rpcBAD_MARKET, - rpcBAD_SECRET, - rpcBAD_SEED, - rpcCOMMAND_MISSING, - rpcDST_ACT_MALFORMED, - rpcDST_ACT_MISSING, - rpcDST_AMT_MALFORMED, - rpcDST_ISR_MALFORMED, - rpcGETS_ACT_MALFORMED, - rpcGETS_AMT_MALFORMED, - rpcHOST_IP_MALFORMED, - rpcLGR_IDXS_INVALID, - rpcLGR_IDX_MALFORMED, - rpcNICKNAME_MALFORMED, - rpcNICKNAME_PERM, - rpcPAYS_ACT_MALFORMED, - rpcPAYS_AMT_MALFORMED, - rpcPORT_MALFORMED, - rpcPUBLIC_MALFORMED, - rpcSRC_ACT_MALFORMED, - rpcSRC_ACT_MISSING, - rpcSRC_ACT_NOT_FOUND, - rpcSRC_AMT_MALFORMED, - rpcSRC_CUR_MALFORMED, - rpcSRC_ISR_MALFORMED, - rpcATX_DEPRECATED, - - // Internal error (should never happen) - rpcINTERNAL, // Generic internal error. - rpcFAIL_GEN_DECRPYT, - rpcNOT_IMPL, - rpcNOT_SUPPORTED, - rpcNO_GEN_DECRPYT, -}; +Json::Value const& logRPCError (Json::Value const& json); +// VFALCO NOTE these are deprecated bool isRpcError (Json::Value jvResult); -Json::Value rpcError (int iError, Json::Value jvResult = Json::Value (Json::objectValue)); +Json::Value rpcError (int iError, + Json::Value jvResult = Json::Value (Json::objectValue)); #endif -// vim:ts=4