mirror of
https://github.com/XRPLF/clio.git
synced 2025-11-21 20:25:52 +00:00
Adds account_channels RPC
This commit is contained in:
@@ -70,7 +70,9 @@ target_sources(reporting PRIVATE
|
|||||||
handlers/BookOffers.cpp
|
handlers/BookOffers.cpp
|
||||||
handlers/LedgerRange.cpp
|
handlers/LedgerRange.cpp
|
||||||
handlers/Ledger.cpp
|
handlers/Ledger.cpp
|
||||||
handlers/LedgerEntry.cpp)
|
handlers/LedgerEntry.cpp
|
||||||
|
handlers/AccountChannels.cpp
|
||||||
|
handlers/AccountLines.cpp)
|
||||||
|
|
||||||
|
|
||||||
message(${Boost_LIBRARIES})
|
message(${Boost_LIBRARIES})
|
||||||
|
|||||||
140
handlers/AccountChannels.cpp
Normal file
140
handlers/AccountChannels.cpp
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
#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>
|
||||||
|
|
||||||
|
void
|
||||||
|
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["amount"] = line[ripple::sfAmount].getText();
|
||||||
|
jDst["balance"] = line[ripple::sfBalance].getText();
|
||||||
|
if (publicKeyType(line[ripple::sfPublicKey]))
|
||||||
|
{
|
||||||
|
ripple::PublicKey const pk(line[ripple::sfPublicKey]);
|
||||||
|
jDst["public_key"] = toBase58(ripple::TokenType::AccountPublic, pk);
|
||||||
|
jDst["public_key_hex"] = strHex(pk);
|
||||||
|
}
|
||||||
|
jDst["settle_delay"] = line[ripple::sfSettleDelay];
|
||||||
|
if (auto const& v = line[~ripple::sfExpiration])
|
||||||
|
jDst["expiration"] = *v;
|
||||||
|
if (auto const& v = line[~ripple::sfCancelAfter])
|
||||||
|
jDst["cancel_after"] = *v;
|
||||||
|
if (auto const& v = line[~ripple::sfSourceTag])
|
||||||
|
jDst["source_tag"] = *v;
|
||||||
|
if (auto const& v = line[~ripple::sfDestinationTag])
|
||||||
|
jDst["destination_tag"] = *v;
|
||||||
|
|
||||||
|
jsonLines.push_back(Json::objectValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
boost::json::object
|
||||||
|
doAccountChannels(
|
||||||
|
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;
|
||||||
|
|
||||||
|
boost::optional<ripple::AccountID> destAccount;
|
||||||
|
if (request.contains("destination_account"))
|
||||||
|
{
|
||||||
|
if (!request.at("destination_account").is_string())
|
||||||
|
{
|
||||||
|
response["error"] = "destination_account should be a string";
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
destAccount = ripple::parseBase58<ripple::AccountID>(
|
||||||
|
request.at("destination_account").as_string().c_str());
|
||||||
|
if (!destAccount)
|
||||||
|
{
|
||||||
|
response["error"] = "Invalid destination account";
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto const rootIndex = ripple::keylet::ownerDir(accountID);
|
||||||
|
auto currentIndex = rootIndex;
|
||||||
|
|
||||||
|
std::vector<ripple::uint256> keys;
|
||||||
|
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
auto ownedNode =
|
||||||
|
backend.fetchLedgerObject(currentIndex.key, *ledgerSequence);
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto objects = backend.fetchLedgerObjects(keys, *ledgerSequence);
|
||||||
|
|
||||||
|
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]};
|
||||||
|
|
||||||
|
if (sle.getType() == ripple::ltPAYCHAN &&
|
||||||
|
sle.getAccountID(ripple::sfAccount) == accountID &&
|
||||||
|
(!destAccount ||
|
||||||
|
*destAccount == sle.getAccountID(ripple::sfDestination)))
|
||||||
|
{
|
||||||
|
addChannel(jsonChannels, sle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
173
handlers/AccountLines.cpp
Normal file
173
handlers/AccountLines.cpp
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
#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
|
||||||
|
addLine(
|
||||||
|
boost::json::array& jsonLines,
|
||||||
|
ripple::SLE const& line,
|
||||||
|
ripple::AccountID const& account,
|
||||||
|
boost::optional<ripple::AccountID> const& peerAccount)
|
||||||
|
{
|
||||||
|
auto flags = line.getFieldU32(ripple::sfFlags);
|
||||||
|
auto lowLimit = line.getFieldAmount(ripple::sfLowLimit);
|
||||||
|
auto highLimit = line.getFieldAmount(ripple::sfHighLimit);
|
||||||
|
auto lowID = lowLimit.getIssuer();
|
||||||
|
auto highID = highLimit.getIssuer();
|
||||||
|
auto lowQualityIn = line.getFieldU32(ripple::sfLowQualityIn);
|
||||||
|
auto lowQualityOut = line.getFieldU32(ripple::sfLowQualityOut);
|
||||||
|
auto highQualityIn = line.getFieldU32(ripple::sfHighQualityIn);
|
||||||
|
auto highQualityOut = line.getFieldU32(ripple::sfHighQualityOut);
|
||||||
|
auto balance = line.getFieldAmount(ripple::sfBalance);
|
||||||
|
|
||||||
|
bool viewLowest = (lowID == account);
|
||||||
|
auto lineLimit = viewLowest ? lowLimit : highLimit;
|
||||||
|
auto lineLimitPeer = !viewLowest ? lowLimit : highLimit;
|
||||||
|
auto lineAccountIDPeer = !viewLowest ? lowID : highID;
|
||||||
|
auto lineQualityIn = viewLowest ? lowQualityIn : highQualityIn;
|
||||||
|
auto lineQualityOut = viewLowest ? lowQualityOut : highQualityOut;
|
||||||
|
|
||||||
|
if (peerAccount and peerAccount != lineAccountIDPeer)
|
||||||
|
return;
|
||||||
|
|
||||||
|
bool lineAuth = flags & (viewLowest ? ripple::lsfLowAuth : ripple::lsfHighAuth);
|
||||||
|
bool lineAuthPeer = flags & (!viewLowest ? ripple::lsfLowAuth : ripple::lsfHighAuth);
|
||||||
|
bool lineNoRipple = flags & (viewLowest ? ripple::lsfLowNoRipple : ripple::lsfHighNoRipple);
|
||||||
|
bool lineDefaultRipple = flags & ripple::lsfDefaultRipple;
|
||||||
|
bool lineNoRipplePeer = flags & (!viewLowest ? ripple::lsfLowNoRipple : ripple::lsfHighNoRipple);
|
||||||
|
bool lineFreeze = flags & (viewLowest ? ripple::lsfLowFreeze : ripple::lsfHighFreeze);
|
||||||
|
bool lineFreezePeer = flags & (!viewLowest ? ripple::lsfLowFreeze : ripple::lsfHighFreeze);
|
||||||
|
|
||||||
|
ripple::STAmount const& saBalance(balance);
|
||||||
|
ripple::STAmount const& saLimit(lineLimit);
|
||||||
|
ripple::STAmount const& saLimitPeer(lineLimitPeer);
|
||||||
|
|
||||||
|
boost::json::object jPeer;
|
||||||
|
jPeer["account"] = ripple::to_string(lineAccountIDPeer);
|
||||||
|
jPeer["balance"] = saBalance.getText();
|
||||||
|
jPeer["currency"] = ripple::to_string(saBalance.issue().currency);
|
||||||
|
jPeer["limit"] = saLimit.getText();
|
||||||
|
jPeer["limit_peer"] = saLimitPeer.getText();
|
||||||
|
jPeer["quality_in"] = lineQualityIn;
|
||||||
|
jPeer["quality_out"] = lineQualityOut;
|
||||||
|
if (lineAuth)
|
||||||
|
jPeer["authorized"] = true;
|
||||||
|
if (lineAuthPeer)
|
||||||
|
jPeer["peer_authorized"] = true;
|
||||||
|
if (lineNoRipple || !lineDefaultRipple)
|
||||||
|
jPeer["no_ripple"] = lineNoRipple;
|
||||||
|
if (lineNoRipple || !lineDefaultRipple)
|
||||||
|
jPeer["no_ripple_peer"] = lineNoRipplePeer;
|
||||||
|
if (lineFreeze)
|
||||||
|
jPeer["freeze"] = true;
|
||||||
|
if (lineFreezePeer)
|
||||||
|
jPeer["freeze_peer"] = true;
|
||||||
|
|
||||||
|
jsonLines.push_back(jPeer);
|
||||||
|
}
|
||||||
|
|
||||||
|
boost::json::object
|
||||||
|
doAccountLines(
|
||||||
|
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;
|
||||||
|
|
||||||
|
boost::optional<ripple::AccountID> peerAccount;
|
||||||
|
if (request.contains("peer"))
|
||||||
|
{
|
||||||
|
if (!request.at("peer").is_string())
|
||||||
|
{
|
||||||
|
response["error"] = "peer should be a string";
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
peerAccount = ripple::parseBase58<ripple::AccountID>(
|
||||||
|
request.at("peer").as_string().c_str());
|
||||||
|
if (!peerAccount)
|
||||||
|
{
|
||||||
|
response["error"] = "Invalid peer account";
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto const rootIndex = ripple::keylet::ownerDir(accountID);
|
||||||
|
auto currentIndex = rootIndex;
|
||||||
|
|
||||||
|
std::vector<ripple::uint256> keys;
|
||||||
|
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
auto ownedNode =
|
||||||
|
backend.fetchLedgerObject(currentIndex.key, *ledgerSequence);
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto objects = backend.fetchLedgerObjects(keys, *ledgerSequence);
|
||||||
|
|
||||||
|
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]);
|
||||||
|
|
||||||
|
if (sle.getType() == ripple::ltRIPPLE_STATE)
|
||||||
|
{
|
||||||
|
addLine(jsonLines, sle, accountID, peerAccount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
@@ -11,38 +11,6 @@
|
|||||||
#include <reporting/DBHelpers.h>
|
#include <reporting/DBHelpers.h>
|
||||||
#include <reporting/Pg.h>
|
#include <reporting/Pg.h>
|
||||||
|
|
||||||
std::optional<std::uint32_t>
|
|
||||||
ledgerSequenceFromRequest(
|
|
||||||
boost::json::object const& request,
|
|
||||||
std::shared_ptr<PgPool> const& pool)
|
|
||||||
{
|
|
||||||
std::stringstream sql;
|
|
||||||
sql << "SELECT ledger_seq FROM ledgers WHERE ";
|
|
||||||
|
|
||||||
if (request.contains("ledger_index"))
|
|
||||||
{
|
|
||||||
sql << "ledger_seq = "
|
|
||||||
<< std::to_string(request.at("ledger_index").as_int64());
|
|
||||||
}
|
|
||||||
else if (request.contains("ledger_hash"))
|
|
||||||
{
|
|
||||||
sql << "ledger_hash = \\\\x" << request.at("ledger_hash").as_string();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
sql.str("");
|
|
||||||
sql << "SELECT max_ledger()";
|
|
||||||
}
|
|
||||||
|
|
||||||
sql << ";";
|
|
||||||
|
|
||||||
auto index = PgQuery(pool)(sql.str().c_str());
|
|
||||||
if (!index || index.isNull())
|
|
||||||
return {};
|
|
||||||
|
|
||||||
return std::optional<std::uint32_t>{index.asInt()};
|
|
||||||
}
|
|
||||||
|
|
||||||
boost::json::object
|
boost::json::object
|
||||||
doBookOffers(
|
doBookOffers(
|
||||||
boost::json::object const& request,
|
boost::json::object const& request,
|
||||||
|
|||||||
@@ -43,7 +43,9 @@ enum RPCCommand {
|
|||||||
ledger_data,
|
ledger_data,
|
||||||
book_offers,
|
book_offers,
|
||||||
ledger_range,
|
ledger_range,
|
||||||
ledger_entry
|
ledger_entry,
|
||||||
|
account_channels,
|
||||||
|
account_lines
|
||||||
};
|
};
|
||||||
std::unordered_map<std::string, RPCCommand> commandMap{
|
std::unordered_map<std::string, RPCCommand> commandMap{
|
||||||
{"tx", tx},
|
{"tx", tx},
|
||||||
@@ -53,7 +55,9 @@ std::unordered_map<std::string, RPCCommand> commandMap{
|
|||||||
{"ledger_entry", ledger_entry},
|
{"ledger_entry", ledger_entry},
|
||||||
{"account_info", account_info},
|
{"account_info", account_info},
|
||||||
{"ledger_data", ledger_data},
|
{"ledger_data", ledger_data},
|
||||||
{"book_offers", book_offers}};
|
{"book_offers", book_offers},
|
||||||
|
{"account_channels", account_channels},
|
||||||
|
{"account_lines", account_lines}};
|
||||||
|
|
||||||
boost::json::object
|
boost::json::object
|
||||||
doAccountInfo(
|
doAccountInfo(
|
||||||
@@ -83,6 +87,14 @@ boost::json::object
|
|||||||
doLedgerRange(
|
doLedgerRange(
|
||||||
boost::json::object const& request,
|
boost::json::object const& request,
|
||||||
BackendInterface const& backend);
|
BackendInterface const& backend);
|
||||||
|
boost::json::object
|
||||||
|
doAccountChannels(
|
||||||
|
boost::json::object const& request,
|
||||||
|
BackendInterface const& backend);
|
||||||
|
boost::json::object
|
||||||
|
doAccountLines(
|
||||||
|
boost::json::object const& request,
|
||||||
|
BackendInterface const& backend);
|
||||||
|
|
||||||
boost::json::object
|
boost::json::object
|
||||||
buildResponse(
|
buildResponse(
|
||||||
@@ -118,6 +130,12 @@ buildResponse(
|
|||||||
case book_offers:
|
case book_offers:
|
||||||
return doBookOffers(request, backend);
|
return doBookOffers(request, backend);
|
||||||
break;
|
break;
|
||||||
|
case account_channels:
|
||||||
|
return doAccountChannels(request, backend);
|
||||||
|
break;
|
||||||
|
case account_lines:
|
||||||
|
return doAccountLines(request, backend);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
BOOST_LOG_TRIVIAL(error) << "Unknown command: " << command;
|
BOOST_LOG_TRIVIAL(error) << "Unknown command: " << command;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user