fix: Use a hardened hash on the STPathElement

This commit is contained in:
Timothy Banks
2026-09-03 17:06:27 -04:00
committed by Bart
parent 6fec2c11bf
commit 53628b70c0
8 changed files with 452 additions and 61 deletions

View File

@@ -141,6 +141,7 @@ words:
- hwrap
- ifndef
- inequation
- Injectivity
- insuf
- insuff
- invasively

View File

@@ -5,9 +5,11 @@
#include <xrpl/protocol/Concepts.h>
#include <xrpl/protocol/UintTypes.h>
#include <cstdint>
#include <ostream>
#include <stdexcept>
#include <string>
#include <type_traits>
#include <variant>
namespace xrpl {
@@ -121,9 +123,32 @@ operator==(PathAsset const& lhs, PathAsset const& rhs)
template <typename Hasher>
void
hash_append(Hasher& h, PathAsset const& pathAsset)
hash_append(Hasher& h, PathAsset const& pathAsset) noexcept
{
std::visit([&]<ValidPathAsset T>(T const& e) { hash_append(h, e); }, pathAsset.value());
using beast::hash_append;
using Variant = std::remove_cvref_t<decltype(pathAsset.value())>;
static_assert(
std::variant_size_v<Variant> < 0xFFu,
"PathAsset's discriminant must fit in a byte, leaving 0xFF reserved.");
// std::visit is not noexcept: it throws bad_variant_access when the variant
// is valueless_by_exception.
if (pathAsset.value().valueless_by_exception()) [[unlikely]]
{
hash_append(h, static_cast<std::uint8_t>(0xFFu));
return;
}
hash_append(h, static_cast<std::uint8_t>(pathAsset.value().index()));
std::visit(
[&]<ValidPathAsset T>(T const& e) noexcept {
static_assert(
noexcept(hash_append(h, e)),
"Every PathAsset alternative must be nothrow-hashable.");
hash_append(h, e);
},
pathAsset.value());
}
inline bool

View File

@@ -12,6 +12,8 @@
#include <xrpl/protocol/UintTypes.h>
#include <cstddef>
#include <cstdint>
#include <memory>
#include <optional>
#include <utility>
#include <vector>
@@ -65,7 +67,7 @@ public:
PathAsset const& asset,
AccountID const& issuer);
[[nodiscard]] auto
[[nodiscard]] std::uint32_t
getNodeType() const;
[[nodiscard]] bool
@@ -109,9 +111,6 @@ public:
[[nodiscard]] bool
isType(Type const& pe) const;
[[nodiscard]] size_t
getHash() const;
bool
operator==(STPathElement const& t) const;
@@ -120,6 +119,17 @@ private:
getHash(STPathElement const& element);
};
template <class Hasher>
void
hash_append(Hasher& h, STPathElement const& e) noexcept
{
using beast::hash_append;
hash_append(h, (e.getNodeType() & STPathElement::TypeAccount) != 0u);
hash_append(h, e.getAccountID());
hash_append(h, e.getPathAsset());
hash_append(h, e.getIssuerID());
}
class STPath final : public CountedObject<STPath>
{
std::vector<STPathElement> path_;
@@ -176,9 +186,10 @@ template <class Hasher>
void
hash_append(Hasher& h, STPath const& p) noexcept
{
using beast::hash_append;
for (auto const& e : p)
{
beast::hash_append(h, e.getHash());
hash_append(h, e);
}
}
@@ -188,13 +199,39 @@ hash_append(Hasher& h, STPath const& p) noexcept
class STPathSet final : public STBase, public CountedObject<STPathSet>
{
std::vector<STPath> value_;
xrpl::hardened_hash_set<STPath> seenHashes_;
/**
* Deduplication index over `value_`, for pathfinding.
* The use of a std::unique_ptr is intentional as it
* only requires 8 additional bytes of storage for the pointer
* as opposed to 64 bytes with an optional. This keeps the size
* of the STPathSet to within the `STVar::kMaxSize` limit of 72 bytes.
*/
std::unique_ptr<hardened_hash_set<STPath>> seen_;
public:
struct DeduplicationTag
{
};
STPathSet() = default;
/**
* Deduplication tagged constructor.
* Use when you want to ensure that the STPathSet does not contain duplicate paths.
*/
explicit STPathSet(DeduplicationTag);
STPathSet(SField const& n);
STPathSet(SerialIter& sit, SField const& name);
STPathSet(STPathSet const& other);
STPathSet(STPathSet&&) = default;
STPathSet&
operator=(STPathSet const& other);
STPathSet&
operator=(STPathSet&&) = default;
~STPathSet() override = default;
void
add(Serializer& s) const override;
@@ -204,6 +241,16 @@ public:
[[nodiscard]] SerializedTypeID
getSType() const override;
/**
* @brief assembleAdd adds a path to the set by combining a base path and a tail element.
*
* @param base The base path.
* @param tail The tail element.
* @return true if the path was added, false if it was a duplicate and not added.
* @remarks Requires the STPathSet to be constructed with the DeduplicationTag. The return value
* indicates whether the combined path was inserted (true) or rejected as a duplicate (false).
* It is fine for callers to ignore the return value.
*/
bool
assembleAdd(STPath const& base, STPathElement const& tail);
@@ -229,22 +276,61 @@ public:
[[nodiscard]] bool
empty() const;
void
/**
* @brief pushBack adds a path to the set.
*
* @param e The path to add.
* @return true if the path was added, false if it was a duplicate and not added.
* @remarks If the STPathSet was constructed with the DeduplicationTag, then this method will
* check for duplicates and only add the path if it is not already present in the
* set. If the STPathSet was constructed without the DeduplicationTag,
* then this method will always add the path to the set, regardless of duplicates.
* It is fine for callers to ignore the return value.
*/
bool
pushBack(STPath const& e);
/**
* @brief emplaceBack adds a path to the set.
*
* @param args The arguments to construct the path with.
* @return true if the path was added, false if it was a duplicate and not added.
* @remarks If the STPathSet was constructed with the DeduplicationTag, then this method will
* check for duplicates and only add the path if it is not already present in the
* set. If the STPathSet was constructed without the DeduplicationTag,
* then this method will always add the path to the set, regardless of duplicates.
* It is fine for callers to ignore the return value.
* @note The path is constructed before the duplicate check, so on a false
* return the constructed path is discarded and any argument
* forwarded as an rvalue is left in a moved-from state. Use
* pushBack when the caller needs to keep its path on rejection.
*/
template <typename... Args>
void
bool
emplaceBack(Args&&... args);
[[nodiscard]] bool
contains(STPath const& path) const;
private:
STBase*
copy(std::size_t n, void* buf) const override;
STBase*
move(std::size_t n, void* buf) override;
/**
* @brief Append a path via `append`, then register it in the deduplication index.
*
* @param append Invoked with `value_`; must append exactly one path to it.
* @return true if the path was kept, false if it was a duplicate and was rolled back.
* @remarks Appends to the vector before touching the index, so that a failed allocation
* there leaves both containers untouched rather than leaving the index holding
* a path the vector does not. If the index insert reports a duplicate, or
* throws, the append is rolled back so the two containers stay consistent; in
* the throwing case the exception propagates. With no index (constructed
* without the DeduplicationTag) the append is unconditional.
*/
template <typename Append>
bool
appendUnique(Append&& append);
friend class detail::STVar;
};
@@ -336,7 +422,7 @@ inline STPathElement::STPathElement(
hashValue_ = getHash(*this);
}
inline auto
inline std::uint32_t
STPathElement::getNodeType() const
{
return type_;
@@ -545,25 +631,50 @@ STPathSet::empty() const
return value_.empty();
}
inline void
STPathSet::pushBack(STPath const& e)
template <typename Append>
inline bool
STPathSet::appendUnique(Append&& append)
{
value_.push_back(e);
seenHashes_.emplace(value_.back());
}
// Append to the vector first, so that a failed allocation there leaves both
// containers untouched rather than leaving the index holding a path the
// vector does not.
append(value_);
template <typename... Args>
inline void
STPathSet::emplaceBack(Args&&... args)
{
value_.emplace_back(std::forward<Args>(args)...);
seenHashes_.emplace(value_.back());
if (seen_ == nullptr)
{
return true;
}
try
{
if (!seen_->insert(value_.back()).second)
{
// Already present: roll back the append.
value_.pop_back();
return false;
}
}
catch (...)
{
// The index insert failed, so roll back the append to keep the vector
// and the index consistent.
value_.pop_back();
throw;
}
return true;
}
inline bool
STPathSet::contains(STPath const& path) const
STPathSet::pushBack(STPath const& e)
{
return seenHashes_.contains(path);
return appendUnique([&](auto& value) { value.push_back(e); });
}
template <typename... Args>
inline bool
STPathSet::emplaceBack(Args&&... args)
{
return appendUnique([&](auto& value) { value.emplace_back(std::forward<Args>(args)...); });
}
} // namespace xrpl

