Adds account_channels RPC

This commit is contained in:
Nathan Nichols
2021-04-23 18:41:47 -05:00
parent e7b212a05c
commit f540ceda35
5 changed files with 336 additions and 35 deletions

View File

@@ -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})

View 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
View 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;
}

View File

@@ -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,

View File

@@ -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;
} }