mirror of
https://github.com/XRPLF/clio.git
synced 2026-04-29 15:37:53 +00:00
impliments account RPC handlers
This commit is contained in:
@@ -72,7 +72,10 @@ target_sources(reporting PRIVATE
|
||||
handlers/Ledger.cpp
|
||||
handlers/LedgerEntry.cpp
|
||||
handlers/AccountChannels.cpp
|
||||
handlers/AccountLines.cpp)
|
||||
handlers/AccountLines.cpp
|
||||
handlers/AccountCurrencies.cpp
|
||||
handlers/AccountOffers.cpp
|
||||
handlers/AccountObjects.cpp)
|
||||
|
||||
|
||||
message(${Boost_LIBRARIES})
|
||||
|
||||
@@ -36,7 +36,7 @@ addChannel(boost::json::array& jsonLines, ripple::SLE const& line)
|
||||
if (auto const& v = line[~ripple::sfDestinationTag])
|
||||
jDst["destination_tag"] = *v;
|
||||
|
||||
jsonLines.push_back(Json::objectValue);
|
||||
jsonLines.push_back(jDst);
|
||||
}
|
||||
|
||||
boost::json::object
|
||||
@@ -95,46 +95,72 @@ doAccountChannels(
|
||||
}
|
||||
}
|
||||
|
||||
auto const rootIndex = ripple::keylet::ownerDir(accountID);
|
||||
auto currentIndex = rootIndex;
|
||||
|
||||
std::vector<ripple::uint256> keys;
|
||||
|
||||
for (;;)
|
||||
std::uint32_t limit = 200;
|
||||
if (request.contains("limit"))
|
||||
{
|
||||
auto ownedNode =
|
||||
backend.fetchLedgerObject(currentIndex.key, *ledgerSequence);
|
||||
if(!request.at("limit").is_int64())
|
||||
{
|
||||
response["error"] = "limit must be integer";
|
||||
return response;
|
||||
}
|
||||
|
||||
ripple::SerialIter it{ownedNode->data(), ownedNode->size()};
|
||||
ripple::SLE dir{it, currentIndex.key};
|
||||
for (auto const& key : dir.getFieldV256(ripple::sfIndexes))
|
||||
keys.push_back(key);
|
||||
|
||||
auto const uNodeNext = dir.getFieldU64(ripple::sfIndexNext);
|
||||
if (uNodeNext == 0)
|
||||
break;
|
||||
|
||||
currentIndex = ripple::keylet::page(rootIndex, uNodeNext);
|
||||
limit = request.at("limit").as_int64();
|
||||
if (limit <= 0)
|
||||
{
|
||||
response["error"] = "limit must be positive";
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
auto objects = backend.fetchLedgerObjects(keys, *ledgerSequence);
|
||||
ripple::uint256 cursor = beast::zero;
|
||||
if (request.contains("cursor"))
|
||||
{
|
||||
if(!request.at("cursor").is_string())
|
||||
{
|
||||
response["error"] = "limit must be string";
|
||||
return response;
|
||||
}
|
||||
|
||||
auto bytes = ripple::strUnHex(request.at("cursor").as_string().c_str());
|
||||
if (bytes and bytes->size() == 32)
|
||||
{
|
||||
response["error"] = "invalid cursor";
|
||||
return response;
|
||||
}
|
||||
|
||||
cursor = ripple::uint256::fromVoid(bytes->data());
|
||||
}
|
||||
|
||||
response["channels"] = boost::json::value(boost::json::array_kind);
|
||||
boost::json::array& jsonChannels = response.at("channels").as_array();
|
||||
|
||||
for (auto i = 0; i < objects.size(); ++i)
|
||||
{
|
||||
ripple::SerialIter it{objects[i].data(), objects[i].size()};
|
||||
ripple::SLE sle{it, keys[i]};
|
||||
|
||||
auto const addToResponse = [&](ripple::SLE const& sle) {
|
||||
if (sle.getType() == ripple::ltPAYCHAN &&
|
||||
sle.getAccountID(ripple::sfAccount) == accountID &&
|
||||
(!destAccount ||
|
||||
*destAccount == sle.getAccountID(ripple::sfDestination)))
|
||||
{
|
||||
if (limit-- == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
addChannel(jsonChannels, sle);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
auto nextCursor =
|
||||
traverseOwnedNodes(
|
||||
backend,
|
||||
accountID,
|
||||
*ledgerSequence,
|
||||
cursor,
|
||||
addToResponse);
|
||||
|
||||
if (nextCursor)
|
||||
response["next_cursor"] = ripple::strHex(*nextCursor);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
95
handlers/AccountCurrencies.cpp
Normal file
95
handlers/AccountCurrencies.cpp
Normal file
@@ -0,0 +1,95 @@
|
||||
#include <ripple/app/ledger/Ledger.h>
|
||||
#include <ripple/basics/StringUtilities.h>
|
||||
#include <ripple/protocol/ErrorCodes.h>
|
||||
#include <ripple/protocol/Indexes.h>
|
||||
#include <ripple/protocol/STLedgerEntry.h>
|
||||
#include <ripple/protocol/jss.h>
|
||||
#include <boost/json.hpp>
|
||||
#include <algorithm>
|
||||
#include <handlers/RPCHelpers.h>
|
||||
#include <reporting/BackendInterface.h>
|
||||
#include <reporting/DBHelpers.h>
|
||||
#include <reporting/Pg.h>
|
||||
|
||||
boost::json::object
|
||||
doAccountCurrencies(
|
||||
boost::json::object const& request,
|
||||
BackendInterface const& backend)
|
||||
{
|
||||
boost::json::object response;
|
||||
|
||||
auto ledgerSequence = ledgerSequenceFromRequest(request, backend);
|
||||
if (!ledgerSequence)
|
||||
{
|
||||
response["error"] = "Empty database";
|
||||
return response;
|
||||
}
|
||||
|
||||
if(!request.contains("account"))
|
||||
{
|
||||
response["error"] = "Must contain account";
|
||||
return response;
|
||||
}
|
||||
|
||||
if(!request.at("account").is_string())
|
||||
{
|
||||
response["error"] = "Account must be a string";
|
||||
return response;
|
||||
}
|
||||
|
||||
ripple::AccountID accountID;
|
||||
auto parsed = ripple::parseBase58<ripple::AccountID>(
|
||||
request.at("account").as_string().c_str());
|
||||
|
||||
if (!parsed)
|
||||
{
|
||||
response["error"] = "Invalid account";
|
||||
return response;
|
||||
}
|
||||
|
||||
accountID = *parsed;
|
||||
|
||||
|
||||
std::set<std::string> send, receive;
|
||||
auto const addToResponse = [&](ripple::SLE const& sle) {
|
||||
if (sle.getType() == ripple::ltRIPPLE_STATE)
|
||||
{
|
||||
ripple::STAmount const& 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 (balance < lineLimit)
|
||||
receive.insert(ripple::to_string(balance.getCurrency()));
|
||||
if ((-balance) < lineLimitPeer)
|
||||
send.insert(ripple::to_string(balance.getCurrency()));
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
traverseOwnedNodes(
|
||||
backend,
|
||||
accountID,
|
||||
*ledgerSequence,
|
||||
beast::zero,
|
||||
addToResponse);
|
||||
|
||||
response["send_currencies"] = boost::json::value(boost::json::array_kind);
|
||||
boost::json::array& jsonSend = response.at("send_currencies").as_array();
|
||||
|
||||
for (auto const& currency : send)
|
||||
jsonSend.push_back(currency.c_str());
|
||||
|
||||
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());
|
||||
|
||||
return response;
|
||||
}
|
||||
@@ -131,43 +131,70 @@ doAccountLines(
|
||||
}
|
||||
}
|
||||
|
||||
auto const rootIndex = ripple::keylet::ownerDir(accountID);
|
||||
auto currentIndex = rootIndex;
|
||||
|
||||
std::vector<ripple::uint256> keys;
|
||||
|
||||
for (;;)
|
||||
std::uint32_t limit = 200;
|
||||
if (request.contains("limit"))
|
||||
{
|
||||
auto ownedNode =
|
||||
backend.fetchLedgerObject(currentIndex.key, *ledgerSequence);
|
||||
if(!request.at("limit").is_int64())
|
||||
{
|
||||
response["error"] = "limit must be integer";
|
||||
return response;
|
||||
}
|
||||
|
||||
ripple::SerialIter it{ownedNode->data(), ownedNode->size()};
|
||||
ripple::SLE dir{it, currentIndex.key};
|
||||
for (auto const& key : dir.getFieldV256(ripple::sfIndexes))
|
||||
keys.push_back(key);
|
||||
|
||||
auto const uNodeNext = dir.getFieldU64(ripple::sfIndexNext);
|
||||
if (uNodeNext == 0)
|
||||
break;
|
||||
|
||||
currentIndex = ripple::keylet::page(rootIndex, uNodeNext);
|
||||
limit = request.at("limit").as_int64();
|
||||
if (limit <= 0)
|
||||
{
|
||||
response["error"] = "limit must be positive";
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
auto objects = backend.fetchLedgerObjects(keys, *ledgerSequence);
|
||||
ripple::uint256 cursor = beast::zero;
|
||||
if (request.contains("cursor"))
|
||||
{
|
||||
if(!request.at("cursor").is_string())
|
||||
{
|
||||
response["error"] = "limit must be string";
|
||||
return response;
|
||||
}
|
||||
|
||||
auto bytes = ripple::strUnHex(request.at("cursor").as_string().c_str());
|
||||
if (bytes and bytes->size() != 32)
|
||||
{
|
||||
response["error"] = "invalid cursor";
|
||||
return response;
|
||||
}
|
||||
|
||||
cursor = ripple::uint256::fromVoid(bytes->data());
|
||||
}
|
||||
|
||||
|
||||
response["lines"] = boost::json::value(boost::json::array_kind);
|
||||
boost::json::array& jsonLines = response.at("lines").as_array();
|
||||
|
||||
for (auto i = 0; i < objects.size(); ++i)
|
||||
{
|
||||
ripple::SerialIter it{objects[i].data(), objects[i].size()};
|
||||
ripple::SLE sle(it, keys[i]);
|
||||
|
||||
auto const addToResponse = [&](ripple::SLE const& sle) {
|
||||
if (sle.getType() == ripple::ltRIPPLE_STATE)
|
||||
{
|
||||
if (limit-- == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
addLine(jsonLines, sle, accountID, peerAccount);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
auto nextCursor =
|
||||
traverseOwnedNodes(
|
||||
backend,
|
||||
accountID,
|
||||
*ledgerSequence,
|
||||
cursor,
|
||||
addToResponse);
|
||||
|
||||
if (nextCursor)
|
||||
response["next_cursor"] = ripple::strHex(*nextCursor);
|
||||
|
||||
return response;
|
||||
}
|
||||
125
handlers/AccountObjects.cpp
Normal file
125
handlers/AccountObjects.cpp
Normal file
@@ -0,0 +1,125 @@
|
||||
#include <ripple/app/paths/RippleState.h>
|
||||
#include <ripple/app/ledger/Ledger.h>
|
||||
#include <ripple/basics/StringUtilities.h>
|
||||
#include <ripple/protocol/ErrorCodes.h>
|
||||
#include <ripple/protocol/Indexes.h>
|
||||
#include <ripple/protocol/STLedgerEntry.h>
|
||||
#include <ripple/protocol/jss.h>
|
||||
#include <boost/json.hpp>
|
||||
#include <algorithm>
|
||||
#include <handlers/RPCHelpers.h>
|
||||
#include <reporting/BackendInterface.h>
|
||||
#include <reporting/DBHelpers.h>
|
||||
|
||||
std::unordered_map<std::string, ripple::LedgerEntryType> types {
|
||||
{"state", ripple::ltRIPPLE_STATE},
|
||||
{"ticket", ripple::ltTICKET},
|
||||
{"signer_list", ripple::ltSIGNER_LIST},
|
||||
{"payment_channel", ripple::ltPAYCHAN},
|
||||
{"offer", ripple::ltOFFER},
|
||||
{"escrow", ripple::ltESCROW},
|
||||
{"deposit_preauth", ripple::ltDEPOSIT_PREAUTH},
|
||||
{"check", ripple::ltCHECK},
|
||||
};
|
||||
|
||||
boost::json::object
|
||||
doAccountObjects(
|
||||
boost::json::object const& request,
|
||||
BackendInterface const& backend)
|
||||
{
|
||||
boost::json::object response;
|
||||
|
||||
auto ledgerSequence = ledgerSequenceFromRequest(request, backend);
|
||||
if (!ledgerSequence)
|
||||
{
|
||||
response["error"] = "Empty database";
|
||||
return response;
|
||||
}
|
||||
|
||||
if(!request.contains("account"))
|
||||
{
|
||||
response["error"] = "Must contain account";
|
||||
return response;
|
||||
}
|
||||
|
||||
if(!request.at("account").is_string())
|
||||
{
|
||||
response["error"] = "Account must be a string";
|
||||
return response;
|
||||
}
|
||||
|
||||
ripple::AccountID accountID;
|
||||
auto parsed = ripple::parseBase58<ripple::AccountID>(
|
||||
request.at("account").as_string().c_str());
|
||||
|
||||
if (!parsed)
|
||||
{
|
||||
response["error"] = "Invalid account";
|
||||
return response;
|
||||
}
|
||||
|
||||
accountID = *parsed;
|
||||
|
||||
ripple::uint256 cursor = beast::zero;
|
||||
if (request.contains("cursor"))
|
||||
{
|
||||
if(!request.at("cursor").is_string())
|
||||
{
|
||||
response["error"] = "limit must be string";
|
||||
return response;
|
||||
}
|
||||
|
||||
auto bytes = ripple::strUnHex(request.at("cursor").as_string().c_str());
|
||||
if (bytes and bytes->size() != 32)
|
||||
{
|
||||
response["error"] = "invalid cursor";
|
||||
return response;
|
||||
}
|
||||
|
||||
cursor = ripple::uint256::fromVoid(bytes->data());
|
||||
}
|
||||
|
||||
std::optional<ripple::LedgerEntryType> objectType = {};
|
||||
if (request.contains("type"))
|
||||
{
|
||||
if(!request.at("type").is_string())
|
||||
{
|
||||
response["error"] = "type must be string";
|
||||
return response;
|
||||
}
|
||||
|
||||
std::string typeAsString = request.at("type").as_string().c_str();
|
||||
if(types.find(typeAsString) == types.end())
|
||||
{
|
||||
response["error"] = "invalid object type";
|
||||
return response;
|
||||
}
|
||||
|
||||
objectType = types[typeAsString];
|
||||
}
|
||||
|
||||
response["objects"] = boost::json::value(boost::json::array_kind);
|
||||
boost::json::array& jsonObjects = response.at("objects").as_array();
|
||||
|
||||
auto const addToResponse = [&](ripple::SLE const& sle) {
|
||||
if (!objectType || objectType == sle.getType())
|
||||
{
|
||||
jsonObjects.push_back(getJson(sle));
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
auto nextCursor =
|
||||
traverseOwnedNodes(
|
||||
backend,
|
||||
accountID,
|
||||
*ledgerSequence,
|
||||
cursor,
|
||||
addToResponse);
|
||||
|
||||
if (nextCursor)
|
||||
response["next_cursor"] = ripple::strHex(*nextCursor);
|
||||
|
||||
return response;
|
||||
}
|
||||
164
handlers/AccountOffers.cpp
Normal file
164
handlers/AccountOffers.cpp
Normal file
@@ -0,0 +1,164 @@
|
||||
#include <ripple/app/paths/RippleState.h>
|
||||
#include <ripple/app/ledger/Ledger.h>
|
||||
#include <ripple/basics/StringUtilities.h>
|
||||
#include <ripple/protocol/ErrorCodes.h>
|
||||
#include <ripple/protocol/Indexes.h>
|
||||
#include <ripple/protocol/STLedgerEntry.h>
|
||||
#include <ripple/protocol/jss.h>
|
||||
#include <boost/json.hpp>
|
||||
#include <algorithm>
|
||||
#include <handlers/RPCHelpers.h>
|
||||
#include <reporting/BackendInterface.h>
|
||||
#include <reporting/DBHelpers.h>
|
||||
|
||||
void
|
||||
addOffer(boost::json::array& offersJson, ripple::SLE const& offer)
|
||||
{
|
||||
auto quality = getQuality(offer.getFieldH256(ripple::sfBookDirectory));
|
||||
ripple::STAmount rate = ripple::amountFromQuality(quality);
|
||||
|
||||
ripple::STAmount takerPays = offer.getFieldAmount(ripple::sfTakerPays);
|
||||
ripple::STAmount takerGets = offer.getFieldAmount(ripple::sfTakerGets);
|
||||
|
||||
boost::json::object obj;
|
||||
|
||||
if (!takerPays.native())
|
||||
{
|
||||
obj["taker_pays"] = boost::json::value(boost::json::object_kind);
|
||||
boost::json::object& takerPaysJson = obj.at("taker_pays").as_object();
|
||||
|
||||
takerPaysJson["value"] = takerPays.getText();
|
||||
takerPaysJson["currency"] = ripple::to_string(takerPays.getCurrency());
|
||||
takerPaysJson["issuer"] = ripple::to_string(takerPays.getIssuer());
|
||||
}
|
||||
else
|
||||
{
|
||||
obj["taker_pays"] = takerPays.getText();
|
||||
}
|
||||
|
||||
if (!takerGets.native())
|
||||
{
|
||||
obj["taker_gets"] = boost::json::value(boost::json::object_kind);
|
||||
boost::json::object& takerGetsJson = obj.at("taker_gets").as_object();
|
||||
|
||||
takerGetsJson["value"] = takerGets.getText();
|
||||
takerGetsJson["currency"] = ripple::to_string(takerGets.getCurrency());
|
||||
takerGetsJson["issuer"] = ripple::to_string(takerGets.getIssuer());
|
||||
}
|
||||
else
|
||||
{
|
||||
obj["taker_gets"] = takerGets.getText();
|
||||
}
|
||||
|
||||
obj["seq"] = offer.getFieldU32(ripple::sfSequence);
|
||||
obj["flags"] = offer.getFieldU32(ripple::sfFlags);
|
||||
obj["quality"] = rate.getText();
|
||||
if (offer.isFieldPresent(ripple::sfExpiration))
|
||||
obj["expiration"] = offer.getFieldU32(ripple::sfExpiration);
|
||||
|
||||
offersJson.push_back(obj);
|
||||
};
|
||||
boost::json::object
|
||||
doAccountOffers(
|
||||
boost::json::object const& request,
|
||||
BackendInterface const& backend)
|
||||
{
|
||||
boost::json::object response;
|
||||
|
||||
auto ledgerSequence = ledgerSequenceFromRequest(request, backend);
|
||||
if (!ledgerSequence)
|
||||
{
|
||||
response["error"] = "Empty database";
|
||||
return response;
|
||||
}
|
||||
|
||||
if(!request.contains("account"))
|
||||
{
|
||||
response["error"] = "Must contain account";
|
||||
return response;
|
||||
}
|
||||
|
||||
if(!request.at("account").is_string())
|
||||
{
|
||||
response["error"] = "Account must be a string";
|
||||
return response;
|
||||
}
|
||||
|
||||
ripple::AccountID accountID;
|
||||
auto parsed = ripple::parseBase58<ripple::AccountID>(
|
||||
request.at("account").as_string().c_str());
|
||||
|
||||
if (!parsed)
|
||||
{
|
||||
response["error"] = "Invalid account";
|
||||
return response;
|
||||
}
|
||||
|
||||
accountID = *parsed;
|
||||
|
||||
std::uint32_t limit = 200;
|
||||
if (request.contains("limit"))
|
||||
{
|
||||
if(!request.at("limit").is_int64())
|
||||
{
|
||||
response["error"] = "limit must be integer";
|
||||
return response;
|
||||
}
|
||||
|
||||
limit = request.at("limit").as_int64();
|
||||
if (limit <= 0)
|
||||
{
|
||||
response["error"] = "limit must be positive";
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
ripple::uint256 cursor = beast::zero;
|
||||
if (request.contains("cursor"))
|
||||
{
|
||||
if(!request.at("cursor").is_string())
|
||||
{
|
||||
response["error"] = "limit must be string";
|
||||
return response;
|
||||
}
|
||||
|
||||
auto bytes = ripple::strUnHex(request.at("cursor").as_string().c_str());
|
||||
if (bytes and bytes->size() != 32)
|
||||
{
|
||||
response["error"] = "invalid cursor";
|
||||
return response;
|
||||
}
|
||||
|
||||
cursor = ripple::uint256::fromVoid(bytes->data());
|
||||
}
|
||||
|
||||
response["offers"] = boost::json::value(boost::json::array_kind);
|
||||
boost::json::array& jsonLines = response.at("offers").as_array();
|
||||
|
||||
auto const addToResponse = [&](ripple::SLE const& sle) {
|
||||
if (sle.getType() == ripple::ltOFFER)
|
||||
{
|
||||
if (limit-- == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
addOffer(jsonLines, sle);
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
auto nextCursor =
|
||||
traverseOwnedNodes(
|
||||
backend,
|
||||
accountID,
|
||||
*ledgerSequence,
|
||||
cursor,
|
||||
addToResponse);
|
||||
|
||||
if (nextCursor)
|
||||
response["next_cursor"] = ripple::strHex(*nextCursor);
|
||||
|
||||
return response;
|
||||
}
|
||||
@@ -80,3 +80,59 @@ ledgerSequenceFromRequest(
|
||||
return request.at("ledger_index").as_int64();
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<ripple::uint256>
|
||||
traverseOwnedNodes(
|
||||
BackendInterface const& backend,
|
||||
ripple::AccountID const& accountID,
|
||||
std::uint32_t sequence,
|
||||
ripple::uint256 const& cursor,
|
||||
std::function<bool(ripple::SLE)> atOwnedNode)
|
||||
{
|
||||
auto const rootIndex = ripple::keylet::ownerDir(accountID);
|
||||
auto currentIndex = rootIndex;
|
||||
|
||||
std::vector<ripple::uint256> keys;
|
||||
std::optional<ripple::uint256> nextCursor = {};
|
||||
|
||||
for (;;)
|
||||
{
|
||||
auto ownedNode =
|
||||
backend.fetchLedgerObject(currentIndex.key, sequence);
|
||||
|
||||
if (!ownedNode)
|
||||
{
|
||||
throw std::runtime_error("Could not find owned node");
|
||||
}
|
||||
|
||||
ripple::SerialIter it{ownedNode->data(), ownedNode->size()};
|
||||
ripple::SLE dir{it, currentIndex.key};
|
||||
|
||||
for (auto const& key : dir.getFieldV256(ripple::sfIndexes))
|
||||
{
|
||||
if (key >= cursor)
|
||||
keys.push_back(key);
|
||||
}
|
||||
|
||||
auto const uNodeNext = dir.getFieldU64(ripple::sfIndexNext);
|
||||
if (uNodeNext == 0)
|
||||
break;
|
||||
|
||||
currentIndex = ripple::keylet::page(rootIndex, uNodeNext);
|
||||
}
|
||||
|
||||
auto objects = backend.fetchLedgerObjects(keys, sequence);
|
||||
|
||||
for (auto i = 0; i < objects.size(); ++i)
|
||||
{
|
||||
ripple::SerialIter it{objects[i].data(), objects[i].size()};
|
||||
ripple::SLE sle(it, keys[i]);
|
||||
if (!atOwnedNode(sle))
|
||||
{
|
||||
nextCursor = keys[i+1];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return nextCursor;
|
||||
}
|
||||
|
||||
@@ -25,4 +25,12 @@ ledgerSequenceFromRequest(
|
||||
boost::json::object const& request,
|
||||
BackendInterface const& backend);
|
||||
|
||||
std::optional<ripple::uint256>
|
||||
traverseOwnedNodes(
|
||||
BackendInterface const& backend,
|
||||
ripple::AccountID const& accountID,
|
||||
std::uint32_t sequence,
|
||||
ripple::uint256 const& cursor,
|
||||
std::function<bool(ripple::SLE)> atOwnedNode);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -45,7 +45,10 @@ enum RPCCommand {
|
||||
ledger_range,
|
||||
ledger_entry,
|
||||
account_channels,
|
||||
account_lines
|
||||
account_lines,
|
||||
account_currencies,
|
||||
account_offers,
|
||||
account_objects
|
||||
};
|
||||
std::unordered_map<std::string, RPCCommand> commandMap{
|
||||
{"tx", tx},
|
||||
@@ -57,7 +60,10 @@ std::unordered_map<std::string, RPCCommand> commandMap{
|
||||
{"ledger_data", ledger_data},
|
||||
{"book_offers", book_offers},
|
||||
{"account_channels", account_channels},
|
||||
{"account_lines", account_lines}};
|
||||
{"account_lines", account_lines},
|
||||
{"account_currencies", account_currencies},
|
||||
{"account_offers", account_offers},
|
||||
{"account_objects", account_objects}};
|
||||
|
||||
boost::json::object
|
||||
doAccountInfo(
|
||||
@@ -95,6 +101,18 @@ boost::json::object
|
||||
doAccountLines(
|
||||
boost::json::object const& request,
|
||||
BackendInterface const& backend);
|
||||
boost::json::object
|
||||
doAccountCurrencies(
|
||||
boost::json::object const& request,
|
||||
BackendInterface const& backend);
|
||||
boost::json::object
|
||||
doAccountOffers(
|
||||
boost::json::object const& request,
|
||||
BackendInterface const& backend);
|
||||
boost::json::object
|
||||
doAccountObjects(
|
||||
boost::json::object const& request,
|
||||
BackendInterface const& backend);
|
||||
|
||||
boost::json::object
|
||||
buildResponse(
|
||||
@@ -136,6 +154,15 @@ buildResponse(
|
||||
case account_lines:
|
||||
return doAccountLines(request, backend);
|
||||
break;
|
||||
case account_currencies:
|
||||
return doAccountCurrencies(request, backend);
|
||||
break;
|
||||
case account_offers:
|
||||
return doAccountOffers(request, backend);
|
||||
break;
|
||||
case account_objects:
|
||||
return doAccountObjects(request, backend);
|
||||
break;
|
||||
default:
|
||||
BOOST_LOG_TRIVIAL(error) << "Unknown command: " << command;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user