fix(consensus): recycle member evidence cache

This commit is contained in:
Nicholas Dudfield
2026-07-12 09:27:16 +07:00
parent 25073d6cb1
commit 1287cce0f7
3 changed files with 102 additions and 7 deletions

View File

@@ -829,6 +829,90 @@ public:
}));
}
}
{
testcase(
"UNLReport member manifest evidence evicts oldest retained "
"master");
ManifestCache cache;
std::optional<Manifest> oldest;
std::optional<Manifest> secondOldest;
std::optional<SecretKey> secondMasterSecret;
std::optional<SecretKey> secondSigningSecret;
for (std::size_t i = 0; i < maxUNLReportMemberEvidenceMasters; ++i)
{
auto const sk = randomSecretKey();
auto const kp = randomKeyPair(KeyType::secp256k1);
auto manifest = makeManifest(
sk, KeyType::ed25519, kp.second, KeyType::secp256k1, 1);
if (i == 0)
oldest = clone(manifest);
else if (i == 1)
{
secondOldest = clone(manifest);
secondMasterSecret = sk;
secondSigningSecret = kp.second;
}
BEAST_EXPECT(cache.observeUNLReportMemberManifestEvidence(
makeSlice(manifest.serialized)));
}
BEAST_EXPECT(oldest && secondOldest);
BEAST_EXPECT(
oldest &&
!cache.observeUNLReportMemberManifestEvidence(
makeSlice(oldest->serialized)));
auto const replacementSecret = randomSecretKey();
auto const replacementKey = randomKeyPair(KeyType::secp256k1);
auto const replacement = makeManifest(
replacementSecret,
KeyType::ed25519,
replacementKey.second,
KeyType::secp256k1,
1);
BEAST_EXPECT(cache.observeUNLReportMemberManifestEvidence(
makeSlice(replacement.serialized)));
auto const snapshot =
cache.getUNLReportMemberManifestEvidenceSnapshot();
BEAST_EXPECT(snapshot.size() == maxUNLReportMemberEvidenceMasters);
BEAST_EXPECT(oldest && !containsEvidence(snapshot, *oldest));
BEAST_EXPECT(
secondOldest && containsEvidence(snapshot, *secondOldest));
BEAST_EXPECT(containsEvidence(snapshot, replacement));
BEAST_EXPECT(secondMasterSecret && secondSigningSecret);
auto const refreshed = makeManifest(
*secondMasterSecret,
KeyType::ed25519,
*secondSigningSecret,
KeyType::secp256k1,
2);
BEAST_EXPECT(cache.observeUNLReportMemberManifestEvidence(
makeSlice(refreshed.serialized)));
auto const nextSecret = randomSecretKey();
auto const nextKey = randomKeyPair(KeyType::secp256k1);
auto const next = makeManifest(
nextSecret,
KeyType::ed25519,
nextKey.second,
KeyType::secp256k1,
1);
BEAST_EXPECT(cache.observeUNLReportMemberManifestEvidence(
makeSlice(next.serialized)));
auto const refreshedSnapshot =
cache.getUNLReportMemberManifestEvidenceSnapshot();
BEAST_EXPECT(
refreshedSnapshot.size() == maxUNLReportMemberEvidenceMasters);
BEAST_EXPECT(
secondOldest &&
!containsEvidence(refreshedSnapshot, *secondOldest));
BEAST_EXPECT(containsEvidence(refreshedSnapshot, refreshed));
BEAST_EXPECT(containsEvidence(refreshedSnapshot, next));
}
}
void

View File

@@ -34,6 +34,8 @@
namespace ripple {
inline constexpr std::size_t maxUNLReportMemberManifestSize = 2048;
inline constexpr std::size_t maxUNLReportMemberEvidenceMasters = 256;
inline constexpr std::size_t maxUNLReportMemberEvidencePerMaster = 2;
/*
Validator key manifests
@@ -266,9 +268,6 @@ private:
beast::Journal j_;
std::shared_mutex mutable mutex_;
static constexpr std::size_t maxUNLReportMemberEvidenceMasters_ = 256;
static constexpr std::size_t maxUNLReportMemberEvidencePerMaster_ = 2;
/** Active manifests stored by master public key. */
hash_map<PublicKey, Manifest> map_;
@@ -285,11 +284,13 @@ private:
struct UNLReportMemberManifestEvidence
{
std::uint32_t sequence = 0;
std::uint64_t retentionOrder = 0;
std::vector<Manifest> manifests;
};
hash_map<PublicKey, UNLReportMemberManifestEvidence>
unlReportMemberManifestEvidence_;
std::uint64_t unlReportMemberEvidenceRetentionOrder_ = 0;
std::atomic<std::uint32_t> seq_{0};

View File

@@ -597,8 +597,17 @@ ManifestCache::observeUNLReportMemberManifestEvidence(Slice serialized)
if (iter == unlReportMemberManifestEvidence_.end())
{
if (unlReportMemberManifestEvidence_.size() >=
maxUNLReportMemberEvidenceMasters_)
return false;
maxUNLReportMemberEvidenceMasters)
{
auto const oldest = std::min_element(
unlReportMemberManifestEvidence_.begin(),
unlReportMemberManifestEvidence_.end(),
[](auto const& lhs, auto const& rhs) {
return lhs.second.retentionOrder <
rhs.second.retentionOrder;
});
unlReportMemberManifestEvidence_.erase(oldest);
}
iter = unlReportMemberManifestEvidence_
.emplace(masterKey, UNLReportMemberManifestEvidence{})
@@ -622,10 +631,11 @@ ManifestCache::observeUNLReportMemberManifestEvidence(Slice serialized)
return false;
}
if (evidence.manifests.size() >= maxUNLReportMemberEvidencePerMaster_)
if (evidence.manifests.size() >= maxUNLReportMemberEvidencePerMaster)
return false;
evidence.manifests.emplace_back(std::move(m));
evidence.retentionOrder = ++unlReportMemberEvidenceRetentionOrder_;
return true;
}
@@ -638,7 +648,7 @@ ManifestCache::getUNLReportMemberManifestEvidenceSnapshot() const
snapshot.reserve(
map_.size() +
unlReportMemberManifestEvidence_.size() *
maxUNLReportMemberEvidencePerMaster_);
maxUNLReportMemberEvidencePerMaster);
for (auto const& [_, manifest] : map_)
{