Commit Graph

63 Commits

Author SHA1 Message Date
Nicholas Dudfield
9324bd59e5 fix(consensus): keep entropy selection rooted in accepted hash
Revert the local entropyFailed selector override introduced during sidecar reconciliation excision. ConsensusEntropy injection is ledger-defining, so accepted entropy roots remain authoritative; nodes without an accepted root still fall back through the normal missing-root path.

Update CSF parity, the focused onPreBuild regression, and the design intent note to capture that local timeout/diagnostic state must not override an accepted root.
2026-07-02 13:50:42 +07:00
Nicholas Dudfield
3c01901119 feat(consensus): excise sidecar reconciliation
Remove peer fetch/acquire/merge support for CE/export sidecar SHAMaps and keep them as local same-process snapshots only.

Harden generic transaction-set paths so sidecar maps are not advertised, served, acquired, or wrapped as RCLTxSet, and delete the stale fetch-positive .testnet suite.

Make entropy failure terminal at selection time and mirror that invariant in the CSF harness; keep Export witness materialization rooted in accepted exportSigSetHash rather than local timeout state.
2026-07-02 12:52:43 +07:00
Nicholas Dudfield
d4650441fb feat(consensus): default sidecar reconciliation off
Compile out sidecar acquisition and merge plumbing unless xahaud_sidecar_reconciliation is enabled, and move fetch-positive .testnet scenarios into an opt-in suite.

When reconciliation is enabled, gate eager fetches on current trusted root support so first-seen transient roots do not trigger network acquisition. This keeps the recovery path available for measurement without making it the default path.

Verified default OFF with x-format-changed, git diff --check, x-quick-check, cmake configure/build, and ./build/rippled --unittest=ConsensusExtensions. ON path syntax-checked; Claude is assigned the parallel ON build/test review in a separate worktree.
2026-06-30 16:10:50 +07:00
Nicholas Dudfield
63f99ad9d8 feat(consensus): salt tx ordering with entropy 2026-06-29 16:32:16 +07:00
Nicholas Dudfield
618314afb7 test(consensus): isolate export sidecar rejection cases 2026-06-29 07:55:06 +07:00
Nicholas Dudfield
0668caacf2 test(consensus): fix round-pinned sidecar fixture 2026-06-29 07:26:19 +07:00
Nicholas Dudfield
d6de1d90b5 fix(consensus): pin RNG round sequence at start 2026-06-28 20:01:08 +07:00
Nicholas Dudfield
4a12f4c231 fix(hook): harden xport and xpop host paths 2026-06-28 19:41:20 +07:00
Nicholas Dudfield
c2bc4fabe8 fix(consensus): filter export signature candidates 2026-06-28 19:02:32 +07:00
Nicholas Dudfield
94ffc86bd9 fix(consensus): cap export sidecar candidates 2026-06-28 18:18:45 +07:00
Nicholas Dudfield
1d158a910b fix(consensus): cap fetched sidecar cardinality 2026-06-28 17:18:14 +07:00
Nicholas Dudfield
cf3a9ef96b fix(consensus): freeze rng commits before reveal 2026-06-28 15:15:17 +07:00
Nicholas Dudfield
806aa56aec fix(consensus): centralize rng admission 2026-06-28 14:52:00 +07:00
Nicholas Dudfield
a714eaa621 fix(consensus): bind entropy reveals to proofed commits 2026-06-28 13:32:26 +07:00
Nicholas Dudfield
73f3b7a167 fix(consensus): reject miskeyed rng sidecar leaves 2026-06-28 11:24:00 +07:00
Nicholas Dudfield
0aa8e3ca67 fix(consensus): reject non-secp rng proposal proofs 2026-06-28 10:16:29 +07:00
Nicholas Dudfield
9d9329ece4 refactor: reuse proof parser and named entropy tiers 2026-06-27 17:32:37 +07:00
Nicholas Dudfield
1b0c633eda refactor(consensus): trim small duplicated extension constants
Reuse SidecarPeerAlignment::quorumAligned for export sidecar gates, name the shared sidecar convergence timeout, and centralize the RNG commitment tuple.

Also make ExportLimits::maxExportsPerHook the value enforced by xport_reserve, with a static assertion against the hook ABI constant.
2026-06-27 15:24:06 +07:00
Nicholas Dudfield
8a81588b71 fix(consensus): let quorum-aligned entropy conflicts proceed
Use the fixed-denominator entropy threshold as the conflict boundary: once our entropy hash is quorum-aligned, a below-threshold conflicting minority or silent peer cannot force fallback by withholding full observation.

