mirror of
https://github.com/XRPLF/rippled.git
synced 2025-11-20 11:05:54 +00:00
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:
@@ -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>
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
458
src/xrpld/rpc/detail/RPCLedgerHelpers.cpp
Normal file
458
src/xrpld/rpc/detail/RPCLedgerHelpers.cpp
Normal 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
|
||||
96
src/xrpld/rpc/detail/RPCLedgerHelpers.h
Normal file
96
src/xrpld/rpc/detail/RPCLedgerHelpers.h
Normal 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
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user