mirror of
https://github.com/XRPLF/rippled.git
synced 2025-11-19 10:35:50 +00:00
Compare commits
11 Commits
bthomee/re
...
ximinez/em
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e9fa9d7aa6 | ||
|
|
ad37461ab2 | ||
|
|
d9c27da529 | ||
|
|
3fb6acd29e | ||
|
|
77b7cef5a7 | ||
|
|
2c187461cc | ||
|
|
2c3f169dec | ||
|
|
23565405ee | ||
|
|
a5d08b0cd5 | ||
|
|
7bf3f543b3 | ||
|
|
c773288df5 |
4
.github/scripts/strategy-matrix/generate.py
vendored
4
.github/scripts/strategy-matrix/generate.py
vendored
@@ -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
|
||||
|
||||
46
.github/scripts/strategy-matrix/linux.json
vendored
46
.github/scripts/strategy-matrix/linux.json
vendored
@@ -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",
|
||||
|
||||
2
external/secp256k1/include/secp256k1.h
vendored
2
external/secp256k1/include/secp256k1.h
vendored
@@ -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.
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -191,6 +191,7 @@ ApplyView::dirRemove(
|
||||
uint256 const& key,
|
||||
bool keepRoot)
|
||||
{
|
||||
keepRoot = false;
|
||||
auto node = peek(keylet::page(directory, page));
|
||||
|
||||
if (!node)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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<
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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)
|
||||
*/
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -2063,7 +2063,6 @@ NetworkOPsImp::beginConsensus(
|
||||
"ripple::NetworkOPsImp::beginConsensus : closedLedger parent matches "
|
||||
"hash");
|
||||
|
||||
if (prevLedger->rules().enabled(featureNegativeUNL))
|
||||
app_.validators().setNegativeUNL(prevLedger->negativeUNL());
|
||||
TrustChanges const changes = app_.validators().updateTrusted(
|
||||
app_.getValidations().getCurrentNodeIDs(),
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -3096,4 +3096,42 @@ ValidVault::finalize(
|
||||
return true;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
void
|
||||
NoEmptyDirectory::visitEntry(
|
||||
bool isDelete,
|
||||
std::shared_ptr<SLE const> const& before,
|
||||
std::shared_ptr<SLE const> const& after)
|
||||
{
|
||||
if (isDelete)
|
||||
return;
|
||||
if (before && before->getType() != ltDIR_NODE)
|
||||
return;
|
||||
if (after && after->getType() != ltDIR_NODE)
|
||||
return;
|
||||
if (!after->isFieldPresent(sfOwner))
|
||||
// Not an account dir
|
||||
return;
|
||||
|
||||
bad_ = after->at(sfIndexes).empty();
|
||||
}
|
||||
|
||||
bool
|
||||
NoEmptyDirectory::finalize(
|
||||
STTx const& tx,
|
||||
TER const result,
|
||||
XRPAmount const,
|
||||
ReadView const& view,
|
||||
beast::Journal const& j)
|
||||
{
|
||||
if (bad_)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: empty owner directory.";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
@@ -783,6 +783,30 @@ public:
|
||||
beast::Journal const&);
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Invariants: An account's directory should never be empty
|
||||
*
|
||||
*/
|
||||
class NoEmptyDirectory
|
||||
{
|
||||
bool bad_ = false;
|
||||
|
||||
public:
|
||||
void
|
||||
visitEntry(
|
||||
bool,
|
||||
std::shared_ptr<SLE const> const&,
|
||||
std::shared_ptr<SLE const> const&);
|
||||
|
||||
bool
|
||||
finalize(
|
||||
STTx const&,
|
||||
TER const,
|
||||
XRPAmount const,
|
||||
ReadView const&,
|
||||
beast::Journal const&);
|
||||
};
|
||||
|
||||
// additional invariant checks can be declared above and then added to this
|
||||
// tuple
|
||||
using InvariantChecks = std::tuple<
|
||||
@@ -806,7 +830,8 @@ using InvariantChecks = std::tuple<
|
||||
ValidPermissionedDEX,
|
||||
ValidAMM,
|
||||
ValidPseudoAccounts,
|
||||
ValidVault>;
|
||||
ValidVault,
|
||||
NoEmptyDirectory>;
|
||||
|
||||
/**
|
||||
* @brief get a tuple of all invariant checks
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -130,513 +130,6 @@ isRelatedToAccount(
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
getAccountObjects(
|
||||
ReadView const& ledger,
|
||||
AccountID const& account,
|
||||
std::optional<std::vector<LedgerEntryType>> const& typeFilter,
|
||||
uint256 dirIndex,
|
||||
uint256 entryIndex,
|
||||
std::uint32_t const limit,
|
||||
Json::Value& jvResult)
|
||||
{
|
||||
// check if dirIndex is valid
|
||||
if (!dirIndex.isZero() && !ledger.read({ltDIR_NODE, dirIndex}))
|
||||
return false;
|
||||
|
||||
auto typeMatchesFilter = [](std::vector<LedgerEntryType> const& typeFilter,
|
||||
LedgerEntryType ledgerType) {
|
||||
auto it = std::find(typeFilter.begin(), typeFilter.end(), ledgerType);
|
||||
return it != typeFilter.end();
|
||||
};
|
||||
|
||||
// if dirIndex != 0, then all NFTs have already been returned. only
|
||||
// iterate NFT pages if the filter says so AND dirIndex == 0
|
||||
bool iterateNFTPages =
|
||||
(!typeFilter.has_value() ||
|
||||
typeMatchesFilter(typeFilter.value(), ltNFTOKEN_PAGE)) &&
|
||||
dirIndex == beast::zero;
|
||||
|
||||
Keylet const firstNFTPage = keylet::nftpage_min(account);
|
||||
|
||||
// we need to check the marker to see if it is an NFTTokenPage index.
|
||||
if (iterateNFTPages && entryIndex != beast::zero)
|
||||
{
|
||||
// if it is we will try to iterate the pages up to the limit
|
||||
// and then change over to the owner directory
|
||||
|
||||
if (firstNFTPage.key != (entryIndex & ~nft::pageMask))
|
||||
iterateNFTPages = false;
|
||||
}
|
||||
|
||||
auto& jvObjects = (jvResult[jss::account_objects] = Json::arrayValue);
|
||||
|
||||
// this is a mutable version of limit, used to seamlessly switch
|
||||
// to iterating directory entries when nftokenpages are exhausted
|
||||
uint32_t mlimit = limit;
|
||||
|
||||
// iterate NFTokenPages preferentially
|
||||
if (iterateNFTPages)
|
||||
{
|
||||
Keylet const first = entryIndex == beast::zero
|
||||
? firstNFTPage
|
||||
: Keylet{ltNFTOKEN_PAGE, entryIndex};
|
||||
|
||||
Keylet const last = keylet::nftpage_max(account);
|
||||
|
||||
// current key
|
||||
uint256 ck = ledger.succ(first.key, last.key.next()).value_or(last.key);
|
||||
|
||||
// current page
|
||||
auto cp = ledger.read(Keylet{ltNFTOKEN_PAGE, ck});
|
||||
|
||||
while (cp)
|
||||
{
|
||||
jvObjects.append(cp->getJson(JsonOptions::none));
|
||||
auto const npm = (*cp)[~sfNextPageMin];
|
||||
if (npm)
|
||||
cp = ledger.read(Keylet(ltNFTOKEN_PAGE, *npm));
|
||||
else
|
||||
cp = nullptr;
|
||||
|
||||
if (--mlimit == 0)
|
||||
{
|
||||
if (cp)
|
||||
{
|
||||
jvResult[jss::limit] = limit;
|
||||
jvResult[jss::marker] = std::string("0,") + to_string(ck);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!npm)
|
||||
break;
|
||||
|
||||
ck = *npm;
|
||||
}
|
||||
|
||||
// if execution reaches here then we're about to transition
|
||||
// to iterating the root directory (and the conventional
|
||||
// behaviour of this RPC function.) Therefore we should
|
||||
// zero entryIndex so as not to terribly confuse things.
|
||||
entryIndex = beast::zero;
|
||||
}
|
||||
|
||||
auto const root = keylet::ownerDir(account);
|
||||
auto found = false;
|
||||
|
||||
if (dirIndex.isZero())
|
||||
{
|
||||
dirIndex = root.key;
|
||||
found = true;
|
||||
}
|
||||
|
||||
auto dir = ledger.read({ltDIR_NODE, dirIndex});
|
||||
if (!dir)
|
||||
{
|
||||
// it's possible the user had nftoken pages but no
|
||||
// directory entries. If there's no nftoken page, we will
|
||||
// give empty array for account_objects.
|
||||
if (mlimit >= limit)
|
||||
jvResult[jss::account_objects] = Json::arrayValue;
|
||||
|
||||
// non-zero dirIndex validity was checked in the beginning of this
|
||||
// function; by this point, it should be zero. This function returns
|
||||
// true regardless of nftoken page presence; if absent, account_objects
|
||||
// is already set as an empty array. Notice we will only return false in
|
||||
// this function when entryIndex can not be found, indicating an invalid
|
||||
// marker error.
|
||||
return true;
|
||||
}
|
||||
|
||||
std::uint32_t i = 0;
|
||||
for (;;)
|
||||
{
|
||||
auto const& entries = dir->getFieldV256(sfIndexes);
|
||||
auto iter = entries.begin();
|
||||
|
||||
if (!found)
|
||||
{
|
||||
iter = std::find(iter, entries.end(), entryIndex);
|
||||
if (iter == entries.end())
|
||||
return false;
|
||||
|
||||
found = true;
|
||||
}
|
||||
|
||||
// it's possible that the returned NFTPages exactly filled the
|
||||
// response. Check for that condition.
|
||||
if (i == mlimit && mlimit < limit)
|
||||
{
|
||||
jvResult[jss::limit] = limit;
|
||||
jvResult[jss::marker] =
|
||||
to_string(dirIndex) + ',' + to_string(*iter);
|
||||
return true;
|
||||
}
|
||||
|
||||
for (; iter != entries.end(); ++iter)
|
||||
{
|
||||
auto const sleNode = ledger.read(keylet::child(*iter));
|
||||
|
||||
if (!typeFilter.has_value() ||
|
||||
typeMatchesFilter(typeFilter.value(), sleNode->getType()))
|
||||
{
|
||||
jvObjects.append(sleNode->getJson(JsonOptions::none));
|
||||
}
|
||||
|
||||
if (++i == mlimit)
|
||||
{
|
||||
if (++iter != entries.end())
|
||||
{
|
||||
jvResult[jss::limit] = limit;
|
||||
jvResult[jss::marker] =
|
||||
to_string(dirIndex) + ',' + to_string(*iter);
|
||||
return true;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
auto const nodeIndex = dir->getFieldU64(sfIndexNext);
|
||||
if (nodeIndex == 0)
|
||||
return true;
|
||||
|
||||
dirIndex = keylet::page(root, nodeIndex).key;
|
||||
dir = ledger.read({ltDIR_NODE, dirIndex});
|
||||
if (!dir)
|
||||
return true;
|
||||
|
||||
if (i == mlimit)
|
||||
{
|
||||
auto const& e = dir->getFieldV256(sfIndexes);
|
||||
if (!e.empty())
|
||||
{
|
||||
jvResult[jss::limit] = limit;
|
||||
jvResult[jss::marker] =
|
||||
to_string(dirIndex) + ',' + to_string(*e.begin());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
bool
|
||||
isValidatedOld(LedgerMaster& ledgerMaster, bool standalone)
|
||||
{
|
||||
if (standalone)
|
||||
return false;
|
||||
|
||||
return ledgerMaster.getValidatedLedgerAge() > Tuning::maxValidatedLedgerAge;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
Status
|
||||
ledgerFromRequest(T& ledger, JsonContext& context)
|
||||
{
|
||||
ledger.reset();
|
||||
|
||||
auto& params = context.params;
|
||||
|
||||
auto indexValue = params[jss::ledger_index];
|
||||
auto hashValue = params[jss::ledger_hash];
|
||||
|
||||
// We need to support the legacy "ledger" field.
|
||||
auto& legacyLedger = params[jss::ledger];
|
||||
if (legacyLedger)
|
||||
{
|
||||
if (legacyLedger.asString().size() > 12)
|
||||
hashValue = legacyLedger;
|
||||
else
|
||||
indexValue = legacyLedger;
|
||||
}
|
||||
|
||||
if (!hashValue.isNull())
|
||||
{
|
||||
if (!hashValue.isString())
|
||||
return {rpcINVALID_PARAMS, "ledgerHashNotString"};
|
||||
|
||||
uint256 ledgerHash;
|
||||
if (!ledgerHash.parseHex(hashValue.asString()))
|
||||
return {rpcINVALID_PARAMS, "ledgerHashMalformed"};
|
||||
return getLedger(ledger, ledgerHash, context);
|
||||
}
|
||||
|
||||
if (!indexValue.isConvertibleTo(Json::stringValue))
|
||||
return {rpcINVALID_PARAMS, "ledgerIndexMalformed"};
|
||||
|
||||
auto const index = indexValue.asString();
|
||||
|
||||
if (index == "current" || index.empty())
|
||||
return getLedger(ledger, LedgerShortcut::CURRENT, context);
|
||||
|
||||
if (index == "validated")
|
||||
return getLedger(ledger, LedgerShortcut::VALIDATED, context);
|
||||
|
||||
if (index == "closed")
|
||||
return getLedger(ledger, LedgerShortcut::CLOSED, context);
|
||||
|
||||
std::uint32_t val;
|
||||
if (!beast::lexicalCastChecked(val, index))
|
||||
return {rpcINVALID_PARAMS, "ledgerIndexMalformed"};
|
||||
|
||||
return getLedger(ledger, val, context);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
template <class T, class R>
|
||||
Status
|
||||
ledgerFromRequest(T& ledger, GRPCContext<R>& context)
|
||||
{
|
||||
R& request = context.params;
|
||||
return ledgerFromSpecifier(ledger, request.ledger(), context);
|
||||
}
|
||||
|
||||
// explicit instantiation of above function
|
||||
template Status
|
||||
ledgerFromRequest<>(
|
||||
std::shared_ptr<ReadView const>&,
|
||||
GRPCContext<org::xrpl::rpc::v1::GetLedgerEntryRequest>&);
|
||||
|
||||
// explicit instantiation of above function
|
||||
template Status
|
||||
ledgerFromRequest<>(
|
||||
std::shared_ptr<ReadView const>&,
|
||||
GRPCContext<org::xrpl::rpc::v1::GetLedgerDataRequest>&);
|
||||
|
||||
// explicit instantiation of above function
|
||||
template Status
|
||||
ledgerFromRequest<>(
|
||||
std::shared_ptr<ReadView const>&,
|
||||
GRPCContext<org::xrpl::rpc::v1::GetLedgerRequest>&);
|
||||
|
||||
template <class T>
|
||||
Status
|
||||
ledgerFromSpecifier(
|
||||
T& ledger,
|
||||
org::xrpl::rpc::v1::LedgerSpecifier const& specifier,
|
||||
Context& context)
|
||||
{
|
||||
ledger.reset();
|
||||
|
||||
using LedgerCase = org::xrpl::rpc::v1::LedgerSpecifier::LedgerCase;
|
||||
LedgerCase ledgerCase = specifier.ledger_case();
|
||||
switch (ledgerCase)
|
||||
{
|
||||
case LedgerCase::kHash: {
|
||||
if (auto hash = uint256::fromVoidChecked(specifier.hash()))
|
||||
{
|
||||
return getLedger(ledger, *hash, context);
|
||||
}
|
||||
return {rpcINVALID_PARAMS, "ledgerHashMalformed"};
|
||||
}
|
||||
case LedgerCase::kSequence:
|
||||
return getLedger(ledger, specifier.sequence(), context);
|
||||
case LedgerCase::kShortcut:
|
||||
[[fallthrough]];
|
||||
case LedgerCase::LEDGER_NOT_SET: {
|
||||
auto const shortcut = specifier.shortcut();
|
||||
if (shortcut ==
|
||||
org::xrpl::rpc::v1::LedgerSpecifier::SHORTCUT_VALIDATED)
|
||||
{
|
||||
return getLedger(ledger, LedgerShortcut::VALIDATED, context);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (shortcut ==
|
||||
org::xrpl::rpc::v1::LedgerSpecifier::SHORTCUT_CURRENT ||
|
||||
shortcut ==
|
||||
org::xrpl::rpc::v1::LedgerSpecifier::
|
||||
SHORTCUT_UNSPECIFIED)
|
||||
{
|
||||
return getLedger(ledger, LedgerShortcut::CURRENT, context);
|
||||
}
|
||||
else if (
|
||||
shortcut ==
|
||||
org::xrpl::rpc::v1::LedgerSpecifier::SHORTCUT_CLOSED)
|
||||
{
|
||||
return getLedger(ledger, LedgerShortcut::CLOSED, context);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Status::OK;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
Status
|
||||
getLedger(T& ledger, uint256 const& ledgerHash, Context& context)
|
||||
{
|
||||
ledger = context.ledgerMaster.getLedgerByHash(ledgerHash);
|
||||
if (ledger == nullptr)
|
||||
return {rpcLGR_NOT_FOUND, "ledgerNotFound"};
|
||||
return Status::OK;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
Status
|
||||
getLedger(T& ledger, uint32_t ledgerIndex, Context& context)
|
||||
{
|
||||
ledger = context.ledgerMaster.getLedgerBySeq(ledgerIndex);
|
||||
if (ledger == nullptr)
|
||||
{
|
||||
auto cur = context.ledgerMaster.getCurrentLedger();
|
||||
if (cur->info().seq == ledgerIndex)
|
||||
{
|
||||
ledger = cur;
|
||||
}
|
||||
}
|
||||
|
||||
if (ledger == nullptr)
|
||||
return {rpcLGR_NOT_FOUND, "ledgerNotFound"};
|
||||
|
||||
if (ledger->info().seq > context.ledgerMaster.getValidLedgerIndex() &&
|
||||
isValidatedOld(context.ledgerMaster, context.app.config().standalone()))
|
||||
{
|
||||
ledger.reset();
|
||||
if (context.apiVersion == 1)
|
||||
return {rpcNO_NETWORK, "InsufficientNetworkMode"};
|
||||
return {rpcNOT_SYNCED, "notSynced"};
|
||||
}
|
||||
|
||||
return Status::OK;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
Status
|
||||
getLedger(T& ledger, LedgerShortcut shortcut, Context& context)
|
||||
{
|
||||
if (isValidatedOld(context.ledgerMaster, context.app.config().standalone()))
|
||||
{
|
||||
if (context.apiVersion == 1)
|
||||
return {rpcNO_NETWORK, "InsufficientNetworkMode"};
|
||||
return {rpcNOT_SYNCED, "notSynced"};
|
||||
}
|
||||
|
||||
if (shortcut == LedgerShortcut::VALIDATED)
|
||||
{
|
||||
ledger = context.ledgerMaster.getValidatedLedger();
|
||||
if (ledger == nullptr)
|
||||
{
|
||||
if (context.apiVersion == 1)
|
||||
return {rpcNO_NETWORK, "InsufficientNetworkMode"};
|
||||
return {rpcNOT_SYNCED, "notSynced"};
|
||||
}
|
||||
|
||||
XRPL_ASSERT(
|
||||
!ledger->open(), "ripple::RPC::getLedger : validated is not open");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (shortcut == LedgerShortcut::CURRENT)
|
||||
{
|
||||
ledger = context.ledgerMaster.getCurrentLedger();
|
||||
XRPL_ASSERT(
|
||||
ledger->open(), "ripple::RPC::getLedger : current is open");
|
||||
}
|
||||
else if (shortcut == LedgerShortcut::CLOSED)
|
||||
{
|
||||
ledger = context.ledgerMaster.getClosedLedger();
|
||||
XRPL_ASSERT(
|
||||
!ledger->open(), "ripple::RPC::getLedger : closed is not open");
|
||||
}
|
||||
else
|
||||
{
|
||||
return {rpcINVALID_PARAMS, "ledgerIndexMalformed"};
|
||||
}
|
||||
|
||||
if (ledger == nullptr)
|
||||
{
|
||||
if (context.apiVersion == 1)
|
||||
return {rpcNO_NETWORK, "InsufficientNetworkMode"};
|
||||
return {rpcNOT_SYNCED, "notSynced"};
|
||||
}
|
||||
|
||||
static auto const minSequenceGap = 10;
|
||||
|
||||
if (ledger->info().seq + minSequenceGap <
|
||||
context.ledgerMaster.getValidLedgerIndex())
|
||||
{
|
||||
ledger.reset();
|
||||
if (context.apiVersion == 1)
|
||||
return {rpcNO_NETWORK, "InsufficientNetworkMode"};
|
||||
return {rpcNOT_SYNCED, "notSynced"};
|
||||
}
|
||||
}
|
||||
return Status::OK;
|
||||
}
|
||||
|
||||
// Explicit instantiation of above three functions
|
||||
template Status
|
||||
getLedger<>(std::shared_ptr<ReadView const>&, uint32_t, Context&);
|
||||
|
||||
template Status
|
||||
getLedger<>(
|
||||
std::shared_ptr<ReadView const>&,
|
||||
LedgerShortcut shortcut,
|
||||
Context&);
|
||||
|
||||
template Status
|
||||
getLedger<>(std::shared_ptr<ReadView const>&, uint256 const&, Context&);
|
||||
|
||||
// The previous version of the lookupLedger command would accept the
|
||||
// "ledger_index" argument as a string and silently treat it as a request to
|
||||
// return the current ledger which, while not strictly wrong, could cause a lot
|
||||
// of confusion.
|
||||
//
|
||||
// The code now robustly validates the input and ensures that the only possible
|
||||
// values for the "ledger_index" parameter are the index of a ledger passed as
|
||||
// an integer or one of the strings "current", "closed" or "validated".
|
||||
// Additionally, the code ensures that the value passed in "ledger_hash" is a
|
||||
// string and a valid hash. Invalid values will return an appropriate error
|
||||
// code.
|
||||
//
|
||||
// In the absence of the "ledger_hash" or "ledger_index" parameters, the code
|
||||
// assumes that "ledger_index" has the value "current".
|
||||
//
|
||||
// Returns a Json::objectValue. If there was an error, it will be in that
|
||||
// return value. Otherwise, the object contains the field "validated" and
|
||||
// optionally the fields "ledger_hash", "ledger_index" and
|
||||
// "ledger_current_index", if they are defined.
|
||||
Status
|
||||
lookupLedger(
|
||||
std::shared_ptr<ReadView const>& ledger,
|
||||
JsonContext& context,
|
||||
Json::Value& result)
|
||||
{
|
||||
if (auto status = ledgerFromRequest(ledger, context))
|
||||
return status;
|
||||
|
||||
auto& info = ledger->info();
|
||||
|
||||
if (!ledger->open())
|
||||
{
|
||||
result[jss::ledger_hash] = to_string(info.hash);
|
||||
result[jss::ledger_index] = info.seq;
|
||||
}
|
||||
else
|
||||
{
|
||||
result[jss::ledger_current_index] = info.seq;
|
||||
}
|
||||
|
||||
result[jss::validated] = context.ledgerMaster.isValidated(*ledger);
|
||||
return Status::OK;
|
||||
}
|
||||
|
||||
Json::Value
|
||||
lookupLedger(std::shared_ptr<ReadView const>& ledger, JsonContext& context)
|
||||
{
|
||||
Json::Value result;
|
||||
if (auto status = lookupLedger(ledger, context, result))
|
||||
status.inject(result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
hash_set<AccountID>
|
||||
parseAccountIds(Json::Value const& jvArray)
|
||||
{
|
||||
@@ -988,123 +481,5 @@ isAccountObjectsValidType(LedgerEntryType const& type)
|
||||
}
|
||||
}
|
||||
|
||||
std::variant<std::shared_ptr<Ledger const>, Json::Value>
|
||||
getLedgerByContext(RPC::JsonContext& context)
|
||||
{
|
||||
auto const hasHash = context.params.isMember(jss::ledger_hash);
|
||||
auto const hasIndex = context.params.isMember(jss::ledger_index);
|
||||
std::uint32_t ledgerIndex = 0;
|
||||
|
||||
auto& ledgerMaster = context.app.getLedgerMaster();
|
||||
LedgerHash ledgerHash;
|
||||
|
||||
if ((hasHash && hasIndex) || !(hasHash || hasIndex))
|
||||
{
|
||||
return RPC::make_param_error(
|
||||
"Exactly one of ledger_hash and ledger_index can be set.");
|
||||
}
|
||||
|
||||
context.loadType = Resource::feeHeavyBurdenRPC;
|
||||
|
||||
if (hasHash)
|
||||
{
|
||||
auto const& jsonHash = context.params[jss::ledger_hash];
|
||||
if (!jsonHash.isString() || !ledgerHash.parseHex(jsonHash.asString()))
|
||||
return RPC::invalid_field_error(jss::ledger_hash);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto const& jsonIndex = context.params[jss::ledger_index];
|
||||
if (!jsonIndex.isInt())
|
||||
return RPC::invalid_field_error(jss::ledger_index);
|
||||
|
||||
// We need a validated ledger to get the hash from the sequence
|
||||
if (ledgerMaster.getValidatedLedgerAge() >
|
||||
RPC::Tuning::maxValidatedLedgerAge)
|
||||
{
|
||||
if (context.apiVersion == 1)
|
||||
return rpcError(rpcNO_CURRENT);
|
||||
return rpcError(rpcNOT_SYNCED);
|
||||
}
|
||||
|
||||
ledgerIndex = jsonIndex.asInt();
|
||||
auto ledger = ledgerMaster.getValidatedLedger();
|
||||
|
||||
if (ledgerIndex >= ledger->info().seq)
|
||||
return RPC::make_param_error("Ledger index too large");
|
||||
if (ledgerIndex <= 0)
|
||||
return RPC::make_param_error("Ledger index too small");
|
||||
|
||||
auto const j = context.app.journal("RPCHandler");
|
||||
// Try to get the hash of the desired ledger from the validated
|
||||
// ledger
|
||||
auto neededHash = hashOfSeq(*ledger, ledgerIndex, j);
|
||||
if (!neededHash)
|
||||
{
|
||||
// Find a ledger more likely to have the hash of the desired
|
||||
// ledger
|
||||
auto const refIndex = getCandidateLedger(ledgerIndex);
|
||||
auto refHash = hashOfSeq(*ledger, refIndex, j);
|
||||
XRPL_ASSERT(
|
||||
refHash,
|
||||
"ripple::RPC::getLedgerByContext : nonzero ledger hash");
|
||||
|
||||
ledger = ledgerMaster.getLedgerByHash(*refHash);
|
||||
if (!ledger)
|
||||
{
|
||||
// We don't have the ledger we need to figure out which
|
||||
// ledger they want. Try to get it.
|
||||
|
||||
if (auto il = context.app.getInboundLedgers().acquire(
|
||||
*refHash, refIndex, InboundLedger::Reason::GENERIC))
|
||||
{
|
||||
Json::Value jvResult = RPC::make_error(
|
||||
rpcLGR_NOT_FOUND,
|
||||
"acquiring ledger containing requested index");
|
||||
jvResult[jss::acquiring] =
|
||||
getJson(LedgerFill(*il, &context));
|
||||
return jvResult;
|
||||
}
|
||||
|
||||
if (auto il = context.app.getInboundLedgers().find(*refHash))
|
||||
{
|
||||
Json::Value jvResult = RPC::make_error(
|
||||
rpcLGR_NOT_FOUND,
|
||||
"acquiring ledger containing requested index");
|
||||
jvResult[jss::acquiring] = il->getJson(0);
|
||||
return jvResult;
|
||||
}
|
||||
|
||||
// Likely the app is shutting down
|
||||
return Json::Value();
|
||||
}
|
||||
|
||||
neededHash = hashOfSeq(*ledger, ledgerIndex, j);
|
||||
}
|
||||
XRPL_ASSERT(
|
||||
neededHash,
|
||||
"ripple::RPC::getLedgerByContext : nonzero needed hash");
|
||||
ledgerHash = neededHash ? *neededHash : beast::zero; // kludge
|
||||
}
|
||||
|
||||
// Try to get the desired ledger
|
||||
// Verify all nodes even if we think we have it
|
||||
auto ledger = context.app.getInboundLedgers().acquire(
|
||||
ledgerHash, ledgerIndex, InboundLedger::Reason::GENERIC);
|
||||
|
||||
// In standalone mode, accept the ledger from the ledger cache
|
||||
if (!ledger && context.app.config().standalone())
|
||||
ledger = ledgerMaster.getLedgerByHash(ledgerHash);
|
||||
|
||||
if (ledger)
|
||||
return ledger;
|
||||
|
||||
if (auto il = context.app.getInboundLedgers().find(ledgerHash))
|
||||
return il->getJson(0);
|
||||
|
||||
return RPC::make_error(
|
||||
rpcNOT_READY, "findCreate failed to return an inbound ledger");
|
||||
}
|
||||
|
||||
} // namespace RPC
|
||||
} // namespace ripple
|
||||
|
||||
@@ -73,81 +73,6 @@ isRelatedToAccount(
|
||||
std::shared_ptr<SLE const> const& sle,
|
||||
AccountID const& accountID);
|
||||
|
||||
/** Gathers all objects for an account in a ledger.
|
||||
@param ledger Ledger to search account objects.
|
||||
@param account AccountID to find objects for.
|
||||
@param typeFilter Gathers objects of these types. empty gathers all types.
|
||||
@param dirIndex Begin gathering account objects from this directory.
|
||||
@param entryIndex Begin gathering objects from this directory node.
|
||||
@param limit Maximum number of objects to find.
|
||||
@param jvResult A JSON result that holds the request objects.
|
||||
*/
|
||||
bool
|
||||
getAccountObjects(
|
||||
ReadView const& ledger,
|
||||
AccountID const& account,
|
||||
std::optional<std::vector<LedgerEntryType>> const& typeFilter,
|
||||
uint256 dirIndex,
|
||||
uint256 entryIndex,
|
||||
std::uint32_t const limit,
|
||||
Json::Value& jvResult);
|
||||
|
||||
/** Get ledger by hash
|
||||
If there is no error in the return value, the ledger pointer will have
|
||||
been filled
|
||||
*/
|
||||
template <class T>
|
||||
Status
|
||||
getLedger(T& ledger, uint256 const& ledgerHash, Context& context);
|
||||
|
||||
/** Get ledger by sequence
|
||||
If there is no error in the return value, the ledger pointer will have
|
||||
been filled
|
||||
*/
|
||||
template <class T>
|
||||
Status
|
||||
getLedger(T& ledger, uint32_t ledgerIndex, Context& context);
|
||||
|
||||
enum LedgerShortcut { CURRENT, CLOSED, VALIDATED };
|
||||
/** Get ledger specified in shortcut.
|
||||
If there is no error in the return value, the ledger pointer will have
|
||||
been filled
|
||||
*/
|
||||
template <class T>
|
||||
Status
|
||||
getLedger(T& ledger, LedgerShortcut shortcut, Context& context);
|
||||
|
||||
/** Look up a ledger from a request and fill a Json::Result with either
|
||||
an error, or data representing a ledger.
|
||||
|
||||
If there is no error in the return value, then the ledger pointer will have
|
||||
been filled.
|
||||
*/
|
||||
Json::Value
|
||||
lookupLedger(std::shared_ptr<ReadView const>&, JsonContext&);
|
||||
|
||||
/** Look up a ledger from a request and fill a Json::Result with the data
|
||||
representing a ledger.
|
||||
|
||||
If the returned Status is OK, the ledger pointer will have been filled.
|
||||
*/
|
||||
Status
|
||||
lookupLedger(
|
||||
std::shared_ptr<ReadView const>&,
|
||||
JsonContext&,
|
||||
Json::Value& result);
|
||||
|
||||
template <class T, class R>
|
||||
Status
|
||||
ledgerFromRequest(T& ledger, GRPCContext<R>& context);
|
||||
|
||||
template <class T>
|
||||
Status
|
||||
ledgerFromSpecifier(
|
||||
T& ledger,
|
||||
org::xrpl::rpc::v1::LedgerSpecifier const& specifier,
|
||||
Context& context);
|
||||
|
||||
hash_set<AccountID>
|
||||
parseAccountIds(Json::Value const& jvArray);
|
||||
|
||||
@@ -194,11 +119,6 @@ chooseLedgerEntryType(Json::Value const& params);
|
||||
bool
|
||||
isAccountObjectsValidType(LedgerEntryType const& type);
|
||||
|
||||
/** Return a ledger based on ledger_hash or ledger_index,
|
||||
or an RPC error */
|
||||
std::variant<std::shared_ptr<Ledger const>, Json::Value>
|
||||
getLedgerByContext(RPC::JsonContext& context);
|
||||
|
||||
std::optional<std::pair<PublicKey, SecretKey>>
|
||||
keypairForSignature(
|
||||
Json::Value const& params,
|
||||
|
||||
458
src/xrpld/rpc/detail/RPCLedgerHelpers.cpp
Normal file
458
src/xrpld/rpc/detail/RPCLedgerHelpers.cpp
Normal file
@@ -0,0 +1,458 @@
|
||||
#include <xrpld/app/ledger/LedgerMaster.h>
|
||||
#include <xrpld/app/ledger/LedgerToJson.h>
|
||||
#include <xrpld/app/ledger/OpenLedger.h>
|
||||
#include <xrpld/app/misc/Transaction.h>
|
||||
#include <xrpld/app/paths/TrustLine.h>
|
||||
#include <xrpld/app/rdb/RelationalDatabase.h>
|
||||
#include <xrpld/app/tx/detail/NFTokenUtils.h>
|
||||
#include <xrpld/rpc/Context.h>
|
||||
#include <xrpld/rpc/DeliveredAmount.h>
|
||||
#include <xrpld/rpc/detail/RPCHelpers.h>
|
||||
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/RPCErr.h>
|
||||
#include <xrpl/protocol/nftPageMask.h>
|
||||
#include <xrpl/resource/Fees.h>
|
||||
|
||||
#include <boost/algorithm/string/case_conv.hpp>
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
|
||||
namespace ripple {
|
||||
namespace RPC {
|
||||
|
||||
namespace {
|
||||
|
||||
bool
|
||||
isValidatedOld(LedgerMaster& ledgerMaster, bool standalone)
|
||||
{
|
||||
if (standalone)
|
||||
return false;
|
||||
|
||||
return ledgerMaster.getValidatedLedgerAge() > Tuning::maxValidatedLedgerAge;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
Status
|
||||
ledgerFromRequest(T& ledger, JsonContext& context)
|
||||
{
|
||||
ledger.reset();
|
||||
|
||||
auto& params = context.params;
|
||||
|
||||
auto indexValue = params[jss::ledger_index];
|
||||
auto hashValue = params[jss::ledger_hash];
|
||||
|
||||
// We need to support the legacy "ledger" field.
|
||||
auto& legacyLedger = params[jss::ledger];
|
||||
if (legacyLedger)
|
||||
{
|
||||
if (legacyLedger.asString().size() > 12)
|
||||
hashValue = legacyLedger;
|
||||
else
|
||||
indexValue = legacyLedger;
|
||||
}
|
||||
|
||||
if (!hashValue.isNull())
|
||||
{
|
||||
if (!hashValue.isString())
|
||||
return {rpcINVALID_PARAMS, "ledgerHashNotString"};
|
||||
|
||||
uint256 ledgerHash;
|
||||
if (!ledgerHash.parseHex(hashValue.asString()))
|
||||
return {rpcINVALID_PARAMS, "ledgerHashMalformed"};
|
||||
return getLedger(ledger, ledgerHash, context);
|
||||
}
|
||||
|
||||
if (!indexValue.isConvertibleTo(Json::stringValue))
|
||||
return {rpcINVALID_PARAMS, "ledgerIndexMalformed"};
|
||||
|
||||
auto const index = indexValue.asString();
|
||||
|
||||
if (index == "current" || index.empty())
|
||||
return getLedger(ledger, LedgerShortcut::CURRENT, context);
|
||||
|
||||
if (index == "validated")
|
||||
return getLedger(ledger, LedgerShortcut::VALIDATED, context);
|
||||
|
||||
if (index == "closed")
|
||||
return getLedger(ledger, LedgerShortcut::CLOSED, context);
|
||||
|
||||
std::uint32_t val;
|
||||
if (!beast::lexicalCastChecked(val, index))
|
||||
return {rpcINVALID_PARAMS, "ledgerIndexMalformed"};
|
||||
|
||||
return getLedger(ledger, val, context);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
template <class T, class R>
|
||||
Status
|
||||
ledgerFromRequest(T& ledger, GRPCContext<R>& context)
|
||||
{
|
||||
R& request = context.params;
|
||||
return ledgerFromSpecifier(ledger, request.ledger(), context);
|
||||
}
|
||||
|
||||
// explicit instantiation of above function
|
||||
template Status
|
||||
ledgerFromRequest<>(
|
||||
std::shared_ptr<ReadView const>&,
|
||||
GRPCContext<org::xrpl::rpc::v1::GetLedgerEntryRequest>&);
|
||||
|
||||
// explicit instantiation of above function
|
||||
template Status
|
||||
ledgerFromRequest<>(
|
||||
std::shared_ptr<ReadView const>&,
|
||||
GRPCContext<org::xrpl::rpc::v1::GetLedgerDataRequest>&);
|
||||
|
||||
// explicit instantiation of above function
|
||||
template Status
|
||||
ledgerFromRequest<>(
|
||||
std::shared_ptr<ReadView const>&,
|
||||
GRPCContext<org::xrpl::rpc::v1::GetLedgerRequest>&);
|
||||
|
||||
template <class T>
|
||||
Status
|
||||
ledgerFromSpecifier(
|
||||
T& ledger,
|
||||
org::xrpl::rpc::v1::LedgerSpecifier const& specifier,
|
||||
Context& context)
|
||||
{
|
||||
ledger.reset();
|
||||
|
||||
using LedgerCase = org::xrpl::rpc::v1::LedgerSpecifier::LedgerCase;
|
||||
LedgerCase ledgerCase = specifier.ledger_case();
|
||||
switch (ledgerCase)
|
||||
{
|
||||
case LedgerCase::kHash: {
|
||||
if (auto hash = uint256::fromVoidChecked(specifier.hash()))
|
||||
{
|
||||
return getLedger(ledger, *hash, context);
|
||||
}
|
||||
return {rpcINVALID_PARAMS, "ledgerHashMalformed"};
|
||||
}
|
||||
case LedgerCase::kSequence:
|
||||
return getLedger(ledger, specifier.sequence(), context);
|
||||
case LedgerCase::kShortcut:
|
||||
[[fallthrough]];
|
||||
case LedgerCase::LEDGER_NOT_SET: {
|
||||
auto const shortcut = specifier.shortcut();
|
||||
if (shortcut ==
|
||||
org::xrpl::rpc::v1::LedgerSpecifier::SHORTCUT_VALIDATED)
|
||||
{
|
||||
return getLedger(ledger, LedgerShortcut::VALIDATED, context);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (shortcut ==
|
||||
org::xrpl::rpc::v1::LedgerSpecifier::SHORTCUT_CURRENT ||
|
||||
shortcut ==
|
||||
org::xrpl::rpc::v1::LedgerSpecifier::
|
||||
SHORTCUT_UNSPECIFIED)
|
||||
{
|
||||
return getLedger(ledger, LedgerShortcut::CURRENT, context);
|
||||
}
|
||||
else if (
|
||||
shortcut ==
|
||||
org::xrpl::rpc::v1::LedgerSpecifier::SHORTCUT_CLOSED)
|
||||
{
|
||||
return getLedger(ledger, LedgerShortcut::CLOSED, context);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Status::OK;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
Status
|
||||
getLedger(T& ledger, uint256 const& ledgerHash, Context& context)
|
||||
{
|
||||
ledger = context.ledgerMaster.getLedgerByHash(ledgerHash);
|
||||
if (ledger == nullptr)
|
||||
return {rpcLGR_NOT_FOUND, "ledgerNotFound"};
|
||||
return Status::OK;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
Status
|
||||
getLedger(T& ledger, uint32_t ledgerIndex, Context& context)
|
||||
{
|
||||
ledger = context.ledgerMaster.getLedgerBySeq(ledgerIndex);
|
||||
if (ledger == nullptr)
|
||||
{
|
||||
auto cur = context.ledgerMaster.getCurrentLedger();
|
||||
if (cur->info().seq == ledgerIndex)
|
||||
{
|
||||
ledger = cur;
|
||||
}
|
||||
}
|
||||
|
||||
if (ledger == nullptr)
|
||||
return {rpcLGR_NOT_FOUND, "ledgerNotFound"};
|
||||
|
||||
if (ledger->info().seq > context.ledgerMaster.getValidLedgerIndex() &&
|
||||
isValidatedOld(context.ledgerMaster, context.app.config().standalone()))
|
||||
{
|
||||
ledger.reset();
|
||||
if (context.apiVersion == 1)
|
||||
return {rpcNO_NETWORK, "InsufficientNetworkMode"};
|
||||
return {rpcNOT_SYNCED, "notSynced"};
|
||||
}
|
||||
|
||||
return Status::OK;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
Status
|
||||
getLedger(T& ledger, LedgerShortcut shortcut, Context& context)
|
||||
{
|
||||
if (isValidatedOld(context.ledgerMaster, context.app.config().standalone()))
|
||||
{
|
||||
if (context.apiVersion == 1)
|
||||
return {rpcNO_NETWORK, "InsufficientNetworkMode"};
|
||||
return {rpcNOT_SYNCED, "notSynced"};
|
||||
}
|
||||
|
||||
if (shortcut == LedgerShortcut::VALIDATED)
|
||||
{
|
||||
ledger = context.ledgerMaster.getValidatedLedger();
|
||||
if (ledger == nullptr)
|
||||
{
|
||||
if (context.apiVersion == 1)
|
||||
return {rpcNO_NETWORK, "InsufficientNetworkMode"};
|
||||
return {rpcNOT_SYNCED, "notSynced"};
|
||||
}
|
||||
|
||||
XRPL_ASSERT(
|
||||
!ledger->open(), "ripple::RPC::getLedger : validated is not open");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (shortcut == LedgerShortcut::CURRENT)
|
||||
{
|
||||
ledger = context.ledgerMaster.getCurrentLedger();
|
||||
XRPL_ASSERT(
|
||||
ledger->open(), "ripple::RPC::getLedger : current is open");
|
||||
}
|
||||
else if (shortcut == LedgerShortcut::CLOSED)
|
||||
{
|
||||
ledger = context.ledgerMaster.getClosedLedger();
|
||||
XRPL_ASSERT(
|
||||
!ledger->open(), "ripple::RPC::getLedger : closed is not open");
|
||||
}
|
||||
else
|
||||
{
|
||||
return {rpcINVALID_PARAMS, "ledgerIndexMalformed"};
|
||||
}
|
||||
|
||||
if (ledger == nullptr)
|
||||
{
|
||||
if (context.apiVersion == 1)
|
||||
return {rpcNO_NETWORK, "InsufficientNetworkMode"};
|
||||
return {rpcNOT_SYNCED, "notSynced"};
|
||||
}
|
||||
|
||||
static auto const minSequenceGap = 10;
|
||||
|
||||
if (ledger->info().seq + minSequenceGap <
|
||||
context.ledgerMaster.getValidLedgerIndex())
|
||||
{
|
||||
ledger.reset();
|
||||
if (context.apiVersion == 1)
|
||||
return {rpcNO_NETWORK, "InsufficientNetworkMode"};
|
||||
return {rpcNOT_SYNCED, "notSynced"};
|
||||
}
|
||||
}
|
||||
return Status::OK;
|
||||
}
|
||||
|
||||
// Explicit instantiation of above three functions
|
||||
template Status
|
||||
getLedger<>(std::shared_ptr<ReadView const>&, uint32_t, Context&);
|
||||
|
||||
template Status
|
||||
getLedger<>(
|
||||
std::shared_ptr<ReadView const>&,
|
||||
LedgerShortcut shortcut,
|
||||
Context&);
|
||||
|
||||
template Status
|
||||
getLedger<>(std::shared_ptr<ReadView const>&, uint256 const&, Context&);
|
||||
|
||||
// The previous version of the lookupLedger command would accept the
|
||||
// "ledger_index" argument as a string and silently treat it as a request to
|
||||
// return the current ledger which, while not strictly wrong, could cause a lot
|
||||
// of confusion.
|
||||
//
|
||||
// The code now robustly validates the input and ensures that the only possible
|
||||
// values for the "ledger_index" parameter are the index of a ledger passed as
|
||||
// an integer or one of the strings "current", "closed" or "validated".
|
||||
// Additionally, the code ensures that the value passed in "ledger_hash" is a
|
||||
// string and a valid hash. Invalid values will return an appropriate error
|
||||
// code.
|
||||
//
|
||||
// In the absence of the "ledger_hash" or "ledger_index" parameters, the code
|
||||
// assumes that "ledger_index" has the value "current".
|
||||
//
|
||||
// Returns a Json::objectValue. If there was an error, it will be in that
|
||||
// return value. Otherwise, the object contains the field "validated" and
|
||||
// optionally the fields "ledger_hash", "ledger_index" and
|
||||
// "ledger_current_index", if they are defined.
|
||||
Status
|
||||
lookupLedger(
|
||||
std::shared_ptr<ReadView const>& ledger,
|
||||
JsonContext& context,
|
||||
Json::Value& result)
|
||||
{
|
||||
if (auto status = ledgerFromRequest(ledger, context))
|
||||
return status;
|
||||
|
||||
auto& info = ledger->info();
|
||||
|
||||
if (!ledger->open())
|
||||
{
|
||||
result[jss::ledger_hash] = to_string(info.hash);
|
||||
result[jss::ledger_index] = info.seq;
|
||||
}
|
||||
else
|
||||
{
|
||||
result[jss::ledger_current_index] = info.seq;
|
||||
}
|
||||
|
||||
result[jss::validated] = context.ledgerMaster.isValidated(*ledger);
|
||||
return Status::OK;
|
||||
}
|
||||
|
||||
Json::Value
|
||||
lookupLedger(std::shared_ptr<ReadView const>& ledger, JsonContext& context)
|
||||
{
|
||||
Json::Value result;
|
||||
if (auto status = lookupLedger(ledger, context, result))
|
||||
status.inject(result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::variant<std::shared_ptr<Ledger const>, Json::Value>
|
||||
getLedgerByContext(RPC::JsonContext& context)
|
||||
{
|
||||
auto const hasHash = context.params.isMember(jss::ledger_hash);
|
||||
auto const hasIndex = context.params.isMember(jss::ledger_index);
|
||||
std::uint32_t ledgerIndex = 0;
|
||||
|
||||
auto& ledgerMaster = context.app.getLedgerMaster();
|
||||
LedgerHash ledgerHash;
|
||||
|
||||
if ((hasHash && hasIndex) || !(hasHash || hasIndex))
|
||||
{
|
||||
return RPC::make_param_error(
|
||||
"Exactly one of ledger_hash and ledger_index can be set.");
|
||||
}
|
||||
|
||||
context.loadType = Resource::feeHeavyBurdenRPC;
|
||||
|
||||
if (hasHash)
|
||||
{
|
||||
auto const& jsonHash = context.params[jss::ledger_hash];
|
||||
if (!jsonHash.isString() || !ledgerHash.parseHex(jsonHash.asString()))
|
||||
return RPC::invalid_field_error(jss::ledger_hash);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto const& jsonIndex = context.params[jss::ledger_index];
|
||||
if (!jsonIndex.isInt())
|
||||
return RPC::invalid_field_error(jss::ledger_index);
|
||||
|
||||
// We need a validated ledger to get the hash from the sequence
|
||||
if (ledgerMaster.getValidatedLedgerAge() >
|
||||
RPC::Tuning::maxValidatedLedgerAge)
|
||||
{
|
||||
if (context.apiVersion == 1)
|
||||
return rpcError(rpcNO_CURRENT);
|
||||
return rpcError(rpcNOT_SYNCED);
|
||||
}
|
||||
|
||||
ledgerIndex = jsonIndex.asInt();
|
||||
auto ledger = ledgerMaster.getValidatedLedger();
|
||||
|
||||
if (ledgerIndex >= ledger->info().seq)
|
||||
return RPC::make_param_error("Ledger index too large");
|
||||
if (ledgerIndex <= 0)
|
||||
return RPC::make_param_error("Ledger index too small");
|
||||
|
||||
auto const j = context.app.journal("RPCHandler");
|
||||
// Try to get the hash of the desired ledger from the validated
|
||||
// ledger
|
||||
auto neededHash = hashOfSeq(*ledger, ledgerIndex, j);
|
||||
if (!neededHash)
|
||||
{
|
||||
// Find a ledger more likely to have the hash of the desired
|
||||
// ledger
|
||||
auto const refIndex = getCandidateLedger(ledgerIndex);
|
||||
auto refHash = hashOfSeq(*ledger, refIndex, j);
|
||||
XRPL_ASSERT(
|
||||
refHash,
|
||||
"ripple::RPC::getLedgerByContext : nonzero ledger hash");
|
||||
|
||||
ledger = ledgerMaster.getLedgerByHash(*refHash);
|
||||
if (!ledger)
|
||||
{
|
||||
// We don't have the ledger we need to figure out which
|
||||
// ledger they want. Try to get it.
|
||||
|
||||
if (auto il = context.app.getInboundLedgers().acquire(
|
||||
*refHash, refIndex, InboundLedger::Reason::GENERIC))
|
||||
{
|
||||
Json::Value jvResult = RPC::make_error(
|
||||
rpcLGR_NOT_FOUND,
|
||||
"acquiring ledger containing requested index");
|
||||
jvResult[jss::acquiring] =
|
||||
getJson(LedgerFill(*il, &context));
|
||||
return jvResult;
|
||||
}
|
||||
|
||||
if (auto il = context.app.getInboundLedgers().find(*refHash))
|
||||
{
|
||||
Json::Value jvResult = RPC::make_error(
|
||||
rpcLGR_NOT_FOUND,
|
||||
"acquiring ledger containing requested index");
|
||||
jvResult[jss::acquiring] = il->getJson(0);
|
||||
return jvResult;
|
||||
}
|
||||
|
||||
// Likely the app is shutting down
|
||||
return Json::Value();
|
||||
}
|
||||
|
||||
neededHash = hashOfSeq(*ledger, ledgerIndex, j);
|
||||
}
|
||||
XRPL_ASSERT(
|
||||
neededHash,
|
||||
"ripple::RPC::getLedgerByContext : nonzero needed hash");
|
||||
ledgerHash = neededHash ? *neededHash : beast::zero; // kludge
|
||||
}
|
||||
|
||||
// Try to get the desired ledger
|
||||
// Verify all nodes even if we think we have it
|
||||
auto ledger = context.app.getInboundLedgers().acquire(
|
||||
ledgerHash, ledgerIndex, InboundLedger::Reason::GENERIC);
|
||||
|
||||
// In standalone mode, accept the ledger from the ledger cache
|
||||
if (!ledger && context.app.config().standalone())
|
||||
ledger = ledgerMaster.getLedgerByHash(ledgerHash);
|
||||
|
||||
if (ledger)
|
||||
return ledger;
|
||||
|
||||
if (auto il = context.app.getInboundLedgers().find(ledgerHash))
|
||||
return il->getJson(0);
|
||||
|
||||
return RPC::make_error(
|
||||
rpcNOT_READY, "findCreate failed to return an inbound ledger");
|
||||
}
|
||||
|
||||
} // namespace RPC
|
||||
} // namespace ripple
|
||||
96
src/xrpld/rpc/detail/RPCLedgerHelpers.h
Normal file
96
src/xrpld/rpc/detail/RPCLedgerHelpers.h
Normal file
@@ -0,0 +1,96 @@
|
||||
#ifndef XRPL_RPC_RPCLEDGERHELPERS_H_INCLUDED
|
||||
#define XRPL_RPC_RPCLEDGERHELPERS_H_INCLUDED
|
||||
|
||||
#include <xrpld/app/misc/NetworkOPs.h>
|
||||
#include <xrpld/app/misc/TxQ.h>
|
||||
#include <xrpld/rpc/Context.h>
|
||||
#include <xrpld/rpc/Status.h>
|
||||
#include <xrpld/rpc/detail/Tuning.h>
|
||||
|
||||
#include <xrpl/beast/core/SemanticVersion.h>
|
||||
#include <xrpl/proto/org/xrpl/rpc/v1/xrp_ledger.pb.h>
|
||||
#include <xrpl/protocol/ApiVersion.h>
|
||||
#include <xrpl/protocol/SecretKey.h>
|
||||
|
||||
#include <optional>
|
||||
#include <variant>
|
||||
|
||||
namespace Json {
|
||||
class Value;
|
||||
}
|
||||
|
||||
namespace ripple {
|
||||
|
||||
class ReadView;
|
||||
class Transaction;
|
||||
|
||||
namespace RPC {
|
||||
|
||||
struct JsonContext;
|
||||
|
||||
/** Get ledger by hash
|
||||
If there is no error in the return value, the ledger pointer will have
|
||||
been filled
|
||||
*/
|
||||
template <class T>
|
||||
Status
|
||||
getLedger(T& ledger, uint256 const& ledgerHash, Context& context);
|
||||
|
||||
/** Get ledger by sequence
|
||||
If there is no error in the return value, the ledger pointer will have
|
||||
been filled
|
||||
*/
|
||||
template <class T>
|
||||
Status
|
||||
getLedger(T& ledger, uint32_t ledgerIndex, Context& context);
|
||||
|
||||
enum LedgerShortcut { CURRENT, CLOSED, VALIDATED };
|
||||
/** Get ledger specified in shortcut.
|
||||
If there is no error in the return value, the ledger pointer will have
|
||||
been filled
|
||||
*/
|
||||
template <class T>
|
||||
Status
|
||||
getLedger(T& ledger, LedgerShortcut shortcut, Context& context);
|
||||
|
||||
/** Look up a ledger from a request and fill a Json::Result with either
|
||||
an error, or data representing a ledger.
|
||||
|
||||
If there is no error in the return value, then the ledger pointer will have
|
||||
been filled.
|
||||
*/
|
||||
Json::Value
|
||||
lookupLedger(std::shared_ptr<ReadView const>&, JsonContext&);
|
||||
|
||||
/** Look up a ledger from a request and fill a Json::Result with the data
|
||||
representing a ledger.
|
||||
|
||||
If the returned Status is OK, the ledger pointer will have been filled.
|
||||
*/
|
||||
Status
|
||||
lookupLedger(
|
||||
std::shared_ptr<ReadView const>&,
|
||||
JsonContext&,
|
||||
Json::Value& result);
|
||||
|
||||
template <class T, class R>
|
||||
Status
|
||||
ledgerFromRequest(T& ledger, GRPCContext<R>& context);
|
||||
|
||||
template <class T>
|
||||
Status
|
||||
ledgerFromSpecifier(
|
||||
T& ledger,
|
||||
org::xrpl::rpc::v1::LedgerSpecifier const& specifier,
|
||||
Context& context);
|
||||
|
||||
/** Return a ledger based on ledger_hash or ledger_index,
|
||||
or an RPC error */
|
||||
std::variant<std::shared_ptr<Ledger const>, Json::Value>
|
||||
getLedgerByContext(RPC::JsonContext& context);
|
||||
|
||||
} // namespace RPC
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
#endif
|
||||
@@ -2,6 +2,7 @@
|
||||
#include <xrpld/app/misc/AMMUtils.h>
|
||||
#include <xrpld/rpc/Context.h>
|
||||
#include <xrpld/rpc/detail/RPCHelpers.h>
|
||||
#include <xrpld/rpc/detail/RPCLedgerHelpers.h>
|
||||
|
||||
#include <xrpl/json/json_value.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include <xrpld/rpc/Context.h>
|
||||
#include <xrpld/rpc/detail/RPCHelpers.h>
|
||||
#include <xrpld/rpc/detail/RPCLedgerHelpers.h>
|
||||
#include <xrpld/rpc/detail/Tuning.h>
|
||||
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#include <xrpld/app/paths/TrustLine.h>
|
||||
#include <xrpld/rpc/Context.h>
|
||||
#include <xrpld/rpc/detail/RPCHelpers.h>
|
||||
#include <xrpld/rpc/detail/RPCLedgerHelpers.h>
|
||||
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/protocol/ErrorCodes.h>
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <xrpld/rpc/Context.h>
|
||||
#include <xrpld/rpc/GRPCHandlers.h>
|
||||
#include <xrpld/rpc/detail/RPCHelpers.h>
|
||||
#include <xrpld/rpc/detail/RPCLedgerHelpers.h>
|
||||
|
||||
#include <xrpl/json/json_value.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include <xrpld/app/paths/TrustLine.h>
|
||||
#include <xrpld/rpc/Context.h>
|
||||
#include <xrpld/rpc/detail/RPCHelpers.h>
|
||||
#include <xrpld/rpc/detail/RPCLedgerHelpers.h>
|
||||
#include <xrpld/rpc/detail/Tuning.h>
|
||||
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include <xrpld/app/tx/detail/NFTokenUtils.h>
|
||||
#include <xrpld/rpc/Context.h>
|
||||
#include <xrpld/rpc/detail/RPCHelpers.h>
|
||||
#include <xrpld/rpc/detail/RPCLedgerHelpers.h>
|
||||
#include <xrpld/rpc/detail/Tuning.h>
|
||||
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
@@ -158,6 +159,198 @@ doAccountNFTs(RPC::JsonContext& context)
|
||||
return result;
|
||||
}
|
||||
|
||||
bool
|
||||
getAccountObjects(
|
||||
ReadView const& ledger,
|
||||
AccountID const& account,
|
||||
std::optional<std::vector<LedgerEntryType>> const& typeFilter,
|
||||
uint256 dirIndex,
|
||||
uint256 entryIndex,
|
||||
std::uint32_t const limit,
|
||||
Json::Value& jvResult)
|
||||
{
|
||||
// check if dirIndex is valid
|
||||
if (!dirIndex.isZero() && !ledger.read({ltDIR_NODE, dirIndex}))
|
||||
return false;
|
||||
|
||||
auto typeMatchesFilter = [](std::vector<LedgerEntryType> const& typeFilter,
|
||||
LedgerEntryType ledgerType) {
|
||||
auto it = std::find(typeFilter.begin(), typeFilter.end(), ledgerType);
|
||||
return it != typeFilter.end();
|
||||
};
|
||||
|
||||
// if dirIndex != 0, then all NFTs have already been returned. only
|
||||
// iterate NFT pages if the filter says so AND dirIndex == 0
|
||||
bool iterateNFTPages =
|
||||
(!typeFilter.has_value() ||
|
||||
typeMatchesFilter(typeFilter.value(), ltNFTOKEN_PAGE)) &&
|
||||
dirIndex == beast::zero;
|
||||
|
||||
Keylet const firstNFTPage = keylet::nftpage_min(account);
|
||||
|
||||
// we need to check the marker to see if it is an NFTTokenPage index.
|
||||
if (iterateNFTPages && entryIndex != beast::zero)
|
||||
{
|
||||
// if it is we will try to iterate the pages up to the limit
|
||||
// and then change over to the owner directory
|
||||
|
||||
if (firstNFTPage.key != (entryIndex & ~nft::pageMask))
|
||||
iterateNFTPages = false;
|
||||
}
|
||||
|
||||
auto& jvObjects = (jvResult[jss::account_objects] = Json::arrayValue);
|
||||
|
||||
// this is a mutable version of limit, used to seamlessly switch
|
||||
// to iterating directory entries when nftokenpages are exhausted
|
||||
uint32_t mlimit = limit;
|
||||
|
||||
// iterate NFTokenPages preferentially
|
||||
if (iterateNFTPages)
|
||||
{
|
||||
Keylet const first = entryIndex == beast::zero
|
||||
? firstNFTPage
|
||||
: Keylet{ltNFTOKEN_PAGE, entryIndex};
|
||||
|
||||
Keylet const last = keylet::nftpage_max(account);
|
||||
|
||||
// current key
|
||||
uint256 ck = ledger.succ(first.key, last.key.next()).value_or(last.key);
|
||||
|
||||
// current page
|
||||
auto cp = ledger.read(Keylet{ltNFTOKEN_PAGE, ck});
|
||||
|
||||
while (cp)
|
||||
{
|
||||
jvObjects.append(cp->getJson(JsonOptions::none));
|
||||
auto const npm = (*cp)[~sfNextPageMin];
|
||||
if (npm)
|
||||
cp = ledger.read(Keylet(ltNFTOKEN_PAGE, *npm));
|
||||
else
|
||||
cp = nullptr;
|
||||
|
||||
if (--mlimit == 0)
|
||||
{
|
||||
if (cp)
|
||||
{
|
||||
jvResult[jss::limit] = limit;
|
||||
jvResult[jss::marker] = std::string("0,") + to_string(ck);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!npm)
|
||||
break;
|
||||
|
||||
ck = *npm;
|
||||
}
|
||||
|
||||
// if execution reaches here then we're about to transition
|
||||
// to iterating the root directory (and the conventional
|
||||
// behaviour of this RPC function.) Therefore we should
|
||||
// zero entryIndex so as not to terribly confuse things.
|
||||
entryIndex = beast::zero;
|
||||
}
|
||||
|
||||
auto const root = keylet::ownerDir(account);
|
||||
auto found = false;
|
||||
|
||||
if (dirIndex.isZero())
|
||||
{
|
||||
dirIndex = root.key;
|
||||
found = true;
|
||||
}
|
||||
|
||||
auto dir = ledger.read({ltDIR_NODE, dirIndex});
|
||||
if (!dir)
|
||||
{
|
||||
// it's possible the user had nftoken pages but no
|
||||
// directory entries. If there's no nftoken page, we will
|
||||
// give empty array for account_objects.
|
||||
if (mlimit >= limit)
|
||||
jvResult[jss::account_objects] = Json::arrayValue;
|
||||
|
||||
// non-zero dirIndex validity was checked in the beginning of this
|
||||
// function; by this point, it should be zero. This function returns
|
||||
// true regardless of nftoken page presence; if absent, account_objects
|
||||
// is already set as an empty array. Notice we will only return false in
|
||||
// this function when entryIndex can not be found, indicating an invalid
|
||||
// marker error.
|
||||
return true;
|
||||
}
|
||||
|
||||
std::uint32_t i = 0;
|
||||
for (;;)
|
||||
{
|
||||
auto const& entries = dir->getFieldV256(sfIndexes);
|
||||
auto iter = entries.begin();
|
||||
|
||||
if (!found)
|
||||
{
|
||||
iter = std::find(iter, entries.end(), entryIndex);
|
||||
if (iter == entries.end())
|
||||
return false;
|
||||
|
||||
found = true;
|
||||
}
|
||||
|
||||
// it's possible that the returned NFTPages exactly filled the
|
||||
// response. Check for that condition.
|
||||
if (i == mlimit && mlimit < limit)
|
||||
{
|
||||
jvResult[jss::limit] = limit;
|
||||
jvResult[jss::marker] =
|
||||
to_string(dirIndex) + ',' + to_string(*iter);
|
||||
return true;
|
||||
}
|
||||
|
||||
for (; iter != entries.end(); ++iter)
|
||||
{
|
||||
auto const sleNode = ledger.read(keylet::child(*iter));
|
||||
|
||||
if (!typeFilter.has_value() ||
|
||||
typeMatchesFilter(typeFilter.value(), sleNode->getType()))
|
||||
{
|
||||
jvObjects.append(sleNode->getJson(JsonOptions::none));
|
||||
}
|
||||
|
||||
if (++i == mlimit)
|
||||
{
|
||||
if (++iter != entries.end())
|
||||
{
|
||||
jvResult[jss::limit] = limit;
|
||||
jvResult[jss::marker] =
|
||||
to_string(dirIndex) + ',' + to_string(*iter);
|
||||
return true;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
auto const nodeIndex = dir->getFieldU64(sfIndexNext);
|
||||
if (nodeIndex == 0)
|
||||
return true;
|
||||
|
||||
dirIndex = keylet::page(root, nodeIndex).key;
|
||||
dir = ledger.read({ltDIR_NODE, dirIndex});
|
||||
if (!dir)
|
||||
return true;
|
||||
|
||||
if (i == mlimit)
|
||||
{
|
||||
auto const& e = dir->getFieldV256(sfIndexes);
|
||||
if (!e.empty())
|
||||
{
|
||||
jvResult[jss::limit] = limit;
|
||||
jvResult[jss::marker] =
|
||||
to_string(dirIndex) + ',' + to_string(*e.begin());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Json::Value
|
||||
doAccountObjects(RPC::JsonContext& context)
|
||||
{
|
||||
@@ -265,7 +458,7 @@ doAccountObjects(RPC::JsonContext& context)
|
||||
return RPC::invalid_field_error(jss::marker);
|
||||
}
|
||||
|
||||
if (!RPC::getAccountObjects(
|
||||
if (!getAccountObjects(
|
||||
*ledger,
|
||||
accountID,
|
||||
typeFilter,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include <xrpld/rpc/Context.h>
|
||||
#include <xrpld/rpc/detail/RPCHelpers.h>
|
||||
#include <xrpld/rpc/detail/RPCLedgerHelpers.h>
|
||||
#include <xrpld/rpc/detail/Tuning.h>
|
||||
|
||||
#include <xrpl/json/json_value.h>
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <xrpld/rpc/BookChanges.h>
|
||||
#include <xrpld/rpc/Context.h>
|
||||
#include <xrpld/rpc/detail/RPCHelpers.h>
|
||||
#include <xrpld/rpc/detail/RPCLedgerHelpers.h>
|
||||
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#include <xrpld/rpc/Context.h>
|
||||
#include <xrpld/rpc/detail/RPCHelpers.h>
|
||||
#include <xrpld/rpc/detail/RPCLedgerHelpers.h>
|
||||
|
||||
#include <xrpl/ledger/CredentialHelpers.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#include <xrpld/app/main/Application.h>
|
||||
#include <xrpld/app/paths/TrustLine.h>
|
||||
#include <xrpld/rpc/Context.h>
|
||||
#include <xrpld/rpc/detail/RPCHelpers.h>
|
||||
#include <xrpld/rpc/detail/RPCLedgerHelpers.h>
|
||||
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#include <xrpld/app/ledger/LedgerMaster.h>
|
||||
#include <xrpld/app/main/Application.h>
|
||||
#include <xrpld/rpc/Context.h>
|
||||
#include <xrpld/rpc/detail/RPCHelpers.h>
|
||||
#include <xrpld/rpc/detail/RPCLedgerHelpers.h>
|
||||
|
||||
#include <xrpl/json/json_value.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <xrpld/rpc/GRPCHandlers.h>
|
||||
#include <xrpld/rpc/Role.h>
|
||||
#include <xrpld/rpc/detail/RPCHelpers.h>
|
||||
#include <xrpld/rpc/detail/RPCLedgerHelpers.h>
|
||||
#include <xrpld/rpc/detail/Tuning.h>
|
||||
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#include <xrpld/rpc/GRPCHandlers.h>
|
||||
#include <xrpld/rpc/detail/RPCHelpers.h>
|
||||
#include <xrpld/rpc/detail/RPCLedgerHelpers.h>
|
||||
|
||||
namespace ripple {
|
||||
std::pair<org::xrpl::rpc::v1::GetLedgerDiffResponse, grpc::Status>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#include <xrpld/rpc/Context.h>
|
||||
#include <xrpld/rpc/GRPCHandlers.h>
|
||||
#include <xrpld/rpc/detail/RPCHelpers.h>
|
||||
#include <xrpld/rpc/detail/RPCLedgerHelpers.h>
|
||||
#include <xrpld/rpc/handlers/LedgerEntryHelpers.h>
|
||||
|
||||
#include <xrpl/basics/StringUtilities.h>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#include <xrpld/app/misc/LoadFeeTrack.h>
|
||||
#include <xrpld/rpc/GRPCHandlers.h>
|
||||
#include <xrpld/rpc/Role.h>
|
||||
#include <xrpld/rpc/detail/RPCHelpers.h>
|
||||
#include <xrpld/rpc/detail/RPCLedgerHelpers.h>
|
||||
#include <xrpld/rpc/handlers/LedgerHandler.h>
|
||||
|
||||
#include <xrpl/protocol/ErrorCodes.h>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#include <xrpld/app/ledger/LedgerToJson.h>
|
||||
#include <xrpld/rpc/detail/RPCHelpers.h>
|
||||
#include <xrpld/rpc/detail/RPCLedgerHelpers.h>
|
||||
|
||||
#include <xrpl/basics/strHex.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#include <xrpld/app/ledger/LedgerToJson.h>
|
||||
#include <xrpld/rpc/Context.h>
|
||||
#include <xrpld/rpc/detail/RPCHelpers.h>
|
||||
#include <xrpld/rpc/detail/RPCLedgerHelpers.h>
|
||||
|
||||
#include <xrpl/protocol/ErrorCodes.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include <xrpld/rpc/Context.h>
|
||||
#include <xrpld/rpc/detail/RPCHelpers.h>
|
||||
#include <xrpld/rpc/detail/RPCLedgerHelpers.h>
|
||||
#include <xrpld/rpc/detail/Tuning.h>
|
||||
|
||||
#include <xrpl/json/json_value.h>
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <xrpld/app/paths/TrustLine.h>
|
||||
#include <xrpld/rpc/Context.h>
|
||||
#include <xrpld/rpc/detail/RPCHelpers.h>
|
||||
#include <xrpld/rpc/detail/RPCLedgerHelpers.h>
|
||||
#include <xrpld/rpc/detail/Tuning.h>
|
||||
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#include <xrpld/app/paths/PathRequests.h>
|
||||
#include <xrpld/rpc/Context.h>
|
||||
#include <xrpld/rpc/detail/LegacyPathFind.h>
|
||||
#include <xrpld/rpc/detail/RPCHelpers.h>
|
||||
#include <xrpld/rpc/detail/RPCLedgerHelpers.h>
|
||||
|
||||
#include <xrpl/protocol/RPCErr.h>
|
||||
#include <xrpl/resource/Fees.h>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#include <xrpld/app/ledger/LedgerMaster.h>
|
||||
#include <xrpld/app/misc/DeliverMax.h>
|
||||
#include <xrpld/rpc/Context.h>
|
||||
#include <xrpld/rpc/detail/RPCHelpers.h>
|
||||
#include <xrpld/rpc/detail/RPCLedgerHelpers.h>
|
||||
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#include <xrpld/rpc/Context.h>
|
||||
#include <xrpld/rpc/detail/RPCHelpers.h>
|
||||
#include <xrpld/rpc/detail/RPCLedgerHelpers.h>
|
||||
|
||||
#include <xrpl/beast/utility/Zero.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
|
||||
Reference in New Issue
Block a user