View File

@@ -34,10 +34,11 @@ concept ValidConstructSTArgs =
// and includes a small-object allocation optimization.
class STVar
{
private:
public:
// The largest "small object" we can accommodate
static constexpr std::size_t kMaxSize = 72;
private:
alignas(std::max_align_t) std::byte d_[kMaxSize] = {};
STBase* p_ = nullptr;

View File

@@ -1,6 +1,8 @@
#include <xrpl/protocol/STPathSet.h>
#include <xrpl/basics/CountedObject.h>
#include <xrpl/basics/Log.h>
#include <xrpl/basics/UnorderedContainers.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/basics/contract.h>
#include <xrpl/beast/hash/uhash.h>
@@ -11,10 +13,12 @@
#include <xrpl/protocol/STBase.h>
#include <xrpl/protocol/Serializer.h>
#include <xrpl/protocol/UintTypes.h>
#include <xrpl/protocol/detail/STVar.h>
#include <xrpl/protocol/jss.h>
#include <algorithm>
#include <cstddef>
#include <memory>
#include <stdexcept>
#include <utility>
#include <vector>
@@ -31,6 +35,11 @@ STPathElement::getHash(STPathElement const& element)
// NIKB NOTE: This doesn't have to be a secure hash as speed is more
// important. We don't even really need to fully hash the whole
// base_uint here, as a few bytes would do for our use.
//
// The note above is only true because the result of this function reaches
// nothing but STPathElement::operator==, where it is a fast-reject
// prefilter ahead of the field comparisons that decide the answer. Do not
// use it to key a container.
for (auto const x : element.getAccountID())
hashAccount += (hashAccount * 257) ^ x;
@@ -51,10 +60,49 @@ STPathElement::getHash(STPathElement const& element)
return (hashAccount ^ hashCurrency ^ hashIssuer);
}
[[nodiscard]] size_t
STPathElement::getHash() const
// For guidance on deciding which option to pursue:
// 1. Try to decrease the size of the STPathSet first. For instance, if a std::optional was
// injected into the type, could you get the same functionality using a std::unique_ptr instead?
// 2. If the size of the STPathSet is already as small as it can be, then consider what the cost
// of increasing STVar::kMaxSize would be on all the other STVar types. Each of those types
// will carry the additional cost of accommodating the larger STPathSet in their SBO.
// 3. If the cost of increasing STVar::kMaxSize is too high, then heap allocate the STPathSet and
// remove this static_assert.
static_assert(
sizeof(STPathSet) <= detail::STVar::kMaxSize,
"STPathSet is too large to fit in STVar's small object optimization. Please verify if it "
"should, if the kMaxSize should be increased, or if STPathSet should be stored on the heap "
"instead of in STVar.");
STPathSet::STPathSet(DeduplicationTag) : seen_{std::make_unique<hardened_hash_set<STPath>>()}
{
return STPathElement::getHash(*this);
}
STPathSet::STPathSet(STPathSet const& other)
: STBase{other}
, CountedObject<STPathSet>{other}
, value_{other.value_}
, seen_{
other.seen_ != nullptr ? std::make_unique<hardened_hash_set<STPath>>(*other.seen_)
: nullptr}
{
}
STPathSet&
STPathSet::operator=(STPathSet const& other)
{
if (this == &other)
{
return *this;
}
auto newSeen = other.seen_ != nullptr
? std::make_unique<hardened_hash_set<STPath>>(*other.seen_)
: nullptr;
STBase::operator=(other);
CountedObject<STPathSet>::operator=(other);
value_ = other.value_;
seen_ = std::move(newSeen);
return *this;
}
STPathSet::STPathSet(SerialIter& sit, SField const& name) : STBase(name)
@@ -72,7 +120,8 @@ STPathSet::STPathSet(SerialIter& sit, SField const& name) : STBase(name)
Throw<std::runtime_error>("empty path");
}
pushBack(path);
// Move rather than converting the vector to an STPath by copy.
value_.emplace_back(std::move(path));
path.clear();
if (iType == STPathElement::TypeNone)
@@ -132,16 +181,10 @@ STPathSet::move(std::size_t n, void* buf)
bool
STPathSet::assembleAdd(STPath const& base, STPathElement const& tail)
{ // assemble base+tail and add it to the set if it's not a duplicate
XRPL_ASSERT(seen_ != nullptr, "xrpl::STPathSet::assembleAdd : DeduplicationTag");
STPath combined = base;
combined.pushBack(tail);
if (!seenHashes_.insert(combined).second)
{
return false;
}
value_.push_back(std::move(combined));
return true;
return appendUnique([&](auto& value) { value.push_back(std::move(combined)); });
}
bool