Update the consensus entropy invariant spine and design doc so the no-veto conflict rule is explicit, and flip the regression test to cover the liveness-preserving path.
2026-06-27 15:09:30 +07:00
Nicholas Dudfield
0a9023c334 fix(export): avoid live manifest filtering of accepted witnesses
Materialize ttEXPORT_SIGNATURES from the accepted export sidecar root during live consensus builds, then let apply trust that tx-stream witness membership while still verifying each signature and enforcing quorum.

Direct apply paths remain conservative, and historical replay uses the same witness-membership rule after manifests may have rotated. Update the intent docs and tests for live, direct, and replay modes.
2026-06-27 13:39:58 +07:00
Nicholas Dudfield
3cfc79ea59 fix(consensus): bind rng sidecar proofs to round parent 2026-06-27 13:04:08 +07:00
Nicholas Dudfield
deb1b2af0b fix(consensus): validate extension sidecar inputs
Reject malformed RNG sidecar signing keys before PublicKey construction so fetched untrusted leaves cannot trigger an uncatchable LogicError abort. Also re-check proposal signatures at the extension harvest boundary so cluster-relayed invalid proposals cannot populate RNG or export sidecar state.
2026-06-27 12:25:52 +07:00
Nicholas Dudfield
4dcb3f3557 fix(consensus): remove local entropy fallback shortcuts
Encode fallback-vs-nonfallback as a ledger-defining decision, remove the prevProposers/bootstrap and visible-peer impossible-quorum shortcuts, and use proofed commit material for the timeout transition. Update unit/CSF/testnet coverage and document the remaining validation-backstopped timing residual.

Tests: x-run-tests --stop-on-fail ConsensusExtensions; x-run-tests --stop-on-fail ConsensusRng; x-format-changed; git diff --check
2026-06-27 12:08:35 +07:00
Nicholas Dudfield
d6e7cf428b fix(consensus): undo b12cee5d4 entropy observation veto 2026-06-26 21:56:40 +07:00
Nicholas Dudfield
1cdf850e4a fix(consensus): guard keyless proposal authoring 2026-06-26 19:35:07 +07:00
Nicholas Dudfield
932ccbe0e5 fix(export): bind trusted wire signatures to proposal digest
Require wire-carried export signatures to match the signed ExtendedPosition digest before harvesting, and keep the per-blob size guard local to the helper.
2026-06-26 17:48:19 +07:00
Nicholas Dudfield
d091ebfb7e Pin extension apply to accepted sidecar roots
Require entropy injection and export apply to consume only sidecar roots accepted by their convergence gates. Local timeout state and later collector growth no longer participate in closed-ledger material once a sidecar root is accepted.

Also guard RCLConsensus::extensionsBusy() under the consensus mutex, cap exported multisig signer arrays to the protocol maximum after canonical signer sorting, and document the remaining accept-vs-timeout liveness tradeoff.

