Compare commits

..

8 Commits

Author SHA1 Message Date
Bart
801c4b95a7 Merge branch 'develop' into bthomee/jq 2026-06-17 10:17:41 -04:00
Bart
8f67a2a1cc Merge branch 'develop' into bthomee/jq 2026-06-10 17:22:32 -04:00
Bart
77aa8715f0 Improve log message 2026-06-06 19:06:31 -04:00
Bart
e67a980849 Improve comment 2026-06-06 18:56:19 -04:00
Bart
5771c38406 Limit number of requested nodes to handle 2026-06-06 18:52:14 -04:00
Bart
ae1b5b6bac Post charge to peer strand 2026-06-06 17:21:19 -04:00
Bart
ecd0136844 Improve log message 2026-06-06 09:29:26 -04:00
Bart
02f20331d5 refactor: Deserialize received nodes once and only in job queue 2026-06-06 09:17:26 -04:00
14 changed files with 65 additions and 583 deletions

View File

@@ -161,7 +161,6 @@ test.peerfinder > xrpl.protocol
test.protocol > test.jtx
test.protocol > test.unit_test
test.protocol > xrpl.basics
test.protocol > xrpld.core
test.protocol > xrpl.json
test.protocol > xrpl.protocol
test.resource > test.unit_test

View File

@@ -20,6 +20,8 @@ _SANITIZER_SUFFIX: dict[str, str] = {
def get_cmake_args(build_type: str, extra_args: str) -> str:
"""Get the full list of CMake arguments for a config."""
args = _BASE_CMAKE_ARGS.copy()
if build_type == "Release":
args.append("-Dassert=ON")
if extra_args:
args.extend(extra_args.split())
return " ".join(args)

View File

@@ -78,7 +78,7 @@ class BaseUInt
// This is really big-endian in byte order.
// We sometimes use std::uint32_t for speed.
std::array<std::uint32_t, kWidth> data_{};
std::array<std::uint32_t, kWidth> data_;
public:
//--------------------------------------------------------------------------

View File

@@ -4,7 +4,7 @@
/*
ASAN flags some false positives with sudden jumps in control flow, like
exceptions, or when encountering coroutine stack switches. This macro can be used to disable ASAN
instrumentation for specific functions.
intrumentation for specific functions.
*/
#if defined(__GNUC__) || defined(__clang__)
#define XRPL_NO_SANITIZE_ADDRESS __attribute__((no_sanitize("address", "hwaddress")))

View File

@@ -36,13 +36,13 @@ checkFields(STTx const& tx, beast::Journal j);
TER
valid(STTx const& tx, ReadView const& view, AccountID const& src, beast::Journal j);
// Check if subject has any credential matching the given domain. If you call it
// Check if subject has any credential maching the given domain. If you call it
// in preclaim and it returns tecEXPIRED, you should call verifyValidDomain in
// doApply. This will ensure that expired credentials are deleted.
TER
validDomain(ReadView const& view, uint256 domainID, AccountID const& subject);
// This function is only called when we are about to return tecNO_PERMISSION
// This function is only called when we about to return tecNO_PERMISSION
// because all the checks for the DepositPreauth authorization failed.
TER
authorizedDepositPreauth(ReadView const& view, STVector256 const& ctx, AccountID const& dst);
@@ -58,7 +58,7 @@ checkArray(STArray const& credentials, unsigned maxSize, beast::Journal j);
} // namespace credentials
// Check expired credentials and for credentials matching DomainID of the ledger
// Check expired credentials and for credentials maching DomainID of the ledger
// object
TER
verifyValidDomain(ApplyView& view, AccountID const& account, uint256 domainID, beast::Journal j);

View File

@@ -22,7 +22,6 @@ in
git-lfs
gnumake
gnupg # needed for signing commits & codecov/codecov-action
graphviz
llvmPackages_22.clang-tools
less # needed for git diff
mold

View File

