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.
This commit is contained in:
Mayukha Vadari
2025-11-19 02:14:39 +05:30
committed by GitHub
parent 3fb6acd29e
commit d9c27da529
27 changed files with 770 additions and 719 deletions

View File

@@ -6,7 +6,7 @@
#include <xrpld/app/misc/Transaction.h>
#include <xrpld/core/Config.h>
#include <xrpld/core/DatabaseCon.h>
#include <xrpld/rpc/detail/RPCHelpers.h>
#include <xrpld/rpc/detail/RPCLedgerHelpers.h>
#include <xrpl/beast/utility/instrumentation.h>

View File

@@ -130,513 +130,6 @@ isRelatedToAccount(
return false;
}
bool
getAccountObjects(
ReadView const& ledger,
AccountID const& account,
std::optional<std::vector<LedgerEntryType>> const& typeFilter,
uint256 dirIndex,
uint256 entryIndex,
std::uint32_t const limit,
Json::Value& jvResult)
{
// check if dirIndex is valid
if (!dirIndex.isZero() && !ledger.read({ltDIR_NODE, dirIndex}))
return false;
auto typeMatchesFilter = [](std::vector<LedgerEntryType> const& typeFilter,
LedgerEntryType ledgerType) {
auto it = std::find(typeFilter.begin(), typeFilter.end(), ledgerType);
return it != typeFilter.end();
};
// if dirIndex != 0, then all NFTs have already been returned. only
// iterate NFT pages if the filter says so AND dirIndex == 0
bool iterateNFTPages =
(!typeFilter.has_value() ||
typeMatchesFilter(typeFilter.value(), ltNFTOKEN_PAGE)) &&
dirIndex == beast::zero;
Keylet const firstNFTPage = keylet::nftpage_min(account);
// we need to check the marker to see if it is an NFTTokenPage index.
if (iterateNFTPages && entryIndex != beast::zero)
{
// if it is we will try to iterate the pages up to the limit
// and then change over to the owner directory
if (firstNFTPage.key != (entryIndex & ~nft::pageMask))
iterateNFTPages = false;
}
auto& jvObjects = (jvResult[jss::account_objects] = Json::arrayValue);
// this is a mutable version of limit, used to seamlessly switch
// to iterating directory entries when nftokenpages are exhausted
uint32_t mlimit = limit;
// iterate NFTokenPages preferentially
if (iterateNFTPages)
{
Keylet const first = entryIndex == beast::zero
? firstNFTPage
: Keylet{ltNFTOKEN_PAGE, entryIndex};
Keylet const last = keylet::nftpage_max(account);
// current key
uint256 ck = ledger.succ(first.key, last.key.next()).value_or(last.key);
// current page
auto cp = ledger.read(Keylet{ltNFTOKEN_PAGE, ck});
while (cp)
{
jvObjects.append(cp->getJson(JsonOptions::none));
auto const npm = (*cp)[~sfNextPageMin];
if (npm)
cp = ledger.read(Keylet(ltNFTOKEN_PAGE, *npm));
else
cp = nullptr;
if (--mlimit == 0)
{
if (cp)
{
jvResult[jss::limit] = limit;
jvResult[jss::marker] = std::string("0,") + to_string(ck);
return true;
}
}
if (!npm)
break;
ck = *npm;
}
// if execution reaches here then we're about to transition
// to iterating the root directory (and the conventional
// behaviour of this RPC function.) Therefore we should
// zero entryIndex so as not to terribly confuse things.
entryIndex = beast::zero;
}
auto const root = keylet::ownerDir(account);
auto found = false;
if (dirIndex.isZero())
{
dirIndex = root.key;
found = true;
}
auto dir = ledger.read({ltDIR_NODE, dirIndex});
if (!dir)
{
// it's possible the user had nftoken pages but no
// directory entries. If there's no nftoken page, we will
// give empty array for account_objects.
if (mlimit >= limit)
jvResult[jss::account_objects] = Json::arrayValue;
// non-zero dirIndex validity was checked in the beginning of this
// function; by this point, it should be zero. This function returns
// true regardless of nftoken page presence; if absent, account_objects
// is already set as an empty array. Notice we will only return false in
// this function when entryIndex can not be found, indicating an invalid
// marker error.
return true;
}
std::uint32_t i = 0;
for (;;)
{
auto const& entries = dir->getFieldV256(sfIndexes);
auto iter = entries.begin();
if (!found)
{
iter = std::find(iter, entries.end(), entryIndex);
if (iter == entries.end())
return false;
found = true;
}
// it's possible that the returned NFTPages exactly filled the
// response. Check for that condition.
if (i == mlimit && mlimit < limit)
{
jvResult[jss::limit] = limit;
jvResult[jss::marker] =
to_string(dirIndex) + ',' + to_string(*iter);
return true;
}
for (; iter != entries.end(); ++iter)
{
auto const sleNode = ledger.read(keylet::child(*iter));
if (!typeFilter.has_value() ||
typeMatchesFilter(typeFilter.value(), sleNode->getType()))
{
jvObjects.append(sleNode->getJson(JsonOptions::none));
}
if (++i == mlimit)
{
if (++iter != entries.end())
{
jvResult[jss::limit] = limit;
jvResult[jss::marker] =
to_string(dirIndex) + ',' + to_string(*iter);
return true;
}
break;
}
}
auto const nodeIndex = dir->getFieldU64(sfIndexNext);
if (nodeIndex == 0)
return true;
dirIndex = keylet::page(root, nodeIndex).key;
dir = ledger.read({ltDIR_NODE, dirIndex});
if (!dir)
return true;
if (i == mlimit)
{
auto const& e = dir->getFieldV256(sfIndexes);
if (!e.empty())
{
jvResult[jss::limit] = limit;
jvResult[jss::marker] =
to_string(dirIndex) + ',' + to_string(*e.begin());
}
return true;
}
}
}
namespace {
bool
isValidatedOld(LedgerMaster& ledgerMaster, bool standalone)
{
if (standalone)
return false;
return ledgerMaster.getValidatedLedgerAge() > Tuning::maxValidatedLedgerAge;
}
template <class T>
Status
ledgerFromRequest(T& ledger, JsonContext& context)
{
ledger.reset();
auto& params = context.params;
auto indexValue = params[jss::ledger_index];
auto hashValue = params[jss::ledger_hash];
// We need to support the legacy "ledger" field.
auto& legacyLedger = params[jss::ledger];
if (legacyLedger)
{
if (legacyLedger.asString().size() > 12)
hashValue = legacyLedger;
else
indexValue = legacyLedger;
}
if (!hashValue.isNull())
{
if (!hashValue.isString())
return {rpcINVALID_PARAMS, "ledgerHashNotString"};
uint256 ledgerHash;
if (!ledgerHash.parseHex(hashValue.asString()))
return {rpcINVALID_PARAMS, "ledgerHashMalformed"};
return getLedger(ledger, ledgerHash, context);
}
if (!indexValue.isConvertibleTo(Json::stringValue))
return {rpcINVALID_PARAMS, "ledgerIndexMalformed"};
auto const index = indexValue.asString();
if (index == "current" || index.empty())
return getLedger(ledger, LedgerShortcut::CURRENT, context);
if (index == "validated")
return getLedger(ledger, LedgerShortcut::VALIDATED, context);
if (index == "closed")
return getLedger(ledger, LedgerShortcut::CLOSED, context);
std::uint32_t val;
if (!beast::lexicalCastChecked(val, index))
return {rpcINVALID_PARAMS, "ledgerIndexMalformed"};
return getLedger(ledger, val, context);
}
} // namespace
template <class T, class R>
Status
ledgerFromRequest(T& ledger, GRPCContext<R>& context)
{
R& request = context.params;
return ledgerFromSpecifier(ledger, request.ledger(), context);
}
// explicit instantiation of above function
template Status
ledgerFromRequest<>(
std::shared_ptr<ReadView const>&,
GRPCContext<org::xrpl::rpc::v1::GetLedgerEntryRequest>&);
// explicit instantiation of above function
template Status
ledgerFromRequest<>(
std::shared_ptr<ReadView const>&,
GRPCContext<org::xrpl::rpc::v1::GetLedgerDataRequest>&);
// explicit instantiation of above function
template Status
ledgerFromRequest<>(
std::shared_ptr<ReadView const>&,
GRPCContext<org::xrpl::rpc::v1::GetLedgerRequest>&);
template <class T>
Status
ledgerFromSpecifier(
T& ledger,
org::xrpl::rpc::v1::LedgerSpecifier const& specifier,
Context& context)
{
ledger.reset();
using LedgerCase = org::xrpl::rpc::v1::LedgerSpecifier::LedgerCase;
LedgerCase ledgerCase = specifier.ledger_case();
switch (ledgerCase)
{
case LedgerCase::kHash: {
if (auto hash = uint256::fromVoidChecked(specifier.hash()))
{
return getLedger(ledger, *hash, context);
}
return {rpcINVALID_PARAMS, "ledgerHashMalformed"};
}
case LedgerCase::kSequence:
return getLedger(ledger, specifier.sequence(), context);
case LedgerCase::kShortcut:
[[fallthrough]];
case LedgerCase::LEDGER_NOT_SET: {
auto const shortcut = specifier.shortcut();
if (shortcut ==
org::xrpl::rpc::v1::LedgerSpecifier::SHORTCUT_VALIDATED)
{
return getLedger(ledger, LedgerShortcut::VALIDATED, context);
}
else
{
if (shortcut ==
org::xrpl::rpc::v1::LedgerSpecifier::SHORTCUT_CURRENT ||
shortcut ==
org::xrpl::rpc::v1::LedgerSpecifier::
SHORTCUT_UNSPECIFIED)
{
return getLedger(ledger, LedgerShortcut::CURRENT, context);
}
else if (
shortcut ==
org::xrpl::rpc::v1::LedgerSpecifier::SHORTCUT_CLOSED)
{
return getLedger(ledger, LedgerShortcut::CLOSED, context);
}
}
}
}
return Status::OK;
}
template <class T>
Status
getLedger(T& ledger, uint256 const& ledgerHash, Context& context)
{
ledger = context.ledgerMaster.getLedgerByHash(ledgerHash);
if (ledger == nullptr)
return {rpcLGR_NOT_FOUND, "ledgerNotFound"};
return Status::OK;
}
template <class T>
Status
getLedger(T& ledger, uint32_t ledgerIndex, Context& context)
{
ledger = context.ledgerMaster.getLedgerBySeq(ledgerIndex);
if (ledger == nullptr)
{
auto cur = context.ledgerMaster.getCurrentLedger();
if (cur->info().seq == ledgerIndex)
{
ledger = cur;
}
}
if (ledger == nullptr)
return {rpcLGR_NOT_FOUND, "ledgerNotFound"};
if (ledger->info().seq > context.ledgerMaster.getValidLedgerIndex() &&
isValidatedOld(context.ledgerMaster, context.app.config().standalone()))
{
ledger.reset();
if (context.apiVersion == 1)
return {rpcNO_NETWORK, "InsufficientNetworkMode"};
return {rpcNOT_SYNCED, "notSynced"};
}
return Status::OK;
}
template <class T>
Status
getLedger(T& ledger, LedgerShortcut shortcut, Context& context)
{
if (isValidatedOld(context.ledgerMaster, context.app.config().standalone()))
{
if (context.apiVersion == 1)
return {rpcNO_NETWORK, "InsufficientNetworkMode"};
return {rpcNOT_SYNCED, "notSynced"};
}
if (shortcut == LedgerShortcut::VALIDATED)
{
ledger = context.ledgerMaster.getValidatedLedger();
if (ledger == nullptr)
{
if (context.apiVersion == 1)
return {rpcNO_NETWORK, "InsufficientNetworkMode"};
return {rpcNOT_SYNCED, "notSynced"};
}
XRPL_ASSERT(
!ledger->open(), "ripple::RPC::getLedger : validated is not open");
}
else
{
if (shortcut == LedgerShortcut::CURRENT)
{
ledger = context.ledgerMaster.getCurrentLedger();
XRPL_ASSERT(
ledger->open(), "ripple::RPC::getLedger : current is open");
}
else if (shortcut == LedgerShortcut::CLOSED)
{
ledger = context.ledgerMaster.getClosedLedger();
XRPL_ASSERT(
!ledger->open(), "ripple::RPC::getLedger : closed is not open");
}
else
{
return {rpcINVALID_PARAMS, "ledgerIndexMalformed"};
}
if (ledger == nullptr)
{
if (context.apiVersion == 1)
return {rpcNO_NETWORK, "InsufficientNetworkMode"};
return {rpcNOT_SYNCED, "notSynced"};
}
static auto const minSequenceGap = 10;
if (ledger->info().seq + minSequenceGap <
context.ledgerMaster.getValidLedgerIndex())
{
ledger.reset();
if (context.apiVersion == 1)
return {rpcNO_NETWORK, "InsufficientNetworkMode"};
return {rpcNOT_SYNCED, "notSynced"};
}
}
return Status::OK;
}
// Explicit instantiation of above three functions
template Status
getLedger<>(std::shared_ptr<ReadView const>&, uint32_t, Context&);
template Status
getLedger<>(
std::shared_ptr<ReadView const>&,
LedgerShortcut shortcut,
Context&);
template Status
getLedger<>(std::shared_ptr<ReadView const>&, uint256 const&, Context&);
// The previous version of the lookupLedger command would accept the
// "ledger_index" argument as a string and silently treat it as a request to
// return the current ledger which, while not strictly wrong, could cause a lot
// of confusion.
//
// The code now robustly validates the input and ensures that the only possible
// values for the "ledger_index" parameter are the index of a ledger passed as
// an integer or one of the strings "current", "closed" or "validated".
// Additionally, the code ensures that the value passed in "ledger_hash" is a
// string and a valid hash. Invalid values will return an appropriate error
// code.
//
// In the absence of the "ledger_hash" or "ledger_index" parameters, the code
// assumes that "ledger_index" has the value "current".
//
// Returns a Json::objectValue. If there was an error, it will be in that
// return value. Otherwise, the object contains the field "validated" and
// optionally the fields "ledger_hash", "ledger_index" and
// "ledger_current_index", if they are defined.
Status
lookupLedger(
std::shared_ptr<ReadView const>& ledger,
JsonContext& context,
Json::Value& result)
{
if (auto status = ledgerFromRequest(ledger, context))
return status;
auto& info = ledger->info();
if (!ledger->open())
{
result[jss::ledger_hash] = to_string(info.hash);
result[jss::ledger_index] = info.seq;
}
else
{
result[jss::ledger_current_index] = info.seq;
}
result[jss::validated] = context.ledgerMaster.isValidated(*ledger);
return Status::OK;
}
Json::Value
lookupLedger(std::shared_ptr<ReadView const>& ledger, JsonContext& context)
{
Json::Value result;
if (auto status = lookupLedger(ledger, context, result))
status.inject(result);
return result;
}
hash_set<AccountID>
parseAccountIds(Json::Value const& jvArray)
{
@@ -988,123 +481,5 @@ isAccountObjectsValidType(LedgerEntryType const& type)
}
}
std::variant<std::shared_ptr<Ledger const>, Json::Value>
getLedgerByContext(RPC::JsonContext& context)
{
auto const hasHash = context.params.isMember(jss::ledger_hash);
auto const hasIndex = context.params.isMember(jss::ledger_index);
std::uint32_t ledgerIndex = 0;
auto& ledgerMaster = context.app.getLedgerMaster();
LedgerHash ledgerHash;
if ((hasHash && hasIndex) || !(hasHash || hasIndex))
{
return RPC::make_param_error(
"Exactly one of ledger_hash and ledger_index can be set.");
}
context.loadType = Resource::feeHeavyBurdenRPC;
if (hasHash)
{
auto const& jsonHash = context.params[jss::ledger_hash];
if (!jsonHash.isString() || !ledgerHash.parseHex(jsonHash.asString()))
return RPC::invalid_field_error(jss::ledger_hash);
}
else
{
auto const& jsonIndex = context.params[jss::ledger_index];
if (!jsonIndex.isInt())
return RPC::invalid_field_error(jss::ledger_index);
// We need a validated ledger to get the hash from the sequence
if (ledgerMaster.getValidatedLedgerAge() >
RPC::Tuning::maxValidatedLedgerAge)
{
if (context.apiVersion == 1)
return rpcError(rpcNO_CURRENT);
return rpcError(rpcNOT_SYNCED);
}
ledgerIndex = jsonIndex.asInt();
auto ledger = ledgerMaster.getValidatedLedger();
if (ledgerIndex >= ledger->info().seq)
return RPC::make_param_error("Ledger index too large");
if (ledgerIndex <= 0)
return RPC::make_param_error("Ledger index too small");
auto const j = context.app.journal("RPCHandler");
// Try to get the hash of the desired ledger from the validated
// ledger
auto neededHash = hashOfSeq(*ledger, ledgerIndex, j);
if (!neededHash)
{
// Find a ledger more likely to have the hash of the desired
// ledger
auto const refIndex = getCandidateLedger(ledgerIndex);
auto refHash = hashOfSeq(*ledger, refIndex, j);
XRPL_ASSERT(
refHash,
"ripple::RPC::getLedgerByContext : nonzero ledger hash");
ledger = ledgerMaster.getLedgerByHash(*refHash);
if (!ledger)
{
// We don't have the ledger we need to figure out which
// ledger they want. Try to get it.
if (auto il = context.app.getInboundLedgers().acquire(
*refHash, refIndex, InboundLedger::Reason::GENERIC))
{
Json::Value jvResult = RPC::make_error(
rpcLGR_NOT_FOUND,
"acquiring ledger containing requested index");
jvResult[jss::acquiring] =
getJson(LedgerFill(*il, &context));
return jvResult;
}
if (auto il = context.app.getInboundLedgers().find(*refHash))
{
Json::Value jvResult = RPC::make_error(
rpcLGR_NOT_FOUND,
"acquiring ledger containing requested index");
jvResult[jss::acquiring] = il->getJson(0);
return jvResult;
}
// Likely the app is shutting down
return Json::Value();
}
neededHash = hashOfSeq(*ledger, ledgerIndex, j);
}
XRPL_ASSERT(
neededHash,
"ripple::RPC::getLedgerByContext : nonzero needed hash");
ledgerHash = neededHash ? *neededHash : beast::zero; // kludge
}
// Try to get the desired ledger
// Verify all nodes even if we think we have it
auto ledger = context.app.getInboundLedgers().acquire(
ledgerHash, ledgerIndex, InboundLedger::Reason::GENERIC);
// In standalone mode, accept the ledger from the ledger cache
if (!ledger && context.app.config().standalone())
ledger = ledgerMaster.getLedgerByHash(ledgerHash);
if (ledger)
return ledger;
if (auto il = context.app.getInboundLedgers().find(ledgerHash))
return il->getJson(0);
return RPC::make_error(
rpcNOT_READY, "findCreate failed to return an inbound ledger");
}
} // namespace RPC
} // namespace ripple