Add regressions for unaccepted export sidecar roots, late export collector mutation, entropy accepted-root handling, exported signer caps, and CSF accepted-sidecar behavior.
2026-06-26 16:50:19 +07:00
Nicholas Dudfield
229eb5b573 refactor(consensus): clarify extension sidecar seams 2026-06-26 13:08:46 +07:00
Nicholas Dudfield
938d3055ce fix(consensus): harden threshold helpers 2026-06-26 11:47:16 +07:00
Nicholas Dudfield
4fb91ea9f5 refactor(consensus): expose threshold policy seams 2026-06-24 18:58:24 +07:00
Nicholas Dudfield
77e48d553c test(export): cover sidecar rejection preflights 2026-06-23 11:53:51 +07:00
Nicholas Dudfield
7f0d2959e8 test(export): cover no-veto sidecar withholding 2026-06-23 10:34:01 +07:00
Nicholas Dudfield
1440d1495f test(consensus): cover split-brain sidecar threshold 2026-06-23 10:20:34 +07:00
Nicholas Dudfield
cacd1f71fe test(consensus): mint tier 2 with active nUNL 2026-06-23 10:11:40 +07:00
Nicholas Dudfield
c5b1cb222d docs(consensus): add test projection markers 2026-06-23 10:03:04 +07:00
Nicholas Dudfield
4d55443976 refactor(runtime-config): scope fault injection controls 2026-06-22 16:51:23 +07:00
Nicholas Dudfield
0d1d649867 refactor(consensus): remove orphaned txset cache callback 2026-06-22 15:33:29 +07:00
Nicholas Dudfield
60469dbd86 refactor(rng): remove explicit-final proposal path 2026-06-22 15:07:18 +07:00
Nicholas Dudfield
b13868b71e fix(export): apply agreed sidecar signatures
Use the agreed exportSigSetHash sidecar map, not the live ExportSigCollector, as the network-mode ttEXPORT signer snapshot. Preserve export convergence state through RNG onPreBuild cleanup so buildLCL apply can see the gate result, and require a UNLReport-backed validator view before non-standalone Export finalization. Add regression coverage for agreed-vs-live collector mutation and onPreBuild export-state preservation.
2026-06-22 09:43:03 +07:00
Nicholas Dudfield
e55c2c6dc8 fix(export): allow quorum-aligned sidecar despite missing observation 2026-06-22 09:00:46 +07:00
Nicholas Dudfield
fb9e2710cc fix(rng): scope sidecar alignment count to the active validator view (F1)
The entropy/export bless-vs-fallback gate counted alignment over ALL tx-converged trusted proposers (currPeerPositions_), while the thresholds and the entropy leaf set are computed over the active validator view. A node that locally trusts proposers outside the on-ledger UNLReport active set could pad alignedParticipants(), inflating the counting universe N above originalViewSize and eroding the Tier-2 intersection margin (2t - N) below the Byzantine floor f -- so two equivocation cohorts padded by non-active aligners could each clear the gate (review finding F1). Backstopped by the 80% validation quorum, but the proof's universe and the code's universe must match.

inspectTxConvergedSidecarPeers now takes an active-view membership predicate and counts only member peers toward alignment; the local +1 is gated on localIsActiveValidator(). Both the RNG entropy gate and the export-sig gate pass ext.isUNLReportMember / ext.localIsActiveValidator, mirroring buildEntropySet / hasQuorumOfCommits' containsNode filter. New method on ConsensusExtensions + CSF Peer + the FakeExtensions stub.

Regression test (Sidecar peer alignment helper): a trusted-but-non-active aligned proposer pushes unfiltered aligned to 2 but filtered aligned stays 1, and a non-active local node's +1 is suppressed -- padding cannot satisfy the gate. ConsensusExtensions 900, ConsensusRng 386, Consensus 1399, all green.

Note: the explicit-final proposal path (ConsensusExtensionsTick.h ~:934) counts over prevProposers and is NOT yet filtered (separate, experimental path).
2026-06-18 09:44:56 +07:00
Nicholas Dudfield
8368e12ab3 fix(rng): require UNLReport view for non-fallback entropy 2026-06-17 11:32:51 +07:00
Nicholas Dudfield
27ae39b91a fix(rng): use a genuinely forkable nUNL config in the anchor test
The tier-2 anchor pin (20d52d8b6) used an 8/6 (original/effective) nUNL config whose rationale was wrong: it computed cohort overlap against the original view (2*4-8=0), but aligned cohorts form in the EFFECTIVE view, so two 4-of-6 cohorts overlap by 2*4-6=2 > floor(8/5)=1 -- 8/6 is NOT actually forkable, the original-view anchor is merely conservative there. The test still caught the originalViewSize->size() regression, but the stated reason was misleading (review catch).

Switch to 10 active / 2 disabled (original 10, effective 8), a genuinely forkable case: a 5-of-8 cohort (what the effective-size threshold would admit) overlaps only 2*5-8=2, which does NOT exceed the f=floor(10/5)=2 faulty the original UNL still tolerates -> an equivocator could mint two distinct tier-2 digests. The correct original anchor requires 7 (overlap 6 > 2) and keeps the band closed (tier2==quorum==7); a regression to size() drops the floor to 5 and re-opens the forkable [5,7) band. Assertions and rationale updated to match; suite green (897, 0 failures).
2026-06-16 17:33:57 +07:00
Nicholas Dudfield
20d52d8b66 test(rng): pin tier-2 threshold to pre-nUNL original view
tier2Threshold() anchors to originalViewSize (pre-nUNL), not the effective post-nUNL size(): nUNL shrinks the effective view while leaving faulty nodes in it, so a sub-quorum fraction of the effective view can exceed the Byzantine bound. A one-line regression to size() would relabel a forkable cohort as participant_aligned -- and no existing test caught it (the only nUNL test never calls tier2Threshold()/selectEntropy, and the only tier-2 selection test has no nUNL, so original == effective there).