@@ -939,46 +939,6 @@ struct LedgerReplayer_test : public beast::unit_test::Suite
BEAST_EXPECT(!reply->has_error());
BEAST_EXPECT(server.msgHandler.processProofPathResponse(reply));
{
// bad reply: invalid hash/key sizes
{
// reply with undersized ledgerhash (31 bytes)
auto bad = std::make_shared<protocol::TMProofPathResponse>(*reply);
bad->set_ledgerhash(std::string(31, '\x01'));
BEAST_EXPECT(!server.msgHandler.processProofPathResponse(bad));
}
{
// reply with oversized ledgerhash (33 bytes)
auto bad = std::make_shared<protocol::TMProofPathResponse>(*reply);
bad->set_ledgerhash(std::string(33, '\x01'));
BEAST_EXPECT(!server.msgHandler.processProofPathResponse(bad));
}
{
// reply with empty ledgerhash
auto bad = std::make_shared<protocol::TMProofPathResponse>(*reply);
bad->set_ledgerhash(std::string());
BEAST_EXPECT(!server.msgHandler.processProofPathResponse(bad));
}
{
// reply with undersized key (31 bytes)
auto bad = std::make_shared<protocol::TMProofPathResponse>(*reply);
bad->set_key(std::string(31, '\x01'));
BEAST_EXPECT(!server.msgHandler.processProofPathResponse(bad));
}
{
// reply with oversized key (33 bytes)
auto bad = std::make_shared<protocol::TMProofPathResponse>(*reply);
bad->set_key(std::string(33, '\x01'));
BEAST_EXPECT(!server.msgHandler.processProofPathResponse(bad));
}
{
// reply with empty key
auto bad = std::make_shared<protocol::TMProofPathResponse>(*reply);
bad->set_key(std::string());
BEAST_EXPECT(!server.msgHandler.processProofPathResponse(bad));
}
}
{
// bad reply
// bad header
@@ -1028,28 +988,6 @@ struct LedgerReplayer_test : public beast::unit_test::Suite
BEAST_EXPECT(!reply->has_error());
BEAST_EXPECT(server.msgHandler.processReplayDeltaResponse(reply));
{
// bad reply: invalid hash sizes
{
// reply with undersized ledgerhash (31 bytes)
auto bad = std::make_shared<protocol::TMReplayDeltaResponse>(*reply);
bad->set_ledgerhash(std::string(31, '\x01'));
BEAST_EXPECT(!server.msgHandler.processReplayDeltaResponse(bad));
}
{
// reply with oversized ledgerhash (33 bytes)
auto bad = std::make_shared<protocol::TMReplayDeltaResponse>(*reply);
bad->set_ledgerhash(std::string(33, '\x01'));
BEAST_EXPECT(!server.msgHandler.processReplayDeltaResponse(bad));
}
{
// reply with empty ledgerhash
auto bad = std::make_shared<protocol::TMReplayDeltaResponse>(*reply);
bad->set_ledgerhash(std::string());
BEAST_EXPECT(!server.msgHandler.processReplayDeltaResponse(bad));
}
}
{
// bad reply
// bad header

View File

@@ -117,34 +117,6 @@ struct base_uint_test : beast::unit_test::Suite
}
}
#ifdef NDEBUG
void
testFromRawSizeMismatch()
{
testcase("base_uint: fromRaw size mismatch");
// Container smaller than the base_uint (8 bytes vs 12 bytes for
// test96). Only the first 8 bytes are copied; the remaining 4 bytes
// stay zero.
{
Blob const tooSmall{1, 2, 3, 4, 5, 6, 7, 8};
test96 const result = test96::fromRaw(tooSmall);
auto const resultText = to_string(result);
BEAST_EXPECTS(resultText == "010203040506070800000000", resultText);
}
// Container larger than the base_uint (16 bytes vs 12 bytes for
// test96). Only the first 12 bytes are copied; the extra bytes are
// ignored.
{
Blob const tooBig{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};
test96 const result = test96::fromRaw(tooBig);
auto const resultText = to_string(result);
BEAST_EXPECTS(resultText == "0102030405060708090A0B0C", resultText);
}
}
#endif
void
run() override
{
@@ -153,10 +125,6 @@ struct base_uint_test : beast::unit_test::Suite
static_assert(!std::is_constructible_v<test96, std::complex<double>>);
static_assert(!std::is_assignable_v<test96&, std::complex<double>>);
#ifdef NDEBUG
testFromRawSizeMismatch();
#endif
testComparisons();
// used to verify set insertion (hashing required)

View File

@@ -1,12 +1,10 @@
#include <xrpl/basics/UnorderedContainers.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/unit_test/suite.h>
#include <xrpl/json/json_value.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Book.h>
#include <xrpl/protocol/Issue.h>
#include <xrpl/protocol/UintTypes.h>
#include <xrpl/protocol/jss.h>
#include <functional>
#include <map>
@@ -865,101 +863,6 @@ public:
//--------------------------------------------------------------------------
void
testIssueFromJson()
{
testcase("issueFromJson");
// Valid XRP — no issuer field
{
json::Value jv;
jv[jss::currency] = "XRP";
auto const issue = issueFromJson(jv);
BEAST_EXPECT(isXRP(issue));
}
// Valid IOU — legitimate issuer
{
json::Value jv;
jv[jss::currency] = "USD";
jv[jss::issuer] = "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh";
auto const issue = issueFromJson(jv);
BEAST_EXPECT(!isXRP(issue));
BEAST_EXPECT(issue.account != noAccount());
}
// noAccount() is the MPT sentinel in binary serialization - must be
// rejected
try
{
json::Value jv;
jv[jss::currency] = "USD";
jv[jss::issuer] = to_string(noAccount());
issueFromJson(jv);
fail("noAccount() accepted as IOU issuer");
}
catch (...)
{
pass();
}
// xrpAccount() is the XRP sentinel (all zeros) - must be rejected
// as IOU issuer
try
{
json::Value jv;
jv[jss::currency] = "USD";
jv[jss::issuer] = to_string(xrpAccount());
issueFromJson(jv);
fail("xrpAccount() accepted as IOU issuer");
}
catch (...)
{
pass();
}
// Invalid base58 — must be rejected
try
{
json::Value jv;
jv[jss::currency] = "USD";
jv[jss::issuer] = "not_a_valid_address";
issueFromJson(jv);
fail("invalid base58 accepted as IOU issuer");
}
catch (...)
{
pass();
}
// Non-XRP currency with no issuer field — must be rejected
try
{
json::Value jv;
jv[jss::currency] = "USD";
issueFromJson(jv);
fail("missing issuer accepted");
}
catch (...)
{
pass();
}
// XRP with an issuer field — must be rejected
try
{
json::Value jv;
jv[jss::currency] = "XRP";
jv[jss::issuer] = "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh";
issueFromJson(jv);
fail("XRP with issuer accepted");
}
catch (...)
{
pass();
}
}
void
run() override
{
@@ -994,9 +897,6 @@ public:
// ---
testIssueDomainSets();
testIssueDomainMaps();
// ---
testIssueFromJson();
}
};

