mirror of
https://github.com/XRPLF/clio.git
synced 2025-11-20 19:56:00 +00:00
add gateway_balances. fix bug with account_currencies
This commit is contained in:
@@ -84,6 +84,7 @@ target_sources(clio PRIVATE
|
||||
src/rpc/handlers/AccountLines.cpp
|
||||
src/rpc/handlers/AccountOffers.cpp
|
||||
src/rpc/handlers/AccountObjects.cpp
|
||||
src/rpc/handlers/GatewayBalances.cpp
|
||||
# Ledger
|
||||
src/rpc/handlers/Ledger.cpp
|
||||
src/rpc/handlers/LedgerData.cpp
|
||||
|
||||
@@ -27,6 +27,9 @@ doAccountObjects(Context const& context);
|
||||
Result
|
||||
doAccountOffers(Context const& context);
|
||||
|
||||
Result
|
||||
doGatewayBalances(Context const& context);
|
||||
|
||||
// channels methods
|
||||
|
||||
Result
|
||||
|
||||
@@ -105,6 +105,7 @@ static std::unordered_map<std::string, std::function<Result(Context const&)>>
|
||||
{"account_objects", &doAccountObjects},
|
||||
{"account_offers", &doAccountOffers},
|
||||
{"account_tx", &doAccountTx},
|
||||
{"gateway_balances", &doGatewayBalances},
|
||||
{"book_offers", &doBookOffers},
|
||||
{"channel_authorize", &doChannelAuthorize},
|
||||
{"channel_verify", &doChannelVerify},
|
||||
@@ -145,6 +146,10 @@ shouldForwardToRippled(Context const& ctx)
|
||||
}
|
||||
}
|
||||
|
||||
if (ctx.method == "account_info" && request.contains("queue") &&
|
||||
request.at("queue").as_bool())
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -152,7 +157,12 @@ Result
|
||||
buildResponse(Context const& ctx)
|
||||
{
|
||||
if (shouldForwardToRippled(ctx))
|
||||
return ctx.balancer->forwardToRippled(ctx.params);
|
||||
{
|
||||
auto res = ctx.balancer->forwardToRippled(ctx.params);
|
||||
if (res.size() == 0)
|
||||
return Status{Error::rpcFAILED_TO_FORWARD};
|
||||
return res;
|
||||
}
|
||||
|
||||
if (handlerTable.find(ctx.method) == handlerTable.end())
|
||||
return Status{Error::rpcUNKNOWN_COMMAND};
|
||||
|
||||
@@ -331,7 +331,7 @@ traverseOwnedNodes(
|
||||
|
||||
if (!ownedNode)
|
||||
{
|
||||
throw std::runtime_error("Could not find owned node");
|
||||
break;
|
||||
}
|
||||
|
||||
ripple::SerialIter it{ownedNode->data(), ownedNode->size()};
|
||||
|
||||
@@ -6,13 +6,12 @@
|
||||
#include <ripple/protocol/jss.h>
|
||||
#include <boost/json.hpp>
|
||||
#include <algorithm>
|
||||
#include <rpc/RPCHelpers.h>
|
||||
#include <backend/BackendInterface.h>
|
||||
#include <backend/DBHelpers.h>
|
||||
#include <backend/Pg.h>
|
||||
#include <rpc/RPCHelpers.h>
|
||||
|
||||
namespace RPC
|
||||
{
|
||||
namespace RPC {
|
||||
|
||||
void
|
||||
addChannel(boost::json::array& jsonLines, ripple::SLE const& line)
|
||||
@@ -20,7 +19,8 @@ addChannel(boost::json::array& jsonLines, ripple::SLE const& line)
|
||||
boost::json::object jDst;
|
||||
jDst["channel_id"] = ripple::to_string(line.key());
|
||||
jDst["account"] = ripple::to_string(line.getAccountID(ripple::sfAccount));
|
||||
jDst["destination_account"] = ripple::to_string(line.getAccountID(ripple::sfDestination));
|
||||
jDst["destination_account"] =
|
||||
ripple::to_string(line.getAccountID(ripple::sfDestination));
|
||||
jDst["amount"] = line[ripple::sfAmount].getText();
|
||||
jDst["balance"] = line[ripple::sfBalance].getText();
|
||||
if (publicKeyType(line[ripple::sfPublicKey]))
|
||||
@@ -54,10 +54,10 @@ doAccountChannels(Context const& context)
|
||||
|
||||
auto lgrInfo = std::get<ripple::LedgerInfo>(v);
|
||||
|
||||
if(!request.contains("account"))
|
||||
if (!request.contains("account"))
|
||||
return Status{Error::rpcINVALID_PARAMS, "missingAccount"};
|
||||
|
||||
if(!request.at("account").is_string())
|
||||
if (!request.at("account").is_string())
|
||||
return Status{Error::rpcINVALID_PARAMS, "accountNotString"};
|
||||
|
||||
auto accountID =
|
||||
@@ -82,7 +82,7 @@ doAccountChannels(Context const& context)
|
||||
std::uint32_t limit = 200;
|
||||
if (request.contains("limit"))
|
||||
{
|
||||
if(!request.at("limit").is_int64())
|
||||
if (!request.at("limit").is_int64())
|
||||
return Status{Error::rpcINVALID_PARAMS, "limitNotInt"};
|
||||
|
||||
limit = request.at("limit").as_int64();
|
||||
@@ -90,13 +90,13 @@ doAccountChannels(Context const& context)
|
||||
return Status{Error::rpcINVALID_PARAMS, "limitNotPositive"};
|
||||
}
|
||||
|
||||
ripple::uint256 cursor;
|
||||
if (request.contains("cursor"))
|
||||
ripple::uint256 marker;
|
||||
if (request.contains("marker"))
|
||||
{
|
||||
if(!request.at("cursor").is_string())
|
||||
return Status{Error::rpcINVALID_PARAMS, "cursorNotString"};
|
||||
if (!request.at("marker").is_string())
|
||||
return Status{Error::rpcINVALID_PARAMS, "markerNotString"};
|
||||
|
||||
if (!cursor.parseHex(request.at("cursor").as_string().c_str()))
|
||||
if (!marker.parseHex(request.at("marker").as_string().c_str()))
|
||||
return Status{Error::rpcINVALID_PARAMS, "malformedCursor"};
|
||||
}
|
||||
|
||||
@@ -108,7 +108,7 @@ doAccountChannels(Context const& context)
|
||||
if (sle.getType() == ripple::ltPAYCHAN &&
|
||||
sle.getAccountID(ripple::sfAccount) == *accountID &&
|
||||
(!destAccount ||
|
||||
*destAccount == sle.getAccountID(ripple::sfDestination)))
|
||||
*destAccount == sle.getAccountID(ripple::sfDestination)))
|
||||
{
|
||||
if (limit-- == 0)
|
||||
{
|
||||
@@ -121,13 +121,8 @@ doAccountChannels(Context const& context)
|
||||
return true;
|
||||
};
|
||||
|
||||
auto nextCursor =
|
||||
traverseOwnedNodes(
|
||||
*context.backend,
|
||||
*accountID,
|
||||
lgrInfo.seq,
|
||||
cursor,
|
||||
addToResponse);
|
||||
auto nextCursor = traverseOwnedNodes(
|
||||
*context.backend, *accountID, lgrInfo.seq, marker, addToResponse);
|
||||
|
||||
response["ledger_hash"] = ripple::strHex(lgrInfo.hash);
|
||||
response["ledger_index"] = lgrInfo.seq;
|
||||
@@ -137,4 +132,4 @@ doAccountChannels(Context const& context)
|
||||
return response;
|
||||
}
|
||||
|
||||
} // namespace RPC
|
||||
} // namespace RPC
|
||||
|
||||
@@ -7,13 +7,10 @@
|
||||
#include <boost/json.hpp>
|
||||
#include <algorithm>
|
||||
|
||||
#include <rpc/RPCHelpers.h>
|
||||
#include <backend/BackendInterface.h>
|
||||
#include <backend/DBHelpers.h>
|
||||
#include <backend/Pg.h>
|
||||
#include <rpc/RPCHelpers.h>
|
||||
|
||||
namespace RPC
|
||||
{
|
||||
namespace RPC {
|
||||
|
||||
Result
|
||||
doAccountCurrencies(Context const& context)
|
||||
@@ -27,10 +24,10 @@ doAccountCurrencies(Context const& context)
|
||||
|
||||
auto lgrInfo = std::get<ripple::LedgerInfo>(v);
|
||||
|
||||
if(!request.contains("account"))
|
||||
if (!request.contains("account"))
|
||||
return Status{Error::rpcINVALID_PARAMS, "missingAccount"};
|
||||
|
||||
if(!request.at("account").is_string())
|
||||
if (!request.at("account").is_string())
|
||||
return Status{Error::rpcINVALID_PARAMS, "accountNotString"};
|
||||
|
||||
auto accountID =
|
||||
@@ -43,14 +40,15 @@ doAccountCurrencies(Context const& context)
|
||||
auto const addToResponse = [&](ripple::SLE const& sle) {
|
||||
if (sle.getType() == ripple::ltRIPPLE_STATE)
|
||||
{
|
||||
ripple::STAmount const& balance =
|
||||
sle.getFieldAmount(ripple::sfBalance);
|
||||
ripple::STAmount balance = sle.getFieldAmount(ripple::sfBalance);
|
||||
|
||||
auto lowLimit = sle.getFieldAmount(ripple::sfLowLimit);
|
||||
auto highLimit = sle.getFieldAmount(ripple::sfHighLimit);
|
||||
bool viewLowest = (lowLimit.getIssuer() == accountID);
|
||||
auto lineLimit = viewLowest ? lowLimit : highLimit;
|
||||
auto lineLimitPeer = !viewLowest ? lowLimit : highLimit;
|
||||
if (!viewLowest)
|
||||
balance.negate();
|
||||
|
||||
if (balance < lineLimit)
|
||||
receive.insert(ripple::to_string(balance.getCurrency()));
|
||||
@@ -62,17 +60,15 @@ doAccountCurrencies(Context const& context)
|
||||
};
|
||||
|
||||
traverseOwnedNodes(
|
||||
*context.backend,
|
||||
*accountID,
|
||||
lgrInfo.seq,
|
||||
beast::zero,
|
||||
addToResponse);
|
||||
*context.backend, *accountID, lgrInfo.seq, beast::zero, addToResponse);
|
||||
|
||||
response["ledger_hash"] = ripple::strHex(lgrInfo.hash);
|
||||
response["ledger_index"] = lgrInfo.seq;
|
||||
|
||||
response["receive_currencies"] = boost::json::value(boost::json::array_kind);
|
||||
boost::json::array& jsonReceive = response.at("receive_currencies").as_array();
|
||||
response["receive_currencies"] =
|
||||
boost::json::value(boost::json::array_kind);
|
||||
boost::json::array& jsonReceive =
|
||||
response.at("receive_currencies").as_array();
|
||||
|
||||
for (auto const& currency : receive)
|
||||
jsonReceive.push_back(currency.c_str());
|
||||
@@ -86,4 +82,4 @@ doAccountCurrencies(Context const& context)
|
||||
return response;
|
||||
}
|
||||
|
||||
} // namespace RPC
|
||||
} // namespace RPC
|
||||
|
||||
@@ -55,6 +55,9 @@ doAccountInfo(Context const& context)
|
||||
else
|
||||
return Status{Error::rpcACT_MALFORMED};
|
||||
|
||||
// We only need to fetch the ledger header because the ledger hash is
|
||||
// supposed to be included in the response. The ledger sequence is specified
|
||||
// in the request
|
||||
auto v = ledgerInfoFromRequest(context);
|
||||
if (auto status = std::get_if<Status>(&v))
|
||||
return *status;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#include <ripple/app/paths/RippleState.h>
|
||||
#include <ripple/app/ledger/Ledger.h>
|
||||
#include <ripple/app/paths/RippleState.h>
|
||||
#include <ripple/basics/StringUtilities.h>
|
||||
#include <ripple/protocol/ErrorCodes.h>
|
||||
#include <ripple/protocol/Indexes.h>
|
||||
@@ -12,11 +12,9 @@
|
||||
#include <backend/BackendInterface.h>
|
||||
#include <backend/DBHelpers.h>
|
||||
|
||||
namespace RPC {
|
||||
|
||||
namespace RPC
|
||||
{
|
||||
|
||||
std::unordered_map<std::string, ripple::LedgerEntryType> types {
|
||||
std::unordered_map<std::string, ripple::LedgerEntryType> types{
|
||||
{"state", ripple::ltRIPPLE_STATE},
|
||||
{"ticket", ripple::ltTICKET},
|
||||
{"signer_list", ripple::ltSIGNER_LIST},
|
||||
@@ -39,10 +37,10 @@ doAccountObjects(Context const& context)
|
||||
|
||||
auto lgrInfo = std::get<ripple::LedgerInfo>(v);
|
||||
|
||||
if(!request.contains("account"))
|
||||
if (!request.contains("account"))
|
||||
return Status{Error::rpcINVALID_PARAMS, "missingAccount"};
|
||||
|
||||
if(!request.at("account").is_string())
|
||||
if (!request.at("account").is_string())
|
||||
return Status{Error::rpcINVALID_PARAMS, "accountNotString"};
|
||||
|
||||
auto accountID =
|
||||
@@ -54,7 +52,7 @@ doAccountObjects(Context const& context)
|
||||
std::uint32_t limit = 200;
|
||||
if (request.contains("limit"))
|
||||
{
|
||||
if(!request.at("limit").is_int64())
|
||||
if (!request.at("limit").is_int64())
|
||||
return Status{Error::rpcINVALID_PARAMS, "limitNotInt"};
|
||||
|
||||
limit = request.at("limit").as_int64();
|
||||
@@ -65,7 +63,7 @@ doAccountObjects(Context const& context)
|
||||
ripple::uint256 cursor;
|
||||
if (request.contains("cursor"))
|
||||
{
|
||||
if(!request.at("cursor").is_string())
|
||||
if (!request.at("cursor").is_string())
|
||||
return Status{Error::rpcINVALID_PARAMS, "cursorNotString"};
|
||||
|
||||
if (!cursor.parseHex(request.at("cursor").as_string().c_str()))
|
||||
@@ -75,11 +73,11 @@ doAccountObjects(Context const& context)
|
||||
std::optional<ripple::LedgerEntryType> objectType = {};
|
||||
if (request.contains("type"))
|
||||
{
|
||||
if(!request.at("type").is_string())
|
||||
if (!request.at("type").is_string())
|
||||
return Status{Error::rpcINVALID_PARAMS, "typeNotString"};
|
||||
|
||||
std::string typeAsString = request.at("type").as_string().c_str();
|
||||
if(types.find(typeAsString) == types.end())
|
||||
if (types.find(typeAsString) == types.end())
|
||||
return Status{Error::rpcINVALID_PARAMS, "typeInvalid"};
|
||||
|
||||
objectType = types[typeAsString];
|
||||
@@ -87,7 +85,7 @@ doAccountObjects(Context const& context)
|
||||
|
||||
response["account"] = ripple::to_string(*accountID);
|
||||
response["account_objects"] = boost::json::value(boost::json::array_kind);
|
||||
boost::json::array& jsonObjects = response.at("objects").as_array();
|
||||
boost::json::array& jsonObjects = response.at("account_objects").as_array();
|
||||
|
||||
auto const addToResponse = [&](ripple::SLE const& sle) {
|
||||
if (!objectType || objectType == sle.getType())
|
||||
@@ -103,13 +101,8 @@ doAccountObjects(Context const& context)
|
||||
return true;
|
||||
};
|
||||
|
||||
auto nextCursor =
|
||||
traverseOwnedNodes(
|
||||
*context.backend,
|
||||
*accountID,
|
||||
lgrInfo.seq,
|
||||
cursor,
|
||||
addToResponse);
|
||||
auto nextCursor = traverseOwnedNodes(
|
||||
*context.backend, *accountID, lgrInfo.seq, cursor, addToResponse);
|
||||
|
||||
response["ledger_hash"] = ripple::strHex(lgrInfo.hash);
|
||||
response["ledger_index"] = lgrInfo.seq;
|
||||
@@ -120,4 +113,4 @@ doAccountObjects(Context const& context)
|
||||
return response;
|
||||
}
|
||||
|
||||
} // namespace RPC
|
||||
} // namespace RPC
|
||||
|
||||
195
src/rpc/handlers/GatewayBalances.cpp
Normal file
195
src/rpc/handlers/GatewayBalances.cpp
Normal file
@@ -0,0 +1,195 @@
|
||||
#include <backend/BackendInterface.h>
|
||||
#include <rpc/RPCHelpers.h>
|
||||
|
||||
namespace RPC {
|
||||
|
||||
Result
|
||||
doGatewayBalances(Context const& context)
|
||||
{
|
||||
auto request = context.params;
|
||||
boost::json::object response = {};
|
||||
|
||||
if (!request.contains("account"))
|
||||
return Status{Error::rpcINVALID_PARAMS, "missingAccount"};
|
||||
|
||||
if (!request.at("account").is_string())
|
||||
return Status{Error::rpcINVALID_PARAMS, "accountNotString"};
|
||||
|
||||
auto accountID =
|
||||
accountFromStringStrict(request.at("account").as_string().c_str());
|
||||
|
||||
if (!accountID)
|
||||
return Status{Error::rpcINVALID_PARAMS, "malformedAccount"};
|
||||
|
||||
auto v = ledgerInfoFromRequest(context);
|
||||
if (auto status = std::get_if<Status>(&v))
|
||||
return *status;
|
||||
|
||||
auto lgrInfo = std::get<ripple::LedgerInfo>(v);
|
||||
|
||||
std::map<ripple::Currency, ripple::STAmount> sums;
|
||||
std::map<ripple::AccountID, std::vector<ripple::STAmount>> hotBalances;
|
||||
std::map<ripple::AccountID, std::vector<ripple::STAmount>> assets;
|
||||
std::map<ripple::AccountID, std::vector<ripple::STAmount>> frozenBalances;
|
||||
std::set<ripple::AccountID> hotWallets;
|
||||
|
||||
if (request.contains("hot_wallet"))
|
||||
{
|
||||
auto getAccountID =
|
||||
[](auto const& j) -> std::optional<ripple::AccountID> {
|
||||
if (j.is_string())
|
||||
{
|
||||
auto const pk = ripple::parseBase58<ripple::PublicKey>(
|
||||
ripple::TokenType::AccountPublic, j.as_string().c_str());
|
||||
if (pk)
|
||||
{
|
||||
return ripple::calcAccountID(*pk);
|
||||
}
|
||||
|
||||
return ripple::parseBase58<ripple::AccountID>(
|
||||
j.as_string().c_str());
|
||||
}
|
||||
return {};
|
||||
};
|
||||
|
||||
auto const& hw = request.at("hot_wallet");
|
||||
bool valid = true;
|
||||
|
||||
// null is treated as a valid 0-sized array of hotwallet
|
||||
if (hw.is_array())
|
||||
{
|
||||
auto const& arr = hw.as_array();
|
||||
for (unsigned i = 0; i < arr.size(); ++i)
|
||||
{
|
||||
if (auto id = getAccountID(arr[i]))
|
||||
hotWallets.insert(*id);
|
||||
else
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
else if (hw.is_string())
|
||||
{
|
||||
if (auto id = getAccountID(hw))
|
||||
hotWallets.insert(*id);
|
||||
else
|
||||
valid = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
valid = false;
|
||||
}
|
||||
|
||||
if (!valid)
|
||||
{
|
||||
response["error"] = "invalidHotWallet";
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
// Traverse the cold wallet's trust lines
|
||||
auto const addToResponse = [&](ripple::SLE const& sle) {
|
||||
if (sle.getType() == ripple::ltRIPPLE_STATE)
|
||||
{
|
||||
ripple::STAmount balance = sle.getFieldAmount(ripple::sfBalance);
|
||||
|
||||
auto lowLimit = sle.getFieldAmount(ripple::sfLowLimit);
|
||||
auto highLimit = sle.getFieldAmount(ripple::sfHighLimit);
|
||||
auto lowID = lowLimit.getIssuer();
|
||||
auto highID = highLimit.getIssuer();
|
||||
bool viewLowest = (lowLimit.getIssuer() == accountID);
|
||||
auto lineLimit = viewLowest ? lowLimit : highLimit;
|
||||
auto lineLimitPeer = !viewLowest ? lowLimit : highLimit;
|
||||
auto flags = sle.getFieldU32(ripple::sfFlags);
|
||||
auto freeze = flags &
|
||||
(viewLowest ? ripple::lsfLowFreeze : ripple::lsfHighFreeze);
|
||||
if (!viewLowest)
|
||||
balance.negate();
|
||||
|
||||
int balSign = balance.signum();
|
||||
if (balSign == 0)
|
||||
return true;
|
||||
|
||||
auto const& peer = !viewLowest ? lowID : highID;
|
||||
|
||||
// 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 wallet
|
||||
hotBalances[peer].push_back(balance);
|
||||
}
|
||||
else if (balSign > 0)
|
||||
{
|
||||
// This is a gateway asset
|
||||
assets[peer].push_back(balance);
|
||||
}
|
||||
else if (freeze)
|
||||
{
|
||||
// An obligation the gateway has frozen
|
||||
frozenBalances[peer].push_back(balance);
|
||||
}
|
||||
else
|
||||
{
|
||||
// normal negative balance, obligation to customer
|
||||
auto& bal = sums[balance.getCurrency()];
|
||||
if (bal == beast::zero)
|
||||
{
|
||||
// This is needed to set the currency code correctly
|
||||
bal = -balance;
|
||||
}
|
||||
else
|
||||
bal -= balance;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
traverseOwnedNodes(
|
||||
*context.backend, *accountID, lgrInfo.seq, beast::zero, addToResponse);
|
||||
|
||||
if (!sums.empty())
|
||||
{
|
||||
boost::json::object obj;
|
||||
for (auto const& [k, v] : sums)
|
||||
{
|
||||
obj[ripple::to_string(k)] = v.getText();
|
||||
}
|
||||
response["obligations"] = std::move(obj);
|
||||
}
|
||||
|
||||
auto toJson =
|
||||
[](std::map<ripple::AccountID, std::vector<ripple::STAmount>> const&
|
||||
balances) {
|
||||
boost::json::object obj;
|
||||
if (!balances.empty())
|
||||
{
|
||||
for (auto const& [accId, accBalances] : balances)
|
||||
{
|
||||
boost::json::array arr;
|
||||
for (auto const& balance : accBalances)
|
||||
{
|
||||
boost::json::object entry;
|
||||
entry["currency"] =
|
||||
ripple::to_string(balance.issue().currency);
|
||||
entry["value"] = balance.getText();
|
||||
arr.push_back(std::move(entry));
|
||||
}
|
||||
obj[ripple::to_string(accId)] = std::move(arr);
|
||||
}
|
||||
}
|
||||
return obj;
|
||||
};
|
||||
|
||||
if (auto balances = toJson(hotBalances); balances.size())
|
||||
response["balances"] = balances;
|
||||
if (auto balances = toJson(frozenBalances); balances.size())
|
||||
response["frozen_balances"] = balances;
|
||||
if (auto balances = toJson(assets); assets.size())
|
||||
response["assets"] = toJson(assets);
|
||||
response["account"] = request.at("account");
|
||||
response["ledger_index"] = lgrInfo.seq;
|
||||
response["ledger_hash"] = ripple::strHex(lgrInfo.hash);
|
||||
return response;
|
||||
}
|
||||
} // namespace RPC
|
||||
Reference in New Issue
Block a user