Add testTier2ThresholdAnchorsToOriginalView: 8 active validators, 2 disabled via NegativeUNL (original 8, effective 6), asserting tier2Threshold()==5 (from the original 8) not 4 (from the effective 6), with quorum/gate cross-checks. Mutation-verified: flipping the production read to size() turns exactly this test red (tier2Threshold + entropyGateThreshold) while the existing tier-2 selection test stays green.

Also: pointer comment at tier2Threshold() linking the invariant to its guard test, and a design-doc fix -- the worked example used n=5, where the band is empty (quorum == participant_aligned == 4) so it illustrated an unreachable tier; now n=6.
2026-06-16 17:02:00 +07:00
Nicholas Dudfield
c3cc3513c9 docs(rng): refresh tier 2 terminology 2026-06-16 10:05:57 +07:00
Nicholas Dudfield
156a8cbb85 refactor(rng): address review minors (dead-API rename + explicit-final dedup)
Two small follow-ups from the Codex review of the tier-2 implementation, both
on the consensus-extension internals:

- Rename shouldZeroEntropy() -> belowValidatorQuorum(). The selector replaced its
  callers, leaving it production-dead and MISNAMED: post-tier-2, "below the 80%
  validator quorum" is no longer "zero entropy" (a participant_aligned set is
  sub-quorum but non-zero). The new name + a doc comment make it a tier-3
  eligibility predicate only and warn against gating injection on it
  (selectEntropy() owns the tiering). Behavior unchanged.

- buildExplicitFinalProposalTxSet now dedups the entropy pseudo-tx by VALUE, not
  type. Its comment claimed it "mirrors onPreBuild", but onPreBuild went
  value-based: it verifies a present pseudo-tx is the EXACT txID it would have
  produced and logs a determinism-violation on mismatch. Explicit-final now does
  the same (skip-duplicate-verified on match, error log on mismatch), returning
  the base unchanged either way. Default-off experimental path, so low blast
  radius, but the two paths now agree.
2026-06-15 17:14:30 +07:00
Nicholas Dudfield
14ebe74a56 fix(rng): derive tier2 threshold from intersection bound (safe at n%5==0)
calculateParticipantThreshold returned ceil(0.6*n). At every n divisible by 5
(n=5,10,15,20,...) that leaves two aligned cohorts overlapping in exactly
floor(0.2*n) = f validators -- NOT strictly greater than f. So up to f Byzantine
nodes can occupy the entire overlap, leaving no honest validator shared between
the two cohorts, and a single equivocator backed by f-1 colluders can split the
round into two distinct aligned digests -> fork. The spec caught n=5 ("never 5")
but the same failure recurs at every multiple of 5.

Derive the floor from the safety invariant instead: the smallest t with
2t - n > floor(n/5), i.e. floor((n + floor(n/5)) / 2) + 1. This equals
ceil(0.6*n) everywhere except multiples of 5, where it is one higher
(n=10 -> 7, not 6). n=5 now collapses the band (tier2 == quorum), so the
"never 5" operational caveat is enforced by the math rather than a footnote.

Tests: the arithmetic test now asserts the defining invariant
2t - n > floor(n/5) AND t <= quorum for every n in 1..256 (this fails at n=10
under the old formula), plus the bumped boundaries. The onPreBuild tier-2 test
moves off n=5 (no band) onto n=6 (tier2=4, quorum=5; 5/4/3 revealers ->
validator_quorum / participant_aligned / fallback).

Found by Codex adversarial review of the tier-2 implementation.
2026-06-15 16:59:55 +07:00
Nicholas Dudfield
580f07ce35 feat(rng): enable tier 2 (participant_aligned) sub-quorum entropy
A 60-79% aligned cohort now mints entropy labelled participant_aligned (tier 2)
instead of falling back. Healthy >=80% rounds are unchanged (validator_quorum),
and a true minority (<60%) still falls back.