View File

@@ -1,24 +1,15 @@
#include <test/jtx/Account.h>
#include <test/jtx/Env.h>
#include <test/jtx/amount.h> // IWYU pragma: keep
#include <test/jtx/envconfig.h>
#include <xrpld/core/Config.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/unit_test/suite.h>
#include <xrpl/json/json_value.h>
#include <xrpl/json/to_string.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Issue.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STIssue.h>
#include <xrpl/protocol/Serializer.h>
#include <xrpl/protocol/UintTypes.h>
#include <xrpl/protocol/jss.h>
#include <memory>
namespace xrpl::test {
@@ -146,143 +137,12 @@ public:
"000000000000000000000000000000000000000000000002");
}
void
testNoAccountIssuerRpc()
{
testcase("noAccount issuer rejected via RPC sign");
using namespace jtx;
Env env{*this, envconfig([](std::unique_ptr<Config> cfg) {
cfg->loadFromString("[signing_support]\ntrue");
return cfg;
})};
Account const alice{"alice"};
env.fund(XRP(10000), alice);
env.close();
json::Value txJson;
txJson[jss::TransactionType] = "AMMDelete";
txJson[jss::Account] = alice.human();
txJson[jss::Asset][jss::currency] = "USD";
txJson[jss::Asset][jss::issuer] = to_string(noAccount());
txJson[jss::Asset2][jss::currency] = "XRP";
json::Value req;
req[jss::tx_json] = txJson;
req[jss::secret] = alice.name();
auto const result = env.rpc("json", "sign", to_string(req))[jss::result];
BEAST_EXPECT(result[jss::status] == "error");
BEAST_EXPECT(result.isMember(jss::error));
}
void
testNoAccountIssuer()
{
testcase("noAccount issuer rejection");
{
json::Value jv;
jv[jss::currency] = "USD";
jv[jss::issuer] = to_string(noAccount());
try
{
issueFromJson(sfAsset, jv);
fail("issueFromJson accepted noAccount() as IOU issuer");
}
catch (...)
{
pass();
}
}
{
Serializer s;
s.addBitString(toCurrency("USD"));
s.addBitString(noAccount());
SerialIter iter(s.slice());
try
{
STIssue const stissue(iter, sfAsset);
fail(
"STIssue deserialization of [USD][noAccount()] should "
"throw");
}
catch (...)
{
pass();
}
}
}
void
testXrpAccountIssuerRpc()
{
testcase("xrpAccount issuer rejected via RPC sign");
using namespace jtx;
Env env{*this, envconfig([](std::unique_ptr<Config> cfg) {
cfg->loadFromString("[signing_support]\ntrue");
return cfg;
})};
Account const alice{"alice"};
env.fund(XRP(10000), alice);
env.close();
json::Value txJson;
txJson[jss::TransactionType] = "AMMDelete";
txJson[jss::Account] = alice.human();
txJson[jss::Asset][jss::currency] = "USD";
txJson[jss::Asset][jss::issuer] = to_string(xrpAccount());
txJson[jss::Asset2][jss::currency] = "XRP";
json::Value req;
req[jss::tx_json] = txJson;
req[jss::secret] = alice.name();
auto const result = env.rpc("json", "sign", to_string(req))[jss::result];
BEAST_EXPECT(result[jss::status] == "error");
BEAST_EXPECT(result.isMember(jss::error));
}
void
testXrpAccountIssuer()
{
testcase("xrpAccount issuer rejection");
{
json::Value jv;
jv[jss::currency] = "USD";
jv[jss::issuer] = to_string(xrpAccount());
try
{
issueFromJson(sfAsset, jv);
fail("issueFromJson accepted xrpAccount() as IOU issuer");
}
catch (...)
{
pass();
}
}
}
void
run() override
{
// compliments other unit tests to ensure complete coverage
testConstructor();
testCompare();
testNoAccountIssuerRpc();
testNoAccountIssuer();
testXrpAccountIssuerRpc();
testXrpAccountIssuer();
}
};

