mirror of
https://github.com/Xahau/xahaud.git
synced 2026-09-23 22:00:19 +00:00
Compare commits
9 Commits
feature-ex
...
multiple-a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3095da8e86 | ||
|
|
4d877e9ac9 | ||
|
|
3a123ed3e4 | ||
|
|
d924099041 | ||
|
|
f5963e38d2 | ||
|
|
580c934561 | ||
|
|
748c29afde | ||
|
|
8b2adb9cc6 | ||
|
|
9d1bc2c99d |
25
.github/workflows/rng-tripwire.yml
vendored
Normal file
25
.github/workflows/rng-tripwire.yml
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
name: rng-tripwire
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
check:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Check for vendor-defined random algorithms
|
||||
run: python3 Builds/rng_tripwire/rng_tripwire.py
|
||||
- name: What happened?
|
||||
if: failure()
|
||||
env:
|
||||
MESSAGE: |
|
||||
A standard-library random algorithm showed up outside the
|
||||
allowlist. These algorithms are not the same on every standard
|
||||
library, so a new call site can change a deterministic trace.
|
||||
|
||||
Either draw through ripple::rand_int, or add the path to
|
||||
Builds/rng_tripwire/allowlist.txt with a one-line justification.
|
||||
Comments are ignored. See Builds/rng_tripwire/allowlist.txt.
|
||||
run: |
|
||||
echo "${MESSAGE}"
|
||||
exit 1
|
||||
11
Builds/rng_tripwire/allowlist.txt
Normal file
11
Builds/rng_tripwire/allowlist.txt
Normal file
@@ -0,0 +1,11 @@
|
||||
# path-prefix justification
|
||||
# A hit under src/ or include/ is accepted only when its path starts with one
|
||||
# of these prefixes. The justification is for reviewers; the script ignores it.
|
||||
|
||||
src/test/ Tests are not on the consensus schedule.
|
||||
include/xrpl/basics/random.h Seeding overloads only. rand_int does not call these distributions.
|
||||
include/xrpl/basics/hardened_hash.h One-time hash salt drawn from OS entropy.
|
||||
include/xrpl/crypto/csprng.h CSPRNG declaration. OS entropy, not a consensus draw.
|
||||
src/libxrpl/crypto/csprng.cpp OS entropy for the CSPRNG.
|
||||
src/xrpld/peerfinder/ default_prng shuffles. Not the harness engine.
|
||||
src/xrpld/overlay/detail/OverlayImpl.cpp std::shuffle of relay peers. Runs only when TX_REDUCE_RELAY_ENABLE is on, and that flag is off. Uses app_.getPrng when it does run.
|
||||
125
Builds/rng_tripwire/rng_tripwire.py
Normal file
125
Builds/rng_tripwire/rng_tripwire.py
Normal file
@@ -0,0 +1,125 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Fail if a vendor-defined <random> algorithm appears outside the allowlist.
|
||||
|
||||
Scans src/ and include/ for the standard algorithms whose mapping is not
|
||||
portable across standard libraries. Comments are ignored. A hit is accepted
|
||||
only when its path is listed in allowlist.txt. Unlisted hits are printed as
|
||||
file:line and the process exits non-zero.
|
||||
"""
|
||||
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
ALGORITHMS = (
|
||||
"uniform_int_distribution",
|
||||
"uniform_real_distribution",
|
||||
"bernoulli_distribution",
|
||||
"binomial_distribution",
|
||||
"negative_binomial_distribution",
|
||||
"geometric_distribution",
|
||||
"poisson_distribution",
|
||||
"exponential_distribution",
|
||||
"gamma_distribution",
|
||||
"weibull_distribution",
|
||||
"extreme_value_distribution",
|
||||
"normal_distribution",
|
||||
"lognormal_distribution",
|
||||
"chi_squared_distribution",
|
||||
"cauchy_distribution",
|
||||
"fisher_f_distribution",
|
||||
"student_t_distribution",
|
||||
"discrete_distribution",
|
||||
"piecewise_constant_distribution",
|
||||
"piecewise_linear_distribution",
|
||||
"sample",
|
||||
"shuffle",
|
||||
"generate_canonical",
|
||||
"random_device",
|
||||
)
|
||||
|
||||
HIT = re.compile(r"\bstd::(?:" + "|".join(ALGORITHMS) + r")\b")
|
||||
SUFFIXES = {".h", ".hh", ".hpp", ".cpp", ".cc", ".cxx", ".ipp", ".inc"}
|
||||
|
||||
|
||||
def repo_root() -> Path:
|
||||
here = Path(__file__).resolve().parent
|
||||
for candidate in (here, *here.parents):
|
||||
if (candidate / "src").is_dir() and (candidate / "include").is_dir():
|
||||
return candidate
|
||||
sys.exit("rng_tripwire: cannot find the repository root")
|
||||
|
||||
|
||||
def load_allowlist(path: Path) -> list[tuple[str, str]]:
|
||||
entries = []
|
||||
for raw in path.read_text().splitlines():
|
||||
line = raw.split("#", 1)[0].strip()
|
||||
if not line:
|
||||
continue
|
||||
prefix, _, why = line.partition(" ")
|
||||
entries.append((prefix.strip(), why.strip()))
|
||||
return entries
|
||||
|
||||
|
||||
def allowed(rel: str, entries: list[tuple[str, str]]) -> bool:
|
||||
for prefix, _why in entries:
|
||||
if rel == prefix or rel.startswith(prefix):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def strip_comments(text: str) -> list[str]:
|
||||
lines = []
|
||||
in_block = False
|
||||
for line in text.splitlines():
|
||||
out = []
|
||||
i = 0
|
||||
while i < len(line):
|
||||
if in_block:
|
||||
end = line.find("*/", i)
|
||||
if end < 0:
|
||||
i = len(line)
|
||||
break
|
||||
in_block = False
|
||||
i = end + 2
|
||||
continue
|
||||
if line.startswith("//", i):
|
||||
break
|
||||
if line.startswith("/*", i):
|
||||
in_block = True
|
||||
i += 2
|
||||
continue
|
||||
out.append(line[i])
|
||||
i += 1
|
||||
lines.append("".join(out))
|
||||
return lines
|
||||
|
||||
|
||||
def main() -> int:
|
||||
root = repo_root()
|
||||
allow_path = Path(__file__).resolve().parent / "allowlist.txt"
|
||||
entries = load_allowlist(allow_path)
|
||||
hits = []
|
||||
for base in ("src", "include"):
|
||||
for path in sorted((root / base).rglob("*")):
|
||||
if not path.is_file() or path.suffix not in SUFFIXES:
|
||||
continue
|
||||
rel = path.relative_to(root).as_posix()
|
||||
try:
|
||||
text = path.read_text(encoding="utf-8")
|
||||
except UnicodeError:
|
||||
text = path.read_text(encoding="latin-1")
|
||||
for number, line in enumerate(strip_comments(text), start=1):
|
||||
if HIT.search(line):
|
||||
hits.append((allowed(rel, entries), f"{rel}:{number}"))
|
||||
unlisted = [item for ok, item in hits if not ok]
|
||||
for ok, item in hits:
|
||||
print(("ALLOW " if ok else "HIT ") + item)
|
||||
print(f"{len(hits)} hits, {len(unlisted)} unlisted")
|
||||
for item in unlisted:
|
||||
print(item)
|
||||
return 1 if unlisted else 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -4,6 +4,7 @@
|
||||
// cases keep the same scenario intent but run real Application/LedgerMaster/
|
||||
// PeerImp/JobQueue code through SteppingNetwork.
|
||||
//------------------------------------------------------------------------------
|
||||
#include <test/consensus/goldens/donor.h>
|
||||
#include <test/jtx/SteppingNetwork.h>
|
||||
#include <test/jtx/SteppingReplay.h>
|
||||
#include <test/jtx/amount.h>
|
||||
@@ -26,72 +27,14 @@ class SteppingCsf_test : public beast::unit_test::suite
|
||||
{
|
||||
// Xahaud's acquisition timeline; semantic lag/fork and replay checks below
|
||||
// are retained alongside this target-specific snapshot.
|
||||
static constexpr std::uint64_t kSlowMinorityFingerprint =
|
||||
0x4aea2f46ca499175ull;
|
||||
static constexpr std::uint64_t kHubNetworkFingerprint =
|
||||
0x75653559986b05c9ull;
|
||||
static constexpr std::uint64_t kDisputeFingerprint = 0x03773da2e07e273cull;
|
||||
static constexpr auto kSlowMinorityFingerprint =
|
||||
goldens::donor::kSlowMinorityFingerprint;
|
||||
static constexpr auto kHubNetworkFingerprint =
|
||||
goldens::donor::kHubNetworkFingerprint;
|
||||
static constexpr auto kDisputeFingerprint =
|
||||
goldens::donor::kDisputeFingerprint;
|
||||
|
||||
struct KProfiledDisputeSample
|
||||
{
|
||||
std::uint32_t k = 0;
|
||||
std::uint64_t fingerprint = 0;
|
||||
std::uint64_t events = 0;
|
||||
std::uint64_t weightedEvents = 0;
|
||||
std::size_t steps = 0;
|
||||
std::size_t beats = 0;
|
||||
std::uint32_t minValidated = 0;
|
||||
std::uint32_t maxValidated = 0;
|
||||
std::uint32_t forkCheckedSeqs = 0;
|
||||
std::uint32_t target = 0;
|
||||
std::uint32_t acceptedSeq = 0;
|
||||
std::uint32_t txASeq = 0;
|
||||
std::uint32_t txBSeq = 0;
|
||||
std::uint64_t clampHits = 0;
|
||||
std::int64_t requestedMs = 0;
|
||||
std::int64_t consumedMs = 0;
|
||||
std::int64_t maxConsumedBeatMs = 0;
|
||||
std::int64_t schedulerMs = 0;
|
||||
std::uint64_t heartbeatEvents = 0;
|
||||
std::uint64_t deliverEvents = 0;
|
||||
std::uint64_t jobEvents = 0;
|
||||
std::uint64_t timerEvents = 0;
|
||||
std::uint32_t firstClampWeight = 0;
|
||||
bool submittedA = false;
|
||||
bool submittedB = false;
|
||||
bool forkFree = false;
|
||||
bool converged = false;
|
||||
bool exactlyOneAccepted = false;
|
||||
bool acceptedSetVerified = false;
|
||||
// Availability before any verification-only backfill. False when
|
||||
// no transaction was accepted, or its historical ledger is missing.
|
||||
bool historyReadyAtSnapshot = false;
|
||||
|
||||
[[nodiscard]] bool
|
||||
operator==(KProfiledDisputeSample const& o) const
|
||||
{
|
||||
return k == o.k && fingerprint == o.fingerprint &&
|
||||
events == o.events && weightedEvents == o.weightedEvents &&
|
||||
steps == o.steps && beats == o.beats &&
|
||||
minValidated == o.minValidated &&
|
||||
maxValidated == o.maxValidated &&
|
||||
forkCheckedSeqs == o.forkCheckedSeqs && target == o.target &&
|
||||
acceptedSeq == o.acceptedSeq && txASeq == o.txASeq &&
|
||||
txBSeq == o.txBSeq && clampHits == o.clampHits &&
|
||||
requestedMs == o.requestedMs && consumedMs == o.consumedMs &&
|
||||
maxConsumedBeatMs == o.maxConsumedBeatMs &&
|
||||
schedulerMs == o.schedulerMs &&
|
||||
heartbeatEvents == o.heartbeatEvents &&
|
||||
deliverEvents == o.deliverEvents && jobEvents == o.jobEvents &&
|
||||
timerEvents == o.timerEvents &&
|
||||
firstClampWeight == o.firstClampWeight &&
|
||||
submittedA == o.submittedA && submittedB == o.submittedB &&
|
||||
forkFree == o.forkFree && converged == o.converged &&
|
||||
exactlyOneAccepted == o.exactlyOneAccepted &&
|
||||
acceptedSetVerified == o.acceptedSetVerified &&
|
||||
historyReadyAtSnapshot == o.historyReadyAtSnapshot;
|
||||
}
|
||||
};
|
||||
using KProfiledDisputeSample = goldens::donor::KProfiledDisputeSample;
|
||||
|
||||
struct SubmittedTx
|
||||
{
|
||||
@@ -253,6 +196,9 @@ class SteppingCsf_test : public beast::unit_test::suite
|
||||
auto const fingerprint = net.traceFingerprint();
|
||||
log << " slow-minority fingerprint 0x" << std::hex << fingerprint
|
||||
<< std::dec << ", traceCount=" << net.traceCount() << std::endl;
|
||||
if (goldens::donor::goldensPrint(*this))
|
||||
goldens::donor::printScalar(
|
||||
*this, "kSlowMinorityFingerprint", fingerprint);
|
||||
BEAST_EXPECT(fingerprint == kSlowMinorityFingerprint);
|
||||
|
||||
std::vector<uint256> payload;
|
||||
@@ -345,6 +291,9 @@ class SteppingCsf_test : public beast::unit_test::suite
|
||||
auto const fingerprint = net.traceFingerprint();
|
||||
log << " hub-network fingerprint 0x" << std::hex << fingerprint
|
||||
<< std::dec << ", traceCount=" << net.traceCount() << std::endl;
|
||||
if (goldens::donor::goldensPrint(*this))
|
||||
goldens::donor::printScalar(
|
||||
*this, "kHubNetworkFingerprint", fingerprint);
|
||||
BEAST_EXPECT(fingerprint == kHubNetworkFingerprint);
|
||||
|
||||
std::vector<uint256> payload;
|
||||
@@ -486,6 +435,9 @@ class SteppingCsf_test : public beast::unit_test::suite
|
||||
log << " dispute fingerprint 0x" << std::hex << fingerprint << std::dec
|
||||
<< ", traceCount=" << net.traceCount()
|
||||
<< ", acceptedSeq=" << acceptedSeq << std::endl;
|
||||
if (goldens::donor::goldensPrint(*this))
|
||||
goldens::donor::printScalar(
|
||||
*this, "kDisputeFingerprint", fingerprint);
|
||||
BEAST_EXPECT(fingerprint == kDisputeFingerprint);
|
||||
|
||||
std::vector<uint256> payload;
|
||||
@@ -553,7 +505,7 @@ class SteppingCsf_test : public beast::unit_test::suite
|
||||
out.minValidated = net.minValidatedSeq();
|
||||
for (std::uint32_t i = 0; i < 5; ++i)
|
||||
out.maxValidated = std::max(out.maxValidated, net.validSeq(i));
|
||||
out.forkCheckedSeqs = out.maxValidated >= 2 ? out.maxValidated - 1 : 0;
|
||||
out.forkCheckedSeqs = net.forkCheckedSeqs();
|
||||
out.clampHits = stats.clampHits;
|
||||
out.requestedMs = asMs(stats.requestedVirtualAdvance);
|
||||
out.consumedMs = asMs(stats.consumedVirtualAdvance);
|
||||
@@ -753,89 +705,15 @@ class SteppingCsf_test : public beast::unit_test::suite
|
||||
// These snapshots calibrate the xahaud implementation. K=3 resolves
|
||||
// here, so K=4 preserves the saturated unresolved control. No
|
||||
// consensus policy is changed to fit a snapshot.
|
||||
std::array<KProfiledDisputeSample, 5> const kExpected = {{
|
||||
{0, 0xc7b9f47f247a148aull,
|
||||
2606, 0,
|
||||
1132, 0,
|
||||
11, 11,
|
||||
10, 11,
|
||||
8, 8,
|
||||
0, 0,
|
||||
0, 0,
|
||||
0, 63030,
|
||||
0, 0,
|
||||
0, 0,
|
||||
0, true,
|
||||
true, true,
|
||||
true, true,
|
||||
true, true},
|
||||
{1, 0x8a2443e7d07ed17bull,
|
||||
2630, 2567,
|
||||
1156, 15,
|
||||
11, 11,
|
||||
10, 11,
|
||||
8, 8,
|
||||
0, 9,
|
||||
12835, 12760,
|
||||
1865, 66820,
|
||||
75, 717,
|
||||
332, 30,
|
||||
2, true,
|
||||
true, true,
|
||||
true, true,
|
||||
true, true},
|
||||
{2, 0x182d5979de1d171full,
|
||||
2708, 2844,
|
||||
1234, 27,
|
||||
11, 11,
|
||||
10, 11,
|
||||
9, 9,
|
||||
0, 26,
|
||||
28440, 27960,
|
||||
2000, 78980,
|
||||
135, 542,
|
||||
513, 42,
|
||||
2, true,
|
||||
true, true,
|
||||
true, true,
|
||||
true, true},
|
||||
{3, 0xcfc0f9405b29cef7ull,
|
||||
4661, 7645,
|
||||
3187, 112,
|
||||
11, 12,
|
||||
11, 11,
|
||||
10, 0,
|
||||
10, 111,
|
||||
114675, 112885,
|
||||
2000, 163905,
|
||||
545, 722,
|
||||
1818, 100,
|
||||
3, true,
|
||||
true, true,
|
||||
true, true,
|
||||
true, false},
|
||||
{4, 0x668228fd72846e90ull,
|
||||
4980, 8331,
|
||||
3506, 160,
|
||||
8, 8,
|
||||
7, 11,
|
||||
0, 0,
|
||||
0, 160,
|
||||
166620, 161000,
|
||||
2000, 212020,
|
||||
725, 603,
|
||||
2046, 130,
|
||||
2, true,
|
||||
true, true,
|
||||
false, false,
|
||||
false, false},
|
||||
}};
|
||||
auto const& kExpected = goldens::donor::kProfiledDispute;
|
||||
|
||||
bool sawResolvedUnderPressure = false;
|
||||
bool sawSaturatedUnresolved = false;
|
||||
for (auto const& expected : kExpected)
|
||||
{
|
||||
auto const first = runProfiledDispute(expected.k);
|
||||
if (goldens::donor::goldensPrint(*this))
|
||||
goldens::donor::printDispute(*this, first);
|
||||
BEAST_EXPECT(first == expected);
|
||||
BEAST_EXPECT(first.submittedA);
|
||||
BEAST_EXPECT(first.submittedB);
|
||||
@@ -874,6 +752,52 @@ class SteppingCsf_test : public beast::unit_test::suite
|
||||
BEAST_EXPECT(sawSaturatedUnresolved);
|
||||
}
|
||||
|
||||
void
|
||||
testProfiledStopReason()
|
||||
{
|
||||
testcase(
|
||||
"profiled stop reason does not re-evaluate a consuming predicate");
|
||||
using namespace jtx;
|
||||
using namespace std::chrono_literals;
|
||||
using Stop = SteppingNetwork::KProfiledRunStats::Stop;
|
||||
SteppingNetwork net(*this);
|
||||
net.validators(1).mesh();
|
||||
if (!BEAST_EXPECT(net.allUp() && net.meshReady()))
|
||||
return;
|
||||
|
||||
SteppingNetwork::KProfiledOptions const options{3, 5ms, {}};
|
||||
std::uint32_t calls = 0;
|
||||
auto const consumed = net.runProfiledUntil(
|
||||
[&] { return ++calls == 1; }, options, {4, 1000});
|
||||
BEAST_EXPECT(consumed.stop == Stop::predicate);
|
||||
BEAST_EXPECT(consumed.beats == 0);
|
||||
BEAST_EXPECT(calls == 1);
|
||||
|
||||
// A budget stop must not call a predicate which the loop never tested.
|
||||
calls = 0;
|
||||
auto const exhausted = net.runProfiledUntil(
|
||||
[&] {
|
||||
++calls;
|
||||
return true;
|
||||
},
|
||||
options,
|
||||
{0, 1000});
|
||||
BEAST_EXPECT(exhausted.stop == Stop::heartbeatBudget);
|
||||
BEAST_EXPECT(exhausted.beats == 0);
|
||||
BEAST_EXPECT(calls == 0);
|
||||
|
||||
calls = 0;
|
||||
auto const steps = net.runProfiledUntil(
|
||||
[&] {
|
||||
++calls;
|
||||
return true;
|
||||
},
|
||||
options,
|
||||
{4, 0});
|
||||
BEAST_EXPECT(steps.stop == Stop::stepLimit);
|
||||
BEAST_EXPECT(calls == 0);
|
||||
}
|
||||
|
||||
public:
|
||||
void
|
||||
run() override
|
||||
@@ -882,6 +806,7 @@ public:
|
||||
testHubNetwork();
|
||||
testDispute();
|
||||
testProfiledDisputeForkHunt();
|
||||
testProfiledStopReason();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -225,7 +225,7 @@ class SteppingDeterminism_test : public beast::unit_test::suite
|
||||
out.minValidated = net.minValidatedSeq();
|
||||
for (std::uint32_t i = 0; i < 3; ++i)
|
||||
out.maxValidated = std::max(out.maxValidated, net.validSeq(i));
|
||||
out.forkCheckedSeqs = out.maxValidated >= 2 ? out.maxValidated - 1 : 0;
|
||||
out.forkCheckedSeqs = net.forkCheckedSeqs();
|
||||
out.clampHits = stats.clampHits;
|
||||
out.requestedMs = asMs(stats.requestedVirtualAdvance);
|
||||
out.consumedMs = asMs(stats.consumedVirtualAdvance);
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
// XRP payments through the local submission path, and replay the whole
|
||||
// tx-bearing timeline bit-for-bit.
|
||||
//------------------------------------------------------------------------------
|
||||
#include <test/consensus/goldens/donor.h>
|
||||
#include <test/jtx/SteppingReplay.h>
|
||||
#include <test/jtx/Traffic.h>
|
||||
#include <test/jtx/amount.h>
|
||||
@@ -28,13 +29,12 @@ class SteppingTraffic_test : public beast::unit_test::suite
|
||||
{
|
||||
using Payload = std::optional<std::vector<uint256>>;
|
||||
|
||||
static constexpr std::uint64_t kTrafficSeed = 0x5452414646494331ull;
|
||||
static constexpr std::uint64_t kTrafficFingerprint = 0x4b1c6cb7b9297b94ull;
|
||||
static constexpr std::uint64_t kTrafficEvents = 1413;
|
||||
static constexpr std::uint64_t kTrafficPayloadFingerprint =
|
||||
// Payload includes xahaud ledger/transaction hashes. Keep the donor
|
||||
// event-order pin above and the independent semantic/replay checks.
|
||||
0xd77bfa4d445420e3ull;
|
||||
static constexpr auto kTrafficSeed = goldens::donor::kTrafficSeed;
|
||||
static constexpr auto kTrafficFingerprint =
|
||||
goldens::donor::kTrafficFingerprint;
|
||||
static constexpr auto kTrafficEvents = goldens::donor::kTrafficEvents;
|
||||
static constexpr auto kTrafficPayloadFingerprint =
|
||||
goldens::donor::kTrafficPayloadFingerprint;
|
||||
|
||||
static std::uint64_t
|
||||
payloadFingerprint(std::vector<uint256> const& payload)
|
||||
@@ -190,6 +190,15 @@ class SteppingTraffic_test : public beast::unit_test::suite
|
||||
auto const content = payloadFingerprint(*payload);
|
||||
log << " traffic content observed: fingerprint 0x" << std::hex
|
||||
<< content << std::dec << std::endl;
|
||||
if (goldens::donor::goldensPrint(*this))
|
||||
{
|
||||
goldens::donor::printScalar(
|
||||
*this, "kTrafficFingerprint", net.traceFingerprint());
|
||||
goldens::donor::printScalar(
|
||||
*this, "kTrafficEvents", net.traceCount());
|
||||
goldens::donor::printScalar(
|
||||
*this, "kTrafficPayloadFingerprint", content);
|
||||
}
|
||||
BEAST_EXPECT(net.traceFingerprint() == kTrafficFingerprint);
|
||||
BEAST_EXPECT(net.traceCount() == kTrafficEvents);
|
||||
BEAST_EXPECT(content == kTrafficPayloadFingerprint);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// SteppingTrust -- per-node UNL shapes in the real stepping harness.
|
||||
//------------------------------------------------------------------------------
|
||||
#include <test/consensus/goldens/donor.h>
|
||||
#include <test/jtx/SteppingNetwork.h>
|
||||
#include <test/jtx/SteppingReplay.h>
|
||||
#include <test/jtx/amount.h>
|
||||
@@ -23,67 +24,7 @@ class SteppingTrust_test : public beast::unit_test::suite
|
||||
{
|
||||
static constexpr std::uint32_t kForkPeers = 10;
|
||||
|
||||
struct KProfiledForkCell
|
||||
{
|
||||
std::uint32_t overlap = 0;
|
||||
std::uint32_t k = 0;
|
||||
std::uint64_t fingerprint = 0;
|
||||
std::uint64_t events = 0;
|
||||
std::uint64_t weightedEvents = 0;
|
||||
std::size_t steps = 0;
|
||||
std::size_t beats = 0;
|
||||
std::uint32_t minValidated = 0;
|
||||
std::uint32_t maxValidated = 0;
|
||||
std::uint32_t forkCheckedSeqs = 0;
|
||||
std::uint32_t target = 0;
|
||||
std::uint32_t divergentSeq = 0;
|
||||
std::uint32_t agreedSeq = 0;
|
||||
std::uint32_t txASeq = 0;
|
||||
std::uint32_t txBSeq = 0;
|
||||
std::uint64_t clampHits = 0;
|
||||
std::int64_t requestedMs = 0;
|
||||
std::int64_t consumedMs = 0;
|
||||
std::int64_t maxConsumedBeatMs = 0;
|
||||
std::int64_t schedulerMs = 0;
|
||||
std::uint64_t heartbeatEvents = 0;
|
||||
std::uint64_t deliverEvents = 0;
|
||||
std::uint64_t jobEvents = 0;
|
||||
std::uint64_t timerEvents = 0;
|
||||
std::uint32_t firstClampWeight = 0;
|
||||
bool submittedA = false;
|
||||
bool submittedB = false;
|
||||
bool forkFree = false;
|
||||
bool forked = false;
|
||||
bool safeResolved = false;
|
||||
bool unresolved = false;
|
||||
bool saturated = false;
|
||||
std::int64_t unitCostMs = 5;
|
||||
|
||||
[[nodiscard]] bool
|
||||
operator==(KProfiledForkCell const& o) const
|
||||
{
|
||||
return overlap == o.overlap && k == o.k &&
|
||||
fingerprint == o.fingerprint && events == o.events &&
|
||||
weightedEvents == o.weightedEvents && steps == o.steps &&
|
||||
beats == o.beats && minValidated == o.minValidated &&
|
||||
maxValidated == o.maxValidated &&
|
||||
forkCheckedSeqs == o.forkCheckedSeqs && target == o.target &&
|
||||
divergentSeq == o.divergentSeq && agreedSeq == o.agreedSeq &&
|
||||
txASeq == o.txASeq && txBSeq == o.txBSeq &&
|
||||
clampHits == o.clampHits && requestedMs == o.requestedMs &&
|
||||
consumedMs == o.consumedMs &&
|
||||
maxConsumedBeatMs == o.maxConsumedBeatMs &&
|
||||
schedulerMs == o.schedulerMs &&
|
||||
heartbeatEvents == o.heartbeatEvents &&
|
||||
deliverEvents == o.deliverEvents && jobEvents == o.jobEvents &&
|
||||
timerEvents == o.timerEvents &&
|
||||
firstClampWeight == o.firstClampWeight &&
|
||||
submittedA == o.submittedA && submittedB == o.submittedB &&
|
||||
forkFree == o.forkFree && forked == o.forked &&
|
||||
safeResolved == o.safeResolved && unresolved == o.unresolved &&
|
||||
saturated == o.saturated && unitCostMs == o.unitCostMs;
|
||||
}
|
||||
};
|
||||
using KProfiledForkCell = goldens::donor::KProfiledForkCell;
|
||||
|
||||
struct ForkShape
|
||||
{
|
||||
@@ -562,7 +503,7 @@ class SteppingTrust_test : public beast::unit_test::suite
|
||||
out.minValidated = net.minValidatedSeq();
|
||||
for (std::uint32_t i = 0; i < kForkPeers; ++i)
|
||||
out.maxValidated = std::max(out.maxValidated, net.validSeq(i));
|
||||
out.forkCheckedSeqs = out.maxValidated >= 2 ? out.maxValidated - 1 : 0;
|
||||
out.forkCheckedSeqs = net.forkCheckedSeqs();
|
||||
out.clampHits = stats.clampHits;
|
||||
out.requestedMs = asMs(stats.requestedVirtualAdvance);
|
||||
out.consumedMs = asMs(stats.consumedVirtualAdvance);
|
||||
@@ -983,107 +924,7 @@ class SteppingTrust_test : public beast::unit_test::suite
|
||||
// Retain the donor's K axis with its 5ms unit. Extra explicitly
|
||||
// labelled 1ms cells also exercise progress under lighter pressure.
|
||||
// A sample is identified by overlap, K AND unitCostMs.
|
||||
std::array<KProfiledForkCell, 9> const kExpected = {{
|
||||
{0, 0, 0xc37088e4d4e3963bull,
|
||||
3439, 0, 2020,
|
||||
0, 8, 8,
|
||||
7, 8, 5,
|
||||
0, 5, 5,
|
||||
0, 0, 0,
|
||||
0, 54020, 0,
|
||||
0, 0, 0,
|
||||
0, true, true,
|
||||
false, true, false,
|
||||
false, false, 5},
|
||||
{0, 1, 0xe24a6cdd8143ee3bull,
|
||||
3435, 4492, 2016,
|
||||
12, 8, 8,
|
||||
7, 8, 5,
|
||||
0, 5, 5,
|
||||
0, 4492, 4492,
|
||||
787, 54345, 120,
|
||||
1312, 582, 0,
|
||||
0, true, true,
|
||||
false, true, false,
|
||||
false, false, 1},
|
||||
{0, 1, 0xc364543a2b0ed316ull,
|
||||
3809, 5335, 2390,
|
||||
26, 8, 8,
|
||||
7, 8, 6,
|
||||
0, 6, 6,
|
||||
25, 26675, 26455,
|
||||
2000, 68465, 250,
|
||||
1279, 807, 52,
|
||||
2, true, true,
|
||||
false, true, false,
|
||||
false, true, 5},
|
||||
{4, 0, 0xdd2d1949c1e5e96full,
|
||||
6104, 0, 4178,
|
||||
0, 8, 8,
|
||||
7, 8, 0,
|
||||
5, 5, 0,
|
||||
0, 0, 0,
|
||||
0, 55020, 0,
|
||||
0, 0, 0,
|
||||
0, true, true,
|
||||
true, false, true,
|
||||
false, false, 5},
|
||||
{4, 1, 0x272a08d874ce16f9ull,
|
||||
6191, 9605, 4265,
|
||||
14, 8, 8,
|
||||
7, 8, 0,
|
||||
5, 5, 0,
|
||||
4, 9605, 9598,
|
||||
1640, 56548, 140,
|
||||
2884, 1217, 22,
|
||||
3, true, true,
|
||||
true, false, true,
|
||||
false, true, 1},
|
||||
{4, 1, 0xcd8cee7af2732ffcull,
|
||||
14875, 32503, 12949,
|
||||
160, 5, 5,
|
||||
4, 8, 0,
|
||||
0, 0, 0,
|
||||
160, 162515, 161000,
|
||||
2000, 203010, 1520,
|
||||
2855, 8127, 445,
|
||||
3, true, true,
|
||||
true, false, false,
|
||||
true, true, 5},
|
||||
{6, 0, 0x7bb058844fa2eae9ull,
|
||||
6098, 0, 4068,
|
||||
0, 8, 8,
|
||||
7, 8, 0,
|
||||
5, 5, 0,
|
||||
0, 0, 0,
|
||||
0, 54020, 0,
|
||||
0, 0, 0,
|
||||
0, true, true,
|
||||
true, false, true,
|
||||
false, false, 5},
|
||||
{6, 1, 0x4d2f69d8884dca4dull,
|
||||
6212, 9314, 4182,
|
||||
13, 8, 8,
|
||||
7, 8, 0,
|
||||
5, 5, 0,
|
||||
7, 9314, 9299,
|
||||
1632, 55612, 130,
|
||||
2952, 1082, 16,
|
||||
3, true, true,
|
||||
true, false, true,
|
||||
false, true, 1},
|
||||
{6, 1, 0x525af9afc81f4861ull,
|
||||
15382, 32495, 13352,
|
||||
160, 5, 12,
|
||||
11, 8, 0,
|
||||
0, 6, 0,
|
||||
160, 162475, 161000,
|
||||
2000, 203010, 1550,
|
||||
4008, 7343, 449,
|
||||
2, true, true,
|
||||
true, false, false,
|
||||
true, true, 5},
|
||||
}};
|
||||
auto const& kExpected = goldens::donor::kProfiledFork;
|
||||
|
||||
bool sawForkUnderPressureControl = false;
|
||||
bool sawSafeBaseline = false;
|
||||
@@ -1095,6 +936,8 @@ class SteppingTrust_test : public beast::unit_test::suite
|
||||
expected.overlap,
|
||||
expected.k,
|
||||
std::chrono::milliseconds{expected.unitCostMs});
|
||||
if (goldens::donor::goldensPrint(*this))
|
||||
goldens::donor::printFork(*this, cell);
|
||||
BEAST_EXPECT(cell == expected);
|
||||
BEAST_EXPECT(cell.submittedA);
|
||||
BEAST_EXPECT(cell.submittedB);
|
||||
|
||||
443
src/test/consensus/goldens/donor.h
Normal file
443
src/test/consensus/goldens/donor.h
Normal file
@@ -0,0 +1,443 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
Per-lineage golden manifest for the donor stepping suites.
|
||||
|
||||
lineage: donor / #814
|
||||
seed base: 0xFAB1E5EED0000000 (harness default)
|
||||
traffic seed: 0x5452414646494331
|
||||
topology: slow-minority and dispute are 5 validators, mesh.
|
||||
The hub pin is 5 validators, each linked only to one
|
||||
non-validator hub. Traffic is 3 validators, mesh.
|
||||
Trust fork cells are N=10 with the overlap in the row.
|
||||
pacing: profiled dispute K in {0,1,2,3,4}, unitCost 5ms,
|
||||
heartbeat budget 160. This lineage has one K=3 row;
|
||||
it resolves. There is no second K=3 seed.
|
||||
RNG mapping: c527e0a721 (engine-range randomU64 plus the zero-bit
|
||||
group fix)
|
||||
captured at: c527e0a721. These are the pins recaptured at
|
||||
58ff1777fb after the portable rand_int mapping
|
||||
and unchanged since; extracted from the test
|
||||
bodies on top of f5963e38d2.
|
||||
platforms: macOS libc++ holds these literals.
|
||||
|
||||
Outcome-bearing (the assertion is the whole row, not only the hash):
|
||||
kProfiledDispute, kProfiledFork.
|
||||
Content-bearing (ledger hashes plus transaction ids and sequences,
|
||||
not the scheduler event order):
|
||||
kTrafficPayloadFingerprint.
|
||||
Pure event-order pins (a hash or a count, no outcome table):
|
||||
kSlowMinorityFingerprint, kHubNetworkFingerprint,
|
||||
kDisputeFingerprint, kTrafficFingerprint, kTrafficEvents.
|
||||
|
||||
Regeneration: --unittest-arg=goldens=print with one of
|
||||
SteppingCsf, SteppingTrust, or SteppingTraffic. Each prints GOLDEN
|
||||
lines from the observed values. Review that diff; do not treat it as
|
||||
automatic. A pinned 2000 is the highest virtual time one beat
|
||||
consumed, not a fixed per-beat budget.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef XRPL_TEST_CONSENSUS_GOLDENS_DONOR_H_INCLUDED
|
||||
#define XRPL_TEST_CONSENSUS_GOLDENS_DONOR_H_INCLUDED
|
||||
|
||||
#include <xrpl/beast/unit_test/suite.h>
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <ios>
|
||||
#include <string>
|
||||
|
||||
namespace ripple::test::goldens::donor {
|
||||
|
||||
inline constexpr std::uint64_t kSlowMinorityFingerprint = 0x4aea2f46ca499175ull;
|
||||
inline constexpr std::uint64_t kHubNetworkFingerprint = 0x75653559986b05c9ull;
|
||||
inline constexpr std::uint64_t kDisputeFingerprint = 0x03773da2e07e273cull;
|
||||
inline constexpr std::uint64_t kTrafficSeed = 0x5452414646494331ull;
|
||||
inline constexpr std::uint64_t kTrafficFingerprint = 0x4b1c6cb7b9297b94ull;
|
||||
inline constexpr std::uint64_t kTrafficEvents = 1413;
|
||||
inline constexpr std::uint64_t kTrafficPayloadFingerprint =
|
||||
0xd77bfa4d445420e3ull;
|
||||
|
||||
struct KProfiledDisputeSample
|
||||
{
|
||||
std::uint32_t k = 0;
|
||||
std::uint64_t fingerprint = 0;
|
||||
std::uint64_t events = 0;
|
||||
std::uint64_t weightedEvents = 0;
|
||||
std::size_t steps = 0;
|
||||
std::size_t beats = 0;
|
||||
std::uint32_t minValidated = 0;
|
||||
std::uint32_t maxValidated = 0;
|
||||
std::uint32_t forkCheckedSeqs = 0;
|
||||
std::uint32_t target = 0;
|
||||
std::uint32_t acceptedSeq = 0;
|
||||
std::uint32_t txASeq = 0;
|
||||
std::uint32_t txBSeq = 0;
|
||||
std::uint64_t clampHits = 0;
|
||||
std::int64_t requestedMs = 0;
|
||||
std::int64_t consumedMs = 0;
|
||||
// Highest virtual time one beat consumed. A pinned 2000 is that
|
||||
// observed maximum, not a fixed per-beat budget.
|
||||
std::int64_t maxConsumedBeatMs = 0;
|
||||
std::int64_t schedulerMs = 0;
|
||||
std::uint64_t heartbeatEvents = 0;
|
||||
std::uint64_t deliverEvents = 0;
|
||||
std::uint64_t jobEvents = 0;
|
||||
std::uint64_t timerEvents = 0;
|
||||
std::uint32_t firstClampWeight = 0;
|
||||
bool submittedA = false;
|
||||
bool submittedB = false;
|
||||
bool forkFree = false;
|
||||
bool converged = false;
|
||||
bool exactlyOneAccepted = false;
|
||||
bool acceptedSetVerified = false;
|
||||
// Availability before any verification-only backfill. False when
|
||||
// no transaction was accepted, or its historical ledger is missing.
|
||||
bool historyReadyAtSnapshot = false;
|
||||
|
||||
[[nodiscard]] bool
|
||||
operator==(KProfiledDisputeSample const& o) const
|
||||
{
|
||||
return k == o.k && fingerprint == o.fingerprint && events == o.events &&
|
||||
weightedEvents == o.weightedEvents && steps == o.steps &&
|
||||
beats == o.beats && minValidated == o.minValidated &&
|
||||
maxValidated == o.maxValidated &&
|
||||
forkCheckedSeqs == o.forkCheckedSeqs && target == o.target &&
|
||||
acceptedSeq == o.acceptedSeq && txASeq == o.txASeq &&
|
||||
txBSeq == o.txBSeq && clampHits == o.clampHits &&
|
||||
requestedMs == o.requestedMs && consumedMs == o.consumedMs &&
|
||||
maxConsumedBeatMs == o.maxConsumedBeatMs &&
|
||||
schedulerMs == o.schedulerMs &&
|
||||
heartbeatEvents == o.heartbeatEvents &&
|
||||
deliverEvents == o.deliverEvents && jobEvents == o.jobEvents &&
|
||||
timerEvents == o.timerEvents &&
|
||||
firstClampWeight == o.firstClampWeight &&
|
||||
submittedA == o.submittedA && submittedB == o.submittedB &&
|
||||
forkFree == o.forkFree && converged == o.converged &&
|
||||
exactlyOneAccepted == o.exactlyOneAccepted &&
|
||||
acceptedSetVerified == o.acceptedSetVerified &&
|
||||
historyReadyAtSnapshot == o.historyReadyAtSnapshot;
|
||||
}
|
||||
};
|
||||
|
||||
inline constexpr std::array<KProfiledDisputeSample, 5> kProfiledDispute = {{
|
||||
{0, 0xc7b9f47f247a148aull,
|
||||
2606, 0,
|
||||
1132, 0,
|
||||
11, 11,
|
||||
10, 11,
|
||||
8, 8,
|
||||
0, 0,
|
||||
0, 0,
|
||||
0, 63030,
|
||||
0, 0,
|
||||
0, 0,
|
||||
0, true,
|
||||
true, true,
|
||||
true, true,
|
||||
true, true},
|
||||
{1, 0x8a2443e7d07ed17bull,
|
||||
2630, 2567,
|
||||
1156, 15,
|
||||
11, 11,
|
||||
10, 11,
|
||||
8, 8,
|
||||
0, 9,
|
||||
12835, 12760,
|
||||
1865, 66820,
|
||||
75, 717,
|
||||
332, 30,
|
||||
2, true,
|
||||
true, true,
|
||||
true, true,
|
||||
true, true},
|
||||
{2, 0x182d5979de1d171full,
|
||||
2708, 2844,
|
||||
1234, 27,
|
||||
11, 11,
|
||||
10, 11,
|
||||
9, 9,
|
||||
0, 26,
|
||||
28440, 27960,
|
||||
2000, 78980,
|
||||
135, 542,
|
||||
513, 42,
|
||||
2, true,
|
||||
true, true,
|
||||
true, true,
|
||||
true, true},
|
||||
{3, 0xcfc0f9405b29cef7ull,
|
||||
4661, 7645,
|
||||
3187, 112,
|
||||
11, 12,
|
||||
11, 11,
|
||||
10, 0,
|
||||
10, 111,
|
||||
114675, 112885,
|
||||
2000, 163905,
|
||||
545, 722,
|
||||
1818, 100,
|
||||
3, true,
|
||||
true, true,
|
||||
true, true,
|
||||
true, false},
|
||||
{4, 0x668228fd72846e90ull,
|
||||
4980, 8331,
|
||||
3506, 160,
|
||||
8, 8,
|
||||
7, 11,
|
||||
0, 0,
|
||||
0, 160,
|
||||
166620, 161000,
|
||||
2000, 212020,
|
||||
725, 603,
|
||||
2046, 130,
|
||||
2, true,
|
||||
true, true,
|
||||
false, false,
|
||||
false, false},
|
||||
}};
|
||||
|
||||
struct KProfiledForkCell
|
||||
{
|
||||
std::uint32_t overlap = 0;
|
||||
std::uint32_t k = 0;
|
||||
std::uint64_t fingerprint = 0;
|
||||
std::uint64_t events = 0;
|
||||
std::uint64_t weightedEvents = 0;
|
||||
std::size_t steps = 0;
|
||||
std::size_t beats = 0;
|
||||
std::uint32_t minValidated = 0;
|
||||
std::uint32_t maxValidated = 0;
|
||||
std::uint32_t forkCheckedSeqs = 0;
|
||||
std::uint32_t target = 0;
|
||||
std::uint32_t divergentSeq = 0;
|
||||
std::uint32_t agreedSeq = 0;
|
||||
std::uint32_t txASeq = 0;
|
||||
std::uint32_t txBSeq = 0;
|
||||
std::uint64_t clampHits = 0;
|
||||
std::int64_t requestedMs = 0;
|
||||
std::int64_t consumedMs = 0;
|
||||
std::int64_t maxConsumedBeatMs = 0;
|
||||
std::int64_t schedulerMs = 0;
|
||||
std::uint64_t heartbeatEvents = 0;
|
||||
std::uint64_t deliverEvents = 0;
|
||||
std::uint64_t jobEvents = 0;
|
||||
std::uint64_t timerEvents = 0;
|
||||
std::uint32_t firstClampWeight = 0;
|
||||
bool submittedA = false;
|
||||
bool submittedB = false;
|
||||
bool forkFree = false;
|
||||
bool forked = false;
|
||||
bool safeResolved = false;
|
||||
bool unresolved = false;
|
||||
bool saturated = false;
|
||||
std::int64_t unitCostMs = 5;
|
||||
|
||||
[[nodiscard]] bool
|
||||
operator==(KProfiledForkCell const& o) const
|
||||
{
|
||||
return overlap == o.overlap && k == o.k &&
|
||||
fingerprint == o.fingerprint && events == o.events &&
|
||||
weightedEvents == o.weightedEvents && steps == o.steps &&
|
||||
beats == o.beats && minValidated == o.minValidated &&
|
||||
maxValidated == o.maxValidated &&
|
||||
forkCheckedSeqs == o.forkCheckedSeqs && target == o.target &&
|
||||
divergentSeq == o.divergentSeq && agreedSeq == o.agreedSeq &&
|
||||
txASeq == o.txASeq && txBSeq == o.txBSeq &&
|
||||
clampHits == o.clampHits && requestedMs == o.requestedMs &&
|
||||
consumedMs == o.consumedMs &&
|
||||
maxConsumedBeatMs == o.maxConsumedBeatMs &&
|
||||
schedulerMs == o.schedulerMs &&
|
||||
heartbeatEvents == o.heartbeatEvents &&
|
||||
deliverEvents == o.deliverEvents && jobEvents == o.jobEvents &&
|
||||
timerEvents == o.timerEvents &&
|
||||
firstClampWeight == o.firstClampWeight &&
|
||||
submittedA == o.submittedA && submittedB == o.submittedB &&
|
||||
forkFree == o.forkFree && forked == o.forked &&
|
||||
safeResolved == o.safeResolved && unresolved == o.unresolved &&
|
||||
saturated == o.saturated && unitCostMs == o.unitCostMs;
|
||||
}
|
||||
};
|
||||
|
||||
inline constexpr std::array<KProfiledForkCell, 9> kProfiledFork = {{
|
||||
{0, 0, 0xc37088e4d4e3963bull,
|
||||
3439, 0, 2020,
|
||||
0, 8, 8,
|
||||
7, 8, 5,
|
||||
0, 5, 5,
|
||||
0, 0, 0,
|
||||
0, 54020, 0,
|
||||
0, 0, 0,
|
||||
0, true, true,
|
||||
false, true, false,
|
||||
false, false, 5},
|
||||
{0, 1, 0xe24a6cdd8143ee3bull,
|
||||
3435, 4492, 2016,
|
||||
12, 8, 8,
|
||||
7, 8, 5,
|
||||
0, 5, 5,
|
||||
0, 4492, 4492,
|
||||
787, 54345, 120,
|
||||
1312, 582, 0,
|
||||
0, true, true,
|
||||
false, true, false,
|
||||
false, false, 1},
|
||||
{0, 1, 0xc364543a2b0ed316ull,
|
||||
3809, 5335, 2390,
|
||||
26, 8, 8,
|
||||
7, 8, 6,
|
||||
0, 6, 6,
|
||||
25, 26675, 26455,
|
||||
2000, 68465, 250,
|
||||
1279, 807, 52,
|
||||
2, true, true,
|
||||
false, true, false,
|
||||
false, true, 5},
|
||||
{4, 0, 0xdd2d1949c1e5e96full,
|
||||
6104, 0, 4178,
|
||||
0, 8, 8,
|
||||
7, 8, 0,
|
||||
5, 5, 0,
|
||||
0, 0, 0,
|
||||
0, 55020, 0,
|
||||
0, 0, 0,
|
||||
0, true, true,
|
||||
true, false, true,
|
||||
false, false, 5},
|
||||
{4, 1, 0x272a08d874ce16f9ull,
|
||||
6191, 9605, 4265,
|
||||
14, 8, 8,
|
||||
7, 8, 0,
|
||||
5, 5, 0,
|
||||
4, 9605, 9598,
|
||||
1640, 56548, 140,
|
||||
2884, 1217, 22,
|
||||
3, true, true,
|
||||
true, false, true,
|
||||
false, true, 1},
|
||||
{4, 1, 0xcd8cee7af2732ffcull,
|
||||
14875, 32503, 12949,
|
||||
160, 5, 5,
|
||||
4, 8, 0,
|
||||
0, 0, 0,
|
||||
160, 162515, 161000,
|
||||
2000, 203010, 1520,
|
||||
2855, 8127, 445,
|
||||
3, true, true,
|
||||
true, false, false,
|
||||
true, true, 5},
|
||||
{6, 0, 0x7bb058844fa2eae9ull,
|
||||
6098, 0, 4068,
|
||||
0, 8, 8,
|
||||
7, 8, 0,
|
||||
5, 5, 0,
|
||||
0, 0, 0,
|
||||
0, 54020, 0,
|
||||
0, 0, 0,
|
||||
0, true, true,
|
||||
true, false, true,
|
||||
false, false, 5},
|
||||
{6, 1, 0x4d2f69d8884dca4dull,
|
||||
6212, 9314, 4182,
|
||||
13, 8, 8,
|
||||
7, 8, 0,
|
||||
5, 5, 0,
|
||||
7, 9314, 9299,
|
||||
1632, 55612, 130,
|
||||
2952, 1082, 16,
|
||||
3, true, true,
|
||||
true, false, true,
|
||||
false, true, 1},
|
||||
{6, 1, 0x525af9afc81f4861ull,
|
||||
15382, 32495, 13352,
|
||||
160, 5, 12,
|
||||
11, 8, 0,
|
||||
0, 6, 0,
|
||||
160, 162475, 161000,
|
||||
2000, 203010, 1550,
|
||||
4008, 7343, 449,
|
||||
2, true, true,
|
||||
true, false, false,
|
||||
true, true, 5},
|
||||
}};
|
||||
|
||||
[[nodiscard]] inline bool
|
||||
goldensPrint(beast::unit_test::suite const& s)
|
||||
{
|
||||
return s.arg().find("goldens=print") != std::string::npos;
|
||||
}
|
||||
|
||||
inline void
|
||||
printDispute(beast::unit_test::suite& s, KProfiledDisputeSample const& r)
|
||||
{
|
||||
s.log << "GOLDEN-DISPUTE"
|
||||
<< " k=" << r.k << " fp=0x" << std::hex << r.fingerprint << std::dec
|
||||
<< " events=" << r.events << " weightedEvents=" << r.weightedEvents
|
||||
<< " steps=" << r.steps << " beats=" << r.beats
|
||||
<< " minValidated=" << r.minValidated
|
||||
<< " maxValidated=" << r.maxValidated
|
||||
<< " forkCheckedSeqs=" << r.forkCheckedSeqs << " target=" << r.target
|
||||
<< " acceptedSeq=" << r.acceptedSeq << " txASeq=" << r.txASeq
|
||||
<< " txBSeq=" << r.txBSeq << " clampHits=" << r.clampHits
|
||||
<< " requestedMs=" << r.requestedMs << " consumedMs=" << r.consumedMs
|
||||
<< " maxBeatMs=" << r.maxConsumedBeatMs
|
||||
<< " schedulerMs=" << r.schedulerMs
|
||||
<< " heartbeat=" << r.heartbeatEvents
|
||||
<< " deliver=" << r.deliverEvents << " job=" << r.jobEvents
|
||||
<< " timer=" << r.timerEvents
|
||||
<< " firstClampWeight=" << r.firstClampWeight
|
||||
<< " submitted=" << r.submittedA << "/" << r.submittedB
|
||||
<< " forkFree=" << r.forkFree << " converged=" << r.converged
|
||||
<< " exactlyOne=" << r.exactlyOneAccepted
|
||||
<< " acceptedSet=" << r.acceptedSetVerified
|
||||
<< " historyReady=" << r.historyReadyAtSnapshot << std::endl;
|
||||
}
|
||||
|
||||
inline void
|
||||
printFork(beast::unit_test::suite& s, KProfiledForkCell const& r)
|
||||
{
|
||||
s.log << "GOLDEN-FORK"
|
||||
<< " overlap=" << r.overlap << " k=" << r.k
|
||||
<< " unitCostMs=" << r.unitCostMs << " fp=0x" << std::hex
|
||||
<< r.fingerprint << std::dec << " events=" << r.events
|
||||
<< " weightedEvents=" << r.weightedEvents << " steps=" << r.steps
|
||||
<< " beats=" << r.beats << " minValidated=" << r.minValidated
|
||||
<< " maxValidated=" << r.maxValidated
|
||||
<< " forkCheckedSeqs=" << r.forkCheckedSeqs << " target=" << r.target
|
||||
<< " divergentSeq=" << r.divergentSeq << " agreedSeq=" << r.agreedSeq
|
||||
<< " txASeq=" << r.txASeq << " txBSeq=" << r.txBSeq
|
||||
<< " clampHits=" << r.clampHits << " requestedMs=" << r.requestedMs
|
||||
<< " consumedMs=" << r.consumedMs
|
||||
<< " maxBeatMs=" << r.maxConsumedBeatMs
|
||||
<< " schedulerMs=" << r.schedulerMs
|
||||
<< " heartbeat=" << r.heartbeatEvents
|
||||
<< " deliver=" << r.deliverEvents << " job=" << r.jobEvents
|
||||
<< " timer=" << r.timerEvents
|
||||
<< " firstClampWeight=" << r.firstClampWeight
|
||||
<< " submitted=" << r.submittedA << "/" << r.submittedB
|
||||
<< " forkFree=" << r.forkFree << " forked=" << r.forked
|
||||
<< " safeResolved=" << r.safeResolved
|
||||
<< " unresolved=" << r.unresolved << " saturated=" << r.saturated
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
inline void
|
||||
printScalar(
|
||||
beast::unit_test::suite& s,
|
||||
char const* name,
|
||||
std::uint64_t observed)
|
||||
{
|
||||
auto const decimal = std::string(name).find("Events") != std::string::npos;
|
||||
s.log << "GOLDEN-SCALAR " << name << "=";
|
||||
if (decimal)
|
||||
s.log << observed;
|
||||
else
|
||||
s.log << "0x" << std::hex << observed << std::dec;
|
||||
s.log << std::endl;
|
||||
}
|
||||
|
||||
} // namespace ripple::test::goldens::donor
|
||||
|
||||
#endif
|
||||
@@ -59,6 +59,11 @@ public:
|
||||
// advances time in the same units it will later drive.
|
||||
using time_point = std::chrono::steady_clock::time_point;
|
||||
using duration = std::chrono::steady_clock::duration;
|
||||
// Fingerprints fold duration::count() as-is. This clock is nanoseconds;
|
||||
// a coarser period would move every pinned fingerprint.
|
||||
static_assert(
|
||||
std::ratio_equal_v<duration::period, std::nano>,
|
||||
"trace fingerprints assume a nanosecond steady_clock");
|
||||
|
||||
// Priority class among events at the SAME virtual instant (lower runs
|
||||
// first). Spaced by 10 so intermediate tiers can be slotted in without
|
||||
@@ -93,8 +98,9 @@ public:
|
||||
};
|
||||
|
||||
// PROVENANCE, not ordering: what CREATED an event. Orthogonal to Tier (its
|
||||
// same-instant priority). Used in fence-violation diagnostics today and in
|
||||
// beat traces later; never consulted for scheduling decisions.
|
||||
// same-instant priority). The queue orders by (when, tier, nodeId, seq),
|
||||
// so Kind is not a sort key. Profiled pacing does consult it: event cost
|
||||
// scales by the kind weight.
|
||||
enum class Kind {
|
||||
other = 0,
|
||||
heartbeat, // a driver's per-node heartbeat trigger
|
||||
@@ -231,8 +237,19 @@ public:
|
||||
[[nodiscard]] duration
|
||||
eventCost(std::uint32_t nodeId, Kind kind) const
|
||||
{
|
||||
return unitCost * static_cast<std::int64_t>(k) *
|
||||
static_cast<std::int64_t>(eventWeight(nodeId, kind));
|
||||
auto const scale = static_cast<std::uint64_t>(k) *
|
||||
static_cast<std::uint64_t>(eventWeight(nodeId, kind));
|
||||
if (unitCost.count() < 0)
|
||||
Throw<std::overflow_error>(
|
||||
"HarnessScheduler::ProfiledPacer event cost overflow");
|
||||
auto const unit = static_cast<std::uint64_t>(unitCost.count());
|
||||
if (unit != 0 &&
|
||||
scale > static_cast<std::uint64_t>(
|
||||
std::numeric_limits<duration::rep>::max()) /
|
||||
unit)
|
||||
Throw<std::overflow_error>(
|
||||
"HarnessScheduler::ProfiledPacer event cost overflow");
|
||||
return unitCost * static_cast<duration::rep>(scale);
|
||||
}
|
||||
|
||||
[[nodiscard]] duration
|
||||
|
||||
@@ -245,6 +245,9 @@ struct NodeSpec
|
||||
// the LAST gated timer, virtualized). Empty -> production nullptr:
|
||||
// PeerImp keeps its raw asio member untouched.
|
||||
TimeoutCounterTimerFactory peerTimerFactory;
|
||||
// Startup NetClock override, applied before the app runs. Threaded virtual
|
||||
// nodes use the runner's current time, including time spent stopped.
|
||||
std::optional<NetClock::time_point> restoredNetClock;
|
||||
};
|
||||
|
||||
// The stepping implementation of the acquire-retry timer seam (issue 005 /
|
||||
@@ -463,6 +466,8 @@ public:
|
||||
}
|
||||
|
||||
tk_->set(app_->getLedgerMaster().getClosedLedger()->info().closeTime);
|
||||
if (spec.restoredNetClock)
|
||||
tk_->set(*spec.restoredNetClock);
|
||||
// Don't start timers explicitly; the consensus heartbeat is armed by
|
||||
// setStateTimer in setup() (Application.cpp:1422) for non-standalone.
|
||||
app_->start(false);
|
||||
@@ -571,6 +576,10 @@ class MultiNode
|
||||
// callback maps scheduler virtual time onto each node's NetClock from this
|
||||
// base.
|
||||
NetClock::time_point netBase_{};
|
||||
// Current network time in non-stepping virtual mode, owned by the test
|
||||
// thread. Unlike live node clocks, it advances while every node is stopped.
|
||||
// Keep the same per-tick whole-second conversion as the existing driver.
|
||||
std::optional<NetClock::time_point> threadedNetTime_;
|
||||
struct NodeSlot
|
||||
{
|
||||
TempDir dbDir;
|
||||
@@ -801,7 +810,12 @@ public:
|
||||
LedgerStart::Fresh,
|
||||
/*injectedPrng=*/slots_[id]->prng.get(),
|
||||
std::move(timerFactory),
|
||||
std::move(peerTimerFactory)}));
|
||||
std::move(peerTimerFactory),
|
||||
/*restoredNetClock=*/threadedNetTime_}));
|
||||
|
||||
if (steadyClock_ && !stepper_ && !threadedNetTime_ &&
|
||||
nodes_.back()->isUp())
|
||||
threadedNetTime_ = nodes_.back()->clock().now();
|
||||
|
||||
// Capture the genesis NetClock base from the first node for
|
||||
// syncClocks(). Gate on isUp(): a setup failure resets app_ (destroying
|
||||
@@ -813,7 +827,8 @@ public:
|
||||
// at genesis close time while the network's virtual clocks are far
|
||||
// ahead — the real handshake rejects that skew ("Peer clock is too
|
||||
// far off"). Sync every clock to scheduler time, exactly as
|
||||
// restartNode does; a no-op for the normal t=0 bring-up.
|
||||
// restartNode does; a no-op for the normal t=0 bring-up. Threaded
|
||||
// virtual nodes receive threadedNetTime_ before app startup above.
|
||||
if (stepper_ && nodes_.back()->isUp())
|
||||
syncClocks(stepper_->now());
|
||||
return *nodes_.back();
|
||||
@@ -937,7 +952,8 @@ private:
|
||||
ledgerStart,
|
||||
/*injectedPrng=*/slots_[i]->prng.get(),
|
||||
std::move(timerFactory),
|
||||
std::move(peerTimerFactory)});
|
||||
std::move(peerTimerFactory),
|
||||
/*restoredNetClock=*/threadedNetTime_});
|
||||
if (stepper_ && nodes_[i]->isUp())
|
||||
syncClocks(stepper_->now());
|
||||
return *nodes_[i];
|
||||
@@ -1488,13 +1504,9 @@ public:
|
||||
stats.beat = ++threadedBeat_;
|
||||
stats.transportStart = simActivitySnapshot();
|
||||
|
||||
// 1) steady clock (elapsed-time source for openTime / round duration).
|
||||
steadyClock_->advance(dt);
|
||||
// 2) NetClock in lockstep (truncates to whole seconds; pass dt >= 1s).
|
||||
auto const netDt = duration_cast<NetClock::duration>(dt);
|
||||
for (auto& n : nodes_)
|
||||
if (n)
|
||||
n->clock().set(n->clock().now() + netDt);
|
||||
// Advance both domains once, including the runner's current NetClock
|
||||
// used when a stopped node returns before the next heartbeat.
|
||||
advanceInjectedClocks(dt);
|
||||
|
||||
struct Signal
|
||||
{
|
||||
@@ -1825,6 +1837,17 @@ public:
|
||||
std::vector<SteppingController::duration> nodeLag;
|
||||
std::vector<std::vector<SteppingController::duration>> nodeLagPerBeat;
|
||||
|
||||
// Why runSteppingProfiled's beat loop stopped. none means the
|
||||
// unpaced runStepping path, which does not record a reason.
|
||||
enum class Stop : std::uint8_t {
|
||||
none,
|
||||
heartbeatBudget,
|
||||
stepLimit,
|
||||
targetReached,
|
||||
predicate
|
||||
};
|
||||
Stop stop = Stop::none;
|
||||
|
||||
[[nodiscard]] bool
|
||||
saturated() const
|
||||
{
|
||||
@@ -1932,9 +1955,10 @@ public:
|
||||
auto const stop = [this, target]() { return minValidated() >= target; };
|
||||
KProfiledRunStats runStats;
|
||||
SteppingController::ProfiledStepStats stepStats;
|
||||
bool stoppedByPredicate = false;
|
||||
for (std::size_t k = 1;
|
||||
k <= maxHeartbeats && runStats.steps < maxSteps && !stop() &&
|
||||
!(stopAfterBeat && stopAfterBeat());
|
||||
!(stopAfterBeat && (stoppedByPredicate = stopAfterBeat()));
|
||||
++k)
|
||||
{
|
||||
auto const beforeConsumed = stepStats.consumedAdvance;
|
||||
@@ -1958,6 +1982,17 @@ public:
|
||||
afterBeat();
|
||||
}
|
||||
|
||||
if (stop())
|
||||
runStats.stop = KProfiledRunStats::Stop::targetReached;
|
||||
else if (runStats.steps >= maxSteps)
|
||||
runStats.stop = KProfiledRunStats::Stop::stepLimit;
|
||||
// Record the decision already made at the boundary. A predicate may
|
||||
// consume a one-shot event; evaluating it again changes its meaning.
|
||||
else if (stoppedByPredicate)
|
||||
runStats.stop = KProfiledRunStats::Stop::predicate;
|
||||
else
|
||||
runStats.stop = KProfiledRunStats::Stop::heartbeatBudget;
|
||||
|
||||
runStats.minValidated = minValidated();
|
||||
runStats.clampHits = stepStats.clampHits;
|
||||
runStats.requestedVirtualAdvance = stepStats.requestedAdvance;
|
||||
@@ -2066,6 +2101,8 @@ private:
|
||||
|
||||
steadyClock_->advance(dt);
|
||||
auto const netDt = std::chrono::duration_cast<NetClock::duration>(dt);
|
||||
if (threadedNetTime_)
|
||||
*threadedNetTime_ += netDt;
|
||||
for (auto& n : nodes_)
|
||||
if (n)
|
||||
n->clock().set(n->clock().now() + netDt);
|
||||
|
||||
@@ -1567,6 +1567,19 @@ public:
|
||||
return true;
|
||||
}
|
||||
|
||||
// How many sequences validatedForkFree walks: 2 through the highest live
|
||||
// validated sequence, inclusive. That is max-1 when max >= 2. Not a count
|
||||
// of pairwise hash comparisons.
|
||||
[[nodiscard]] std::uint32_t
|
||||
forkCheckedSeqs()
|
||||
{
|
||||
std::uint32_t hi = 0;
|
||||
for (std::uint32_t i = 0; i < net_.size(); ++i)
|
||||
if (net_.isLive(i))
|
||||
hi = std::max(hi, validSeq(i));
|
||||
return hi >= 2 ? hi - 1 : 0;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::shared_ptr<Ledger const>
|
||||
ledger(std::uint32_t node, std::uint32_t seq)
|
||||
{
|
||||
|
||||
@@ -24,9 +24,13 @@
|
||||
#include <boost/lexical_cast.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdlib>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <unistd.h>
|
||||
#include <vector>
|
||||
|
||||
namespace ripple {
|
||||
@@ -234,9 +238,40 @@ multi_runner_base<IsParent>::multi_runner_base()
|
||||
{
|
||||
if (IsParent)
|
||||
{
|
||||
// cleanup any leftover state for any previous failed runs
|
||||
boost::interprocess::shared_memory_object::remove(shared_mem_name_);
|
||||
boost::interprocess::message_queue::remove(message_queue_name_);
|
||||
// One name pair per parent process. Spawned children inherit the
|
||||
// environment; the in-process single-job child reads it too.
|
||||
auto const pid =
|
||||
std::to_string(static_cast<unsigned long long>(::getpid()));
|
||||
shared_mem_name_ = std::string(shared_mem_prefix_) + "." + pid;
|
||||
message_queue_name_ =
|
||||
std::string(message_queue_prefix_) + "." + pid;
|
||||
if (::setenv(shared_mem_env_, shared_mem_name_.c_str(), 1) != 0 ||
|
||||
::setenv(message_queue_env_, message_queue_name_.c_str(), 1) !=
|
||||
0)
|
||||
{
|
||||
throw std::runtime_error(
|
||||
"failed to publish unit-test ipc names");
|
||||
}
|
||||
|
||||
// Drop a leftover object for this pid. Do not touch another
|
||||
// process's names.
|
||||
boost::interprocess::shared_memory_object::remove(
|
||||
shared_mem_name_.c_str());
|
||||
boost::interprocess::message_queue::remove(
|
||||
message_queue_name_.c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
char const* const shm = std::getenv(shared_mem_env_);
|
||||
char const* const mq = std::getenv(message_queue_env_);
|
||||
if (shm == nullptr || shm[0] == '\0' || mq == nullptr ||
|
||||
mq[0] == '\0')
|
||||
{
|
||||
throw std::runtime_error(
|
||||
"unit-test child missing parent ipc names");
|
||||
}
|
||||
shared_mem_name_ = shm;
|
||||
message_queue_name_ = mq;
|
||||
}
|
||||
|
||||
shared_mem_ = boost::interprocess::shared_memory_object{
|
||||
@@ -244,7 +279,7 @@ multi_runner_base<IsParent>::multi_runner_base()
|
||||
IsParent,
|
||||
boost::interprocess::create_only_t,
|
||||
boost::interprocess::open_only_t>{},
|
||||
shared_mem_name_,
|
||||
shared_mem_name_.c_str(),
|
||||
boost::interprocess::read_write};
|
||||
|
||||
if (IsParent)
|
||||
@@ -253,7 +288,7 @@ multi_runner_base<IsParent>::multi_runner_base()
|
||||
message_queue_ =
|
||||
std::make_unique<boost::interprocess::message_queue>(
|
||||
boost::interprocess::create_only,
|
||||
message_queue_name_,
|
||||
message_queue_name_.c_str(),
|
||||
/*max messages*/ 16,
|
||||
/*max message size*/ 1 << 20);
|
||||
}
|
||||
@@ -261,7 +296,8 @@ multi_runner_base<IsParent>::multi_runner_base()
|
||||
{
|
||||
message_queue_ =
|
||||
std::make_unique<boost::interprocess::message_queue>(
|
||||
boost::interprocess::open_only, message_queue_name_);
|
||||
boost::interprocess::open_only,
|
||||
message_queue_name_.c_str());
|
||||
}
|
||||
|
||||
region_ = boost::interprocess::mapped_region{
|
||||
@@ -275,8 +311,12 @@ multi_runner_base<IsParent>::multi_runner_base()
|
||||
{
|
||||
if (IsParent)
|
||||
{
|
||||
boost::interprocess::shared_memory_object::remove(shared_mem_name_);
|
||||
boost::interprocess::message_queue::remove(message_queue_name_);
|
||||
if (!shared_mem_name_.empty())
|
||||
boost::interprocess::shared_memory_object::remove(
|
||||
shared_mem_name_.c_str());
|
||||
if (!message_queue_name_.empty())
|
||||
boost::interprocess::message_queue::remove(
|
||||
message_queue_name_.c_str());
|
||||
}
|
||||
throw;
|
||||
}
|
||||
@@ -288,8 +328,9 @@ multi_runner_base<IsParent>::~multi_runner_base()
|
||||
if (IsParent)
|
||||
{
|
||||
inner_->~inner();
|
||||
boost::interprocess::shared_memory_object::remove(shared_mem_name_);
|
||||
boost::interprocess::message_queue::remove(message_queue_name_);
|
||||
boost::interprocess::shared_memory_object::remove(
|
||||
shared_mem_name_.c_str());
|
||||
boost::interprocess::message_queue::remove(message_queue_name_.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -152,11 +152,20 @@ class multi_runner_base
|
||||
print_results(S& s);
|
||||
};
|
||||
|
||||
static constexpr const char* shared_mem_name_ = "RippledUnitTestSharedMem";
|
||||
// name of the message queue a multi_runner_child will use to communicate
|
||||
// with multi_runner_parent
|
||||
static constexpr const char* message_queue_name_ =
|
||||
// Prefixes only. The parent appends ".<pid>" and publishes the full names
|
||||
// in the environment before a child is constructed or spawned. Fixed
|
||||
// names collided: every --unittest process, including one job, removes
|
||||
// and recreates these objects.
|
||||
static constexpr char const* shared_mem_prefix_ =
|
||||
"RippledUnitTestSharedMem";
|
||||
static constexpr char const* message_queue_prefix_ =
|
||||
"RippledUnitTestMessageQueue";
|
||||
static constexpr char const* shared_mem_env_ = "XRPLD_UNIT_TEST_SHARED_MEM";
|
||||
static constexpr char const* message_queue_env_ =
|
||||
"XRPLD_UNIT_TEST_MESSAGE_QUEUE";
|
||||
|
||||
std::string shared_mem_name_;
|
||||
std::string message_queue_name_;
|
||||
|
||||
// `inner_` will be created in shared memory
|
||||
inner* inner_;
|
||||
|
||||
@@ -302,6 +302,9 @@ runUnitTests(
|
||||
args.emplace_back("--unittest-child");
|
||||
}
|
||||
|
||||
// Children inherit XRPLD_UNIT_TEST_SHARED_MEM and
|
||||
// XRPLD_UNIT_TEST_MESSAGE_QUEUE, published by multi_runner_parent.
|
||||
// Do not pass an environment that drops those variables.
|
||||
for (std::size_t i = 0; i < num_jobs; ++i)
|
||||
children.emplace_back(
|
||||
boost::process::exe = exe_name, boost::process::args = args);
|
||||
|
||||
Reference in New Issue
Block a user