Merge branch 'develop' into ximinez/emptydirectoryinvariant

This commit is contained in:
Ed Hennis
2025-11-18 22:51:22 -05:00
committed by GitHub
50 changed files with 834 additions and 806 deletions

View File

@@ -130,8 +130,8 @@ def generate_strategy_matrix(all: bool, config: Config) -> list:
if os['distro_name'] == 'rhel' and architecture['platform'] == 'linux/arm64':
continue
# We skip all clang-20 on arm64 due to boost 1.86 build error
if f'{os['compiler_name']}-{os['compiler_version']}' == 'clang-20' and architecture['platform'] == 'linux/arm64':
# We skip all clang 20+ on arm64 due to Boost build error.
if f'{os['compiler_name']}-{os['compiler_version']}' in ['clang-20', 'clang-21'] and architecture['platform'] == 'linux/arm64':
continue
# Enable code coverage for Debian Bookworm using GCC 15 in Debug and no

View File

@@ -15,63 +15,91 @@
"distro_version": "bookworm",
"compiler_name": "gcc",
"compiler_version": "12",
"image_sha": "e1782cd"
"image_sha": "0525eae"
},
{
"distro_name": "debian",
"distro_version": "bookworm",
"compiler_name": "gcc",
"compiler_version": "13",
"image_sha": "e1782cd"
"image_sha": "0525eae"
},
{
"distro_name": "debian",
"distro_version": "bookworm",
"compiler_name": "gcc",
"compiler_version": "14",
"image_sha": "e1782cd"
"image_sha": "0525eae"
},
{
"distro_name": "debian",
"distro_version": "bookworm",
"compiler_name": "gcc",
"compiler_version": "15",
"image_sha": "e1782cd"
"image_sha": "0525eae"
},
{
"distro_name": "debian",
"distro_version": "bookworm",
"compiler_name": "clang",
"compiler_version": "16",
"image_sha": "e1782cd"
"image_sha": "0525eae"
},
{
"distro_name": "debian",
"distro_version": "bookworm",
"compiler_name": "clang",
"compiler_version": "17",
"image_sha": "e1782cd"
"image_sha": "0525eae"
},
{
"distro_name": "debian",
"distro_version": "bookworm",
"compiler_name": "clang",
"compiler_version": "18",
"image_sha": "e1782cd"
"image_sha": "0525eae"
},
{
"distro_name": "debian",
"distro_version": "bookworm",
"compiler_name": "clang",
"compiler_version": "19",
"image_sha": "e1782cd"
"image_sha": "0525eae"
},
{
"distro_name": "debian",
"distro_version": "bookworm",
"compiler_name": "clang",
"compiler_version": "20",
"image_sha": "e1782cd"
"image_sha": "0525eae"
},
{
"distro_name": "debian",
"distro_version": "trixie",
"compiler_name": "gcc",
"compiler_version": "14",
"image_sha": "0525eae"
},
{
"distro_name": "debian",
"distro_version": "trixie",
"compiler_name": "gcc",
"compiler_version": "15",
"image_sha": "0525eae"
},
{
"distro_name": "debian",
"distro_version": "trixie",
"compiler_name": "clang",
"compiler_version": "20",
"image_sha": "0525eae"
},
{
"distro_name": "debian",
"distro_version": "trixie",
"compiler_name": "clang",
"compiler_version": "21",
"image_sha": "0525eae"
},
{
"distro_name": "rhel",

View File

@@ -541,7 +541,7 @@ SECP256K1_API int secp256k1_ecdsa_signature_serialize_compact(
/** Verify an ECDSA signature.
*
* Returns: 1: correct signature
* 0: incorrect or unparseable signature
* 0: incorrect or unparsable signature
* Args: ctx: pointer to a context object
* In: sig: the signature being verified.
* msghash32: the 32-byte message hash being verified.

View File

@@ -159,7 +159,7 @@ public:
@param count the number of items the slab allocator can allocate; note
that a count of 0 is valid and means that the allocator
is, effectively, disabled. This can be very useful in some
contexts (e.g. when mimimal memory usage is needed) and
contexts (e.g. when minimal memory usage is needed) and
allows for graceful failure.
*/
constexpr explicit SlabAllocator(

View File

@@ -546,7 +546,7 @@ operator<=>(base_uint<Bits, Tag> const& lhs, base_uint<Bits, Tag> const& rhs)
// This comparison might seem wrong on a casual inspection because it
// compares data internally stored as std::uint32_t byte-by-byte. But
// note that the underlying data is stored in big endian, even if the
// plaform is little endian. This makes the comparison correct.
// platform is little endian. This makes the comparison correct.
//
// FIXME: use std::lexicographical_compare_three_way once support is
// added to MacOS.

View File

@@ -9,7 +9,7 @@ namespace ripple {
/*
* MSVC 2019 version 16.9.0 added [[nodiscard]] to the std comparison
* operator() functions. boost::bimap checks that the comparitor is a
* operator() functions. boost::bimap checks that the comparator is a
* BinaryFunction, in part by calling the function and ignoring the value.
* These two things don't play well together. These wrapper classes simply
* strip [[nodiscard]] from operator() for use in boost::bimap.

View File

@@ -39,7 +39,7 @@ public:
The argument string is available to suites and
allows for customization of the test. Each suite
defines its own syntax for the argumnet string.
defines its own syntax for the argument string.
The same argument is passed to all suites.
*/
void

View File

@@ -66,7 +66,6 @@ XRPL_FEATURE(XRPFees, Supported::yes, VoteBehavior::DefaultNo
XRPL_FEATURE(DisallowIncoming, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FIX (RemoveNFTokenAutoTrustLine, Supported::yes, VoteBehavior::DefaultYes)
XRPL_FEATURE(FlowSortStrands, Supported::yes, VoteBehavior::DefaultYes)
XRPL_FEATURE(NegativeUNL, Supported::yes, VoteBehavior::DefaultYes)
XRPL_FEATURE(RequireFullyCanonicalSig, Supported::yes, VoteBehavior::DefaultYes)
XRPL_FEATURE(DeletableAccounts, Supported::yes, VoteBehavior::DefaultYes)
XRPL_FEATURE(Checks, Supported::yes, VoteBehavior::DefaultYes)
@@ -130,6 +129,7 @@ XRPL_RETIRE_FEATURE(HardenedValidations)
XRPL_RETIRE_FEATURE(ImmediateOfferKilled)
XRPL_RETIRE_FEATURE(MultiSign)
XRPL_RETIRE_FEATURE(MultiSignReserve)
XRPL_RETIRE_FEATURE(NegativeUNL)
XRPL_RETIRE_FEATURE(NonFungibleTokensV1_1)
XRPL_RETIRE_FEATURE(PayChan)
XRPL_RETIRE_FEATURE(SortedDirectories)

View File

@@ -966,8 +966,8 @@ class PermissionedDEX_test : public beast::unit_test::suite
{
testcase("Remove unfunded offer");
// checking that an unfunded offer will be implictly removed by a
// successfuly payment tx
// checking that an unfunded offer will be implicitly removed by a
// successful payment tx
Env env(*this, features);
auto const& [gw, domainOwner, alice, bob, carol, USD, domainID, credType] =
PermissionedDEX(env);

View File

@@ -1740,7 +1740,7 @@ private:
// locals[0]: from 0 to maxKeys - 4
// locals[1]: from 1 to maxKeys - 2
// locals[2]: from 2 to maxKeys
// interesection of at least 2: same as locals[1]
// intersection of at least 2: same as locals[1]
// intersection when 1 is dropped: from 2 to maxKeys - 4
constexpr static int publishers = 3;
std::array<

View File

@@ -49,7 +49,7 @@ public:
.first;
if (valid != Validity::Valid)
fail("Non-Fully canoncial signature was not permitted");
fail("Non-Fully canonical signature was not permitted");
}
{
@@ -63,7 +63,7 @@ public:
fully_canonical.app().config())
.first;
if (valid == Validity::Valid)
fail("Non-Fully canoncial signature was permitted");
fail("Non-Fully canonical signature was permitted");
}
pass();

View File

@@ -15,7 +15,6 @@ namespace test {
/*
* This file implements the following negative UNL related tests:
* -- test filling and applying ttUNL_MODIFY Tx and ledger update
* -- test ttUNL_MODIFY Tx failure without featureNegativeUNL amendment
* -- test the NegativeUNLVote class. The test cases are split to multiple
* test classes to allow parallel execution.
* -- test the negativeUNLFilter function
@@ -208,7 +207,7 @@ class NegativeUNL_test : public beast::unit_test::suite
testcase("Create UNLModify Tx and apply to ledgers");
jtx::Env env(*this, jtx::testable_amendments() | featureNegativeUNL);
jtx::Env env(*this, jtx::testable_amendments());
std::vector<PublicKey> publicKeys = createPublicKeys(3);
// genesis ledger
auto l = std::make_shared<Ledger>(
@@ -216,7 +215,6 @@ class NegativeUNL_test : public beast::unit_test::suite
env.app().config(),
std::vector<uint256>{},
env.app().getNodeFamily());
BEAST_EXPECT(l->rules().enabled(featureNegativeUNL));
// Record the public keys and ledger sequences of expected negative UNL
// validators when we build the ledger history
@@ -500,44 +498,6 @@ class NegativeUNL_test : public beast::unit_test::suite
}
};
class NegativeUNLNoAmendment_test : public beast::unit_test::suite
{
void
testNegativeUNLNoAmendment()
{
testcase("No negative UNL amendment");
jtx::Env env(*this, jtx::testable_amendments() - featureNegativeUNL);
std::vector<PublicKey> publicKeys = createPublicKeys(1);
// genesis ledger
auto l = std::make_shared<Ledger>(
create_genesis,
env.app().config(),
std::vector<uint256>{},
env.app().getNodeFamily());
BEAST_EXPECT(!l->rules().enabled(featureNegativeUNL));
// generate more ledgers
for (auto i = 0; i < 256 - 1; ++i)
{
l = std::make_shared<Ledger>(
*l, env.app().timeKeeper().closeTime());
}
BEAST_EXPECT(l->seq() == 256);
auto txDisable_0 = createTx(true, l->seq(), publicKeys[0]);
OpenView accum(&*l);
BEAST_EXPECT(applyAndTestResult(env, accum, txDisable_0, false));
accum.apply(*l);
BEAST_EXPECT(negUnlSizeTest(l, 0, false, false));
}
void
run() override
{
testNegativeUNLNoAmendment();
}
};
/**
* Utility class for creating validators and ledger history
*/
@@ -563,7 +523,7 @@ struct NetworkHistory
};
NetworkHistory(beast::unit_test::suite& suite, Parameter const& p)
: env(suite, jtx::testable_amendments() | featureNegativeUNL)
: env(suite, jtx::testable_amendments())
, param(p)
, validations(env.app().getValidations())
{
@@ -1867,7 +1827,6 @@ class NegativeUNLVoteFilterValidations_test : public beast::unit_test::suite
};
BEAST_DEFINE_TESTSUITE(NegativeUNL, consensus, ripple);
BEAST_DEFINE_TESTSUITE(NegativeUNLNoAmendment, consensus, ripple);
BEAST_DEFINE_TESTSUITE(NegativeUNLVoteInternal, consensus, ripple);
BEAST_DEFINE_TESTSUITE_MANUAL(NegativeUNLVoteScoreTable, consensus, ripple);

View File

@@ -1183,7 +1183,7 @@ r.ripple.com:51235
BEAST_EXPECT(cfg.IPS_FIXED[6] == "12.34.12.123 12345");
BEAST_EXPECT(cfg.IPS_FIXED[7] == "12.34.12.123 12345");
// all ipv6 should be ignored by colon replacer, howsoever formated
// all ipv6 should be ignored by colon replacer, howsoever formatted
BEAST_EXPECT(cfg.IPS_FIXED[8] == "::");
BEAST_EXPECT(cfg.IPS_FIXED[9] == "2001:db8::");
BEAST_EXPECT(cfg.IPS_FIXED[10] == "::1");

View File

@@ -79,7 +79,7 @@ public:
void
testSQLiteFileNames()
{
// confirm that files are given the correct exensions
// confirm that files are given the correct extensions
testcase("sqliteFileNames");
BasicConfig c;
setupSQLiteConfig(c, getDatabasePath());

View File

@@ -243,7 +243,7 @@ struct XRP_t
}
/** Returns an amount of XRP as PrettyAmount,
which is trivially convertable to STAmount
which is trivially convertible to STAmount
@param v The number of XRP (not drops)
*/

View File

@@ -6,7 +6,7 @@
namespace ripple {
namespace test {
// frequently used macros defined here for convinience.
// frequently used macros defined here for convenience.
#define PORT_WS "port_ws"
#define PORT_RPC "port_rpc"
#define PORT_PEER "port_peer"

View File

@@ -227,7 +227,7 @@ public:
BEAST_EXPECT(!c->load(s4));
// Check if we properly terminate when we encounter
// a malformed or unparseable entry:
// a malformed or unparsable entry:
auto const node1 = randomNode();
auto const node2 = randomNode();

View File

@@ -124,7 +124,8 @@ class Feature_test : public beast::unit_test::suite
featureToName(fixRemoveNFTokenAutoTrustLine) ==
"fixRemoveNFTokenAutoTrustLine");
BEAST_EXPECT(featureToName(featureFlow) == "Flow");
BEAST_EXPECT(featureToName(featureNegativeUNL) == "NegativeUNL");
BEAST_EXPECT(
featureToName(featureDeletableAccounts) == "DeletableAccounts");
BEAST_EXPECT(
featureToName(fixIncludeKeyletFields) == "fixIncludeKeyletFields");
BEAST_EXPECT(featureToName(featureTokenEscrow) == "TokenEscrow");

View File

@@ -346,9 +346,7 @@ RCLConsensus::Adaptor::onClose(
prevLedger, validations, initialSet, j_);
}
}
else if (
prevLedger->isVotingLedger() &&
prevLedger->rules().enabled(featureNegativeUNL))
else if (prevLedger->isVotingLedger())
{
// previous ledger was a voting ledger,
// so the current consensus session is for a flag ledger,
@@ -1015,8 +1013,7 @@ RCLConsensus::Adaptor::preStartRound(
inboundTransactions_.newRound(prevLgr.seq());
// Notify NegativeUNLVote that new validators are added
if (prevLgr.ledger_->rules().enabled(featureNegativeUNL) &&
!nowTrusted.empty())
if (!nowTrusted.empty())
nUnlVote_.newValidators(prevLgr.seq() + 1, nowTrusted);
// propose only if we're in sync with the network (and validating)

View File

@@ -28,7 +28,7 @@ buildLedgerImpl(
{
auto built = std::make_shared<Ledger>(*parent, closeTime);
if (built->isFlagLedger() && built->rules().enabled(featureNegativeUNL))
if (built->isFlagLedger())
{
built->updateNegativeUNL();
}

View File

@@ -2063,8 +2063,7 @@ NetworkOPsImp::beginConsensus(
"ripple::NetworkOPsImp::beginConsensus : closedLedger parent matches "
"hash");
if (prevLedger->rules().enabled(featureNegativeUNL))
app_.validators().setNegativeUNL(prevLedger->negativeUNL());
app_.validators().setNegativeUNL(prevLedger->negativeUNL());
TrustChanges const changes = app_.validators().updateTrusted(
app_.getValidations().getCurrentNodeIDs(),
closingInfo.parentCloseTime,

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

@@ -51,13 +51,6 @@ Transactor::invokePreflight<Change>(PreflightContext const& ctx)
return temBAD_SEQUENCE;
}
if (ctx.tx.getTxnType() == ttUNL_MODIFY &&
!ctx.rules.enabled(featureNegativeUNL))
{
JLOG(ctx.j.warn()) << "Change: NegativeUNL not enabled";
return temDISABLED;
}
return tesSUCCESS;
}

View File

@@ -176,7 +176,7 @@ public:
// DEPRECATED public data members
// Tells us if we checked the connection. Outbound connections
// are always considered checked since we successfuly connected.
// are always considered checked since we successfully connected.
bool checked;
// Set to indicate if the connection can receive incoming at the

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>