View File

@@ -23,7 +23,6 @@
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/Seed.h>
#include <xrpl/protocol/TxFlags.h>
#include <xrpl/protocol/jss.h>
#include <xrpl/protocol/nft.h>
@@ -33,7 +32,6 @@
#include <cstdint>
#include <iterator>
#include <optional>
#include <ranges>
#include <vector>
namespace xrpl::test {
@@ -1352,86 +1350,6 @@ public:
}
}
void
testAccountObjectDoesntShowCancelledOffers()
{
testcase("AccountObjectDoesntShowCancelledOffers");
using namespace jtx;
Env env(*this);
Account const alice{"alice"};
Account const bob{"bob"};
auto const eur = bob["EUR"];
env.fund(XRP(10000), alice, bob);
env.close();
auto const rpcAccountObjects = [&](std::optional<uint32_t> limit = std::nullopt) {
json::Value params;
params[jss::account] = alice.human();
if (limit.has_value())
{
params[jss::limit] = *limit;
}
return env.rpc("json", "account_objects", to_string(params));
};
auto const numEntries = 33;
std::vector<uint32_t> seqs;
seqs.reserve(numEntries);
for ([[maybe_unused]] auto _ : std::ranges::iota_view{0, numEntries})
{
json::Value params;
params[jss::secret] = toBase58(generateSeed("alice"));
params[jss::tx_json] = offer(alice, eur(1), XRP(2));
auto const res = env.rpc("json", "submit", to_string(params))[jss::result];
BEAST_EXPECT(res[jss::status].asString() == "success");
seqs.push_back(env.seq(alice));
}
auto res = rpcAccountObjects();
BEAST_EXPECT(res[jss::result][jss::account_objects].size() == numEntries);
BEAST_EXPECT(not res[jss::result].isMember(jss::limit));
BEAST_EXPECT(not res[jss::result].isMember(jss::marker));
for (auto const s : std::views::all(seqs) | std::views::take(numEntries - 1))
{
json::Value params;
params[jss::secret] = toBase58(generateSeed("alice"));
params[jss::tx_json] = offerCancel(alice, s - 1);
auto const res = env.rpc("json", "submit", to_string(params))[jss::result];
BEAST_EXPECT(res[jss::status].asString() == "success");
}
res = rpcAccountObjects();
BEAST_EXPECT(res[jss::result][jss::account_objects].size() == 1);
BEAST_EXPECT(not res[jss::result].isMember(jss::limit));
BEAST_EXPECT(not res[jss::result].isMember(jss::marker));
{
json::Value params;
params[jss::secret] = toBase58(generateSeed("alice"));
json::Value txJson;
txJson[jss::TransactionType] = jss::NFTokenMint;
txJson[jss::Account] = to_string(alice.id());
txJson["NFTokenTaxon"] = 1;
params[jss::tx_json] = txJson;
auto const res = env.rpc("json", "submit", to_string(params))[jss::result];
BEAST_EXPECT(res[jss::status].asString() == "success");
}
env.close();
res = rpcAccountObjects();
BEAST_EXPECT(res[jss::result][jss::account_objects].size() == 2);
BEAST_EXPECT(not res[jss::result].isMember(jss::limit));
BEAST_EXPECT(not res[jss::result].isMember(jss::marker));
res = rpcAccountObjects(1);
BEAST_EXPECT(res[jss::result][jss::account_objects].size() == 1);
BEAST_EXPECT(res[jss::result][jss::limit].asUInt() == 1);
BEAST_EXPECT(res[jss::result].isMember(jss::marker));
}
void
run() override
{
@@ -1442,7 +1360,6 @@ public:
testNFTsMarker();
testAccountNFTs();
testAccountObjectMarker();
testAccountObjectDoesntShowCancelledOffers();
}
};