Mechanics:
- entropyGateThreshold() = min(quorumThreshold(), tier2Threshold()): the bar at
  which the pipeline engages and the entropy conflict gate resolves. In the
  normal band this is the 60% floor (of the ORIGINAL, pre-nUNL view); under
  heavy nUNL the band collapses to the 80% quorum and tier 2 vanishes.
- selectEntropy() labels the AGREED entropySetMap_ by participant count:
  >= quorum -> validator_quorum, >= tier2 -> participant_aligned, else fallback.
- Tick.h gates (bootstrap-skip, impossible-quorum, commit-timeout, entropy
  conflict gate) key off entropyGateThreshold() so sub-quorum rounds reach
  injection instead of short-circuiting.

Determinism: the tier LABEL is a function of the agreed set's leaf count
(identical on every node holding that hash); the local entropyGateThreshold
alignment only decides proceed-vs-fall-back, so divergent local views fall back
rather than fork. The 60% floor is over the ORIGINAL view -- the
quorum-intersection bound that stops a single equivocator minting two distinct
aligned digests under the ~20% Byzantine bound.

hasQuorumOfCommits() is deliberately LEFT at 80%: healthy networks keep their
exact fast-path and only step down to tier 2 via the commit-timeout path, so
this adds zero behavior change above quorum (the degraded band pays one pipeline
timeout; a fast-path is a separable follow-up). Folds into featureConsensusEntropy
(not yet active), so no new amendment.

Tests: new onPreBuild tier-2 case (5-validator view; 4/3/2 revealers ->
validator_quorum / participant_aligned / fallback) plus threshold assertions.
CSF Peer and the tick test stub mirror entropyGateThreshold() to quorumThreshold()
for now -- the end-to-end tier-2 sims (which lower it) land next. Extracted
makeUNLReportLedger / harvestCommitReveal test helpers, now shared across the
view and harvest tests.
2026-06-15 15:55:43 +07:00
Nicholas Dudfield
378d6b78c8 feat(rng): add pre-nUNL originalViewSize + tier2 participant threshold
Foundation for Tier 2 (participant_aligned) sub-quorum entropy. No behavior
change — nothing consumes these yet; the tier ladder, gates, and selector
arrive in later commits.

- ActiveValidatorView::originalViewSize: master-key count BEFORE the nUNL
  subtraction. size() stays the effective (post-nUNL) count used by the 80%
  validator-quorum gate; originalViewSize is the original-UNL denominator that
  the 60% Tier 2 floor anchors to, since nUNL can shrink the effective view
  while leaving faulty nodes in it.
- calculateParticipantThreshold(): ceil(0.6 * count), the quorum-intersection
  floor (two such cohorts always share an honest validator under the ~20%
  Byzantine bound).
- ConsensusExtensions::tier2Threshold(): ceil(0.6 * originalView), anchored to
  originalViewSize.

Tests: originalViewSize asserted on the UNLReport, fallback, and real-ledger
nUNL paths; new arithmetic testcase locks ceil(0.6) and the sizing-note
boundaries (5->3 banded/unsafe, 6->4 smallest-safe, 8->5 one-nUNL-off).
2026-06-15 15:11:41 +07:00
Nicholas Dudfield
9d4b97c824 fix(rng): defensive mismatch-log reads + mismatch-path test
Follow-up to d6481a386, addressing review of that diff:

- The mismatch-log branch in onPreBuild read present pseudo-tx fields
  (sfDigest/sfEntropyTier/sfEntropyCount) unconditionally — the same
  throw hazard fairRng was hardened against, here inside onPreBuild
  during build. Read them defensively (isFieldPresent ? value :
  '<missing>'). (Note: STTx deserialization enforces all soeREQUIRED
  fields, so a tier-less ttCONSENSUS_ENTROPY cannot actually reach the
  set — this is belt-and-suspenders, not a reachable bug.)
- Clarify the comment + add action=keep-agreed-and-flag: this is
  detect-and-log, NOT rejection. The present pseudo-tx is KEPT and still
  applied at BuildLedger; a hard-fail policy on mismatch is a deliberate
  future decision (determinism-violation vs halt-risk-under-skew).
- Add testOnPreBuildEntropyMismatchKeepsAgreed: a present-but-different
  entropy pseudo-tx is kept (not replaced), set stays at one entry.
- Fix stale 'type-based dedup' comment in the standalone test.
2026-06-12 17:09:53 +07:00