mirror of
https://github.com/XRPLF/rippled.git
synced 2026-09-28 15:58:07 +00:00
526 lines
20 KiB
C++
526 lines
20 KiB
C++
/**
|
|
* @file ValidationTracker.cpp
|
|
* Unit tests for xrpl::telemetry::ValidationTracker.
|
|
*/
|
|
|
|
#include <xrpld/telemetry/ValidationTracker.h>
|
|
|
|
#include <xrpl/basics/base_uint.h>
|
|
#include <xrpl/protocol/Protocol.h>
|
|
|
|
#include <gtest/gtest.h>
|
|
|
|
#include <chrono>
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
#include <thread>
|
|
|
|
using namespace xrpl;
|
|
using namespace xrpl::telemetry;
|
|
|
|
/**
|
|
* Helper to create a unique uint256 from an integer seed.
|
|
*/
|
|
static uint256
|
|
makeHash(std::uint64_t n)
|
|
{
|
|
return uint256(n);
|
|
}
|
|
|
|
/**
|
|
* Test fixture providing a fresh ValidationTracker per test.
|
|
*/
|
|
class ValidationTrackerTest : public ::testing::Test
|
|
{
|
|
protected:
|
|
ValidationTracker tracker_;
|
|
};
|
|
|
|
// ---------------------------------------------------------------
|
|
// 1. Normal agreement
|
|
// Record both our validation and network validation for the
|
|
// same hash, then reconcile after the grace period elapses.
|
|
// ---------------------------------------------------------------
|
|
TEST_F(ValidationTrackerTest, NormalAgreement)
|
|
{
|
|
auto const hash = makeHash(1);
|
|
LedgerIndex const seq = 100;
|
|
|
|
tracker_.recordOurValidation(hash, seq);
|
|
tracker_.recordNetworkValidation(hash, seq);
|
|
|
|
// Immediately after recording, nothing is reconciled yet
|
|
// (grace period has not elapsed).
|
|
tracker_.reconcile();
|
|
EXPECT_EQ(tracker_.totalValidationsSent(), 1u);
|
|
EXPECT_EQ(tracker_.totalValidationsChecked(), 1u);
|
|
|
|
// Wait for the grace period (8 seconds) to elapse, then reconcile.
|
|
std::this_thread::sleep_for(std::chrono::seconds(9));
|
|
tracker_.reconcile();
|
|
|
|
EXPECT_EQ(tracker_.totalAgreements(), 1u);
|
|
EXPECT_EQ(tracker_.totalMissed(), 0u);
|
|
EXPECT_EQ(tracker_.agreements1h(), 1u);
|
|
EXPECT_EQ(tracker_.missed1h(), 0u);
|
|
EXPECT_DOUBLE_EQ(tracker_.agreementPct1h(), 100.0);
|
|
}
|
|
|
|
// ---------------------------------------------------------------
|
|
// 2. Missed validation
|
|
// Only the network validates; we never do. After grace period
|
|
// the event should be reconciled as a miss.
|
|
// ---------------------------------------------------------------
|
|
TEST_F(ValidationTrackerTest, MissedValidation)
|
|
{
|
|
auto const hash = makeHash(2);
|
|
LedgerIndex const seq = 200;
|
|
|
|
tracker_.recordNetworkValidation(hash, seq);
|
|
|
|
// Wait for grace period then reconcile.
|
|
std::this_thread::sleep_for(std::chrono::seconds(9));
|
|
tracker_.reconcile();
|
|
|
|
EXPECT_EQ(tracker_.totalAgreements(), 0u);
|
|
EXPECT_EQ(tracker_.totalMissed(), 1u);
|
|
EXPECT_EQ(tracker_.agreements1h(), 0u);
|
|
EXPECT_EQ(tracker_.missed1h(), 1u);
|
|
EXPECT_DOUBLE_EQ(tracker_.agreementPct1h(), 0.0);
|
|
}
|
|
|
|
// ---------------------------------------------------------------
|
|
// 3. Late repair
|
|
// Network validates first, grace period elapses (miss), then
|
|
// our validation arrives within the 5-minute repair window and
|
|
// the miss is flipped to an agreement.
|
|
// ---------------------------------------------------------------
|
|
TEST_F(ValidationTrackerTest, LateRepair)
|
|
{
|
|
auto const hash = makeHash(3);
|
|
LedgerIndex const seq = 300;
|
|
|
|
// Network validates, but we do not (yet).
|
|
tracker_.recordNetworkValidation(hash, seq);
|
|
|
|
// Grace period elapses -- reconciled as a miss.
|
|
std::this_thread::sleep_for(std::chrono::seconds(9));
|
|
tracker_.reconcile();
|
|
EXPECT_EQ(tracker_.totalMissed(), 1u);
|
|
EXPECT_EQ(tracker_.totalAgreements(), 0u);
|
|
EXPECT_EQ(tracker_.missed1h(), 1u);
|
|
|
|
// Late arrival of our validation (within repair window).
|
|
tracker_.recordOurValidation(hash, seq);
|
|
tracker_.reconcile();
|
|
|
|
// Miss should be repaired to agreement.
|
|
EXPECT_EQ(tracker_.totalAgreements(), 1u);
|
|
EXPECT_EQ(tracker_.totalMissed(), 0u);
|
|
EXPECT_EQ(tracker_.agreements1h(), 1u);
|
|
EXPECT_EQ(tracker_.missed1h(), 0u);
|
|
EXPECT_DOUBLE_EQ(tracker_.agreementPct1h(), 100.0);
|
|
}
|
|
|
|
// ---------------------------------------------------------------
|
|
// 4. Empty window returns 0%
|
|
// When no events have been recorded the percentage methods
|
|
// must return 0.0, not NaN or any other value.
|
|
// ---------------------------------------------------------------
|
|
TEST_F(ValidationTrackerTest, EmptyWindowReturnsZero)
|
|
{
|
|
EXPECT_DOUBLE_EQ(tracker_.agreementPct1h(), 0.0);
|
|
EXPECT_DOUBLE_EQ(tracker_.agreementPct24h(), 0.0);
|
|
EXPECT_EQ(tracker_.agreements1h(), 0u);
|
|
EXPECT_EQ(tracker_.missed1h(), 0u);
|
|
EXPECT_EQ(tracker_.agreements24h(), 0u);
|
|
EXPECT_EQ(tracker_.missed24h(), 0u);
|
|
EXPECT_EQ(tracker_.totalAgreements(), 0u);
|
|
EXPECT_EQ(tracker_.totalMissed(), 0u);
|
|
EXPECT_EQ(tracker_.totalAgreementsEver(), 0u);
|
|
EXPECT_EQ(tracker_.totalMissedEver(), 0u);
|
|
EXPECT_EQ(tracker_.totalValidationsSent(), 0u);
|
|
EXPECT_EQ(tracker_.totalValidationsChecked(), 0u);
|
|
}
|
|
|
|
// ---------------------------------------------------------------
|
|
// 5. Grace period boundary
|
|
// Events recorded less than 8 seconds ago must NOT be
|
|
// reconciled. Verify that an immediate reconcile is a no-op.
|
|
// ---------------------------------------------------------------
|
|
TEST_F(ValidationTrackerTest, GracePeriodBoundary)
|
|
{
|
|
auto const hash = makeHash(5);
|
|
LedgerIndex const seq = 500;
|
|
|
|
tracker_.recordOurValidation(hash, seq);
|
|
tracker_.recordNetworkValidation(hash, seq);
|
|
|
|
// Reconcile immediately -- grace period has not elapsed.
|
|
tracker_.reconcile();
|
|
|
|
// Nothing should be reconciled yet.
|
|
EXPECT_EQ(tracker_.totalAgreements(), 0u);
|
|
EXPECT_EQ(tracker_.totalMissed(), 0u);
|
|
EXPECT_EQ(tracker_.agreements1h(), 0u);
|
|
EXPECT_EQ(tracker_.missed1h(), 0u);
|
|
|
|
// Lifetime send/check counters should still be incremented.
|
|
EXPECT_EQ(tracker_.totalValidationsSent(), 1u);
|
|
EXPECT_EQ(tracker_.totalValidationsChecked(), 1u);
|
|
}
|
|
|
|
// ---------------------------------------------------------------
|
|
// 6. Max pending events -- trimming
|
|
// Add more than kMaxPendingEvents (1000) events. After
|
|
// reconciliation and a second reconcile pass the pending map
|
|
// should be trimmed. Lifetime totals must remain consistent.
|
|
// ---------------------------------------------------------------
|
|
TEST_F(ValidationTrackerTest, MaxPendingEventsTrimming)
|
|
{
|
|
constexpr std::size_t kCount = 1100;
|
|
|
|
for (std::size_t i = 0; i < kCount; ++i)
|
|
{
|
|
auto const hash = makeHash(i + 1);
|
|
auto const seq = static_cast<LedgerIndex>(i + 1);
|
|
tracker_.recordOurValidation(hash, seq);
|
|
tracker_.recordNetworkValidation(hash, seq);
|
|
}
|
|
|
|
EXPECT_EQ(tracker_.totalValidationsSent(), kCount);
|
|
EXPECT_EQ(tracker_.totalValidationsChecked(), kCount);
|
|
|
|
// Wait for grace period so all events can be reconciled.
|
|
std::this_thread::sleep_for(std::chrono::seconds(9));
|
|
tracker_.reconcile();
|
|
|
|
// All events should be reconciled as agreements.
|
|
EXPECT_EQ(tracker_.totalAgreements(), kCount);
|
|
EXPECT_EQ(tracker_.totalMissed(), 0u);
|
|
|
|
// Reconcile again to trigger pending eviction / trimming.
|
|
// The pending map should be trimmed, but totals remain correct.
|
|
tracker_.reconcile();
|
|
EXPECT_EQ(tracker_.totalAgreements(), kCount);
|
|
EXPECT_EQ(tracker_.totalMissed(), 0u);
|
|
}
|
|
|
|
// ---------------------------------------------------------------
|
|
// 7. Multiple distinct ledgers -- mixed results
|
|
// Record a mix of agreements and misses to verify that window
|
|
// counts and percentages are computed correctly.
|
|
// ---------------------------------------------------------------
|
|
TEST_F(ValidationTrackerTest, MixedAgreementsAndMisses)
|
|
{
|
|
// 3 agreements: both sides validate.
|
|
for (int i = 1; i <= 3; ++i)
|
|
{
|
|
auto const hash = makeHash(static_cast<std::uint64_t>(i));
|
|
tracker_.recordOurValidation(hash, static_cast<LedgerIndex>(i));
|
|
tracker_.recordNetworkValidation(hash, static_cast<LedgerIndex>(i));
|
|
}
|
|
|
|
// 2 misses: only network validates.
|
|
for (int i = 4; i <= 5; ++i)
|
|
{
|
|
auto const hash = makeHash(static_cast<std::uint64_t>(i));
|
|
tracker_.recordNetworkValidation(hash, static_cast<LedgerIndex>(i));
|
|
}
|
|
|
|
// Wait for grace period then reconcile.
|
|
std::this_thread::sleep_for(std::chrono::seconds(9));
|
|
tracker_.reconcile();
|
|
|
|
EXPECT_EQ(tracker_.totalAgreements(), 3u);
|
|
EXPECT_EQ(tracker_.totalMissed(), 2u);
|
|
EXPECT_EQ(tracker_.agreements1h(), 3u);
|
|
EXPECT_EQ(tracker_.missed1h(), 2u);
|
|
|
|
// 3 out of 5 = 60%
|
|
EXPECT_DOUBLE_EQ(tracker_.agreementPct1h(), 60.0);
|
|
}
|
|
|
|
// ---------------------------------------------------------------
|
|
// 8. Duplicate recording for same hash
|
|
// Recording the same hash multiple times should not create
|
|
// duplicate pending entries or double-count totals beyond the
|
|
// per-call increments.
|
|
// ---------------------------------------------------------------
|
|
TEST_F(ValidationTrackerTest, DuplicateRecordingSameHash)
|
|
{
|
|
auto const hash = makeHash(42);
|
|
LedgerIndex const seq = 42;
|
|
|
|
// Record our validation twice for the same hash.
|
|
tracker_.recordOurValidation(hash, seq);
|
|
tracker_.recordOurValidation(hash, seq);
|
|
tracker_.recordNetworkValidation(hash, seq);
|
|
|
|
// Each call increments the lifetime counter.
|
|
EXPECT_EQ(tracker_.totalValidationsSent(), 2u);
|
|
EXPECT_EQ(tracker_.totalValidationsChecked(), 1u);
|
|
|
|
// But only one pending event exists, so only one agreement.
|
|
std::this_thread::sleep_for(std::chrono::seconds(9));
|
|
tracker_.reconcile();
|
|
|
|
EXPECT_EQ(tracker_.totalAgreements(), 1u);
|
|
EXPECT_EQ(tracker_.totalMissed(), 0u);
|
|
}
|
|
|
|
// ---------------------------------------------------------------
|
|
// 9. Only-we-validated scenario
|
|
// We validate but the network does not. After grace period
|
|
// this should be a miss (not an agreement).
|
|
// ---------------------------------------------------------------
|
|
TEST_F(ValidationTrackerTest, OnlyWeValidated)
|
|
{
|
|
auto const hash = makeHash(99);
|
|
LedgerIndex const seq = 99;
|
|
|
|
tracker_.recordOurValidation(hash, seq);
|
|
|
|
std::this_thread::sleep_for(std::chrono::seconds(9));
|
|
tracker_.reconcile();
|
|
|
|
EXPECT_EQ(tracker_.totalAgreements(), 0u);
|
|
EXPECT_EQ(tracker_.totalMissed(), 1u);
|
|
EXPECT_EQ(tracker_.missed1h(), 1u);
|
|
EXPECT_DOUBLE_EQ(tracker_.agreementPct1h(), 0.0);
|
|
}
|
|
|
|
// ---------------------------------------------------------------
|
|
// 10. A counted ledger is never counted twice
|
|
// reconcile() drops the oldest reconciled events once the
|
|
// pending map passes kMaxPendingEvents. A validation arriving
|
|
// for one of those ledgers afterwards must not reach the
|
|
// agreement or missed totals a second time.
|
|
//
|
|
// Two hashes are made the oldest so the trim drops both:
|
|
// - evictedMiss (network only) reconciles as a miss. Our late
|
|
// validation cannot repair an entry the trim dropped, so
|
|
// totalMissed staying at 1 proves the trim really dropped
|
|
// it. A fixture where the trim did not run would repair it
|
|
// and report 0 misses.
|
|
// - evictedAgreed (both sides) reconciles as an agreement.
|
|
// Re-recording both sides is what double-counts an
|
|
// agreement.
|
|
// ---------------------------------------------------------------
|
|
TEST_F(ValidationTrackerTest, CountedLedgerNotCountedTwice)
|
|
{
|
|
// The trim drops the oldest reconciled entries first. Each pause makes
|
|
// the next record time strictly larger, so these two are the oldest.
|
|
auto const evictedMiss = makeHash(1);
|
|
tracker_.recordNetworkValidation(evictedMiss, 1);
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(5));
|
|
|
|
auto const evictedAgreed = makeHash(2);
|
|
tracker_.recordOurValidation(evictedAgreed, 2);
|
|
tracker_.recordNetworkValidation(evictedAgreed, 2);
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(5));
|
|
|
|
// Fill to three over the bound so the trim drops three entries: the two
|
|
// above plus one filler.
|
|
constexpr std::size_t kFill = ValidationTracker::kMaxPendingEvents + 1;
|
|
for (std::size_t i = 0; i < kFill; ++i)
|
|
{
|
|
auto const hash = makeHash(i + 3);
|
|
auto const seq = static_cast<LedgerIndex>(i + 3);
|
|
tracker_.recordOurValidation(hash, seq);
|
|
tracker_.recordNetworkValidation(hash, seq);
|
|
}
|
|
|
|
std::this_thread::sleep_for(std::chrono::seconds(9));
|
|
tracker_.reconcile();
|
|
|
|
// Every filler plus evictedAgreed agrees; evictedMiss is the one miss.
|
|
EXPECT_EQ(tracker_.totalAgreements(), kFill + 1);
|
|
EXPECT_EQ(tracker_.totalMissed(), 1u);
|
|
EXPECT_EQ(tracker_.agreements1h(), kFill + 1);
|
|
EXPECT_EQ(tracker_.missed1h(), 1u);
|
|
|
|
// Validations arrive again for the two dropped ledgers.
|
|
tracker_.recordOurValidation(evictedMiss, 1);
|
|
tracker_.recordOurValidation(evictedAgreed, 2);
|
|
tracker_.recordNetworkValidation(evictedAgreed, 2);
|
|
|
|
// Long enough for a re-created pending entry to pass the grace period.
|
|
std::this_thread::sleep_for(std::chrono::seconds(9));
|
|
tracker_.reconcile();
|
|
|
|
// Both ledgers were already counted, so every total is unchanged.
|
|
EXPECT_EQ(tracker_.totalAgreements(), kFill + 1);
|
|
EXPECT_EQ(tracker_.totalMissed(), 1u);
|
|
EXPECT_EQ(tracker_.agreements1h(), kFill + 1);
|
|
EXPECT_EQ(tracker_.missed1h(), 1u);
|
|
|
|
// The send and check counters count messages, not ledgers, so the
|
|
// repeated validations do count towards them.
|
|
EXPECT_EQ(tracker_.totalValidationsSent(), kFill + 3);
|
|
EXPECT_EQ(tracker_.totalValidationsChecked(), kFill + 3);
|
|
}
|
|
|
|
// ---------------------------------------------------------------
|
|
// 10. Gross miss tally is monotonic across a late repair
|
|
// The gross lifetime tallies (totalAgreementsEver/totalMissedEver)
|
|
// back the monotonic Prometheus _total counters. A late repair must
|
|
// move the NET totals (miss -> agreement) but must NOT move the gross
|
|
// tallies: a miss already counted stays counted, and the repair does
|
|
// not add a second (agreement) count for the same ledger.
|
|
// ---------------------------------------------------------------
|
|
TEST_F(ValidationTrackerTest, GrossMissedNeverDecrementsOnRepair)
|
|
{
|
|
auto const hash = makeHash(10);
|
|
LedgerIndex const seq = 1000;
|
|
|
|
// Network validates, we do not (yet).
|
|
tracker_.recordNetworkValidation(hash, seq);
|
|
|
|
// Grace period elapses -- reconciled as a miss.
|
|
std::this_thread::sleep_for(std::chrono::seconds(9));
|
|
tracker_.reconcile();
|
|
|
|
// Net and gross both show exactly one initial miss, zero agreements.
|
|
EXPECT_EQ(tracker_.totalMissed(), 1u);
|
|
EXPECT_EQ(tracker_.totalMissedEver(), 1u);
|
|
EXPECT_EQ(tracker_.totalAgreements(), 0u);
|
|
EXPECT_EQ(tracker_.totalAgreementsEver(), 0u);
|
|
|
|
// Late arrival of our validation repairs the miss to an agreement.
|
|
tracker_.recordOurValidation(hash, seq);
|
|
tracker_.reconcile();
|
|
|
|
// Net totals reflect the repair...
|
|
EXPECT_EQ(tracker_.totalMissed(), 0u);
|
|
EXPECT_EQ(tracker_.totalAgreements(), 1u);
|
|
// ...but the gross tallies are frozen at first classification: the miss
|
|
// stays counted and no agreement was added (repair path excluded).
|
|
EXPECT_EQ(tracker_.totalMissedEver(), 1u);
|
|
EXPECT_EQ(tracker_.totalAgreementsEver(), 0u);
|
|
}
|
|
|
|
// ---------------------------------------------------------------
|
|
// 11. Gross tallies count initial classification only (additive)
|
|
// With a mix of initial agreements and misses the gross tallies equal
|
|
// the net totals. A subsequent repair shifts the net totals but leaves
|
|
// the gross tallies unchanged, and the gross sum equals the number of
|
|
// reconciled ledgers (the additive invariant the _total counters rely on).
|
|
// ---------------------------------------------------------------
|
|
TEST_F(ValidationTrackerTest, GrossAgreementsCountInitialOnly)
|
|
{
|
|
// 3 initial agreements: both sides validate.
|
|
for (int i = 1; i <= 3; ++i)
|
|
{
|
|
auto const h = makeHash(static_cast<std::uint64_t>(i));
|
|
tracker_.recordOurValidation(h, static_cast<LedgerIndex>(i));
|
|
tracker_.recordNetworkValidation(h, static_cast<LedgerIndex>(i));
|
|
}
|
|
|
|
// 2 initial misses: only network validates.
|
|
for (int i = 4; i <= 5; ++i)
|
|
{
|
|
auto const h = makeHash(static_cast<std::uint64_t>(i));
|
|
tracker_.recordNetworkValidation(h, static_cast<LedgerIndex>(i));
|
|
}
|
|
|
|
// Grace period elapses -- all five reconciled at first classification.
|
|
std::this_thread::sleep_for(std::chrono::seconds(9));
|
|
tracker_.reconcile();
|
|
|
|
// Before any repair, gross equals net.
|
|
EXPECT_EQ(tracker_.totalAgreements(), 3u);
|
|
EXPECT_EQ(tracker_.totalAgreementsEver(), 3u);
|
|
EXPECT_EQ(tracker_.totalMissed(), 2u);
|
|
EXPECT_EQ(tracker_.totalMissedEver(), 2u);
|
|
|
|
// Repair one of the misses (hash 4) within the repair window.
|
|
tracker_.recordOurValidation(makeHash(4), 4);
|
|
tracker_.reconcile();
|
|
|
|
// Net totals shift by the repair...
|
|
EXPECT_EQ(tracker_.totalAgreements(), 4u);
|
|
EXPECT_EQ(tracker_.totalMissed(), 1u);
|
|
// ...gross tallies stay at the initial classification.
|
|
EXPECT_EQ(tracker_.totalAgreementsEver(), 3u);
|
|
EXPECT_EQ(tracker_.totalMissedEver(), 2u);
|
|
|
|
// Additive invariant: gross agree + gross miss == ledgers reconciled.
|
|
EXPECT_EQ(tracker_.totalAgreementsEver() + tracker_.totalMissedEver(), 5u);
|
|
}
|
|
|
|
// ---------------------------------------------------------------
|
|
// 12. Pending map stays bounded when nothing ever reconciles
|
|
// reconcile() is the only pruning path, and it runs only from
|
|
// the observable-gauge callbacks -- which need telemetry both
|
|
// compiled in and enabled. A node with telemetry off, or with
|
|
// [telemetry] enabled=0, therefore never reconciles, so the
|
|
// record methods have to bound the map themselves.
|
|
// ---------------------------------------------------------------
|
|
TEST_F(ValidationTrackerTest, PendingStaysBoundedWithoutReconcile)
|
|
{
|
|
constexpr std::uint64_t kFirstBatch = 4000;
|
|
constexpr std::uint64_t kSecondBatch = 8000;
|
|
|
|
for (std::uint64_t i = 0; i < kFirstBatch; ++i)
|
|
tracker_.recordOurValidation(makeHash(i), static_cast<LedgerIndex>(i));
|
|
|
|
auto const afterFirst = tracker_.pendingCount();
|
|
|
|
for (std::uint64_t i = kFirstBatch; i < kSecondBatch; ++i)
|
|
tracker_.recordOurValidation(makeHash(i), static_cast<LedgerIndex>(i));
|
|
|
|
auto const afterSecond = tracker_.pendingCount();
|
|
|
|
// Bounded at all: far fewer entries retained than recorded.
|
|
EXPECT_LT(afterFirst, kFirstBatch);
|
|
|
|
// Bounded at a fixed cap, not merely growing more slowly: doubling the
|
|
// input leaves the size unchanged. Asserted without naming the private
|
|
// constant, so the test survives a change to its value.
|
|
EXPECT_EQ(afterFirst, afterSecond);
|
|
|
|
// Every recorded validation is still counted, so bounding the map does not
|
|
// cost us the lifetime totals the gauges report.
|
|
EXPECT_EQ(tracker_.totalValidationsSent(), kSecondBatch);
|
|
}
|
|
|
|
// ---------------------------------------------------------------
|
|
// Bounding the map must not lose a ledger from the verdict totals.
|
|
//
|
|
// totalAgreements() and totalMissed() move only when an event is
|
|
// classified, which normally happens in reconcile() after the grace
|
|
// period. An event dropped to hold the bound therefore has to be
|
|
// classified on the way out, or the ledger is counted as neither an
|
|
// agreement nor a miss and both totals silently under-report.
|
|
//
|
|
// Recorded with both validations present and no reconcile() call, so
|
|
// every classification here comes from the eviction path alone.
|
|
// ---------------------------------------------------------------
|
|
TEST_F(ValidationTrackerTest, EvictionUnderPressureStillCountsEveryLedger)
|
|
{
|
|
// Comfortably past the cap, so eviction runs many times.
|
|
constexpr std::uint64_t kCount = 3000;
|
|
|
|
for (std::uint64_t i = 0; i < kCount; ++i)
|
|
{
|
|
auto const hash = makeHash(i + 1);
|
|
auto const seq = static_cast<LedgerIndex>(i + 1);
|
|
tracker_.recordOurValidation(hash, seq);
|
|
tracker_.recordNetworkValidation(hash, seq);
|
|
}
|
|
|
|
// Held at the cap, so evictions definitely happened.
|
|
auto const stillPending = tracker_.pendingCount();
|
|
EXPECT_LT(stillPending, kCount);
|
|
|
|
// Every evicted ledger reached a verdict, and each had both validations, so
|
|
// every one of them is an agreement rather than a miss.
|
|
auto const evicted = kCount - stillPending;
|
|
EXPECT_EQ(tracker_.totalAgreements(), evicted);
|
|
EXPECT_EQ(tracker_.totalMissed(), 0u);
|
|
|
|
// Nothing is counted twice: the ledgers still pending have no verdict yet.
|
|
EXPECT_EQ(tracker_.totalAgreements() + tracker_.totalMissed(), evicted);
|
|
}
|