Files
rippled/src/xrpld/rpc/handlers/AccountChannels.cpp
Mayukha Vadari d9c27da529 refactor: split up RPCHelpers.h into two (#6047)
This PR splits `RPCHelpers.h` into two files, by moving out all the ledger-fetching-related functions into a separate file, `RPCLedgerHelpers.h`. It also moves `getAccountObjects` to `AccountObjects.h`, since it is only used in that one place.
2025-11-18 15:44:39 -05:00

198 lines
6.3 KiB
C++

#include <xrpld/rpc/Context.h>
#include <xrpld/rpc/detail/RPCHelpers.h>
#include <xrpld/rpc/detail/RPCLedgerHelpers.h>
#include <xrpld/rpc/detail/Tuning.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/View.h>
#include <xrpl/protocol/ErrorCodes.h>
#include <xrpl/protocol/PublicKey.h>
#include <xrpl/protocol/RPCErr.h>
#include <xrpl/protocol/jss.h>
#include <xrpl/resource/Fees.h>
namespace ripple {
void
addChannel(Json::Value& jsonLines, SLE const& line)
{
Json::Value& jDst(jsonLines.append(Json::objectValue));
jDst[jss::channel_id] = to_string(line.key());
jDst[jss::account] = to_string(line[sfAccount]);
jDst[jss::destination_account] = to_string(line[sfDestination]);
jDst[jss::amount] = line[sfAmount].getText();
jDst[jss::balance] = line[sfBalance].getText();
if (publicKeyType(line[sfPublicKey]))
{
PublicKey const pk(line[sfPublicKey]);
jDst[jss::public_key] = toBase58(TokenType::AccountPublic, pk);
jDst[jss::public_key_hex] = strHex(pk);
}
jDst[jss::settle_delay] = line[sfSettleDelay];
if (auto const& v = line[~sfExpiration])
jDst[jss::expiration] = *v;
if (auto const& v = line[~sfCancelAfter])
jDst[jss::cancel_after] = *v;
if (auto const& v = line[~sfSourceTag])
jDst[jss::source_tag] = *v;
if (auto const& v = line[~sfDestinationTag])
jDst[jss::destination_tag] = *v;
}
// {
// account: <account>
// ledger_hash : <ledger>
// ledger_index : <ledger_index>
// limit: integer // optional
// marker: opaque // optional, resume previous query
// }
Json::Value
doAccountChannels(RPC::JsonContext& context)
{
auto const& params(context.params);
if (!params.isMember(jss::account))
return RPC::missing_field_error(jss::account);
if (!params[jss::account].isString())
return RPC::invalid_field_error(jss::account);
std::shared_ptr<ReadView const> ledger;
auto result = RPC::lookupLedger(ledger, context);
if (!ledger)
return result;
auto id = parseBase58<AccountID>(params[jss::account].asString());
if (!id)
{
return rpcError(rpcACT_MALFORMED);
}
AccountID const accountID{std::move(id.value())};
if (!ledger->exists(keylet::account(accountID)))
return rpcError(rpcACT_NOT_FOUND);
std::string strDst;
if (params.isMember(jss::destination_account))
strDst = params[jss::destination_account].asString();
auto const raDstAccount = [&]() -> std::optional<AccountID> {
return strDst.empty() ? std::nullopt : parseBase58<AccountID>(strDst);
}();
if (!strDst.empty() && !raDstAccount)
return rpcError(rpcACT_MALFORMED);
unsigned int limit;
if (auto err = readLimitField(limit, RPC::Tuning::accountChannels, context))
return *err;
Json::Value jsonChannels{Json::arrayValue};
struct VisitData
{
std::vector<std::shared_ptr<SLE const>> items;
AccountID const& accountID;
std::optional<AccountID> const& raDstAccount;
};
VisitData visitData = {{}, accountID, raDstAccount};
visitData.items.reserve(limit);
uint256 startAfter = beast::zero;
std::uint64_t startHint = 0;
if (params.isMember(jss::marker))
{
if (!params[jss::marker].isString())
return RPC::expected_field_error(jss::marker, "string");
// Marker is composed of a comma separated index and start hint. The
// former will be read as hex, and the latter using boost lexical cast.
std::stringstream marker(params[jss::marker].asString());
std::string value;
if (!std::getline(marker, value, ','))
return rpcError(rpcINVALID_PARAMS);
if (!startAfter.parseHex(value))
return rpcError(rpcINVALID_PARAMS);
if (!std::getline(marker, value, ','))
return rpcError(rpcINVALID_PARAMS);
try
{
startHint = boost::lexical_cast<std::uint64_t>(value);
}
catch (boost::bad_lexical_cast&)
{
return rpcError(rpcINVALID_PARAMS);
}
// We then must check if the object pointed to by the marker is actually
// owned by the account in the request.
auto const sle = ledger->read({ltANY, startAfter});
if (!sle)
return rpcError(rpcINVALID_PARAMS);
if (!RPC::isRelatedToAccount(*ledger, sle, accountID))
return rpcError(rpcINVALID_PARAMS);
}
auto count = 0;
std::optional<uint256> marker = {};
std::uint64_t nextHint = 0;
if (!forEachItemAfter(
*ledger,
accountID,
startAfter,
startHint,
limit + 1,
[&visitData, &accountID, &count, &limit, &marker, &nextHint](
std::shared_ptr<SLE const> const& sleCur) {
if (!sleCur)
{
// LCOV_EXCL_START
UNREACHABLE("ripple::doAccountChannels : null SLE");
return false;
// LCOV_EXCL_STOP
}
if (++count == limit)
{
marker = sleCur->key();
nextHint = RPC::getStartHint(sleCur, visitData.accountID);
}
if (count <= limit && sleCur->getType() == ltPAYCHAN &&
(*sleCur)[sfAccount] == accountID &&
(!visitData.raDstAccount ||
*visitData.raDstAccount == (*sleCur)[sfDestination]))
{
visitData.items.emplace_back(sleCur);
}
return true;
}))
{
return rpcError(rpcINVALID_PARAMS);
}
// Both conditions need to be checked because marker is set on the limit-th
// item, but if there is no item on the limit + 1 iteration, then there is
// no need to return a marker.
if (count == limit + 1 && marker)
{
result[jss::limit] = limit;
result[jss::marker] =
to_string(*marker) + "," + std::to_string(nextHint);
}
result[jss::account] = toBase58(accountID);
for (auto const& item : visitData.items)
addChannel(jsonChannels, *item);
context.loadType = Resource::feeMediumBurdenRPC;
result[jss::channels] = std::move(jsonChannels);
return result;
}
} // namespace ripple