add gateway_balances. fix bug with account_currencies

This commit is contained in:
CJ Cobb
2021-09-10 17:41:00 -04:00
parent 01aed9f6e8
commit f18c8f67e2
9 changed files with 266 additions and 70 deletions

View File

@@ -84,6 +84,7 @@ target_sources(clio PRIVATE
src/rpc/handlers/AccountLines.cpp src/rpc/handlers/AccountLines.cpp
src/rpc/handlers/AccountOffers.cpp src/rpc/handlers/AccountOffers.cpp
src/rpc/handlers/AccountObjects.cpp src/rpc/handlers/AccountObjects.cpp
src/rpc/handlers/GatewayBalances.cpp
# Ledger # Ledger
src/rpc/handlers/Ledger.cpp src/rpc/handlers/Ledger.cpp
src/rpc/handlers/LedgerData.cpp src/rpc/handlers/LedgerData.cpp

View File

@@ -27,6 +27,9 @@ doAccountObjects(Context const& context);
Result Result
doAccountOffers(Context const& context); doAccountOffers(Context const& context);
Result
doGatewayBalances(Context const& context);
// channels methods // channels methods
Result Result

View File

@@ -105,6 +105,7 @@ static std::unordered_map<std::string, std::function<Result(Context const&)>>
{"account_objects", &doAccountObjects}, {"account_objects", &doAccountObjects},
{"account_offers", &doAccountOffers}, {"account_offers", &doAccountOffers},
{"account_tx", &doAccountTx}, {"account_tx", &doAccountTx},
{"gateway_balances", &doGatewayBalances},
{"book_offers", &doBookOffers}, {"book_offers", &doBookOffers},
{"channel_authorize", &doChannelAuthorize}, {"channel_authorize", &doChannelAuthorize},
{"channel_verify", &doChannelVerify}, {"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; return false;
} }
@@ -152,7 +157,12 @@ Result
buildResponse(Context const& ctx) buildResponse(Context const& ctx)
{ {
if (shouldForwardToRippled(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()) if (handlerTable.find(ctx.method) == handlerTable.end())
return Status{Error::rpcUNKNOWN_COMMAND}; return Status{Error::rpcUNKNOWN_COMMAND};

View File

@@ -331,7 +331,7 @@ traverseOwnedNodes(
if (!ownedNode) if (!ownedNode)
{ {
throw std::runtime_error("Could not find owned node"); break;
} }
ripple::SerialIter it{ownedNode->data(), ownedNode->size()}; ripple::SerialIter it{ownedNode->data(), ownedNode->size()};

View File

@@ -6,13 +6,12 @@
#include <ripple/protocol/jss.h> #include <ripple/protocol/jss.h>
#include <boost/json.hpp> #include <boost/json.hpp>
#include <algorithm> #include <algorithm>
#include <rpc/RPCHelpers.h>
#include <backend/BackendInterface.h> #include <backend/BackendInterface.h>
#include <backend/DBHelpers.h> #include <backend/DBHelpers.h>
#include <backend/Pg.h> #include <backend/Pg.h>
#include <rpc/RPCHelpers.h>
namespace RPC namespace RPC {
{
void void
addChannel(boost::json::array& jsonLines, ripple::SLE const& line) 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; boost::json::object jDst;
jDst["channel_id"] = ripple::to_string(line.key()); jDst["channel_id"] = ripple::to_string(line.key());
jDst["account"] = ripple::to_string(line.getAccountID(ripple::sfAccount)); 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["amount"] = line[ripple::sfAmount].getText();
jDst["balance"] = line[ripple::sfBalance].getText(); jDst["balance"] = line[ripple::sfBalance].getText();
if (publicKeyType(line[ripple::sfPublicKey])) if (publicKeyType(line[ripple::sfPublicKey]))
@@ -54,10 +54,10 @@ doAccountChannels(Context const& context)
auto lgrInfo = std::get<ripple::LedgerInfo>(v); auto lgrInfo = std::get<ripple::LedgerInfo>(v);
if(!request.contains("account")) if (!request.contains("account"))
return Status{Error::rpcINVALID_PARAMS, "missingAccount"}; return Status{Error::rpcINVALID_PARAMS, "missingAccount"};
if(!request.at("account").is_string()) if (!request.at("account").is_string())
return Status{Error::rpcINVALID_PARAMS, "accountNotString"}; return Status{Error::rpcINVALID_PARAMS, "accountNotString"};
auto accountID = auto accountID =
@@ -82,7 +82,7 @@ doAccountChannels(Context const& context)
std::uint32_t limit = 200; std::uint32_t limit = 200;
if (request.contains("limit")) if (request.contains("limit"))
{ {
if(!request.at("limit").is_int64()) if (!request.at("limit").is_int64())
return Status{Error::rpcINVALID_PARAMS, "limitNotInt"}; return Status{Error::rpcINVALID_PARAMS, "limitNotInt"};
limit = request.at("limit").as_int64(); limit = request.at("limit").as_int64();
@@ -90,13 +90,13 @@ doAccountChannels(Context const& context)
return Status{Error::rpcINVALID_PARAMS, "limitNotPositive"}; return Status{Error::rpcINVALID_PARAMS, "limitNotPositive"};
} }
ripple::uint256 cursor; ripple::uint256 marker;
if (request.contains("cursor")) if (request.contains("marker"))
{ {
if(!request.at("cursor").is_string()) if (!request.at("marker").is_string())
return Status{Error::rpcINVALID_PARAMS, "cursorNotString"}; 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"}; return Status{Error::rpcINVALID_PARAMS, "malformedCursor"};
} }
@@ -121,13 +121,8 @@ doAccountChannels(Context const& context)
return true; return true;
}; };
auto nextCursor = auto nextCursor = traverseOwnedNodes(
traverseOwnedNodes( *context.backend, *accountID, lgrInfo.seq, marker, addToResponse);
*context.backend,
*accountID,
lgrInfo.seq,
cursor,
addToResponse);
response["ledger_hash"] = ripple::strHex(lgrInfo.hash); response["ledger_hash"] = ripple::strHex(lgrInfo.hash);
response["ledger_index"] = lgrInfo.seq; response["ledger_index"] = lgrInfo.seq;

View File

@@ -7,13 +7,10 @@
#include <boost/json.hpp> #include <boost/json.hpp>
#include <algorithm> #include <algorithm>
#include <rpc/RPCHelpers.h>
#include <backend/BackendInterface.h> #include <backend/BackendInterface.h>
#include <backend/DBHelpers.h> #include <rpc/RPCHelpers.h>
#include <backend/Pg.h>
namespace RPC namespace RPC {
{
Result Result
doAccountCurrencies(Context const& context) doAccountCurrencies(Context const& context)
@@ -27,10 +24,10 @@ doAccountCurrencies(Context const& context)
auto lgrInfo = std::get<ripple::LedgerInfo>(v); auto lgrInfo = std::get<ripple::LedgerInfo>(v);
if(!request.contains("account")) if (!request.contains("account"))
return Status{Error::rpcINVALID_PARAMS, "missingAccount"}; return Status{Error::rpcINVALID_PARAMS, "missingAccount"};
if(!request.at("account").is_string()) if (!request.at("account").is_string())
return Status{Error::rpcINVALID_PARAMS, "accountNotString"}; return Status{Error::rpcINVALID_PARAMS, "accountNotString"};
auto accountID = auto accountID =
@@ -43,14 +40,15 @@ doAccountCurrencies(Context const& context)
auto const addToResponse = [&](ripple::SLE const& sle) { auto const addToResponse = [&](ripple::SLE const& sle) {
if (sle.getType() == ripple::ltRIPPLE_STATE) if (sle.getType() == ripple::ltRIPPLE_STATE)
{ {
ripple::STAmount const& balance = ripple::STAmount balance = sle.getFieldAmount(ripple::sfBalance);
sle.getFieldAmount(ripple::sfBalance);
auto lowLimit = sle.getFieldAmount(ripple::sfLowLimit); auto lowLimit = sle.getFieldAmount(ripple::sfLowLimit);
auto highLimit = sle.getFieldAmount(ripple::sfHighLimit); auto highLimit = sle.getFieldAmount(ripple::sfHighLimit);
bool viewLowest = (lowLimit.getIssuer() == accountID); bool viewLowest = (lowLimit.getIssuer() == accountID);
auto lineLimit = viewLowest ? lowLimit : highLimit; auto lineLimit = viewLowest ? lowLimit : highLimit;
auto lineLimitPeer = !viewLowest ? lowLimit : highLimit; auto lineLimitPeer = !viewLowest ? lowLimit : highLimit;
if (!viewLowest)
balance.negate();
if (balance < lineLimit) if (balance < lineLimit)
receive.insert(ripple::to_string(balance.getCurrency())); receive.insert(ripple::to_string(balance.getCurrency()));
@@ -62,17 +60,15 @@ doAccountCurrencies(Context const& context)
}; };
traverseOwnedNodes( traverseOwnedNodes(
*context.backend, *context.backend, *accountID, lgrInfo.seq, beast::zero, addToResponse);
*accountID,
lgrInfo.seq,
beast::zero,
addToResponse);
response["ledger_hash"] = ripple::strHex(lgrInfo.hash); response["ledger_hash"] = ripple::strHex(lgrInfo.hash);
response["ledger_index"] = lgrInfo.seq; response["ledger_index"] = lgrInfo.seq;
response["receive_currencies"] = boost::json::value(boost::json::array_kind); response["receive_currencies"] =
boost::json::array& jsonReceive = response.at("receive_currencies").as_array(); boost::json::value(boost::json::array_kind);
boost::json::array& jsonReceive =
response.at("receive_currencies").as_array();
for (auto const& currency : receive) for (auto const& currency : receive)
jsonReceive.push_back(currency.c_str()); jsonReceive.push_back(currency.c_str());

View File

@@ -55,6 +55,9 @@ doAccountInfo(Context const& context)
else else
return Status{Error::rpcACT_MALFORMED}; 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); auto v = ledgerInfoFromRequest(context);
if (auto status = std::get_if<Status>(&v)) if (auto status = std::get_if<Status>(&v))
return *status; return *status;

View File

@@ -1,5 +1,5 @@
#include <ripple/app/paths/RippleState.h>
#include <ripple/app/ledger/Ledger.h> #include <ripple/app/ledger/Ledger.h>
#include <ripple/app/paths/RippleState.h>
#include <ripple/basics/StringUtilities.h> #include <ripple/basics/StringUtilities.h>
#include <ripple/protocol/ErrorCodes.h> #include <ripple/protocol/ErrorCodes.h>
#include <ripple/protocol/Indexes.h> #include <ripple/protocol/Indexes.h>
@@ -12,11 +12,9 @@
#include <backend/BackendInterface.h> #include <backend/BackendInterface.h>
#include <backend/DBHelpers.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}, {"state", ripple::ltRIPPLE_STATE},
{"ticket", ripple::ltTICKET}, {"ticket", ripple::ltTICKET},
{"signer_list", ripple::ltSIGNER_LIST}, {"signer_list", ripple::ltSIGNER_LIST},
@@ -39,10 +37,10 @@ doAccountObjects(Context const& context)
auto lgrInfo = std::get<ripple::LedgerInfo>(v); auto lgrInfo = std::get<ripple::LedgerInfo>(v);
if(!request.contains("account")) if (!request.contains("account"))
return Status{Error::rpcINVALID_PARAMS, "missingAccount"}; return Status{Error::rpcINVALID_PARAMS, "missingAccount"};
if(!request.at("account").is_string()) if (!request.at("account").is_string())
return Status{Error::rpcINVALID_PARAMS, "accountNotString"}; return Status{Error::rpcINVALID_PARAMS, "accountNotString"};
auto accountID = auto accountID =
@@ -54,7 +52,7 @@ doAccountObjects(Context const& context)
std::uint32_t limit = 200; std::uint32_t limit = 200;
if (request.contains("limit")) if (request.contains("limit"))
{ {
if(!request.at("limit").is_int64()) if (!request.at("limit").is_int64())
return Status{Error::rpcINVALID_PARAMS, "limitNotInt"}; return Status{Error::rpcINVALID_PARAMS, "limitNotInt"};
limit = request.at("limit").as_int64(); limit = request.at("limit").as_int64();
@@ -65,7 +63,7 @@ doAccountObjects(Context const& context)
ripple::uint256 cursor; ripple::uint256 cursor;
if (request.contains("cursor")) if (request.contains("cursor"))
{ {
if(!request.at("cursor").is_string()) if (!request.at("cursor").is_string())
return Status{Error::rpcINVALID_PARAMS, "cursorNotString"}; return Status{Error::rpcINVALID_PARAMS, "cursorNotString"};
if (!cursor.parseHex(request.at("cursor").as_string().c_str())) if (!cursor.parseHex(request.at("cursor").as_string().c_str()))
@@ -75,11 +73,11 @@ doAccountObjects(Context const& context)
std::optional<ripple::LedgerEntryType> objectType = {}; std::optional<ripple::LedgerEntryType> objectType = {};
if (request.contains("type")) if (request.contains("type"))
{ {
if(!request.at("type").is_string()) if (!request.at("type").is_string())
return Status{Error::rpcINVALID_PARAMS, "typeNotString"}; return Status{Error::rpcINVALID_PARAMS, "typeNotString"};
std::string typeAsString = request.at("type").as_string().c_str(); 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"}; return Status{Error::rpcINVALID_PARAMS, "typeInvalid"};
objectType = types[typeAsString]; objectType = types[typeAsString];
@@ -87,7 +85,7 @@ doAccountObjects(Context const& context)
response["account"] = ripple::to_string(*accountID); response["account"] = ripple::to_string(*accountID);
response["account_objects"] = boost::json::value(boost::json::array_kind); 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) { auto const addToResponse = [&](ripple::SLE const& sle) {
if (!objectType || objectType == sle.getType()) if (!objectType || objectType == sle.getType())
@@ -103,13 +101,8 @@ doAccountObjects(Context const& context)
return true; return true;
}; };
auto nextCursor = auto nextCursor = traverseOwnedNodes(
traverseOwnedNodes( *context.backend, *accountID, lgrInfo.seq, cursor, addToResponse);
*context.backend,
*accountID,
lgrInfo.seq,
cursor,
addToResponse);
response["ledger_hash"] = ripple::strHex(lgrInfo.hash); response["ledger_hash"] = ripple::strHex(lgrInfo.hash);
response["ledger_index"] = lgrInfo.seq; response["ledger_index"] = lgrInfo.seq;

View 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