View File

@@ -73,81 +73,6 @@ isRelatedToAccount(
std::shared_ptr<SLE const> const& sle,
AccountID const& accountID);
/** Gathers all objects for an account in a ledger.
@param ledger Ledger to search account objects.
@param account AccountID to find objects for.
@param typeFilter Gathers objects of these types. empty gathers all types.
@param dirIndex Begin gathering account objects from this directory.
@param entryIndex Begin gathering objects from this directory node.
@param limit Maximum number of objects to find.
@param jvResult A JSON result that holds the request objects.
*/
bool
getAccountObjects(
ReadView const& ledger,
AccountID const& account,
std::optional<std::vector<LedgerEntryType>> const& typeFilter,
uint256 dirIndex,
uint256 entryIndex,
std::uint32_t const limit,
Json::Value& jvResult);
/** Get ledger by hash
If there is no error in the return value, the ledger pointer will have
been filled
*/
template <class T>
Status
getLedger(T& ledger, uint256 const& ledgerHash, Context& context);
/** Get ledger by sequence
If there is no error in the return value, the ledger pointer will have
been filled
*/
template <class T>
Status
getLedger(T& ledger, uint32_t ledgerIndex, Context& context);
enum LedgerShortcut { CURRENT, CLOSED, VALIDATED };
/** Get ledger specified in shortcut.
If there is no error in the return value, the ledger pointer will have
been filled
*/
template <class T>
Status
getLedger(T& ledger, LedgerShortcut shortcut, Context& context);
/** Look up a ledger from a request and fill a Json::Result with either
an error, or data representing a ledger.
If there is no error in the return value, then the ledger pointer will have
been filled.
*/
Json::Value
lookupLedger(std::shared_ptr<ReadView const>&, JsonContext&);
/** Look up a ledger from a request and fill a Json::Result with the data
representing a ledger.
If the returned Status is OK, the ledger pointer will have been filled.
*/
Status
lookupLedger(
std::shared_ptr<ReadView const>&,
JsonContext&,
Json::Value& result);
template <class T, class R>
Status
ledgerFromRequest(T& ledger, GRPCContext<R>& context);
template <class T>
Status
ledgerFromSpecifier(
T& ledger,
org::xrpl::rpc::v1::LedgerSpecifier const& specifier,
Context& context);
hash_set<AccountID>
parseAccountIds(Json::Value const& jvArray);
@@ -194,11 +119,6 @@ chooseLedgerEntryType(Json::Value const& params);
bool
isAccountObjectsValidType(LedgerEntryType const& type);
/** Return a ledger based on ledger_hash or ledger_index,
or an RPC error */
std::variant<std::shared_ptr<Ledger const>, Json::Value>
getLedgerByContext(RPC::JsonContext& context);
std::optional<std::pair<PublicKey, SecretKey>>
keypairForSignature(
Json::Value const& params,

View File

@@ -0,0 +1,458 @@
#include <xrpld/app/ledger/LedgerMaster.h>
#include <xrpld/app/ledger/LedgerToJson.h>
#include <xrpld/app/ledger/OpenLedger.h>
#include <xrpld/app/misc/Transaction.h>
#include <xrpld/app/paths/TrustLine.h>
#include <xrpld/app/rdb/RelationalDatabase.h>
#include <xrpld/app/tx/detail/NFTokenUtils.h>
#include <xrpld/rpc/Context.h>
#include <xrpld/rpc/DeliveredAmount.h>
#include <xrpld/rpc/detail/RPCHelpers.h>
#include <xrpl/ledger/View.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/RPCErr.h>
#include <xrpl/protocol/nftPageMask.h>
#include <xrpl/resource/Fees.h>
#include <boost/algorithm/string/case_conv.hpp>
#include <boost/algorithm/string/predicate.hpp>
namespace ripple {
namespace RPC {
namespace {
bool
isValidatedOld(LedgerMaster& ledgerMaster, bool standalone)
{
if (standalone)
return false;
return ledgerMaster.getValidatedLedgerAge() > Tuning::maxValidatedLedgerAge;
}
template <class T>
Status
ledgerFromRequest(T& ledger, JsonContext& context)
{
ledger.reset();
auto& params = context.params;
auto indexValue = params[jss::ledger_index];
auto hashValue = params[jss::ledger_hash];
// We need to support the legacy "ledger" field.
auto& legacyLedger = params[jss::ledger];
if (legacyLedger)
{
if (legacyLedger.asString().size() > 12)
hashValue = legacyLedger;
else
indexValue = legacyLedger;
}
if (!hashValue.isNull())
{
if (!hashValue.isString())
return {rpcINVALID_PARAMS, "ledgerHashNotString"};
uint256 ledgerHash;
if (!ledgerHash.parseHex(hashValue.asString()))
return {rpcINVALID_PARAMS, "ledgerHashMalformed"};
return getLedger(ledger, ledgerHash, context);
}
if (!indexValue.isConvertibleTo(Json::stringValue))
return {rpcINVALID_PARAMS, "ledgerIndexMalformed"};
auto const index = indexValue.asString();
if (index == "current" || index.empty())
return getLedger(ledger, LedgerShortcut::CURRENT, context);
if (index == "validated")
return getLedger(ledger, LedgerShortcut::VALIDATED, context);
if (index == "closed")
return getLedger(ledger, LedgerShortcut::CLOSED, context);
std::uint32_t val;
if (!beast::lexicalCastChecked(val, index))
return {rpcINVALID_PARAMS, "ledgerIndexMalformed"};
return getLedger(ledger, val, context);
}
} // namespace
template <class T, class R>
Status
ledgerFromRequest(T& ledger, GRPCContext<R>& context)
{
R& request = context.params;
return ledgerFromSpecifier(ledger, request.ledger(), context);
}
// explicit instantiation of above function
template Status
ledgerFromRequest<>(
std::shared_ptr<ReadView const>&,
GRPCContext<org::xrpl::rpc::v1::GetLedgerEntryRequest>&);
// explicit instantiation of above function
template Status
ledgerFromRequest<>(
std::shared_ptr<ReadView const>&,
GRPCContext<org::xrpl::rpc::v1::GetLedgerDataRequest>&);
// explicit instantiation of above function
template Status
ledgerFromRequest<>(
std::shared_ptr<ReadView const>&,
GRPCContext<org::xrpl::rpc::v1::GetLedgerRequest>&);
template <class T>
Status
ledgerFromSpecifier(
T& ledger,
org::xrpl::rpc::v1::LedgerSpecifier const& specifier,
Context& context)
{
ledger.reset();
using LedgerCase = org::xrpl::rpc::v1::LedgerSpecifier::LedgerCase;
LedgerCase ledgerCase = specifier.ledger_case();
switch (ledgerCase)
{
case LedgerCase::kHash: {
if (auto hash = uint256::fromVoidChecked(specifier.hash()))
{
return getLedger(ledger, *hash, context);
}
return {rpcINVALID_PARAMS, "ledgerHashMalformed"};
}
case LedgerCase::kSequence:
return getLedger(ledger, specifier.sequence(), context);
case LedgerCase::kShortcut:
[[fallthrough]];
case LedgerCase::LEDGER_NOT_SET: {
auto const shortcut = specifier.shortcut();
if (shortcut ==
org::xrpl::rpc::v1::LedgerSpecifier::SHORTCUT_VALIDATED)
{
return getLedger(ledger, LedgerShortcut::VALIDATED, context);
}
else
{
if (shortcut ==
org::xrpl::rpc::v1::LedgerSpecifier::SHORTCUT_CURRENT ||
shortcut ==
org::xrpl::rpc::v1::LedgerSpecifier::
SHORTCUT_UNSPECIFIED)
{
return getLedger(ledger, LedgerShortcut::CURRENT, context);
}
else if (
shortcut ==
org::xrpl::rpc::v1::LedgerSpecifier::SHORTCUT_CLOSED)
{
return getLedger(ledger, LedgerShortcut::CLOSED, context);
}
}
}
}
return Status::OK;
}
template <class T>
Status
getLedger(T& ledger, uint256 const& ledgerHash, Context& context)
{
ledger = context.ledgerMaster.getLedgerByHash(ledgerHash);
if (ledger == nullptr)
return {rpcLGR_NOT_FOUND, "ledgerNotFound"};
return Status::OK;
}
template <class T>
Status
getLedger(T& ledger, uint32_t ledgerIndex, Context& context)
{
ledger = context.ledgerMaster.getLedgerBySeq(ledgerIndex);
if (ledger == nullptr)
{
auto cur = context.ledgerMaster.getCurrentLedger();
if (cur->info().seq == ledgerIndex)
{
ledger = cur;
}
}
if (ledger == nullptr)
return {rpcLGR_NOT_FOUND, "ledgerNotFound"};
if (ledger->info().seq > context.ledgerMaster.getValidLedgerIndex() &&
isValidatedOld(context.ledgerMaster, context.app.config().standalone()))
{
ledger.reset();
if (context.apiVersion == 1)
return {rpcNO_NETWORK, "InsufficientNetworkMode"};
return {rpcNOT_SYNCED, "notSynced"};
}
return Status::OK;
}
template <class T>
Status
getLedger(T& ledger, LedgerShortcut shortcut, Context& context)
{
if (isValidatedOld(context.ledgerMaster, context.app.config().standalone()))
{
if (context.apiVersion == 1)
return {rpcNO_NETWORK, "InsufficientNetworkMode"};
return {rpcNOT_SYNCED, "notSynced"};
}
if (shortcut == LedgerShortcut::VALIDATED)
{
ledger = context.ledgerMaster.getValidatedLedger();
if (ledger == nullptr)
{
if (context.apiVersion == 1)
return {rpcNO_NETWORK, "InsufficientNetworkMode"};
return {rpcNOT_SYNCED, "notSynced"};
}
XRPL_ASSERT(
!ledger->open(), "ripple::RPC::getLedger : validated is not open");
}
else
{
if (shortcut == LedgerShortcut::CURRENT)
{
ledger = context.ledgerMaster.getCurrentLedger();
XRPL_ASSERT(
ledger->open(), "ripple::RPC::getLedger : current is open");
}
else if (shortcut == LedgerShortcut::CLOSED)
{
ledger = context.ledgerMaster.getClosedLedger();
XRPL_ASSERT(
!ledger->open(), "ripple::RPC::getLedger : closed is not open");
}
else
{
return {rpcINVALID_PARAMS, "ledgerIndexMalformed"};
}
if (ledger == nullptr)
{
if (context.apiVersion == 1)
return {rpcNO_NETWORK, "InsufficientNetworkMode"};
return {rpcNOT_SYNCED, "notSynced"};
}
static auto const minSequenceGap = 10;
if (ledger->info().seq + minSequenceGap <
context.ledgerMaster.getValidLedgerIndex())
{
ledger.reset();
if (context.apiVersion == 1)
return {rpcNO_NETWORK, "InsufficientNetworkMode"};
return {rpcNOT_SYNCED, "notSynced"};
}
}
return Status::OK;
}
// Explicit instantiation of above three functions
template Status
getLedger<>(std::shared_ptr<ReadView const>&, uint32_t, Context&);
template Status
getLedger<>(
std::shared_ptr<ReadView const>&,
LedgerShortcut shortcut,
Context&);
template Status
getLedger<>(std::shared_ptr<ReadView const>&, uint256 const&, Context&);
// The previous version of the lookupLedger command would accept the
// "ledger_index" argument as a string and silently treat it as a request to
// return the current ledger which, while not strictly wrong, could cause a lot
// of confusion.
//
// The code now robustly validates the input and ensures that the only possible
// values for the "ledger_index" parameter are the index of a ledger passed as
// an integer or one of the strings "current", "closed" or "validated".
// Additionally, the code ensures that the value passed in "ledger_hash" is a
// string and a valid hash. Invalid values will return an appropriate error
// code.
//
// In the absence of the "ledger_hash" or "ledger_index" parameters, the code
// assumes that "ledger_index" has the value "current".
//
// Returns a Json::objectValue. If there was an error, it will be in that
// return value. Otherwise, the object contains the field "validated" and
// optionally the fields "ledger_hash", "ledger_index" and
// "ledger_current_index", if they are defined.
Status
lookupLedger(
std::shared_ptr<ReadView const>& ledger,
JsonContext& context,
Json::Value& result)
{
if (auto status = ledgerFromRequest(ledger, context))
return status;
auto& info = ledger->info();
if (!ledger->open())
{
result[jss::ledger_hash] = to_string(info.hash);
result[jss::ledger_index] = info.seq;
}
else
{
result[jss::ledger_current_index] = info.seq;
}
result[jss::validated] = context.ledgerMaster.isValidated(*ledger);
return Status::OK;
}
Json::Value
lookupLedger(std::shared_ptr<ReadView const>& ledger, JsonContext& context)
{
Json::Value result;
if (auto status = lookupLedger(ledger, context, result))
status.inject(result);
return result;
}
std::variant<std::shared_ptr<Ledger const>, Json::Value>
getLedgerByContext(RPC::JsonContext& context)
{
auto const hasHash = context.params.isMember(jss::ledger_hash);
auto const hasIndex = context.params.isMember(jss::ledger_index);
std::uint32_t ledgerIndex = 0;
auto& ledgerMaster = context.app.getLedgerMaster();
LedgerHash ledgerHash;
if ((hasHash && hasIndex) || !(hasHash || hasIndex))
{
return RPC::make_param_error(
"Exactly one of ledger_hash and ledger_index can be set.");
}
context.loadType = Resource::feeHeavyBurdenRPC;
if (hasHash)
{
auto const& jsonHash = context.params[jss::ledger_hash];
if (!jsonHash.isString() || !ledgerHash.parseHex(jsonHash.asString()))
return RPC::invalid_field_error(jss::ledger_hash);
}
else
{
auto const& jsonIndex = context.params[jss::ledger_index];
if (!jsonIndex.isInt())
return RPC::invalid_field_error(jss::ledger_index);
// We need a validated ledger to get the hash from the sequence
if (ledgerMaster.getValidatedLedgerAge() >
RPC::Tuning::maxValidatedLedgerAge)
{
if (context.apiVersion == 1)
return rpcError(rpcNO_CURRENT);
return rpcError(rpcNOT_SYNCED);
}
ledgerIndex = jsonIndex.asInt();
auto ledger = ledgerMaster.getValidatedLedger();
if (ledgerIndex >= ledger->info().seq)
return RPC::make_param_error("Ledger index too large");
if (ledgerIndex <= 0)
return RPC::make_param_error("Ledger index too small");
auto const j = context.app.journal("RPCHandler");
// Try to get the hash of the desired ledger from the validated
// ledger
auto neededHash = hashOfSeq(*ledger, ledgerIndex, j);
if (!neededHash)
{
// Find a ledger more likely to have the hash of the desired
// ledger
auto const refIndex = getCandidateLedger(ledgerIndex);
auto refHash = hashOfSeq(*ledger, refIndex, j);
XRPL_ASSERT(
refHash,
"ripple::RPC::getLedgerByContext : nonzero ledger hash");
ledger = ledgerMaster.getLedgerByHash(*refHash);
if (!ledger)
{
// We don't have the ledger we need to figure out which
// ledger they want. Try to get it.
if (auto il = context.app.getInboundLedgers().acquire(
*refHash, refIndex, InboundLedger::Reason::GENERIC))
{
Json::Value jvResult = RPC::make_error(
rpcLGR_NOT_FOUND,
"acquiring ledger containing requested index");
jvResult[jss::acquiring] =
getJson(LedgerFill(*il, &context));
return jvResult;
}
if (auto il = context.app.getInboundLedgers().find(*refHash))
{
Json::Value jvResult = RPC::make_error(
rpcLGR_NOT_FOUND,
"acquiring ledger containing requested index");
jvResult[jss::acquiring] = il->getJson(0);
return jvResult;
}
// Likely the app is shutting down
return Json::Value();
}
neededHash = hashOfSeq(*ledger, ledgerIndex, j);
}
XRPL_ASSERT(
neededHash,
"ripple::RPC::getLedgerByContext : nonzero needed hash");
ledgerHash = neededHash ? *neededHash : beast::zero; // kludge
}
// Try to get the desired ledger
// Verify all nodes even if we think we have it
auto ledger = context.app.getInboundLedgers().acquire(
ledgerHash, ledgerIndex, InboundLedger::Reason::GENERIC);
// In standalone mode, accept the ledger from the ledger cache
if (!ledger && context.app.config().standalone())
ledger = ledgerMaster.getLedgerByHash(ledgerHash);
if (ledger)
return ledger;
if (auto il = context.app.getInboundLedgers().find(ledgerHash))
return il->getJson(0);
return RPC::make_error(
rpcNOT_READY, "findCreate failed to return an inbound ledger");
}
} // namespace RPC
} // namespace ripple

View File

@@ -0,0 +1,96 @@
#ifndef XRPL_RPC_RPCLEDGERHELPERS_H_INCLUDED
#define XRPL_RPC_RPCLEDGERHELPERS_H_INCLUDED
#include <xrpld/app/misc/NetworkOPs.h>
#include <xrpld/app/misc/TxQ.h>
#include <xrpld/rpc/Context.h>
#include <xrpld/rpc/Status.h>
#include <xrpld/rpc/detail/Tuning.h>
#include <xrpl/beast/core/SemanticVersion.h>
#include <xrpl/proto/org/xrpl/rpc/v1/xrp_ledger.pb.h>
#include <xrpl/protocol/ApiVersion.h>
#include <xrpl/protocol/SecretKey.h>
#include <optional>
#include <variant>
namespace Json {
class Value;
}
namespace ripple {
class ReadView;
class Transaction;
namespace RPC {
struct JsonContext;
/** Get ledger by hash
If there is no error in the return value, the ledger pointer will have
been filled
*/
template <class T>
Status
getLedger(T& ledger, uint256 const& ledgerHash, Context& context);
/** Get ledger by sequence
If there is no error in the return value, the ledger pointer will have
been filled
*/
template <class T>
Status
getLedger(T& ledger, uint32_t ledgerIndex, Context& context);
enum LedgerShortcut { CURRENT, CLOSED, VALIDATED };
/** Get ledger specified in shortcut.
If there is no error in the return value, the ledger pointer will have
been filled
*/
template <class T>
Status
getLedger(T& ledger, LedgerShortcut shortcut, Context& context);
/** Look up a ledger from a request and fill a Json::Result with either
an error, or data representing a ledger.
If there is no error in the return value, then the ledger pointer will have
been filled.
*/
Json::Value
lookupLedger(std::shared_ptr<ReadView const>&, JsonContext&);
/** Look up a ledger from a request and fill a Json::Result with the data
representing a ledger.
If the returned Status is OK, the ledger pointer will have been filled.
*/
Status
lookupLedger(
std::shared_ptr<ReadView const>&,
JsonContext&,
Json::Value& result);
template <class T, class R>
Status
ledgerFromRequest(T& ledger, GRPCContext<R>& context);
template <class T>
Status
ledgerFromSpecifier(
T& ledger,
org::xrpl::rpc::v1::LedgerSpecifier const& specifier,
Context& context);
/** Return a ledger based on ledger_hash or ledger_index,
or an RPC error */
std::variant<std::shared_ptr<Ledger const>, Json::Value>
getLedgerByContext(RPC::JsonContext& context);
} // namespace RPC
} // namespace ripple
#endif

View File

@@ -2,6 +2,7 @@
#include <xrpld/app/misc/AMMUtils.h>
#include <xrpld/rpc/Context.h>
#include <xrpld/rpc/detail/RPCHelpers.h>
#include <xrpld/rpc/detail/RPCLedgerHelpers.h>
#include <xrpl/json/json_value.h>
#include <xrpl/ledger/ReadView.h>

View File

@@ -1,5 +1,6 @@
#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>

View File

@@ -1,6 +1,6 @@
#include <xrpld/app/paths/TrustLine.h>
#include <xrpld/rpc/Context.h>
#include <xrpld/rpc/detail/RPCHelpers.h>
#include <xrpld/rpc/detail/RPCLedgerHelpers.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/protocol/ErrorCodes.h>

View File

@@ -3,6 +3,7 @@
#include <xrpld/rpc/Context.h>
#include <xrpld/rpc/GRPCHandlers.h>
#include <xrpld/rpc/detail/RPCHelpers.h>
#include <xrpld/rpc/detail/RPCLedgerHelpers.h>
#include <xrpl/json/json_value.h>
#include <xrpl/ledger/ReadView.h>

View File

@@ -1,6 +1,7 @@
#include <xrpld/app/paths/TrustLine.h>
#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>

View File

@@ -1,6 +1,7 @@
#include <xrpld/app/tx/detail/NFTokenUtils.h>
#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>
@@ -158,6 +159,198 @@ doAccountNFTs(RPC::JsonContext& context)
return result;
}
bool
getAccountObjects(
ReadView const& ledger,
AccountID const& account,
std::optional<std::vector<LedgerEntryType>> const& typeFilter,
uint256 dirIndex,
uint256 entryIndex,
std::uint32_t const limit,
Json::Value& jvResult)
{
// check if dirIndex is valid
if (!dirIndex.isZero() && !ledger.read({ltDIR_NODE, dirIndex}))
return false;
auto typeMatchesFilter = [](std::vector<LedgerEntryType> const& typeFilter,
LedgerEntryType ledgerType) {
auto it = std::find(typeFilter.begin(), typeFilter.end(), ledgerType);
return it != typeFilter.end();
};
// if dirIndex != 0, then all NFTs have already been returned. only
// iterate NFT pages if the filter says so AND dirIndex == 0
bool iterateNFTPages =
(!typeFilter.has_value() ||
typeMatchesFilter(typeFilter.value(), ltNFTOKEN_PAGE)) &&
dirIndex == beast::zero;
Keylet const firstNFTPage = keylet::nftpage_min(account);
// we need to check the marker to see if it is an NFTTokenPage index.
if (iterateNFTPages && entryIndex != beast::zero)
{
// if it is we will try to iterate the pages up to the limit
// and then change over to the owner directory
if (firstNFTPage.key != (entryIndex & ~nft::pageMask))
iterateNFTPages = false;
}
auto& jvObjects = (jvResult[jss::account_objects] = Json::arrayValue);
// this is a mutable version of limit, used to seamlessly switch
// to iterating directory entries when nftokenpages are exhausted
uint32_t mlimit = limit;
// iterate NFTokenPages preferentially
if (iterateNFTPages)
{
Keylet const first = entryIndex == beast::zero
? firstNFTPage
: Keylet{ltNFTOKEN_PAGE, entryIndex};
Keylet const last = keylet::nftpage_max(account);
// current key
uint256 ck = ledger.succ(first.key, last.key.next()).value_or(last.key);
// current page
auto cp = ledger.read(Keylet{ltNFTOKEN_PAGE, ck});
while (cp)
{
jvObjects.append(cp->getJson(JsonOptions::none));
auto const npm = (*cp)[~sfNextPageMin];
if (npm)
cp = ledger.read(Keylet(ltNFTOKEN_PAGE, *npm));
else
cp = nullptr;
if (--mlimit == 0)
{
if (cp)
{
jvResult[jss::limit] = limit;
jvResult[jss::marker] = std::string("0,") + to_string(ck);
return true;
}
}
if (!npm)
break;
ck = *npm;
}
// if execution reaches here then we're about to transition
// to iterating the root directory (and the conventional
// behaviour of this RPC function.) Therefore we should
// zero entryIndex so as not to terribly confuse things.
entryIndex = beast::zero;
}
auto const root = keylet::ownerDir(account);
auto found = false;
if (dirIndex.isZero())
{
dirIndex = root.key;
found = true;
}
auto dir = ledger.read({ltDIR_NODE, dirIndex});
if (!dir)
{
// it's possible the user had nftoken pages but no
// directory entries. If there's no nftoken page, we will
// give empty array for account_objects.
if (mlimit >= limit)
jvResult[jss::account_objects] = Json::arrayValue;
// non-zero dirIndex validity was checked in the beginning of this
// function; by this point, it should be zero. This function returns
// true regardless of nftoken page presence; if absent, account_objects
// is already set as an empty array. Notice we will only return false in
// this function when entryIndex can not be found, indicating an invalid
// marker error.
return true;
}
std::uint32_t i = 0;
for (;;)
{
auto const& entries = dir->getFieldV256(sfIndexes);
auto iter = entries.begin();
if (!found)
{
iter = std::find(iter, entries.end(), entryIndex);
if (iter == entries.end())
return false;
found = true;
}
// it's possible that the returned NFTPages exactly filled the
// response. Check for that condition.
if (i == mlimit && mlimit < limit)
{
jvResult[jss::limit] = limit;
jvResult[jss::marker] =
to_string(dirIndex) + ',' + to_string(*iter);
return true;
}
for (; iter != entries.end(); ++iter)
{
auto const sleNode = ledger.read(keylet::child(*iter));
if (!typeFilter.has_value() ||
typeMatchesFilter(typeFilter.value(), sleNode->getType()))
{
jvObjects.append(sleNode->getJson(JsonOptions::none));
}
if (++i == mlimit)
{
if (++iter != entries.end())
{
jvResult[jss::limit] = limit;
jvResult[jss::marker] =
to_string(dirIndex) + ',' + to_string(*iter);
return true;
}
break;
}
}
auto const nodeIndex = dir->getFieldU64(sfIndexNext);
if (nodeIndex == 0)
return true;
dirIndex = keylet::page(root, nodeIndex).key;
dir = ledger.read({ltDIR_NODE, dirIndex});
if (!dir)
return true;
if (i == mlimit)
{
auto const& e = dir->getFieldV256(sfIndexes);
if (!e.empty())
{
jvResult[jss::limit] = limit;
jvResult[jss::marker] =
to_string(dirIndex) + ',' + to_string(*e.begin());
}
return true;
}
}
}
Json::Value
doAccountObjects(RPC::JsonContext& context)
{
@@ -265,7 +458,7 @@ doAccountObjects(RPC::JsonContext& context)
return RPC::invalid_field_error(jss::marker);
}
if (!RPC::getAccountObjects(
if (!getAccountObjects(
*ledger,
accountID,
typeFilter,

View File

@@ -1,5 +1,6 @@
#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/json/json_value.h>

View File

@@ -3,6 +3,7 @@
#include <xrpld/rpc/BookChanges.h>
#include <xrpld/rpc/Context.h>
#include <xrpld/rpc/detail/RPCHelpers.h>
#include <xrpld/rpc/detail/RPCLedgerHelpers.h>
#include <xrpl/basics/Log.h>
#include <xrpl/ledger/ReadView.h>

View File

@@ -1,5 +1,5 @@
#include <xrpld/rpc/Context.h>
#include <xrpld/rpc/detail/RPCHelpers.h>
#include <xrpld/rpc/detail/RPCLedgerHelpers.h>
#include <xrpl/ledger/CredentialHelpers.h>
#include <xrpl/ledger/ReadView.h>

View File

@@ -1,7 +1,7 @@
#include <xrpld/app/main/Application.h>
#include <xrpld/app/paths/TrustLine.h>
#include <xrpld/rpc/Context.h>
#include <xrpld/rpc/detail/RPCHelpers.h>
#include <xrpld/rpc/detail/RPCLedgerHelpers.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/protocol/AccountID.h>

View File

@@ -1,7 +1,7 @@
#include <xrpld/app/ledger/LedgerMaster.h>
#include <xrpld/app/main/Application.h>
#include <xrpld/rpc/Context.h>
#include <xrpld/rpc/detail/RPCHelpers.h>
#include <xrpld/rpc/detail/RPCLedgerHelpers.h>
#include <xrpl/json/json_value.h>
#include <xrpl/ledger/ReadView.h>

View File

@@ -3,6 +3,7 @@
#include <xrpld/rpc/GRPCHandlers.h>
#include <xrpld/rpc/Role.h>
#include <xrpld/rpc/detail/RPCHelpers.h>
#include <xrpld/rpc/detail/RPCLedgerHelpers.h>
#include <xrpld/rpc/detail/Tuning.h>
#include <xrpl/ledger/ReadView.h>

View File

@@ -1,5 +1,5 @@
#include <xrpld/rpc/GRPCHandlers.h>
#include <xrpld/rpc/detail/RPCHelpers.h>
#include <xrpld/rpc/detail/RPCLedgerHelpers.h>
namespace ripple {
std::pair<org::xrpl::rpc::v1::GetLedgerDiffResponse, grpc::Status>

View File

@@ -1,6 +1,6 @@
#include <xrpld/rpc/Context.h>
#include <xrpld/rpc/GRPCHandlers.h>
#include <xrpld/rpc/detail/RPCHelpers.h>
#include <xrpld/rpc/detail/RPCLedgerHelpers.h>
#include <xrpld/rpc/handlers/LedgerEntryHelpers.h>
#include <xrpl/basics/StringUtilities.h>

View File

@@ -3,7 +3,7 @@
#include <xrpld/app/misc/LoadFeeTrack.h>
#include <xrpld/rpc/GRPCHandlers.h>
#include <xrpld/rpc/Role.h>
#include <xrpld/rpc/detail/RPCHelpers.h>
#include <xrpld/rpc/detail/RPCLedgerHelpers.h>
#include <xrpld/rpc/handlers/LedgerHandler.h>
#include <xrpl/protocol/ErrorCodes.h>

View File

@@ -1,5 +1,5 @@
#include <xrpld/app/ledger/LedgerToJson.h>
#include <xrpld/rpc/detail/RPCHelpers.h>
#include <xrpld/rpc/detail/RPCLedgerHelpers.h>
#include <xrpl/basics/strHex.h>
#include <xrpl/ledger/ReadView.h>

View File

@@ -1,6 +1,6 @@
#include <xrpld/app/ledger/LedgerToJson.h>
#include <xrpld/rpc/Context.h>
#include <xrpld/rpc/detail/RPCHelpers.h>
#include <xrpld/rpc/detail/RPCLedgerHelpers.h>
#include <xrpl/protocol/ErrorCodes.h>
#include <xrpl/protocol/jss.h>

View File

@@ -1,5 +1,6 @@
#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/json/json_value.h>

View File

@@ -3,6 +3,7 @@
#include <xrpld/app/paths/TrustLine.h>
#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>

View File

@@ -2,7 +2,7 @@
#include <xrpld/app/paths/PathRequests.h>
#include <xrpld/rpc/Context.h>
#include <xrpld/rpc/detail/LegacyPathFind.h>
#include <xrpld/rpc/detail/RPCHelpers.h>
#include <xrpld/rpc/detail/RPCLedgerHelpers.h>
#include <xrpl/protocol/RPCErr.h>
#include <xrpl/resource/Fees.h>

View File

@@ -1,7 +1,7 @@
#include <xrpld/app/ledger/LedgerMaster.h>
#include <xrpld/app/misc/DeliverMax.h>
#include <xrpld/rpc/Context.h>
#include <xrpld/rpc/detail/RPCHelpers.h>
#include <xrpld/rpc/detail/RPCLedgerHelpers.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/protocol/jss.h>

View File

@@ -1,5 +1,5 @@
#include <xrpld/rpc/Context.h>
#include <xrpld/rpc/detail/RPCHelpers.h>
#include <xrpld/rpc/detail/RPCLedgerHelpers.h>
#include <xrpl/beast/utility/Zero.h>
#include <xrpl/json/json_value.h>