View File

@@ -25,6 +25,7 @@
#include <xrpld/rpc/detail/Tuning.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/basics/hardened_hash.h>
#include <xrpl/beast/unit_test/suite.h>
#include <xrpl/core/Job.h>
#include <xrpl/core/JobQueue.h>
@@ -46,16 +47,20 @@
#include <xrpl/resource/Consumer.h>
#include <xrpl/resource/Fees.h>
#include <algorithm>
#include <chrono>
#include <condition_variable>
#include <cstddef>
#include <cstdint>
#include <memory>
#include <mutex>
#include <optional>
#include <set>
#include <string>
#include <string_view>
#include <tuple>
#include <utility>
#include <vector>
namespace xrpl::test {
@@ -1943,7 +1948,7 @@ public:
static constexpr AccountID kAccountID7{kAccount7};
static constexpr AccountID kAccountID8{kAccount8};
auto ps = STPathSet{};
auto ps = STPathSet{STPathSet::DeduplicationTag{}};
auto createPathElements = [](auto const& account1, auto const& account2) {
auto base = STPath{};
@@ -2017,6 +2022,215 @@ public:
BEAST_EXPECT(ps.size() == 6);
}
void
testPushBackDeduplication()
{
testcase("STPathSet::pushBack/emplaceBack deduplication");
// pushBack and emplaceBack reject duplicates on a set built with the
// DeduplicationTag, and append unconditionally without it. Both
// report which happened. The unconditional case is the one the wire
// and JSON paths rely on: collapsing duplicates there would change the
// signed content of a transaction.
static constexpr AccountID kAccountID1{"A3F19C7B2E5D08146FB93A7C0E2D5184BC6F3A09"};
static constexpr AccountID kAccountID2{"1D7E4B90C2A6F3851E0B9D47A2C5F8136E0A4B7D"};
static constexpr AccountID kAccountID3{"F08C36A1D95E27B40CA1F63E8D204B7950E1C3A6"};
auto makePath = [](AccountID const& account) {
auto p = STPath{};
p.pushBack(STPathElement{STPathElement::TypeAccount, account, xrpCurrency(), account});
return p;
};
auto const first = makePath(kAccountID1);
auto const second = makePath(kAccountID2);
auto const third = makePath(kAccountID3);
// Deduplicating set: the second insert of a path is rejected, and the
// rejection is reported rather than silently swallowed.
{
auto ps = STPathSet{STPathSet::DeduplicationTag{}};
BEAST_EXPECT(ps.pushBack(first));
BEAST_EXPECT(ps.size() == 1);
BEAST_EXPECT(!ps.pushBack(first));
BEAST_EXPECT(ps.size() == 1);
// emplaceBack sees paths registered by pushBack...
BEAST_EXPECT(!ps.emplaceBack(first));
BEAST_EXPECT(ps.size() == 1);
BEAST_EXPECT(ps.emplaceBack(second));
BEAST_EXPECT(ps.size() == 2);
// ...and pushBack sees paths registered by emplaceBack.
BEAST_EXPECT(!ps.pushBack(second));
BEAST_EXPECT(ps.size() == 2);
// emplaceBack's forwarding form registers the same way.
BEAST_EXPECT(ps.emplaceBack(std::vector<STPathElement>{third.front()}));
BEAST_EXPECT(ps.size() == 3);
BEAST_EXPECT(!ps.pushBack(third));
BEAST_EXPECT(ps.size() == 3);
// A rejected duplicate must not disturb what is already stored.
BEAST_EXPECT(ps[0] == first);
BEAST_EXPECT(ps[1] == second);
BEAST_EXPECT(ps[2] == third);
}
// Without the tag there is no index, so duplicates are appended and
// both methods report success every time.
{
auto plain = STPathSet{};
BEAST_EXPECT(plain.pushBack(first));
BEAST_EXPECT(plain.pushBack(first));
BEAST_EXPECT(plain.emplaceBack(first));
BEAST_EXPECT(plain.size() == 3);
auto named = STPathSet{sfPaths};
BEAST_EXPECT(named.pushBack(first));
BEAST_EXPECT(named.pushBack(first));
BEAST_EXPECT(named.size() == 2);
}
}
void
testPathHashInjectivity()
{
testcase("STPathElement hash injectivity");
auto const zeroCurrency =
STPathElement{AccountID{}, PathAsset{Currency{}}, AccountID{}, true};
auto const zeroMPT = STPathElement{AccountID{}, PathAsset{MPTID{}}, AccountID{}, true};
BEAST_EXPECT(!(zeroCurrency == zeroMPT));
auto path = [](std::vector<STPathElement> const& elements) {
auto p = STPath{};
for (auto const& element : elements)
p.pushBack(element);
return p;
};
auto const currencyFirst = path({zeroCurrency, zeroMPT});
auto const mptFirst = path({zeroMPT, zeroCurrency});
BEAST_EXPECT(!(currencyFirst == mptFirst));
auto const hasher = HardenedHash<>{};
BEAST_EXPECT(hasher(currencyFirst) != hasher(mptFirst));
auto mask = std::vector<int>{0, 0, 1, 1};
auto hashes = std::set<std::size_t>{};
auto orderings = 0uz;
do
{
auto elements = std::vector<STPathElement>{};
for (auto const isMPT : mask)
{
elements.push_back(isMPT != 0 ? zeroMPT : zeroCurrency);
}
hashes.insert(hasher(path(elements)));
++orderings;
} while (std::ranges::next_permutation(mask).found);
BEAST_EXPECT(orderings == 6);
BEAST_EXPECT(hashes.size() == orderings);
auto seen = hardened_hash_set<STPath>{};
for (auto const& p : {currencyFirst, mptFirst})
{
seen.emplace(p);
}
BEAST_EXPECT(seen.size() == 2);
// The other half of the invariant: equal elements must hash equally.
// STPathElement::operator== masks type_ down to the TypeAccount bit, so
// elements whose remaining type bits differ still compare equal --
// hashing the full type_ would give them distinct hashes and silently
// defeat deduplication.
static constexpr AccountID kAccount{"A3F19C7B2E5D08146FB93A7C0E2D5184BC6F3A09"};
static constexpr AccountID kIssuer{"1D7E4B90C2A6F3851E0B9D47A2C5F8136E0A4B7D"};
auto const equivalent = std::vector<std::pair<STPathElement, STPathElement>>{
// forceAsset toggles TypeCurrency on an XRP asset.
{STPathElement{kAccount, PathAsset{xrpCurrency()}, kIssuer, true},
STPathElement{kAccount, PathAsset{xrpCurrency()}, kIssuer, false}},
// An explicit type mask vs. one derived from the populated fields.
{STPathElement{STPathElement::TypeAccount, kAccount, xrpCurrency(), kIssuer},
STPathElement{kAccount, PathAsset{xrpCurrency()}, kIssuer, false}},
};
for (auto const& [lhs, rhs] : equivalent)
{
BEAST_EXPECT(lhs.getNodeType() != rhs.getNodeType());
BEAST_EXPECT(lhs == rhs);
auto const lhsPath = path({lhs});
auto const rhsPath = path({rhs});
BEAST_EXPECT(hasher(lhsPath) == hasher(rhsPath));
auto equal = hardened_hash_set<STPath>{};
equal.emplace(lhsPath);
equal.emplace(rhsPath);
BEAST_EXPECT(equal.size() == 1);
}
}
void
testDeserializationPreservesDuplicates()
{
testcase("STPathSet deserialization preserves duplicate paths");
// The `Paths` field of a signed transaction must round-trip byte for
// byte. The deduplication index exists solely for pathfinding, so the
// deserializing constructor must never engage it: collapsing duplicates
// on parse would silently change the signed content of a transaction.
static constexpr AccountID kAccountID1{"A3F19C7B2E5D08146FB93A7C0E2D5184BC6F3A09"};
static constexpr AccountID kAccountID2{"1D7E4B90C2A6F3851E0B9D47A2C5F8136E0A4B7D"};
auto const element =
STPathElement{kAccountID1, PathAsset{xrpCurrency()}, kAccountID2, true};
auto path = STPath{};
path.pushBack(element);
static constexpr auto kDuplicates = 64uz;
auto original = STPathSet{sfPaths};
for (auto i = 0uz; i < kDuplicates; ++i)
{
original.pushBack(path);
}
// No index was requested, so nothing is deduplicated on the way in.
BEAST_EXPECT(original.size() == kDuplicates);
auto s = Serializer{};
original.add(s);
auto sit = SerialIter{s.slice()};
auto const parsed = STPathSet{sit, sfPaths};
// The duplicates survive the round trip...
BEAST_EXPECT(parsed.size() == kDuplicates);
BEAST_EXPECT(parsed.isEquivalent(original));
// ...and re-serializing reproduces the original bytes exactly.
auto serialized = Serializer{};
parsed.add(serialized);
BEAST_EXPECT(serialized.getData() == s.getData());
// A parsed set holds no index, so appending to it stays append-only.
auto appended = parsed;
appended.pushBack(path);
BEAST_EXPECT(appended.size() == kDuplicates + 1);
}
void
run() override
{
@@ -2031,6 +2245,9 @@ public:
issuesPathNegativeRippleClientIssue23Larger();
qualityPathsQualitySetAndTest();
testAssembleAddDeduplication();
testPushBackDeduplication();
testPathHashInjectivity();
testDeserializationPreservesDuplicates();
trustAutoClearTrustNormalClear();
trustAutoClearTrustAutoClear();
norippleCombinations();

View File

@@ -862,10 +862,11 @@ Pathfinder::addPathsForType(
return it->second;
// Otherwise, if the type has no nodes, return the empty path.
if (pathType.empty())
return paths_[pathType];
if (continueCallback && !continueCallback())
return paths_[{}];
if (pathType.empty() || (continueCallback && !continueCallback()))
{
static auto const kEmptyPath = PathType{};
return paths_.try_emplace(kEmptyPath, STPathSet::DeduplicationTag{}).first->second;
}
// Otherwise, get the paths for the parent PathType by calling
// addPathsForType recursively.
@@ -873,7 +874,7 @@ Pathfinder::addPathsForType(
parentPathType.pop_back();
STPathSet const& parentPaths = addPathsForType(parentPathType, continueCallback);
STPathSet& pathsOut = paths_[pathType];
STPathSet& pathsOut = paths_.try_emplace(pathType, STPathSet::DeduplicationTag{}).first->second;
JLOG(j_.debug()) << "getPaths< adding onto '" << pathTypeToString(parentPathType)
<< "' to get '" << pathTypeToString(pathType) << "'";
@@ -959,15 +960,6 @@ Pathfinder::isNoRippleOut(STPath const& currentPath)
return endElement.hasCurrency() && isNoRipple(fromAccount, toAccount, endElement.getCurrency());
}
void
addUniquePath(STPathSet& pathSet, STPath const& path)
{
if (!pathSet.contains(path))
{
pathSet.pushBack(path);
}
}
void
Pathfinder::addLink(
STPath const& currentPath, // The path to build from
@@ -999,7 +991,7 @@ Pathfinder::addLink(
{ // non-default path to XRP destination
JLOG(j_.trace()) << "complete path found ax: "
<< currentPath.getJson(JsonOptions::Values::None);
addUniquePath(completePaths_, currentPath);
completePaths_.pushBack(currentPath);
}
}
else
@@ -1107,7 +1099,7 @@ Pathfinder::addLink(
JLOG(j_.trace())
<< "complete path found ae: "
<< currentPath.getJson(JsonOptions::Values::None);
addUniquePath(completePaths_, currentPath);
completePaths_.pushBack(currentPath);
}
}
else if (!bDestOnly)
@@ -1237,11 +1229,12 @@ Pathfinder::addLink(
// complete
JLOG(j_.trace()) << "complete path found bx: "
<< currentPath.getJson(JsonOptions::Values::None);
addUniquePath(completePaths_, newPath);
completePaths_.pushBack(newPath);
}
else
{
incompletePaths.pushBack(newPath);
[[maybe_unused]] auto result = incompletePaths.pushBack(newPath);
XRPL_ASSERT(result, "xrpl::Pathfinder::addLink : unique path");
}
}
else if (!currentPath.hasSeen(
@@ -1283,7 +1276,7 @@ Pathfinder::addLink(
// complete
JLOG(j_.trace()) << "complete path found ba: "
<< currentPath.getJson(JsonOptions::Values::None);
addUniquePath(completePaths_, newPath);
completePaths_.pushBack(newPath);
}
else
{

View File

@@ -207,7 +207,7 @@ private:
std::shared_ptr<AssetCache> rLCache_;
STPathElement source_;
STPathSet completePaths_;
STPathSet completePaths_{STPathSet::DeduplicationTag{}};
std::vector<PathRank> pathRanks_;
std::map<PathType, STPathSet> paths_;