Compare commits

...

21 Commits

Author SHA1 Message Date
Valentin Balaschenko
645fddaf82 remove unused 2025-11-19 11:48:16 +00:00
Valentin Balaschenko
265ea4b270 Merge branch 'develop' into vlntb/malloc-trim 2025-11-19 11:38:54 +00:00
Valentin Balaschenko
e77bd4e2d8 remove untested 2025-11-19 11:38:18 +00:00
sunnyraindy
ad37461ab2 chore: Fix some typos in comments (#6040) 2025-11-18 20:21:35 -05:00
Mayukha Vadari
d9c27da529 refactor: split up RPCHelpers.h into two (#6047)
This PR splits `RPCHelpers.h` into two files, by moving out all the ledger-fetching-related functions into a separate file, `RPCLedgerHelpers.h`. It also moves `getAccountObjects` to `AccountObjects.h`, since it is only used in that one place.
2025-11-18 15:44:39 -05:00
Bart
3fb6acd29e ci: Fix filtering out of Clang 20+ on ARM (#6046)
This change fixes the strategy matrix check to filter out the Clang 20+ on ARM, which still fail due to problems with Boost.
2025-11-17 21:54:13 +00:00
Bart
77b7cef5a7 ci: Use new Debian Trixie images (#6034)
This change uses the new Debian Trixie CI images added by XRPLF/ci#83.
2025-11-17 19:31:19 +00:00
Jingchen
2c187461cc refactor: Retire NegativeUNL amendment (#6033)
Amendments activated for more than 2 years can be retired. This change retires the NegativeUNL amendment.
2025-11-17 14:02:10 +00:00
Valentin Balaschenko
6a8a1b7e28 Merge branch 'develop' into vlntb/malloc-trim 2025-11-14 17:33:23 +02:00
Valentin Balaschenko
efe7177d1b load mode with relaxed ordering 2025-11-14 13:07:04 +00:00
Valentin Balaschenko
2b2b361c87 add malloc trim after sync complete 2025-11-14 13:01:38 +00:00
Valentin Balaschenko
ff8b4353bc malloc trim once orderbook update finished 2025-11-14 12:11:20 +00:00
Valentin Balaschenko
50d606539c fixing test 2025-11-13 17:16:53 +00:00
Valentin Balaschenko
d85f7073dd Merge branch 'develop' into vlntb/malloc-trim 2025-11-13 15:57:05 +02:00
Valentin Balaschenko
334382f031 cleanup and notes 2025-11-13 13:56:36 +00:00
Valentin Balaschenko
2d41bfec05 Merge branch 'develop' into vlntb/malloc-trim 2025-11-12 15:36:29 +02:00
Valentin Balaschenko
52c83684cd unit tests + refactore 2025-11-12 13:35:21 +00:00
Valentin Balaschenko
72b34e6615 efficient call from doSweep and online delete 2025-11-11 16:53:02 +00:00
Valentin Balaschenko
a1ed175b66 trim min internal 2025-11-11 16:08:04 +00:00
Valentin Balaschenko
3fdd42af63 encapsulate and instrument 2025-11-11 15:19:50 +00:00
Valentin Balaschenko
ac5554e9f5 testing malloc trim 2025-11-05 21:01:13 +00:00
55 changed files with 1245 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

@@ -0,0 +1,70 @@
#ifndef XRPL_BASICS_MALLOCTRIM_H_INCLUDED
#define XRPL_BASICS_MALLOCTRIM_H_INCLUDED
#include <xrpl/beast/utility/Journal.h>
#include <optional>
#include <string>
namespace ripple {
// -----------------------------------------------------------------------------
// Allocator interaction note:
// - This facility invokes glibc's malloc_trim(0) on Linux/glibc to request that
// ptmalloc return free heap pages to the OS.
// - If an alternative allocator (e.g. jemalloc or tcmalloc) is linked or
// preloaded (LD_PRELOAD), calling glibc's malloc_trim typically has no effect
// on the *active* heap. The call is harmless but may not reclaim memory
// because those allocators manage their own arenas.
// - Only glibc sbrk/arena space is eligible for trimming; large mmap-backed
// allocations are usually returned to the OS on free regardless of trimming.
// - Call at known reclamation points (e.g., after cache sweeps / online delete)
// and consider rate limiting to avoid churn.
// -----------------------------------------------------------------------------
struct MallocTrimReport
{
bool supported{false};
int trimResult{-1};
long rssBeforeKB{-1};
long rssAfterKB{-1};
[[nodiscard]] long
deltaKB() const noexcept
{
if (rssBeforeKB < 0 || rssAfterKB < 0)
return 0;
return rssAfterKB - rssBeforeKB;
}
};
/**
* @brief Attempt to return freed memory to the operating system.
*
* On Linux with glibc malloc, this issues ::malloc_trim(0), which may release
* free space from ptmalloc arenas back to the kernel. On other platforms, or if
* a different allocator is in use, this function is a no-op and the report will
* indicate that trimming is unsupported or had no effect.
*
* @param tag Optional identifier for logging/debugging purposes.
* @param journal Journal for diagnostic logging.
* @return Report containing before/after metrics and the trim result.
*
* @note If an alternative allocator (jemalloc/tcmalloc) is linked or preloaded,
* calling glibc's malloc_trim may have no effect on the active heap. The
* call is harmless but typically does not reclaim memory under those
* allocators.
*
* @note Only memory served from glibc's sbrk/arena heaps is eligible for trim.
* Large allocations satisfied via mmap are usually returned on free
* independently of trimming.
*
* @note Intended for use after operations that free significant memory (e.g.,
* cache sweeps, ledger cleanup, online delete). Consider rate limiting.
*/
MallocTrimReport
mallocTrim(std::optional<std::string> const& tag, beast::Journal journal);
} // namespace ripple
#endif

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

@@ -0,0 +1,121 @@
#include <xrpl/basics/Log.h>
#include <xrpl/basics/MallocTrim.h>
#include <boost/predef.h>
#include <cstdio>
#include <fstream>
#if defined(__GLIBC__) && BOOST_OS_LINUX
#include <malloc.h>
#include <unistd.h>
namespace {
pid_t const cachedPid = ::getpid();
} // namespace
#endif
namespace ripple {
namespace detail {
#if defined(__GLIBC__) && BOOST_OS_LINUX
long
parseVmRSSkB(std::string const& status)
{
std::istringstream iss(status);
std::string line;
while (std::getline(iss, line))
{
// Allow leading spaces/tabs before the key.
auto const firstNonWs = line.find_first_not_of(" \t");
if (firstNonWs == std::string::npos)
continue;
constexpr char key[] = "VmRSS:";
constexpr auto keyLen = sizeof(key) - 1;
// Require the line (after leading whitespace) to start with "VmRSS:".
// Check if we have enough characters and the substring matches.
if (firstNonWs + keyLen > line.size() ||
line.substr(firstNonWs, keyLen) != key)
continue;
// Move past "VmRSS:" and any following whitespace.
auto pos = firstNonWs + keyLen;
while (pos < line.size() &&
std::isspace(static_cast<unsigned char>(line[pos])))
{
++pos;
}
long value = -1;
if (std::sscanf(line.c_str() + pos, "%ld", &value) == 1)
return value;
// Found the key but couldn't parse a number.
return -1;
}
// No VmRSS line found.
return -1;
}
#endif // __GLIBC__ && BOOST_OS_LINUX
} // namespace detail
MallocTrimReport
mallocTrim(
[[maybe_unused]] std::optional<std::string> const& tag,
beast::Journal journal)
{
MallocTrimReport report;
#if !(defined(__GLIBC__) && BOOST_OS_LINUX)
JLOG(journal.debug()) << "malloc_trim not supported on this platform";
#else
report.supported = true;
if (journal.debug())
{
auto readFile = [](std::string const& path) -> std::string {
std::ifstream ifs(path);
if (!ifs.is_open())
return {};
return std::string(
std::istreambuf_iterator<char>(ifs),
std::istreambuf_iterator<char>());
};
std::string const tagStr = tag.value_or("default");
std::string const statusPath =
"/proc/" + std::to_string(cachedPid) + "/status";
auto const statusBefore = readFile(statusPath);
report.rssBeforeKB = detail::parseVmRSSkB(statusBefore);
report.trimResult = ::malloc_trim(0);
auto const statusAfter = readFile(statusPath);
report.rssAfterKB = detail::parseVmRSSkB(statusAfter);
JLOG(journal.debug())
<< "malloc_trim tag=" << tagStr << " result=" << report.trimResult
<< " rss_before=" << report.rssBeforeKB << "kB"
<< " rss_after=" << report.rssAfterKB << "kB"
<< " delta=" << report.deltaKB() << "kB";
}
else
{
report.trimResult = ::malloc_trim(0);
}
#endif
return report;
}
} // namespace ripple

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

@@ -0,0 +1,207 @@
#include <xrpl/basics/MallocTrim.h>
#include <boost/predef.h>
#include <doctest/doctest.h>
using namespace ripple;
#if defined(__GLIBC__) && BOOST_OS_LINUX
namespace ripple::detail {
long
parseVmRSSkB(std::string const& status);
} // namespace ripple::detail
#endif
TEST_CASE("MallocTrimReport structure")
{
// Test default construction
MallocTrimReport report;
CHECK(report.supported == false);
CHECK(report.trimResult == -1);
CHECK(report.rssBeforeKB == -1);
CHECK(report.rssAfterKB == -1);
CHECK(report.deltaKB() == 0);
// Test deltaKB calculation - memory freed
report.rssBeforeKB = 1000;
report.rssAfterKB = 800;
CHECK(report.deltaKB() == -200);
// Test deltaKB calculation - memory increased
report.rssBeforeKB = 500;
report.rssAfterKB = 600;
CHECK(report.deltaKB() == 100);
// Test deltaKB calculation - no change
report.rssBeforeKB = 1234;
report.rssAfterKB = 1234;
CHECK(report.deltaKB() == 0);
}
#if defined(__GLIBC__) && BOOST_OS_LINUX
TEST_CASE("parseVmRSSkB")
{
using ripple::detail::parseVmRSSkB;
// Test standard format
{
std::string status = "VmRSS: 123456 kB\n";
long result = parseVmRSSkB(status);
CHECK(result == 123456);
}
// Test with multiple lines
{
std::string status =
"Name: rippled\n"
"VmPeak: 1234567 kB\n"
"VmSize: 1234567 kB\n"
"VmRSS: 987654 kB\n"
"VmData: 123456 kB\n";
long result = parseVmRSSkB(status);
CHECK(result == 987654);
}
// Test with minimal whitespace
{
std::string status = "VmRSS: 42 kB";
long result = parseVmRSSkB(status);
CHECK(result == 42);
}
// Test with extra whitespace
{
std::string status = "VmRSS: 999999 kB";
long result = parseVmRSSkB(status);
CHECK(result == 999999);
}
// Test with tabs
{
std::string status = "VmRSS:\t\t12345 kB";
long result = parseVmRSSkB(status);
// Note: tabs are not explicitly handled as spaces, this documents
// current behavior
CHECK(result == 12345);
}
// Test zero value
{
std::string status = "VmRSS: 0 kB\n";
long result = parseVmRSSkB(status);
CHECK(result == 0);
}
// Test missing VmRSS
{
std::string status =
"Name: rippled\n"
"VmPeak: 1234567 kB\n"
"VmSize: 1234567 kB\n";
long result = parseVmRSSkB(status);
CHECK(result == -1);
}
// Test empty string
{
std::string status = "";
long result = parseVmRSSkB(status);
CHECK(result == -1);
}
// Test malformed data (VmRSS but no number)
{
std::string status = "VmRSS: \n";
long result = parseVmRSSkB(status);
// sscanf should fail to parse and return -1 unchanged
CHECK(result == -1);
}
// Test malformed data (VmRSS but invalid number)
{
std::string status = "VmRSS: abc kB\n";
long result = parseVmRSSkB(status);
// sscanf should fail and return -1 unchanged
CHECK(result == -1);
}
// Test partial match (should not match "NotVmRSS:")
{
std::string status = "NotVmRSS: 123456 kB\n";
long result = parseVmRSSkB(status);
CHECK(result == -1);
}
}
#endif
TEST_CASE("mallocTrim basic functionality")
{
beast::Journal journal{beast::Journal::getNullSink()};
// Test with no tag
{
MallocTrimReport report = mallocTrim(std::nullopt, journal);
#if defined(__GLIBC__) && BOOST_OS_LINUX
// On Linux with glibc, should be supported
CHECK(report.supported == true);
// trimResult should be 0 or 1 (success indicators)
CHECK(report.trimResult >= 0);
#else
// On other platforms, should be unsupported
CHECK(report.supported == false);
CHECK(report.trimResult == -1);
CHECK(report.rssBeforeKB == -1);
CHECK(report.rssAfterKB == -1);
#endif
}
// Test with tag
{
MallocTrimReport report =
mallocTrim(std::optional<std::string>("test_tag"), journal);
#if defined(__GLIBC__) && BOOST_OS_LINUX
CHECK(report.supported == true);
CHECK(report.trimResult >= 0);
#else
CHECK(report.supported == false);
#endif
}
}
TEST_CASE("mallocTrim with debug logging")
{
beast::Journal journal{beast::Journal::getNullSink()};
MallocTrimReport report =
mallocTrim(std::optional<std::string>("debug_test"), journal);
#if defined(__GLIBC__) && BOOST_OS_LINUX
CHECK(report.supported == true);
// The function should complete without crashing
#else
CHECK(report.supported == false);
#endif
}
TEST_CASE("mallocTrim repeated calls")
{
beast::Journal journal{beast::Journal::getNullSink()};
// Call malloc_trim multiple times to ensure it's safe
for (int i = 0; i < 5; ++i)
{
MallocTrimReport report = mallocTrim(
std::optional<std::string>("iteration_" + std::to_string(i)),
journal);
#if defined(__GLIBC__) && BOOST_OS_LINUX
CHECK(report.supported == true);
CHECK(report.trimResult >= 0);
#else
CHECK(report.supported == false);
#endif
}
}

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

@@ -37,6 +37,7 @@
#include <xrpld/shamap/NodeFamily.h>
#include <xrpl/basics/ByteUtilities.h>
#include <xrpl/basics/MallocTrim.h>
#include <xrpl/basics/ResolverAsio.h>
#include <xrpl/basics/random.h>
#include <xrpl/beast/asio/io_latency_probe.h>
@@ -1106,6 +1107,8 @@ public:
<< "; size after: " << cachedSLEs_.size();
}
mallocTrim(std::optional<std::string>("doSweep"), m_journal);
// Set timer to do another sweep later.
setSweepTimer();
}

View File

@@ -34,6 +34,7 @@
#include <xrpld/rpc/MPTokenIssuanceID.h>
#include <xrpld/rpc/ServerHandler.h>
#include <xrpl/basics/MallocTrim.h>
#include <xrpl/basics/UptimeClock.h>
#include <xrpl/basics/mulDiv.h>
#include <xrpl/basics/safe_cast.h>
@@ -2063,8 +2064,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,
@@ -2547,10 +2547,14 @@ NetworkOPsImp::setMode(OperatingMode om)
if (mMode == om)
return;
auto const oldMode = mMode.load(std::memory_order_relaxed);
mMode = om;
accounting_.mode(om);
if (oldMode != OperatingMode::FULL && om == OperatingMode::FULL)
mallocTrim(std::optional<std::string>("SyncComplete"), m_journal);
JLOG(m_journal.info()) << "STATE->" << strOperatingMode();
pubServer();
}

View File

@@ -5,6 +5,7 @@
#include <xrpld/app/rdb/backend/SQLiteDatabase.h>
#include <xrpld/core/ConfigSections.h>
#include <xrpl/basics/MallocTrim.h>
#include <xrpl/beast/core/CurrentThreadName.h>
#include <xrpl/nodestore/Scheduler.h>
#include <xrpl/nodestore/detail/DatabaseRotatingImp.h>
@@ -545,6 +546,8 @@ SHAMapStoreImp::clearCaches(LedgerIndex validatedSeq)
{
ledgerMaster_->clearLedgerCachePrior(validatedSeq);
fullBelowCache_->clear();
mallocTrim(std::optional<std::string>("clearCaches"), journal_);
}
void
@@ -610,6 +613,8 @@ SHAMapStoreImp::clearPrior(LedgerIndex lastRotated)
});
if (healthWait() == stopping)
return;
mallocTrim(std::optional<std::string>("clearPrior"), journal_);
}
SHAMapStoreImp::HealthResult

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>