View File

@@ -2,7 +2,6 @@
#include <test/jtx/Env.h>
#include <test/jtx/amount.h>
#include <test/jtx/envconfig.h>
#include <test/jtx/multisign.h>
#include <test/jtx/noop.h>
#include <test/jtx/pay.h>
#include <test/jtx/seq.h>
@@ -882,125 +881,6 @@ class Transaction_test : public beast::unit_test::Suite
}
}
void
testSignForNetworkIDValidation()
{
testcase("SignFor NetworkID validation");
using namespace test::jtx;
Account const owner{"owner"};
Account const signer{"signer"};
auto makeConfig = [](std::uint32_t networkID) {
return envconfig([networkID](std::unique_ptr<Config> cfg) {
cfg->networkId = networkID;
return cfg;
});
};
auto setupEnv = [&](Env& env) {
env.fund(XRP(10'000), owner, signer);
env.close();
env(signers(owner, 1, {{signer, 1}}));
env.close();
};
auto makeTx = [&](Env& env) {
json::Value tx;
tx[jss::TransactionType] = jss::AccountSet;
tx[jss::Account] = owner.human();
tx[jss::Sequence] = env.seq(owner);
tx[jss::Fee] = "100";
tx[jss::SigningPubKey] = "";
return tx;
};
auto signFor = [&](Env& env, json::Value const& tx) {
json::Value signReq;
signReq[jss::tx_json] = tx;
signReq[jss::account] = signer.human();
signReq[jss::secret] = signer.name();
return env.rpc("json", "sign_for", to_string(signReq))[jss::result];
};
// Test case: NetworkID < 1024 - field is not required
{
Env env{*this, makeConfig(500)};
setupEnv(env);
auto tx = makeTx(env);
auto result = signFor(env, tx);
BEAST_EXPECT(result[jss::status] == "success");
BEAST_EXPECT(!result[jss::tx_json].isMember(jss::NetworkID));
}
// Test case: NetworkID > 1024 - missing NetworkID field
{
Env env{*this, makeConfig(2040)};
setupEnv(env);
auto tx = makeTx(env);
auto result = signFor(env, tx);
BEAST_EXPECT(result[jss::error] == "invalidParams");
BEAST_EXPECT(result[jss::error_message] == "Missing field 'tx_json.NetworkID'.");
}
// Test case: NetworkID > 1024 - NetworkID field is not a number
{
Env env{*this, makeConfig(2040)};
setupEnv(env);
auto tx = makeTx(env);
tx[jss::NetworkID] = "not_a_number";
auto result = signFor(env, tx);
BEAST_EXPECT(result[jss::error] == "invalidParams");
BEAST_EXPECT(result[jss::error_message] == "Invalid field 'tx_json.NetworkID'.");
}
// Test case: NetworkID > 1024 - NetworkID field is not integral
{
Env env{*this, makeConfig(2040)};
setupEnv(env);
auto tx = makeTx(env);
tx[jss::NetworkID] = 2040.1;
auto result = signFor(env, tx);
BEAST_EXPECT(result[jss::error] == "invalidParams");
BEAST_EXPECT(result[jss::error_message] == "Invalid field 'tx_json.NetworkID'.");
}
// Test case: NetworkID > 1024 - NetworkID field is different from
// actual NetworkID
{
Env env{*this, makeConfig(2040)};
setupEnv(env);
auto tx = makeTx(env);
tx[jss::NetworkID] = 9999;
auto result = signFor(env, tx);
BEAST_EXPECT(result[jss::error] == "invalidParams");
BEAST_EXPECT(result[jss::error_message] == "Invalid field 'tx_json.NetworkID'.");
}
// Test case: NetworkID > 1024 - NetworkID field is correct
{
Env env{*this, makeConfig(2040)};
setupEnv(env);
auto tx = makeTx(env);
tx[jss::NetworkID] = 2040;
auto result = signFor(env, tx);
BEAST_EXPECT(result[jss::status] == "success");
BEAST_EXPECT(result[jss::tx_json][jss::NetworkID].asUInt() == 2040);
}
}
public:
void
run() override
@@ -1010,8 +890,6 @@ public:
FeatureBitset const all{testableAmendments()};
testWithFeats(all);
testSignForNetworkIDValidation();
}
void

View File

@@ -61,6 +61,7 @@
#include <xrpl/resource/Gossip.h>
#include <xrpl/server/LoadFeeTrack.h>
#include <xrpl/server/NetworkOPs.h>
#include <xrpl/shamap/SHAMap.h>
#include <xrpl/shamap/SHAMapNodeID.h>
#include <xrpl/tx/apply.h>
@@ -1507,23 +1508,12 @@ PeerImp::onMessage(std::shared_ptr<protocol::TMGetLedger> const& m)
}
}
// Verify ledger node IDs
if (itype != protocol::liBASE)
// Verify ledger node counts. Full parsing of the node IDs is deferred to the job, so the I/O
// thread is not burdened with SHAMapNodeID deserialization for every TMGetLedger message.
if (itype != protocol::liBASE && m->nodeids_size() <= 0)
{
if (m->nodeids_size() <= 0)
{
badData("Invalid ledger node IDs");
return;
}
for (auto const& nodeId : m->nodeids())
{
if (deserializeSHAMapNodeID(nodeId) == std::nullopt)
{
badData("Invalid SHAMap node ID");
return;
}
}
badData("Invalid ledger node IDs");
return;
}
// Verify query type
@@ -1543,11 +1533,40 @@ PeerImp::onMessage(std::shared_ptr<protocol::TMGetLedger> const& m)
}
}
// Queue a job to process the request
// Queue a job to process the request.
std::weak_ptr<PeerImp> const weak = shared_from_this();
app_.getJobQueue().addJob(JtLedgerReq, "RcvGetLedger", [weak, m]() {
if (auto peer = weak.lock())
peer->processLedgerRequest(m);
app_.getJobQueue().addJob(JtLedgerReq, "RcvGetLedger", [weak, m, itype]() {
auto peer = weak.lock();
if (!peer)
return;
std::vector<SHAMapNodeID> nodeIDs;
if (itype != protocol::liBASE)
{
nodeIDs.reserve(std::min(m->nodeids_size(), Tuning::kSoftMaxReplyNodes));
for (auto const& nodeId : m->nodeids())
{
if (nodeIDs.size() >= Tuning::kSoftMaxReplyNodes)
{
// Charge the peer for sending too many node IDs, but continue processing the
// received node IDs up to the limit. If the request is legitimate then at least
// they will get a response and won't have to resend these nodes in their next
// request.
peer->charge(
Resource::kFeeModerateBurdenPeer, "TMGetLedger: too many node IDs");
break;
}
auto parsed = deserializeSHAMapNodeID(nodeId);
if (!parsed)
{
peer->charge(Resource::kFeeInvalidData, "TMGetLedger: Invalid node ID");
return;
}
nodeIDs.push_back(std::move(*parsed));
}
}
peer->processLedgerRequest(m, std::move(nodeIDs));
});
}
@@ -3319,7 +3338,9 @@ PeerImp::getTxSet(std::shared_ptr<protocol::TMGetLedger> const& m) const
}
void
PeerImp::processLedgerRequest(std::shared_ptr<protocol::TMGetLedger> const& m)
PeerImp::processLedgerRequest(
std::shared_ptr<protocol::TMGetLedger> const& m,
std::vector<SHAMapNodeID> nodeIDs)
{
// Do not resource charge a peer responding to a relay
if (!m->has_requestcookie())
@@ -3404,26 +3425,23 @@ PeerImp::processLedgerRequest(std::shared_ptr<protocol::TMGetLedger> const& m)
}
// Add requested node data to reply
if (m->nodeids_size() > 0)
if (!nodeIDs.empty())
{
std::uint32_t const defaultDepth = isHighLatency() ? 2 : 1;
auto const queryDepth{m->has_querydepth() ? m->querydepth() : defaultDepth};
std::vector<std::pair<SHAMapNodeID, Blob>> data;
for (int i = 0;
i < m->nodeids_size() && ledgerData.nodes_size() < Tuning::kSoftMaxReplyNodes;
++i)
data.reserve(Tuning::kSoftMaxReplyNodes);
for (auto const& nodeID : nodeIDs)
{
auto const shaMapNodeId{deserializeSHAMapNodeID(m->nodeids(i))};
if (ledgerData.nodes_size() >= Tuning::kSoftMaxReplyNodes)
break;
data.clear();
data.reserve(Tuning::kSoftMaxReplyNodes);
try
{
// NOLINTNEXTLINE(bugprone-unchecked-optional-access) nodeids checked in onGetLedger
if (map->getNodeFat(*shaMapNodeId, data, fatLeaves, queryDepth))
if (map->getNodeFat(nodeID, data, fatLeaves, queryDepth))
{
JLOG(pJournal_.trace())
<< "processLedgerRequest: getNodeFat got " << data.size() << " nodes";
@@ -3432,9 +3450,9 @@ PeerImp::processLedgerRequest(std::shared_ptr<protocol::TMGetLedger> const& m)
{
if (ledgerData.nodes_size() >= Tuning::kHardMaxReplyNodes)
break;
protocol::TMLedgerNode* node{ledgerData.add_nodes()};
node->set_nodeid(d.first.getRawString());
node->set_nodedata(d.second.data(), d.second.size());
protocol::TMLedgerNode* ledgerNode{ledgerData.add_nodes()};
ledgerNode->set_nodeid(d.first.getRawString());
ledgerNode->set_nodedata(d.second.data(), d.second.size());
}
}
else
@@ -3473,14 +3491,14 @@ PeerImp::processLedgerRequest(std::shared_ptr<protocol::TMGetLedger> const& m)
info += ", no hash specified";
JLOG(pJournal_.warn())
<< "processLedgerRequest: getNodeFat with nodeId " << *shaMapNodeId
<< "processLedgerRequest: getNodeFat with nodeId " << nodeID
<< " and ledger info type " << info << " throws exception: " << e.what();
}
}
JLOG(pJournal_.info()) << "processLedgerRequest: Got request for " << m->nodeids_size()
<< " nodes at depth " << queryDepth << ", return "
<< ledgerData.nodes_size() << " nodes";
<< " node IDs (processed " << nodeIDs.size() << ") at depth "
<< queryDepth << ", return " << ledgerData.nodes_size() << " nodes";
}
if (ledgerData.nodes_size() == 0)

View File

@@ -14,6 +14,7 @@
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/STValidation.h>
#include <xrpl/resource/Fees.h>
#include <xrpl/shamap/SHAMapNodeID.h>
#include <boost/circular_buffer.hpp>
#include <boost/endian/conversion.hpp>
@@ -629,7 +630,9 @@ private:
getTxSet(std::shared_ptr<protocol::TMGetLedger> const& m) const;
void
processLedgerRequest(std::shared_ptr<protocol::TMGetLedger> const& m);
processLedgerRequest(
std::shared_ptr<protocol::TMGetLedger> const& m,
std::vector<SHAMapNodeID> nodeIDs);
protected:
// Kept `protected` so test subclasses (see