diff --git a/Builds/VisualStudio2013/RippleD.vcxproj b/Builds/VisualStudio2013/RippleD.vcxproj index 57cb67315f..19986c9567 100644 --- a/Builds/VisualStudio2013/RippleD.vcxproj +++ b/Builds/VisualStudio2013/RippleD.vcxproj @@ -2967,6 +2967,9 @@ True + + True + True diff --git a/Builds/VisualStudio2013/RippleD.vcxproj.filters b/Builds/VisualStudio2013/RippleD.vcxproj.filters index f91c2c3253..03058285dc 100644 --- a/Builds/VisualStudio2013/RippleD.vcxproj.filters +++ b/Builds/VisualStudio2013/RippleD.vcxproj.filters @@ -3651,6 +3651,9 @@ ripple\rpc\handlers + + ripple\rpc\handlers + ripple\rpc\handlers diff --git a/src/ripple/app/paths/RippleState.h b/src/ripple/app/paths/RippleState.h index 175ab301dc..fac14266c5 100644 --- a/src/ripple/app/paths/RippleState.h +++ b/src/ripple/app/paths/RippleState.h @@ -42,7 +42,7 @@ public: using pointer = std::shared_ptr ; public: - RippleState() = delete; + RippleState () = delete; virtual ~RippleState() = default; diff --git a/src/ripple/net/impl/RPCCall.cpp b/src/ripple/net/impl/RPCCall.cpp index c1a1cc19ce..7151e6e2a9 100644 --- a/src/ripple/net/impl/RPCCall.cpp +++ b/src/ripple/net/impl/RPCCall.cpp @@ -790,6 +790,46 @@ private: return jvRequest; } + // parse gateway balances + // gateway_balances [] [ [ ]] + + Json::Value parseGatewayBalances (Json::Value const& jvParams) + { + unsigned int index = 0; + const unsigned int size = jvParams.size (); + + Json::Value jvRequest; + + std::string param = jvParams[index++].asString (); + if (param.empty ()) + return RPC::make_param_error ("Invalid first parameter"); + + if (param[0] != 'r') + { + if (param.size() == 64) + jvRequest[jss::ledger_hash] = param; + else + jvRequest[jss::ledger_index] = param; + + if (size <= index) + return RPC::make_param_error ("Invalid hotwallet"); + + param = jvParams[index++].asString (); + } + + jvRequest[jss::account] = param; + + if (index < size) + { + Json::Value& hotWallets = + (jvRequest["hotwallet"] = Json::arrayValue); + while (index < size) + hotWallets.append (jvParams[index++].asString ()); + } + + return jvRequest; + } + public: //-------------------------------------------------------------------------- @@ -858,6 +898,7 @@ public: { "consensus_info", &RPCParser::parseAsIs, 0, 0 }, { "feature", &RPCParser::parseFeature, 0, 2 }, { "fetch_info", &RPCParser::parseFetchInfo, 0, 1 }, + { "gateway_balances", &RPCParser::parseGatewayBalances , 1, -1 }, { "get_counts", &RPCParser::parseGetCounts, 0, 1 }, { "json", &RPCParser::parseJson, 2, 2 }, { "ledger", &RPCParser::parseLedger, 0, 2 }, diff --git a/src/ripple/protocol/JsonFields.h b/src/ripple/protocol/JsonFields.h index 14319b8978..f251eb9e43 100644 --- a/src/ripple/protocol/JsonFields.h +++ b/src/ripple/protocol/JsonFields.h @@ -79,8 +79,10 @@ JSS ( age ); // out: UniqueNodeList, NetworkOPs JSS ( alternatives ); // out: PathRequest, RipplePathFind JSS ( amendment_blocked ); // out: NetworkOPs JSS ( asks ); // out: Subscribe +JSS ( assets ); // out: GatewayBalances JSS ( authorized ); // out: AccountLines JSS ( balance ); // out: AccountLines +JSS ( balances ); // out: GatewayBalances JSS ( base ); // out: LogLevel JSS ( base_fee ); // out: NetworkOPs JSS ( base_fee_xrp ); // out: NetworkOPs @@ -252,6 +254,7 @@ JSS ( node_reads_total ); // out: GetCounts JSS ( node_writes ); // out: GetCounts JSS ( node_written_bytes ); // out: GetCounts JSS ( nodes ); // out: LedgerEntrySet, PathState +JSS ( obligations ); // out: GatewayBalances JSS ( offer ); // in: LedgerEntry JSS ( offers ); // out: NetworkOPs, AccountOffers, Subscribe JSS ( offline ); // in: TransactionSign diff --git a/src/ripple/rpc/handlers/GatewayBalances.cpp b/src/ripple/rpc/handlers/GatewayBalances.cpp new file mode 100644 index 0000000000..425f3b33b8 --- /dev/null +++ b/src/ripple/rpc/handlers/GatewayBalances.cpp @@ -0,0 +1,229 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2012-2014 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include +#include +#include +#include + +namespace ripple { + +// Query: +// 1) Specify ledger to query. +// 2) Specify issuer account (cold wallet) in "account" field. +// 3) Specify accounts that hold gateway assets (such as hot wallets) +// using "hotwallet" field which should be either a string (if just +// one wallet) or an array of strings (if more than one). + +// Response: +// 1) Array, "obligations", indicating the total obligations of the +// gateway in each currency. Obligations to specified hot wallets +// are not counted here. +// 2) Object, "balances", indicating balances in each account +// that holds gateway assets. (Those specified in the "hotwallet" +// field.) +// 3) Object of "assets" indicating accounts that owe the gateway. +// (Gateways typically do not hold positive balances. This is unusual.) + +// gateway_balances [] [ [ hotWallets; + + if (params.isMember ("hotwallet")) + { + Json::Value const& hw = params["hotwallet"]; + bool valid = true; + + auto addHotWallet = [&valid, &hotWallets](Json::Value const& j) + { + if (j.isString()) + { + RippleAddress ra; + if (! ra.setAccountPublic (j.asString ()) && + ! ra.setAccountID (j.asString())) + { + valid = false; + } + else + hotWallets.insert (ra.getAccountID ()); + } + else + { + valid = false; + } + }; + + if (hw.isArray()) + { + for (unsigned i = 0; i < hw.size(); ++i) + addHotWallet (hw[i]); + } + else if (hw.isString()) + { + addHotWallet (hw); + } + else + { + valid = false; + } + + if (! valid) + { + result[jss::error] = "invalidHotWallet"; + return result; + } + + } + + std::map sums; + std::map > hotBalances; + std::map > assets; + + // Traverse the cold wallet's trust lines + forEachItem(*ledger, accountID, getApp().getSLECache(), + [&](std::shared_ptr const& sle) + { + auto rs = RippleState::makeItem (accountID, sle); + + if (!rs) + return; + + int balSign = rs->getBalance().signum(); + if (balSign == 0) + return; + + auto const& peer = rs->getAccountIDPeer(); + + // Here, a negative balance means the cold wallet owes (normal) + // A positive balance means the cold wallet has an asset (unusual) + + if (hotWallets.count (peer) > 0) + { + // This is a specified hot wallt + hotBalances[peer].push_back (-rs->getBalance ()); + } + else if (balSign > 0) + { + // This is a gateway asset + assets[peer].push_back (rs->getBalance ()); + } + else + { + // normal negative balance, obligation to customer + auto& bal = sums[rs->getBalance().getCurrency()]; + if (bal == zero) + { + // This is needed to set the currency code correctly + bal = -rs->getBalance(); + } + else + bal -= rs->getBalance(); + } + }); + + if (! sums.empty()) + { + Json::Value& j = (result [jss::obligations] = Json::objectValue); + for (auto const& e : sums) + { + j[to_string (e.first)] = e.second.getText (); + } + } + + if (! hotBalances.empty()) + { + Json::Value& j = (result [jss::balances] = Json::objectValue); + for (auto const& account : hotBalances) + { + Json::Value& balanceArray = (j[to_string (account.first)] = Json::arrayValue); + for (auto const& balance : account.second) + { + Json::Value& entry = balanceArray.append (Json::objectValue); + entry[jss::currency] = to_string (balance.issue ().currency); + entry[jss::value] = balance.getText(); + } + } + } + + if (! assets.empty()) + { + Json::Value& j = (result [jss::assets] = Json::objectValue); + + for (auto const& account : assets) + { + Json::Value& balanceArray = (j[to_string (account.first)] = Json::arrayValue); + for (auto const& balance : account.second) + { + Json::Value& entry = balanceArray.append (Json::objectValue); + entry[jss::currency] = to_string (balance.issue ().currency); + entry[jss::value] = balance.getText(); + } + } + } + + return result; +} + +} // ripple diff --git a/src/ripple/rpc/handlers/Handlers.h b/src/ripple/rpc/handlers/Handlers.h index 0ccebad638..44bc746358 100644 --- a/src/ripple/rpc/handlers/Handlers.h +++ b/src/ripple/rpc/handlers/Handlers.h @@ -37,6 +37,7 @@ Json::Value doConnect (RPC::Context&); Json::Value doConsensusInfo (RPC::Context&); Json::Value doFeature (RPC::Context&); Json::Value doFetchInfo (RPC::Context&); +Json::Value doGatewayBalances (RPC::Context&); Json::Value doGetCounts (RPC::Context&); Json::Value doInternal (RPC::Context&); Json::Value doLedgerAccept (RPC::Context&); diff --git a/src/ripple/rpc/impl/Handler.cpp b/src/ripple/rpc/impl/Handler.cpp index 019f1c2d92..1703b417b9 100644 --- a/src/ripple/rpc/impl/Handler.cpp +++ b/src/ripple/rpc/impl/Handler.cpp @@ -109,6 +109,7 @@ HandlerTable HANDLERS({ { "can_delete", byRef (&doCanDelete), Role::ADMIN, NO_CONDITION }, { "connect", byRef (&doConnect), Role::ADMIN, NO_CONDITION }, { "consensus_info", byRef (&doConsensusInfo), Role::ADMIN, NO_CONDITION }, + { "gateway_balances", byRef (&doGatewayBalances), Role::USER, NO_CONDITION }, { "get_counts", byRef (&doGetCounts), Role::ADMIN, NO_CONDITION }, { "internal", byRef (&doInternal), Role::ADMIN, NO_CONDITION }, { "feature", byRef (&doFeature), Role::ADMIN, NO_CONDITION }, diff --git a/src/ripple/unity/rpcx.cpp b/src/ripple/unity/rpcx.cpp index ec30fc67b4..65255522a4 100644 --- a/src/ripple/unity/rpcx.cpp +++ b/src/ripple/unity/rpcx.cpp @@ -49,6 +49,7 @@ #include #include #include +#include #include #include #include