Compare commits

..

727 Commits

Author SHA1 Message Date
Nicholas Dudfield
ff5d010e10 test(formal): model sidecar observation gate 2026-06-26 20:54:46 +07:00
Nicholas Dudfield
20815f11a0 Merge branch 'feature-export-rng' into feature-export-rng-lean
# Conflicts:
#	src/xrpld/consensus/ConsensusParms.h
2026-06-26 11:56:17 +07:00
Nicholas Dudfield
938d3055ce fix(consensus): harden threshold helpers 2026-06-26 11:47:16 +07:00
Nicholas Dudfield
ff24f10b9f Merge branch 'feature-export-rng' into feature-export-rng-lean 2026-06-26 10:27:25 +07:00
Nicholas Dudfield
aee3a638ee fix(consensus): avoid NegativeUNL cap overflow 2026-06-26 10:26:47 +07:00
Nicholas Dudfield
e6ac99624e build(formal): update levelization ordering 2026-06-26 10:06:46 +07:00
Nicholas Dudfield
b9f54b9ddb ci(formal): publish Lean verification artifacts 2026-06-26 10:02:05 +07:00
Nicholas Dudfield
d54ad32d65 fix(formal): resolve support branch merge drift 2026-06-26 10:01:44 +07:00
Nicholas Dudfield
feb2123e2c merge feature-export-rng into formal branch 2026-06-26 09:38:08 +07:00
Nicholas Dudfield
56d196739c test(formal): tighten Lean bridge coverage 2026-06-25 17:52:23 +07:00
Nicholas Dudfield
79c2562492 test(formal): harden Lean bridge checks 2026-06-25 17:24:27 +07:00
Nicholas Dudfield
a509de3d39 build(formal): centralize Lean toolchain checks 2026-06-25 14:14:56 +07:00
Nicholas Dudfield
4fb91ea9f5 refactor(consensus): expose threshold policy seams 2026-06-24 18:58:24 +07:00
Nicholas Dudfield
691e2b07eb test(formal): widen Lean drift cross-checks 2026-06-24 17:50:25 +07:00
Nicholas Dudfield
ffe7b51336 test(formal): widen Lean consensus drift checks 2026-06-24 16:27:03 +07:00
Nicholas Dudfield
d7a5863b93 test(formal): add optional Lean threshold cross-check 2026-06-24 15:50:34 +07:00
Nicholas Dudfield
acb492a2c9 docs(consensus): polish guided review excerpts 2026-06-24 14:50:43 +07:00
Nicholas Dudfield
92fe444323 docs(consensus): tighten guided review anchors 2026-06-24 14:15:23 +07:00
Nicholas Dudfield
9549901014 docs(consensus): clarify extended position identity 2026-06-24 13:35:25 +07:00
Nicholas Dudfield
9f6e7dd315 refactor(consensus): make extended position identity explicit 2026-06-24 13:10:57 +07:00
Nicholas Dudfield
f6d986bdbc chore: remove stale branch TODO comments 2026-06-24 12:48:52 +07:00
Nicholas Dudfield
e55bf43986 test: document consensus entropy fixture opt-in 2026-06-24 12:38:09 +07:00
Nicholas Dudfield
b4beb92c34 chore: match levelization output 2026-06-23 16:49:01 +07:00
Nicholas Dudfield
40edbfc7f2 chore: update levelization results 2026-06-23 16:46:25 +07:00
Nicholas Dudfield
ff205b1b81 docs(hooks): generate entropy API comments 2026-06-23 16:43:45 +07:00
Nicholas Dudfield
ddfb1dbeb6 docs: clarify entropy and runtime test APIs 2026-06-23 16:42:05 +07:00
Nicholas Dudfield
8bf1ece0a0 test(export): pin expiry and no-veto boundaries 2026-06-23 16:42:02 +07:00
Nicholas Dudfield
639e153f3b fix(consensus): gate proposal extensions by parent ledger 2026-06-23 16:41:58 +07:00
Nicholas Dudfield
8e542c32e0 test(testnet): clarify directed runtime latency syntax 2026-06-23 16:19:10 +07:00
Nicholas Dudfield
38f4d53ebf test(testnet): add runtime latency probe suite 2026-06-23 16:04:58 +07:00
Nicholas Dudfield
fe66a11c69 build(test): allow external hook Env tests 2026-06-23 13:38:43 +07:00
Nicholas Dudfield
77e48d553c test(export): cover sidecar rejection preflights 2026-06-23 11:53:51 +07:00
Nicholas Dudfield
439031dc92 Merge remote-tracking branch 'origin/dev' into feature-export-rng 2026-06-23 10:55:43 +07:00
Nicholas Dudfield
1a5b934881 test(csf): model sidecar split-brain equivocation 2026-06-23 10:52:38 +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
57f5a9d6cc test(testnet): cover export without UNLReport 2026-06-23 10:14:09 +07:00
Nicholas Dudfield
cacd1f71fe test(consensus): mint tier 2 with active nUNL 2026-06-23 10:11:40 +07:00
Nicholas Dudfield
93a6f0fbec docs(testnet): add export degradation projection marker 2026-06-23 10:07:02 +07:00
Nicholas Dudfield
c5b1cb222d docs(consensus): add test projection markers 2026-06-23 10:03:04 +07:00
Nicholas Dudfield
e447f9f021 docs(consensus): add export apply projection markers 2026-06-23 09:55:01 +07:00
Nicholas Dudfield
0757094ed2 docs(consensus): add formal proof projection markers 2026-06-23 09:41:55 +07:00
Nicholas Dudfield
4b8107d57c docs(consensus): clarify nUNL entropy gate thresholds 2026-06-23 08:42:22 +07:00
Nicholas Dudfield
d4b9e2f22c chore(formal): ignore local Lean proof workspace 2026-06-23 08:14:03 +07:00
Nicholas Dudfield
3fc199017e docs(cmake): note runtime config conan opt-in 2026-06-22 16:57:30 +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
3970912735 chore: update levelization results 2026-06-22 13:31:56 +07:00
Nicholas Dudfield
456f4144ba docs(rng): mark explicit-final for removal 2026-06-22 12:50:19 +07:00
Nicholas Dudfield
570cad4c44 test(export): pin network apply regressions 2026-06-22 12:14:42 +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
288b9e6d25 docs(rng): clarify commit substate pipelining 2026-06-22 09:06:12 +07:00
Nicholas Dudfield
e55c2c6dc8 fix(export): allow quorum-aligned sidecar despite missing observation 2026-06-22 09:00:46 +07:00
Nicholas Dudfield
35e981e509 docs(rng): align ConsensusExtensionsDesign with the UNLReport-only + F1 changes
A drift review (10-section adversarial pass + skeptical verification) found the design doc had fallen behind two recent code changes: the UNLReport-only gate (8368e12ab) and the F1 active-view alignment filter (fb9e2710c).

- Fallback Semantics: add the second fallback trigger -- a non-UNLReport-backed view mints consensus_fallback regardless of alignment.
- Validator Set And Quorum: note the config-fallback view is !fromUNLReport and forces consensus_fallback entropy.
- Entropy Alignment Rules: state the UNLReport precondition for non-fallback tiers; correct the alignment-count formula to the active-view-filtered count (non-active proposers and a non-active local +1 excluded -- the F1 fix); separate the two distinct counts (the peer-alignment GATE count vs the agreed entropySetMap_ leaf count that drives the tier LABEL); flag the explicit-final unfiltered exception.
- Worked examples: note they assume a UNLReport-anchored view (else all fall back).
- Export Principles: document that export success also requires full observation (peersSeen == txConverged), not just quorum alignment.
- Review Checklist: add items for the UNLReport-anchored-tier and alignment-count-universe (F1) invariants.

Doc-only. Raw drift findings kept in .ai-docs (gitignored).
2026-06-22 08:41:11 +07:00
Richard Holland
bb244ef772 put release builds into a candidate folder to prevent auto-update scripts running before smoke tests (#761) 2026-06-21 12:12:43 +10:00
Nicholas Dudfield
b9a733c831 docs(rng): flag the F1 gap on the explicit-final alignment path
The explicit-final proposal path counts participants/alignment over the unfiltered trusted-proposer set (ctx.peerPositions) plus an unconditional local +1, not the active validator view -- the same gap the F1 fix closed for the main entropy gate (inspectTxConvergedSidecarPeers). It is default-off/experimental with no robust timing model found (per the existing TBD, may never ship); if it is ever enabled it must apply the same active-view membership filter. Comment-only.

Follow-up to F1 (fb9e2710c).
2026-06-18 13:32:38 +07:00
Nicholas Dudfield
301e546aa9 chore(rng): assert active view source at round start 2026-06-18 10:42:28 +07:00
Nicholas Dudfield
e2da1db6d2 chore(rng): harden entropy fallback diagnostics 2026-06-18 10:32:07 +07:00
Nicholas Dudfield
16cd02156e test(rng): use a nonzero sentinel in the hook entropy-requirement test
testInvalidEntropyRequirements rejects four invalid dice/random requirements then returned accept(0,0,0) on success. But a valid dice(6,..) returns 0..5, so a regression that let the min_tier=0 requirement through (returning a value that happened to be 0) would still pass ~1/6 of the time -- the weakest spot is exactly the min_tier lower bound it most needs to prove (review finding). Return sentinel 42 (distinct from any dice/random result and from INVALID_ARGUMENT) and assert ==42, so any leaked requirement returns its own non-42 code and fails. WASM block recompiled; ConsensusEntropy 138 tests, 0 failures.

Follow-up to F1 (fb9e2710c).
2026-06-18 09:54:30 +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
c8fad50d66 Merge origin/dev into feature-export-rng
# Conflicts:
#	src/xrpld/app/hook/applyHook.h
2026-06-17 11:45:42 +07:00
Nicholas Dudfield
d9b5fc26fc style(rng): apply clang-format 2026-06-17 11:35:05 +07:00
Nicholas Dudfield
72d03620f9 test(rng): require UNLReport in testnet entropy suites 2026-06-17 11:33:04 +07:00
Nicholas Dudfield
8368e12ab3 fix(rng): require UNLReport view for non-fallback entropy 2026-06-17 11:32:51 +07:00
Nicholas Dudfield
23f745bd01 fix(hook): validate entropy requirements 2026-06-17 11:32:38 +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
Richard Holland
639ea34377 Fixhookmap (#756) 2026-06-16 17:06:25 +10:00
tequ
089c0dc3fe Fix ammLPHolds logic to include escrowed cases (#757) 2026-06-16 15:26:29 +10:00
Nicholas Dudfield
b070785dee fix(rng): make tier-2 scenario's 5/6 check fallback-tolerant
The 5/6 phase asserted validator_quorum on whichever single ledger the validated tip happened to sit on. But the ledger right at the node-5 drop can be a transient consensus_fallback (EntropyTier=1, count=0) — deterministic and by design, while the commit/reveal pipeline re-primes — so when the tip landed there the assert failed (tier=1, observed ~1 run in 4). Same class of bug as the old post-recovery flake: depending on exactly where the tip lands.

Now settle 4 ledgers past the drop, then scan the post-drop validated ledgers for a clean validator_quorum (tier 3, count >= quorum). The window is entirely 5-node cohorts, so a tier-3 there has count == 5 (faithful to '5/6 still tier 3'); the transition fallback is tolerated, not asserted on. Verified 3/3: each run found tier=3 count=5.

Also harden _closed_entropy() to raise on != 1 ConsensusEntropy pseudo-tx (mirroring get_entropy_tx) instead of silently skipping — a duplicate/missing injection now fails with a clear error rather than resurfacing as a generic 'no tier-2 ledger'.
2026-06-16 11:15:55 +07:00
tequ
78167e09c0 change sfHookOn of ltHookDefinition to soeOptional for HookOnV2 (#755) 2026-06-16 13:58:53 +10:00
tequ
607a7fdf98 Change AMM to Supported::no (#758) 2026-06-16 13:56:58 +10:00
Nicholas Dudfield
def617e3f9 test(rng): harden tier-2 testnet scenario per review + robust on-ledger check
Codex review minors: assert_validator_quorum at the 5/6 boundary (EntropyTier=3 AND count >= quorum AND non-zero digest, not just tier==3 — catches a bad tier/count pairing); added explicit assert_validator_quorum / assert_consensus_fallback helpers and an entropy_fields() warning that its is_fallback (tier != 3) lumps participant_aligned in with fallback (safe only where no tier-2 band exists).

Robustness: tier 2 is below the validation quorum, so the validated tip stalls and which provisional ledgers it later reaches is timing-dependent (the post-recovery inspection was fragile). Replaced it with direct inspection of the surviving cohort's CLOSED ledger via ledger('closed') DURING the window; recovery is now a pure liveness check.

Verified on a live testnet: PASS — EntropyTier=2 count=4 confirmed on 4 distinct provisional ledgers (seq 7-10).
2026-06-16 10:51:33 +07:00
Nicholas Dudfield
c322b59961 test(rng): tier-2 (participant_aligned) testnet scenario at n=6
A 6-node smoke (the smallest NON-degenerate tier-2 size: tier2 floor 4, quorum 5) driving the 4/6 band. n=5 has no band (tier2 == quorum), which is why the existing degradation smoke only ever sees tier 3 / fallback.

Tier 2 is below the 80% validation quorum, so the 4/6 cohort's ledgers are provisional: the scenario confirms tier-2 injection from the cohort's logs during the window, then verifies the on-ledger EntropyTier=2 count=4 POST-RECOVERY once those ledgers become canonical and validate (the mechanism the degradation smoke also relies on). Adds the assert_participant_aligned helper and the node_count:6 suite entry.

Verified on a live testnet: PASS — 12 tier-2 injections in the 4/6 window, seq 7 & 8 validated as participant_aligned (count 4).
2026-06-16 10:28:57 +07:00
Nicholas Dudfield
c3cc3513c9 docs(rng): refresh tier 2 terminology 2026-06-16 10:05:57 +07:00
Nicholas Dudfield
a0be935227 docs(rng): correct stale tier-2 comments
Codex final-review minors, both comment-only (no behavior change): EntropyTier participant_aligned no longer says "reserved for a future" tier (it is implemented in this stack); and the commit-timeout path comment said "fixed UNL quorum" but the code now uses entropyGateThreshold() (min(quorum, tier2)).
2026-06-16 09:45:08 +07:00
Nicholas Dudfield
6b1b18dd38 refactor(rng): guard CSF entropy finalize to reveal-type sidecar
Codex final-review minor (optional hardening): finalizeRoundEntropy now requires the fetched sidecar entry to be reveal-type before counting it as entropy. lastEntropySetHash_ only ever names a reveal set (hashRngSet's per-type salt rules out a cross-type hash collision), so this is purely defensive — behavior unchanged, all CSF sims green.
2026-06-16 09:30:42 +07:00
Nicholas Dudfield
946f25249b fix(rng): CSF finalizes from advertised set, not live pendingReveals_
Codex review caught a CSF/production fidelity gap: production injects entropy from the AGREED entropySetMap_ (selectEntropy), which is frozen at advertise time — late-fetched or conflicting reveals merge into pendingReveals_ but are NOT injected unless a rebuild republishes the hash. The CSF peer fetched into pendingReveals_ AND finalized from pendingReveals_, so a conflict/fetch sim could count reveals production would never inject from.

Track the last advertised entropy-set hash (buildEntropySet) and finalize from the sidecar-store snapshot under that hash — the analog of the frozen entropySetMap_. Clean/no-conflict sims are unchanged (the snapshot equals pendingReveals_ there); the model is now faithful for conflict/fetch cases too. Production unaffected (test-harness only). Addresses finding 1 of codex-tier2-final-review.
2026-06-15 18:44:05 +07:00
Nicholas Dudfield
a95306142e test(rng): tier-2 mint sim — in-band cohort at n=6
6 validators is the smallest non-degenerate tier-2 size (f=1, one-wide band {4}: tier2=4, quorum=5). Isolate 2 so the surviving 4-cohort is below the 80% quorum but at the tier-2 floor; it mints participant_aligned entropy (count 4), and all four agree on the same non-zero digest with branches==1 — no hang, no fork. Distributed confirmation of the selector ladder the unit tests already cover.
2026-06-15 17:48:53 +07:00
Nicholas Dudfield
8d43154061 feat(rng): model tier 2 in CSF sims + reconcile sub-quorum sims
The CSF peer now mirrors production's tier-2 behavior so the RNG simulations
exercise participant_aligned, not just validator_quorum/fallback:
- entropyGateThreshold() = min(quorumThreshold(), tier2Threshold()), plus a
  tier2Threshold() helper (calculateParticipantThreshold over the sim's UNL).
- finalizeRoundEntropy() labels by aligned count via the 3-tier ladder
  (>= quorum -> validator_quorum, >= tier2 -> participant_aligned, else
  fallback) instead of hardcoding tier 3.

The n%5==0 band-collapse keeps the bulk of the suite unaffected: at n=5
tier2 == quorum == 4, so the gate is unchanged for every n=5 and n<=2 network.
Only the two n=3 sub-quorum sims cross a live band (n=3: tier2=2 < quorum=3) and
now mint tier 2 instead of falling back -- the feature working, with each test's
intent preserved:
- "impossible quorum fallback" -> "quorum-impossible cohort falls to
  participant_aligned": 2 of 3 is below 80% but at the tier-2 floor, so it makes
  progress as tier 2 (no hang, no fork) rather than falling all the way back.
- "persistent loss does not shrink quorum": the 2 survivors now mint the
  labeled-weaker tier 2, NOT tier 3 -- the tier-3 quorum still did not shrink
  (a min_tier=3 hook rejects it). Intent preserved, outcome relabeled.

Other CSF suites (Consensus, ByzantineFailureSim) are unaffected (they do not
enable RNG). Next: dedicated tier-2 mint + conflict sims at n=6 (smallest size
with a non-degenerate band, f=1).
2026-06-15 17:41:47 +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
d4c0ba3769 refactor(rng): unify entropy selection into one deterministic selector
Collapse the duplicated tier-selection logic in onPreBuild and
buildExplicitFinalProposalTxSet into a single selectEntropy() over the AGREED
entropySetMap_. No behavior change on the production (implicit) path — the
onPreBuild fallback/entropy-set/standalone/mismatch tests pass byte-identically.

Fixes the pre-existing divergence flagged in review: buildExplicitFinalProposalTxSet
derived entropy from local pendingReveals_ while onPreBuild used the agreed
entropySetMap_, so the two could mint different digests for the same round.
Both now share the selector, so the implicit and (experimental, default-off)
explicit-final paths — and any two nodes — derive identical entropy from
identical agreed inputs.

selectEntropy() returns {digest, tier, count} and is a pure function of agreed
round state, so it is directly unit-testable. Injection becomes unconditional:
the selector always yields a fallback digest when there is no validator entropy,
so every RNG-enabled ledger still carries exactly one ConsensusEntropy tx.

Sets up the tier-2 (participant_aligned) ladder, which lands next.
2026-06-15 15:22:14 +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
4b219cdef8 test(testnet): use strength-ordered enum names, drop 'Tier 3' shorthand
The 'Tier N' preference-order shorthand collides with EntropyTier's
strength-ordered values (consensus_fallback=1). Use the enum names in
the degradation-smoke comments/descr to remove the ambiguity.
2026-06-12 17:24:23 +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
Nicholas Dudfield
d6481a3869 fix(rng): value-based entropy pseudo-tx dedup + tier-read hardening
Address review findings on the tier 3 commits:

- onPreBuild dedup was type-based (skip if any ttCONSENSUS_ENTROPY
  present), which silently trusts a pre-present pseudo-tx. Injection is
  deterministic, so every honest node derives the identical pseudo-tx
  (identical txID) for the same agreed inputs. Switch to value-based
  dedup: skip only when the present pseudo-tx EQUALS the one we would
  produce; a present-but-different entropy pseudo-tx is a determinism
  violation (version skew / divergent peer) and is now logged at error
  rather than accepted blindly. (The earlier 'cannot reconstruct the
  txID locally' justification was wrong — determinism guarantees it can.)
- fairRng: read sfEntropyTier defensively (missing => 0 => fail closed).
  The field is soeREQUIRED so any entry this code wrote carries it; this
  only guards a pre-tier-3 persisted entry on a long-lived testnet.
- quorum_degradation_smoke: assert EntropyTier==consensus_fallback(1) and
  EntropyCount==0 and non-zero digest explicitly, not just is_fallback.
2026-06-12 16:39:39 +07:00
Nicholas Dudfield
0cf6f73441 test(testnet): align degradation smoke comments with tier 3 semantics 2026-06-11 13:57:53 +07:00
Nicholas Dudfield
08a6f3cd57 docs(consensus): describe tier 3 fallback entropy semantics 2026-06-10 16:58:43 +07:00
Nicholas Dudfield
77d78236e8 feat(hooks): dice/random take required min_tier + min_count args
The entropy-quality requirement becomes an explicit, required argument
at every call site — there is deliberately no default and no network
constant:

- dice(sides) -> dice(sides, min_tier, min_count)
- random(ptr, len) -> random(ptr, len, min_tier, min_count)
- fairRng gates on freshness && tier >= min_tier && count >= min_count;
  the hard-coded 'EntropyCount >= 5' network constant is deleted —
  the hook author states what their application needs and gets
  TOO_LITTLE_ENTROPY when this ledger cannot meet it
- WASM imports have no default parameters, so the old no-argument shape
  was really a hidden constant invisible at the call site; requiring the
  arguments makes weak-entropy acceptance (min_tier=1) a deliberate,
  reviewable opt-in rather than an accident
- hook/extern.h and hook/sfcodes.h regenerated (CI-verified)
- test hooks updated and recompiled; new test pins the requirement
  gate: dice(6, 3, 21) against count=20 returns TOO_LITTLE_ENTROPY
2026-06-10 16:56:05 +07:00
Nicholas Dudfield
c92c0656ec feat(rng): tier 3 consensus-bound fallback entropy
Replace the zero-entropy fallback with a deterministic consensus-bound
digest so every RNG-enabled ledger carries usable entropy:

- sha512Half(HashPrefix::entropyFallback, prevLedgerHash, baseTxSetHash,
  seq) — all inputs are already consensus-agreed at injection time, so
  no new agreement machinery is needed and the digest is identical on
  every node building the same ledger
- new sfEntropyTier (UINT8) on the ttCONSENSUS_ENTROPY pseudo-tx and
  ConsensusEntropy ledger entry: EntropyCount says how many validators
  contributed, EntropyTier says which gate the result passed
  (validator_quorum vs consensus_fallback; participant_aligned reserved)
- the fallback digest derives from the BASE (pre-injection) tx set hash
  to avoid circularity; entropy pseudo-tx dedup is now type-based since
  an explicit-final synthetic set can carry a pseudo-tx whose txID
  implicit nodes cannot re-derive
- unparseable-entropy-set residual now falls back instead of skipping
  injection, so a fresh ConsensusEntropy entry exists every ledger
- CSF Peer mirrors the fallback analog; sims assert deterministic
  non-zero fallback digests across same-LCL peers
- testnet scenarios updated: degraded windows expect labeled fallback
  entropy, never validator-tier

The fallback tier is user-influenceable via tx submission (quiet-ledger
grinding) and is labeled accordingly — hook-facing gating lands with the
min_tier/min_count API change.
2026-06-10 16:32:49 +07:00
Nicholas Dudfield
fbdec3be66 chore: update levelization results 2026-06-10 12:46:09 +07:00
Nicholas Dudfield
bb619cc100 fix: defer proposal feature checks 2026-06-10 11:09:12 +07:00
Nicholas Dudfield
10f22c84f2 fix: preserve malformed proposal rejection order 2026-06-10 09:08:33 +07:00
Nicholas Dudfield
dd21024c0e test: improve export rng coverage 2026-06-09 19:56:46 +07:00
Nicholas Dudfield
16a72172b4 test(coverage): cover xpop empty proof output 2026-06-09 14:29:32 +07:00
Nicholas Dudfield
265012e16a test(coverage): expand runtime config option coverage 2026-06-09 14:26:04 +07:00
Nicholas Dudfield
d072527bc5 test(coverage): cover consensus extension refresh paths 2026-06-09 14:23:20 +07:00
Nicholas Dudfield
5f5ce12fa6 test(coverage): cover export signature helper edges 2026-06-09 14:18:12 +07:00
Nicholas Dudfield
0804a01b9b test(coverage): cover consensus extension tick states 2026-06-09 14:12:54 +07:00
Nicholas Dudfield
80cd1bed34 test(coverage): cover export metadata roundtrip 2026-06-09 13:50:44 +07:00
Nicholas Dudfield
b4e98ac1d7 test(coverage): cover peer proposal wrapper 2026-06-09 13:40:04 +07:00
Nicholas Dudfield
4f7e751fbd test(coverage): avoid xpop levelization dependency 2026-06-09 13:36:25 +07:00
Nicholas Dudfield
748fef6267 test(coverage): cover xpop proof edge cases 2026-06-09 13:32:26 +07:00
Nicholas Dudfield
4d48c9f949 test(coverage): cover export rng helper seams 2026-06-09 13:21:08 +07:00
Nicholas Dudfield
03c1216661 merge: absorb origin/dev 2026-06-09 09:44:39 +07:00
Nicholas Dudfield
537474cb5f style(consensus): apply ci clang-format 2026-06-09 09:20:27 +07:00
Nicholas Dudfield
ec086d6765 test(export): cover signature upgrade race guard 2026-06-08 15:50:32 +07:00
Nicholas Dudfield
7362c1dac1 fix(hook): preserve xport nonce failure result 2026-06-08 15:50:17 +07:00
Nicholas Dudfield
f0550ca625 refactor(export): extract signature upgrade policy 2026-06-08 14:49:16 +07:00
Nicholas Dudfield
526b60bf3d refactor(hook): extract xport wrapper builder 2026-06-08 14:37:58 +07:00
Nicholas Dudfield
9347b47639 refactor(export): extract result assembly 2026-06-08 14:21:44 +07:00
Nicholas Dudfield
9988568a08 refactor(consensus): extract export signature harvester 2026-06-08 14:10:43 +07:00
Nicholas Dudfield
13260b9ef7 test(runtime-config): cover effective config paths 2026-06-08 13:58:39 +07:00
Nicholas Dudfield
65dab780de refactor(consensus): share sidecar alignment scan 2026-06-05 15:07:44 +07:00
Nicholas Dudfield
ee70e4cdbb refactor(consensus): extract active validator view builder 2026-06-05 15:02:51 +07:00
Nicholas Dudfield
7fb1509673 test(testnet): harden scenario feature checks 2026-06-04 15:50:31 +07:00
Nicholas Dudfield
64620e2825 test(testnet): assert export degradation logs 2026-06-02 12:30:07 +08:00
Nicholas Dudfield
a87a7896ca test(testnet): align degradation log checks 2026-06-02 12:25:12 +08:00
Nicholas Dudfield
443aca8611 chore(logging): polish RNG final diagnostics 2026-06-01 14:08:12 +08:00
Nicholas Dudfield
804b76b4ab chore(logging): normalize consensus extension diagnostics 2026-06-01 13:57:46 +08:00
tequ
c55420bcd8 Fix duplicate and incorrect fields in server_definitions (#753) 2026-05-27 07:58:13 +10:00
tequ
cb91b4e88e Restore pre-reserved tem codes (#754) 2026-05-27 07:57:55 +10:00
tequ
90333b6fd0 Fix HookAPI Expected and Refactor Enum classes (#729) 2026-05-26 11:02:17 +10:00
tequ
7f9a9364b0 fix: Ensures canonical order for PriceDataSeries upon PriceOracle creation (#5485) (#744) 2026-05-25 11:34:24 +10:00
tequ
706d31f01d Update .git-blame-ignore-revs (#746) 2026-05-25 11:28:56 +10:00
tequ
083e9e4315 Generate hook extern declarations with C linkage (#750) 2026-05-25 11:27:21 +10:00
tequ
b9e0c56def Guard depth 32 (#653) 2026-05-25 11:10:49 +10:00
Nicholas Dudfield
ab6571a20f fix(runtime-config): avoid overlay dependency 2026-05-22 14:58:22 +08:00
Nicholas Dudfield
331e1606a3 feat(runtime-config): support startup message filters 2026-05-22 14:50:59 +08:00
Nicholas Dudfield
24d6dea1a2 test(testnet): enable consensus extension diagnostics
Add ConsensusExtensions debug logging to the entropy and export scenario suite defaults so participant bitmap diagnostics are captured without live log-level changes.
2026-05-21 10:21:56 +08:00
Nicholas Dudfield
03e0bb5fc3 feat(consensus): add active participant diagnostics
Carry a signed observedParticipantsHash in ExtendedPosition and log a local canonical bitmap over the active validator view for degraded-round debugging. The field is diagnostic only and does not affect proposal equality or quorum thresholds.
2026-05-21 09:56:51 +08:00
Nicholas Dudfield
0a77dbf68e Merge remote-tracking branch 'origin/dev' into feature-export-rng 2026-05-19 12:11:18 +08:00
tequ
663ed4edb8 Named Hook (#718) 2026-05-19 12:00:25 +10:00
tequ
8422758d4d chore: Improve codecov coverage reporting (#4977) (#745) 2026-05-18 12:21:47 +10:00
Nicholas Dudfield
60a9a2c9fb Merge remote-tracking branch 'origin/dev' into feature-export-rng
# Conflicts:
#	include/xrpl/protocol/Feature.h
#	include/xrpl/protocol/detail/features.macro
2026-05-18 09:15:17 +07:00
tequ
586c78e812 fix: Replace badCurrency() checks with isBadCurrency() for improved clarity (#742) 2026-05-18 11:57:25 +10:00
Niq Dudfield
9b50b68d39 perf(ledger): optimize catalogue loading memory usage and performance (#548) 2026-05-06 17:29:44 +10:00
tequ
5e8d26f67a refactor: Calculate numFeatures automatically (#5324) (#739)
Co-authored-by: Ed Hennis <ed@ripple.com>
2026-04-30 18:17:50 +10:00
Nicholas Dudfield
445d0070d8 Merge remote-tracking branch 'origin/dev' into feature-export-rng
# Conflicts:
#	hook/sfcodes.h
#	include/xrpl/protocol/Feature.h
#	include/xrpl/protocol/detail/sfields.macro
2026-04-30 14:22:06 +07:00
tequ
a6186d7855 IOURewardClaim (#500) 2026-04-30 15:27:51 +10:00
Nicholas Dudfield
61a8d8bba7 chore(hook): update generated tx flags 2026-04-30 11:03:58 +07:00
Nicholas Dudfield
fbedb8a73a Merge remote-tracking branch 'origin/dev' into feature-export-rng 2026-04-30 10:57:07 +07:00
tequ
b449599408 Add --definitions CLI flag to output static server definitions without starting server (#708) 2026-04-30 12:42:42 +10:00
Nicholas Dudfield
8ae541fcc1 Merge remote-tracking branch 'origin/dev' into feature-export-rng
# Conflicts:
#	src/test/app/SetHookTSH_test.cpp
#	src/xrpld/app/tx/detail/InvariantCheck.cpp
#	src/xrpld/app/tx/detail/InvariantCheck.h
2026-04-30 08:14:51 +07:00
Nicholas Dudfield
c8f3f6f05f docs(consensus): clarify extension fallback semantics 2026-04-29 15:27:46 +07:00
tequ
9d4e507675 Treat suspicious gcov hit counts as coverage warnings (#737) 2026-04-29 17:06:44 +10:00
tequ
49fd0c33b5 fixIOULockedBalanceInvariant Amendment (#732) 2026-04-29 17:05:18 +10:00
tequ
0ffb6e8c21 Replace vendored magic_enum header with Conan package dependency (#727) 2026-04-29 17:00:25 +10:00
tequ
61138058a6 Delete unused sfHookDefinition (#715) 2026-04-29 16:45:35 +10:00
tequ
8dd17973d0 Add hook header ls_flags.h and tx_flags.h (#717) 2026-04-29 16:41:52 +10:00
Fomo
dd9e6053a0 fix: Expand the pathing tables and lower weight on sabbxd, also return up … (#723) 2026-04-29 14:28:00 +10:00
tequ
8c1be39f70 Fix LedgerNameSpace to ensure uniqueness (#710) 2026-04-29 12:01:57 +10:00
tequ
61d1d6e441 Add test for rejected Remit to AMM account (#698) 2026-04-29 11:20:13 +10:00
tequ
788ba43266 Refactor getFieldU16(sfTransactionType) with getTxnType() (#711) 2026-04-29 11:17:49 +10:00
tequ
55710c4baf Disallow setting a AMM account as Issuer/Destination/Inform (#709) 2026-04-29 11:00:25 +10:00
tequ
7bf5bcaf20 Add new TSH tests (#704) 2026-04-29 10:40:50 +10:00
Niq Dudfield
9f2160f42e fix: resolve switch fall-through in util_keylet unimplemented cases (#701) 2026-04-29 10:37:17 +10:00
tequ
ef7a03ec10 fix owner count assertion at XahauGenesis_test (#688) 2026-04-29 10:32:43 +10:00
tequ
6eb6c41ec6 fix: use OIDC token for Codecov uploads from fork PRs (#736) 2026-04-29 09:50:47 +10:00
tequ
9651f68b2e Add coverage workflow (#661)
Co-authored-by: Bronek Kozicki <brok@incorrekt.com>
Co-authored-by: Niq Dudfield <ndudfield@gmail.com>
2026-04-28 18:29:09 +10:00
tequ
f1f44ae232 Build xahau.h from source and check in workflow https://github.com/Xahau/xahaud/pull/674 2026-04-28 18:28:59 +10:00
Alloy Networks
c461dd9055 change build instructions url 2026-04-28 18:23:32 +10:00
tequ
73cf6d34cd Fix BEAST_ENHANCED_LOGGING not working and restore original behavior 2026-04-28 18:23:32 +10:00
tequ
ea92477d21 Test: hint build_test_hooks.sh when hook wasm is empty in hso() 2026-04-28 18:23:32 +10:00
Nicholas Dudfield
e4c7893bf0 Revert "chore: use improved levelization script with threading and argparse"
This reverts commit 5c1d7d9ae9.
2026-04-28 18:23:32 +10:00
Nicholas Dudfield
ad9d6a6eb3 chore: use improved levelization script with threading and argparse 2026-04-28 18:23:32 +10:00
Nicholas Dudfield
dbbffd917e chore: replace levelization shell script with python
Backport of XRPLF/rippled#6325. The python version runs ~80x faster.
2026-04-28 18:23:31 +10:00
tequ
1d7c7e5603 enable ccache direct_mode 2026-04-28 18:23:31 +10:00
tequ
52843e2321 output ccache configuration in release-builder 2026-04-28 18:23:31 +10:00
tequ
6aabbc940b fix: typo SignersListSet 2026-04-28 18:23:31 +10:00
tequ
3111ecea52 Update util_keylet fee test 2026-04-28 18:23:31 +10:00
tequ
1008508c9b Updated tests to align with the changes merged into the dev branch. 2026-04-28 18:23:31 +10:00
tequ
58e278289b Add tests for Hooks fee 2026-04-28 18:23:31 +10:00
tequ
d3d24f781b Merge fixAMMClawbackRounding amendment into featureAMMClawback amendment 2026-04-28 18:23:31 +10:00
yinyiqian1
131d659032 fixAMMClawbackRounding: adjust last holder's LPToken balance (#5513)
Due to rounding, the LPTokenBalance of the last LP might not match the LP's trustline balance. This was fixed for `AMMWithdraw` in `fixAMMv1_1` by adjusting the LPTokenBalance to be the same as the trustline balance. Since `AMMClawback` is also performing a withdrawal, we need to adjust LPTokenBalance as well in `AMMClawback.`

This change includes:
1. Refactored `verifyAndAdjustLPTokenBalance` function in `AMMUtils`, which both`AMMWithdraw` and `AMMClawback` call to adjust LPTokenBalance.
2. Added the unit test `testLastHolderLPTokenBalance` to test the scenario.
3. Modify the existing unit tests for `fixAMMClawbackRounding`.
2026-04-28 18:23:31 +10:00
tequ
503dee619a Merge fixAMMv1_3 amendment into featureAMM amendment 2026-04-28 18:23:31 +10:00
Gregory Tsipenyuk
1703d96a48 fix: Add AMMv1_3 amendment (#5203)
* Add AMM bid/create/deposit/swap/withdraw/vote invariants:
  - Deposit, Withdrawal invariants: `sqrt(asset1Balance * asset2Balance) >= LPTokens`.
  - Bid: `sqrt(asset1Balance * asset2Balance) > LPTokens` and the pool balances don't change.
  - Create: `sqrt(asset1Balance * assetBalance2) == LPTokens`.
  - Swap: `asset1BalanceAfter * asset2BalanceAfter >= asset1BalanceBefore * asset2BalanceBefore`
     and `LPTokens` don't change.
  - Vote: `LPTokens` and pool balances don't change.
  - All AMM and swap transactions: amounts and tokens are greater than zero, except on withdrawal if all tokens
    are withdrawn.
* Add AMM deposit and withdraw rounding to ensure AMM invariant:
  - On deposit, tokens out are rounded downward and deposit amount is rounded upward.
  - On withdrawal, tokens in are rounded upward and withdrawal amount is rounded downward.
* Add Order Book Offer invariant to verify consumed amounts. Consumed amounts are less than the offer.
* Fix Bid validation. `AuthAccount` can't have duplicate accounts or the submitter account.
2026-04-28 18:23:31 +10:00
Nicholas Dudfield
b12cee5d47 fix(consensus): wait for sidecar observation
Require tx-converged peers to advertise sidecar hashes before accepting RNG entropy or export signature success from local quorum alignment.

The RNG reveal fast path now publishes the entropy set and waits for peer observation instead of accepting in the same tick. On timeout, RNG clears the advertised entropy hash and falls back to deterministic zero.

Add unit and CSF regression coverage for asymmetric peer observation.
2026-04-28 12:20:42 +07:00
Nicholas Dudfield
a3b1e45f4d fix(consensus): bound export sidecar observation
When a candidate set contains ttEXPORT but a node has no local verified export sig material yet, give tx-converged peers one bounded opportunity to advertise an exportSigSetHash before closed-ledger apply.

This is a safety coordination window, not a wait-for-Export-success mechanism. If no advertised sidecar arrives or fetched material cannot be merged by the deadline, Export convergence is marked failed and the transaction retries or expires through normal rules.

Add CSF coverage for a peer that can only succeed by fetching peer-advertised export sidecars, plus a direct ConsensusExtensionsTick test for the pre-advertisement observation window. Document the consensus-extension priority order: safe, fast, works.
2026-04-28 11:15:03 +07:00
Nicholas Dudfield
3938ba7af4 docs(consensus): record export sidecar material flow
Clarify that export sidecar publication is local verified material only, and fetched sidecar leaves must be active-view checked, candidate-tx verified, and promoted into ExportSigCollector before closed-ledger apply can use them.
2026-04-28 10:59:00 +07:00
Nicholas Dudfield
96b1104646 fix(consensus): use quorum for export-only sidecars
Export-only originally used unanimity as a conservative substitute for the CE/RNG sidecar machinery. That made sense before Export had its own signed ExtendedPosition field and exportSigSetHash convergence gate.

Now Export sidecars are signed and converged independently of RNG, so a quorum-aligned exportSigSetHash plus verified active-view signature quorum is deterministic enough for Export-only mode. Keeping unanimity would let one active validator veto an otherwise converged export round.

Update CSF and testnet coverage to treat Export-only the same way: one missing/conflicting signer in a 5-validator network succeeds at 4/5, while below-quorum still retries or expires.
2026-04-28 10:54:04 +07:00
Nicholas Dudfield
92bdd2ed9f fix(consensus): harvest replayed export signatures 2026-04-28 10:16:52 +07:00
Nicholas Dudfield
d87cfdc604 fix(consensus): clear export sigs when export disabled 2026-04-28 09:17:17 +07:00
Nicholas Dudfield
a956abb2d1 docs(consensus): clarify export sig quorum gates 2026-04-28 08:56:53 +07:00
Nicholas Dudfield
aa36a80ab7 docs(consensus): document sidecar acquire rationale 2026-04-28 08:35:45 +07:00
Nicholas Dudfield
e729aa11eb fix(hooks): preserve finalization semantics
Keep hook result/state finalization non-fatal while enforcing the hook-export backlog cap through the transaction-level ApplyContext guard. This avoids resetting non-success tec metadata and preserves hook_again weak execution behavior.
2026-04-28 07:47:20 +07:00
Nicholas Dudfield
c58da3da58 fix(export): cap hook export backlog
Enforce the pending export cap for hook-emitted ttEXPORT work before commit. Replace the non-present sfEmittedTxn template field when building ltEMITTED_TXN entries so in-flight ledger checks see the emitted wrapper.

Overflowing xport emission now returns tecDIR_FULL and leaves the emitted backlog capped at ExportLimits::maxPendingExports.
2026-04-27 22:55:23 +07:00
Nicholas Dudfield
0c2c59d258 fix(export): enforce pending export limits
Cap pending ttEXPORT work in open/apply ledgers, including hook-emitted exports when TxQ drains the emitted directory into the open ledger. Enforce the same bound for per-account shadow tickets so durable pending imports cannot grow unbounded.
2026-04-27 21:30:24 +07:00
Nicholas Dudfield
15662eb1b1 fix(consensus): cap export proposal signatures
Limit outbound TMProposeSet export signature attachments to ExportLimits::maxPendingExports so honest proposals stay within the same bound enforced by inbound proposal validation. Extra exports remain unsigned for that proposal and rely on the existing retry/expiry path.
2026-04-27 21:03:00 +07:00
Nicholas Dudfield
492fe90643 fix(consensus): expire stale export signatures
Stamp export signatures learned from proposals, sidecar sets, and candidate tx-set upgrades with a ledger sequence so cleanupStale can age them out. Remove invalid unverified signatures after tx-local verification fails, with a buffer match check to avoid deleting newer replacements.
2026-04-27 20:56:54 +07:00
Nicholas Dudfield
ea413873b2 fix(consensus): preserve export state without rng 2026-04-27 20:34:58 +07:00
Nicholas Dudfield
625419eab7 fix(consensus): verify export sigs against tx set 2026-04-27 18:07:09 +07:00
Nicholas Dudfield
2218bdd7f3 fix(consensus): require export sigset quorum alignment 2026-04-27 17:36:06 +07:00
Nicholas Dudfield
f13233b00a docs(consensus): clarify validation sidecar signing rule
Remove the stale TMValidation exportSignatures field from the draft proto path now that export signatures ride signed proposal sidecars. Document that any future validation-carried ConsensusExtensions data must be covered by the signed validation payload and duplicate/replay identity, not an unsigned wrapper field.
2026-04-27 15:45:27 +07:00
Nicholas Dudfield
a61f334ca2 docs(consensus): capture extension design principles
Document the consensus-extension invariants for RNG, sidecars, export sig convergence, validator quorum, zero-entropy fallback, and proposal signing. Link the note from the RCL consensus README so future changes have a durable checklist.
2026-04-27 15:33:15 +07:00
Nicholas Dudfield
53a119ce30 fix(consensus): require rng entropy quorum alignment
Count the local proposer when deciding whether the previous round had enough participants for RNG, since prevProposers only tracks peers. This avoids a 4/5 honest quorum being treated as below quorum after one validator diverges.

Allow an already quorum-aligned entropySetHash to proceed despite below-quorum conflicting hashes, while retaining zero-entropy fallback when no entropy hash reaches quorum alignment. Add CSF coverage for a persistent single bogus entropy hash and for conflicting bogus hashes without quorum.
2026-04-27 15:29:36 +07:00
Nicholas Dudfield
63d1197345 fix(consensus): zero rng on unresolved entropy hash conflict 2026-04-27 15:10:39 +07:00
Nicholas Dudfield
aafd5b940b test(consensus): avoid brittle rng lcl quorum check 2026-04-27 14:47:18 +07:00
Nicholas Dudfield
efc497cf23 chore(levelization): refresh app overlay loop summary
This does not introduce a new levelization cycle; the existing xrpld.app <-> xrpld.overlay loop now has equal aggregate include counts after the consensus-extension work. Treat this as essentially the same architectural situation, not a meaningful worsening by itself.

TODO: if we want to fix the boundary properly, extract a small shared consensus-extension wire/interface layer below both app and overlay instead of shaving includes to change the generated ratio.
2026-04-27 14:01:54 +07:00
Nicholas Dudfield
f4e78c9a24 fix(consensus): apply negative unl to sidecar validator view 2026-04-27 12:50:43 +07:00
Nicholas Dudfield
7b5865c69c fix(consensus): sign export proposal attachments 2026-04-27 11:57:29 +07:00
Nicholas Dudfield
9f1ad521e1 fix(consensus): use active validator snapshots for sidecars 2026-04-27 10:59:33 +07:00
Nicholas Dudfield
26bbef8efd fix(consensus): harden sidecar quorum inputs 2026-04-27 10:14:12 +07:00
Nicholas Dudfield
6e71f84867 refactor: add typed sidecar SHAMap sync 2026-04-27 09:58:34 +07:00
Nicholas Dudfield
ab9b48f67a Merge remote-tracking branch 'origin/dev' into feature-export-rng
# Conflicts:
#	.github/workflows/levelization.yml
#	Builds/levelization/README.md
#	Builds/levelization/levelization.py
#	Builds/levelization/levelization.sh
#	cmake/RippledCore.cmake
2026-04-27 09:14:59 +07:00
Alloy Networks
cd00ed72d8 change build instructions url 2026-04-24 11:12:28 +10:00
tequ
05a3e04f2d Fix BEAST_ENHANCED_LOGGING not working and restore original behavior 2026-04-24 11:11:40 +10:00
tequ
66f7294120 Test: hint build_test_hooks.sh when hook wasm is empty in hso() 2026-04-24 11:10:46 +10:00
Nicholas Dudfield
7f6ac75617 Revert "chore: use improved levelization script with threading and argparse"
This reverts commit 5c1d7d9ae9.
2026-04-24 11:09:19 +10:00
Nicholas Dudfield
4150f0383c chore: use improved levelization script with threading and argparse 2026-04-24 11:09:19 +10:00
Nicholas Dudfield
25123b370a chore: replace levelization shell script with python
Backport of XRPLF/rippled#6325. The python version runs ~80x faster.
2026-04-24 11:09:19 +10:00
tequ
f90ed41802 enable ccache direct_mode 2026-04-24 11:06:51 +10:00
tequ
8c4c158d3a output ccache configuration in release-builder 2026-04-24 11:06:51 +10:00
tequ
2d2951875d fix: typo SignersListSet 2026-04-24 11:05:20 +10:00
tequ
9bfca63574 Update util_keylet fee test 2026-04-24 11:00:31 +10:00
tequ
1ba444ae7f Updated tests to align with the changes merged into the dev branch. 2026-04-24 11:00:31 +10:00
tequ
f96d9b6e51 Add tests for Hooks fee 2026-04-24 11:00:31 +10:00
Nicholas Dudfield
04077c1a55 test(testnet): assert zero entropy in degraded ledgers 2026-04-10 12:04:46 +07:00
Nicholas Dudfield
d94079d762 test(rng): relax PartialReveals sync assertion 2026-04-10 11:18:52 +07:00
Nicholas Dudfield
92ec07a1be chore: regenerate hook/sfcodes.h + format fix
Regenerate sfcodes.h to include the new sfSidecarType field
(UINT8, code 20).  Also apply clang-format to ConsensusExtensions.cpp.
2026-04-10 10:36:50 +07:00
Nicholas Dudfield
664db62588 fix: sidecar kind lost on cache hit + harden export sig parse
1. Record SidecarKind in pendingRngFetches_ before calling
   onAcquiredSidecarSet on local-cache-hit path. Without this,
   cached reveal/exportSig sets silently fell back to commit kind
   and were rejected by the sfSidecarType check.

2. Wrap export sig visitLeaves callback in try/catch (matching the
   RNG path) and enforce sfSidecarType == sidecarExportSig before
   processing — closes the shape-only acceptance gap.
2026-04-10 10:22:58 +07:00
Nicholas Dudfield
03a436d918 refactor: convert sidecar SHAMap entries from STTx to STObject
Replace STTx-based sidecar entries with plain STObject(sfGeneric)
using sfSidecarType (UINT8) discriminator. Eliminates unnecessary
transaction envelope overhead (sfSequence, sfFee, sfFlags) and
content-sniffing heuristics from the parse path.

Build: STObject with sidecarRngCommit/sidecarRngReveal/sidecarExportSig
Parse: sfSidecarType dispatch + typed field accessors
2026-04-10 10:14:06 +07:00
Nicholas Dudfield
7474048295 refactor: typed sidecar dispatch — eliminate content-sniffing heuristic
Replace the content-sniffing heuristic in onAcquiredSidecarSet with
typed dispatch based on SidecarKind.

The type is already known at fetch time:
- commitSetHash → SidecarKind::commit
- entropySetHash → SidecarKind::reveal
- exportSigSetHash → SidecarKind::exportSig

pendingRngFetches_ changes from hash_set<uint256> to
hash_map<uint256, SidecarKind>.  When the set arrives,
look up the kind by hash and dispatch — no leaf inspection.

This is the set-classification fix (Option E from the design doc):
no new SField, no STTx changes, no protocol additions, no RNG
proof-chain churn.
2026-04-10 09:18:43 +07:00
Nicholas Dudfield
1ee660529e fix: RPC handler sync, unused local, idiomatic Buffer comparison
- Add rng_poll_ms, no_export_sig, bootstrap_fast_start to the
  runtime_config RPC handler (SET and GET paths) so all ConfigVals
  fields are configurable live via admin RPC.
- Remove unused `added` counter in CSF fetchRngSetIfNeeded (was
  causing compiler warnings after debug logging removal).
- Use Buffer::operator== instead of std::memcmp in upgradeSignature,
  drop <cstring> include.
2026-04-10 08:56:16 +07:00
Nicholas Dudfield
311dfa1c23 chore: add TODO for RuntimeConfig activation gating
Both runtime_config and disconnect RPC handlers are already
Role::ADMIN.  Add a TODO to consider gating the entire
RuntimeConfig system on a config flag or compile-time define
for production nodes.
2026-04-10 08:31:54 +07:00
Nicholas Dudfield
f27cd2c567 refactor: consolidate env vars into RuntimeConfig
Move XAHAU_RNG_POLL_MS and XAHAUD_NO_EXPORT_SIG into RuntimeConfig
as rngPollMs and noExportSig fields.  Both are now configurable via
the XAHAU_RUNTIME_CONFIG JSON blob or individual env vars, and
controllable at runtime via the runtime_config RPC.

rngPollMs is clamped to minimum 50ms (prevents tight-loop polling).
Default remains 250ms.

This removes the last loose std::getenv calls from production code
outside of RuntimeConfig.  All env-var-based configuration now flows
through a single system.
2026-04-10 08:24:20 +07:00
Nicholas Dudfield
f34fdc297c fix(export): close upgradeSignature TOCTOU with buffer comparison
upgradeSignature now takes the verified buffer and compares it against
the currently stored buffer before promoting to verified.  This guards
against concurrent overlay threads overwriting the buffer between the
caller's unverifiedSignatures() snapshot and the upgrade call.

If the stored buffer was overwritten (different size or content), the
upgrade is silently skipped — the new buffer will be verified on its
next encounter.
2026-04-10 08:19:45 +07:00
Nicholas Dudfield
65fa63883d chore: remove CSF debug logging that floods CI output
Strip JLOG(j_.debug()) calls from buildEntropySet, fetchRngSetIfNeeded,
and finalizeRoundEntropy in CSF Peer.h.  These were added for local
debugging and caused CI failures due to output size limits.
2026-04-09 20:21:37 +07:00
Nicholas Dudfield
d8c683fb4c test(rng): fix AlignmentRequired test to run 1 round not 3
Running 3 rounds caused peer 0 to desync on round 2, dropping
prevProposers for the majority on round 3, triggering bootstrap
skip → zero entropy on the last round.  The gate works correctly
(logs show aligned=3, peersSeen=3) but the test was checking the
LAST round's entropy, not the round where the gate was exercised.

Run 1 round after warmup — sufficient to exercise the gate.
2026-04-09 18:09:17 +07:00
Nicholas Dudfield
fd53af304b fix(rng): measure entropy deadline from publish time, not reveal start
The entropy convergence deadline was measured from revealPhaseStart_,
which is set when entering ConvergingReveal.  By the time the entropy
set is published (after reveal timeout + observation tick), most of
the deadline budget was already spent — leaving insufficient time
for peer alignment.

Add entropyPublishStart_ timestamp set when the entropy set is first
published.  All convergence gate deadlines now measure from this
point, giving the full 2x rngREVEAL_TIMEOUT window for peer
proposals to propagate and alignment to be observed.
2026-04-09 18:06:18 +07:00
Nicholas Dudfield
2a3f0ec923 fix(rng): bounded wait for alignment instead of immediate fallback
When peers have published entropySetHash but none match ours yet
(e.g. a subset peer is the only one seen so far), wait for the
bounded deadline instead of immediately falling back to zero.
Other aligned peers may not have published yet — give them time.

Only fall back to zero if no alignment is observed within the
deadline (2x rngREVEAL_TIMEOUT).
2026-04-09 17:58:41 +07:00
Nicholas Dudfield
00f1f7ba30 fix(rng): subset-aware conflict detection in entropy convergence gate
After fetch/merge, if our entropy set hash didn't change, the
conflicting peer had a subset of our data — not a real threat.
Clear the conflict flag so we don't fall back to zero when a peer
simply has fewer reveals than us.

If the hash DID change (merge added data), re-count alignment
with the updated hash before treating it as a real conflict.

This prevents the majority from falling back to zero just because
one peer (e.g. isolated) has a smaller reveal set.
2026-04-09 17:53:58 +07:00
Nicholas Dudfield
49f05e4e47 fix(rng): require positive peer alignment for non-zero entropy
The observation tick alone was insufficient — a node could pass the
gate without any peer confirming its entropySetHash.  Now the gate
requires at least one tx-converged peer with a matching hash before
accepting non-zero entropy.

Three cases after the observation tick:
1. aligned > 0: peers confirm our hash → proceed with entropy
2. conflict: fetch/merge/rebuild → bounded wait → zero fallback
3. aligned=0, peersSeen=0: no peers published yet → bounded wait →
   zero fallback if still no peers at deadline
4. aligned=0, peersSeen>0: peers published but none match → zero

Also:
- CSF finalizeRoundEntropy now uses shouldZeroEntropy() (quorum check)
- Two new TDD tests:
  - testRngNoEntropyWithoutPeerAlignment: healthy network must agree
  - testRngAlignmentRequiredForNonZeroEntropy: isolated peer must not
    produce non-zero entropy that differs from majority
2026-04-09 17:51:51 +07:00
Nicholas Dudfield
1f51b9c594 fix(csf): quorum threshold in shouldZeroEntropy + test adjustments
CSF shouldZeroEntropy() now checks reveals < quorumThreshold (80% of
UNL), matching production.  MajorRevealLoss test adjusted to verify
majority group agreement rather than requiring full synchronization
(peer 0 may desync when it misses most reveals).

All 15 ConsensusRng tests now pass.
2026-04-09 17:40:05 +07:00
Nicholas Dudfield
88a548a8ef fix(rng): observation tick + CSF quorum threshold in shouldZeroEntropy
Two fixes addressing the asymmetric-view problem:

1. Convergence gate now forces one observation tick after first
   publishing the entropySet before accepting.  Previously a node
   could publish + accept in the same tick, never seeing a peer's
   different hash.  The entropySetPublished_ flag ensures at least
   one round-trip for proposal propagation.

2. CSF shouldZeroEntropy() now checks quorum threshold (80% of UNL),
   matching production behavior.  Previously it only checked empty().

Result: PartialReveals test now passes — all 6 peers converge on
the same entropy (count=6) via union merge after the observation tick.
14/15 ConsensusRng tests pass.
2026-04-09 17:31:36 +07:00
Nicholas Dudfield
db302a0f78 fix(rng): add selfSeedReveal to fix CSF reveal counting
The CSF never self-seeded its own reveal into pendingReveals_ because
harvestRngData only processes peer proposals, not self.  The real code
handles this in decorateMessage, but the CSF has no equivalent.

Add selfSeedReveal() called from the tick at reveal transition.
Both the real ConsensusExtensions and the CSF Extensions implement it.
The real code now has belt-and-suspenders: tick + decorateMessage.

This fixes CSF peers having N-1 reveals instead of N, which caused
every peer to compute entropy from a different subset.
2026-04-09 17:23:53 +07:00
Nicholas Dudfield
383d9ec2e7 feat(csf): add SidecarStore for sidecar set fetch/merge simulation
Add a content-addressed SidecarStore to the CSF, simulating the
InboundTransactions SHAMap fetch pipeline.  Tagged entries (commit
or reveal) are published by hash during buildCommitSet/buildEntropySet
and fetched by hash during fetchRngSetIfNeeded, with type-aware
union merge into the correct local pending set.

Also adds debug logging to CSF Extensions for entropy pipeline
troubleshooting.
2026-04-09 17:17:53 +07:00
Nicholas Dudfield
52671bfc99 test(rng): add XAHAU_RNG_TEST env var filter for focused test runs
Set XAHAU_RNG_TEST=<substring> to run only matching test methods.
e.g. XAHAU_RNG_TEST=SingleByzantine runs only that test.
2026-04-09 16:51:26 +07:00
Nicholas Dudfield
8307fca3b9 fix(rng): add entropySetHash convergence gate before accept
Add a bounded pre-accept convergence check for entropySetHash,
closing the gap where two honest validators could accept with
different reveal subsets and compute different entropy (ledger fork).

After publishing the entropy set, the gate:
1. Inspects tx-converged peer positions for conflicting entropySetHash
2. Fetches differing sets via fetchRngSetIfNeeded (union merge)
3. Rebuilds and re-publishes the local entropy set after merge
4. Waits within a bounded window (2x rngREVEAL_TIMEOUT)
5. Falls back to zero entropy if conflict persists past deadline

This follows the same pattern as the existing commitSetHash conflict
handling and exportSigSetHash convergence gate.  Union merge ensures
monotonic set growth — honest timing skew resolves quickly, and
hostile hash spam hits the hard deadline and falls back safely.

The "one bad actor shouldn't deny entropy" optimization (supermajority
vote) is deferred to a follow-up patch per codex recommendation.
2026-04-09 16:30:02 +07:00
Nicholas Dudfield
6526621c16 test(rng): add TDD tests for entropySetHash convergence gate
Three new CSF tests that document expected behavior for the
entropySetHash convergence gate (not yet implemented):

1. testRngEntropyConvergesWithPartialReveals: two groups each drop
   one peer's reveal, creating different quorate subsets.  Must not
   fork — either converge via SHAMap merge or both fall back to zero.

2. testRngEntropyFallbackOnMajorRevealLoss: one peer drops most
   reveals (below quorum locally).  Network must still agree.

3. testRngSingleByzantineCannotDenyEntropy: one Byzantine peer
   (future: forced garbage entropySetHash) should not prevent the
   other 80% from producing valid entropy.

Also adds dropRevealFrom_ test knob to CSF Peer::Extensions for
simulating asymmetric reveal delivery.
2026-04-09 16:26:30 +07:00
Nicholas Dudfield
2a9b1c9c22 fix(export): guard against empty verified sigs + add invariant asserts
- Skip addVerifiedSignature in decorateMessage when sigBuf is empty
  (sign() threw — don't mark a failed sign as "verified")
- Add XRPL_ASSERT in addVerifiedSignature and addUnverifiedSignature
  requiring non-empty signature buffers
- Add XRPL_ASSERT in checkQuorumAndSnapshot verifying that every
  entry in the verified set exists in the signatures map with a
  non-empty buffer
2026-04-09 16:02:35 +07:00
Nicholas Dudfield
54ca21b604 fix(export): verified-only quorum, SHAMap, and transactor upgrade pass
Enforce the contract: source chain finalizes an export only when it
has a quorum of cryptographically verified multisignatures.

ExportSigCollector changes:
- signatureCount() now counts verified entries only
- checkQuorumAndSnapshot() returns verified-only snapshot
- snapshot() and snapshotWithSigs() return verified-only data
- buildExportSigSet (via snapshot) publishes verified-only entries
- unverifiedSignatures() returns sigs needing verification
- upgradeSignature() promotes unverified to verified
- addStandaloneSignature() marks as verified (no consensus to check)
- All add methods now set firstSeenSeq (fixes stale cleanup bug)

Export::doApply changes:
- Upgrade pass before quorum check: deserializes the inner tx (which
  is always available as ctx_.tx), verifies any unverified sigs via
  buildMultiSigningData + verify(), upgrades them in the collector
- Then checks quorum on verified-only count
- Assembles blob from verified-only snapshot

This means:
- Unverified sigs (relay ordering) are local cache only
- They don't count toward quorum until upgraded
- SHAMap convergence operates on verified sigs only
- Destination chain verification remains defense-in-depth
2026-04-09 15:54:41 +07:00
Nicholas Dudfield
462db6004c fix(rng): replace nonexistent leafCount() with std::distance
SHAMap has no leafCount() method — it was a local variable in
SHAMap.cpp, not a public API.  Use std::distance(begin(), end())
on the SHAMap's ForwardRange iterators instead.  Cost is O(n) but
the set is bounded by UNL size (~20-35 entries).
2026-04-09 15:42:04 +07:00
Nicholas Dudfield
cfca708aae fix(rng): remove pendingReveals fallback from entropy output path
shouldZeroEntropy() and sfEntropyCount no longer fall back to
pendingReveals_.  If entropySetMap_ is null, entropy failed — the
pipeline didn't complete, and the map is the only canonical source.

pendingReveals_ is now strictly an internal staging area for the
commit/reveal pipeline.  All final entropy decisions flow through
entropySetMap_, which is the consensus-agreed set.
2026-04-09 15:40:22 +07:00
Nicholas Dudfield
5f70e5259c fix(rng): use entropySetMap for shouldZeroEntropy and sfEntropyCount
The H2 entropy fix switched the digest computation to entropySetMap_
but shouldZeroEntropy() and sfEntropyCount still used pendingReveals_.
Since pendingReveals_ can diverge from the published entropySetMap_
(late reveals mutate it after the map hash is published), two nodes
agreeing on the same entropySetHash could still build different
ttCONSENSUS_ENTROPY pseudo-transactions.

Now shouldZeroEntropy() checks entropySetMap_ leaf count when the map
is available, and sfEntropyCount uses the map's leaf count.  Both
fall back to pendingReveals_ only during pipeline stages before the
map is built.
2026-04-09 15:35:00 +07:00
Nicholas Dudfield
8697c5d821 refactor(export): explicit verified/unverified sig API in collector
Replace the ambiguous addSignature/hasSignature API with clearly
named methods that make verification state explicit:

  addVerifiedSignature()   — sig passed buildMultiSigningData + verify()
  addUnverifiedSignature() — trusted source but no multisign check yet
  addStandaloneSignature() — pubkey-only for standalone/test mode
  hasVerifiedSignature()   — only returns true for verified sigs

Unverified sigs (relay ordering fallback) are no longer treated as
verified by the cache.  When the same sig is encountered again via a
path that CAN verify (e.g. SHAMap merge after the tx arrives), the
verification runs and upgrades it to verified.

addUnverifiedSignature() won't overwrite a verified sig, preventing
downgrade.  SigEntry tracks verified validators in a separate set.
2026-04-09 15:34:13 +07:00
Nicholas Dudfield
9436e5868e fix(export): soften hard reject to best-effort verify for relay ordering
Revert the hard reject when ttEXPORT is not in the open ledger.
Under relay ordering, a node can receive a proposal with export sigs
before the ttEXPORT tx itself arrives.  Dropping these sigs loses a
valid validator contribution for the entire round with no recovery
path until terRETRY_EXPORT on the next round.

Post C1+C2, the proposal-level authentication is sufficient trust:
checkSign() verified the sender holds the private key, and sender
binding verified the embedded pubkey matches.  Store the sig and
let the multisign content be verified on the destination chain.
The collector's stale cleanup (256 ledgers) bounds retention.

When the tx IS in the open ledger (common case), the multisign sig
is still fully verified via buildMultiSigningData + verify().
2026-04-09 15:22:20 +07:00
Nicholas Dudfield
c6fa973cf6 fix(rng): compute entropy from entropySetMap instead of pendingReveals
H2: Compute final entropy from the agreed-upon entropySetMap_ SHAMap
rather than from the local pendingReveals_ in-memory map.

Previously, two nodes with different reveal subsets at timeout would
compute different entropy from their local pendingReveals_ maps,
despite both passing haveConsensus() (which only checks txSetHash).
This could cause a ledger fork.

Now the entropy computation reads directly from the entropySetMap_
whose hash was published in proposals and converged via SHAMap
fetch/merge.  Nodes that agree on entropySetHash deterministically
produce the same entropy regardless of local pendingReveals_ state.

If entropySetMap_ is null (bootstrap skip, pipeline failure), the
existing shouldZeroEntropy() fallback handles it.
2026-04-09 15:18:45 +07:00
Nicholas Dudfield
939e03714c fix(export): cap exportSignatures count per proposal
Reject proposals with more than ExportLimits::maxPendingExports (8)
export sig entries.  Honest validators attach at most one sig per
pending export, bounded by the same limit.  Prevents DoS via
proposals with millions of entries triggering lock contention on
the validator list and collector mutexes.
2026-04-09 15:13:15 +07:00
Nicholas Dudfield
969f98f57e perf(export): skip redundant sig verification via collector lookup
Add hasSignature() to ExportSigCollector — checks if a verified sig
already exists for a given (txHash, validator) pair.  Both the
proposal ingestion path and the SHAMap merge path now check this
before calling verify(), avoiding redundant ed25519 verification
when the same sig arrives via multiple paths.

No external sig cache exists in rippled, so the collector itself
serves as the verification cache: once a sig is stored (always
post-verify), subsequent encounters skip the crypto work.
2026-04-09 15:03:57 +07:00
Nicholas Dudfield
435deb0e78 fix(export): close remaining sig verification gaps
Three fixes from codex review:

1. Remove unsafe fallback in proposal ingestion path: reject export
   sigs when the ttEXPORT tx is not in the open ledger instead of
   storing them unverified.  The tx must be in the open ledger for
   validators to have signed it, so this is not a legitimate case.

2. Add full sig verification to the SHAMap merge path
   (onAcquiredSidecarSet): verify each export sig entry against
   buildMultiSigningData + verify() before storing in the collector.
   Previously this path only checked trusted() on the pubkey,
   allowing a malicious UNL validator to publish a sidecar set with
   forged sigs for other validators.

3. Close cluster mode bypass: always call checkSign() and gate export
   sig harvesting on sigValid, even when cluster() is true.  Cluster
   trust is for relay/resource charging, not for accepting on-chain
   cryptographic artifacts.
2026-04-09 14:59:20 +07:00
Nicholas Dudfield
b80352e512 fix(export): verify multisign signatures at ingestion time
C3: Cryptographically verify each export signature blob against the
inner transaction's signing data before storing in the collector.
Looks up the ttEXPORT tx from the open ledger, reconstructs the
signing data via buildMultiSigningData, and calls verify().

If the tx isn't in our open ledger yet (timing/relay), the sig is
stored unverified as a fallback — it can be verified later at the
SHAMap merge path or will be rejected at Export::doApply if invalid.

This runs on the jtPROPOSAL_t job queue thread (not the IO strand
or transactor), so the verify() cost has no impact on consensus
critical path performance.
2026-04-09 14:43:30 +07:00
Nicholas Dudfield
57c46c61fc fix(export): two-pass sender validation and atomic quorum+snapshot
C2 hardening: validate all export sig blobs before committing any,
preventing partial state if a later blob fails the sender binding
check. Also moves the trusted() check before the loop since senderPK
is constant.

H1: Add checkQuorumAndSnapshot() to ExportSigCollector that performs
the quorum threshold check and signature snapshot under a single lock
acquisition. Export::doApply now uses this instead of separate
signatureCount() + snapshotWithSigs() calls, eliminating the TOCTOU
window where concurrent overlay threads could mutate the collector
between the two operations.
2026-04-09 14:39:35 +07:00
Nicholas Dudfield
37ff13df50 fix(export): move sig harvesting after checkSign and bind pubkey to sender
C1: Move onTrustedPeerMessage() from the synchronous onMessage(TMProposeSet)
handler into checkPropose(), after checkSign() verifies the proposal's
cryptographic signature. Previously, export sigs were ingested before
signature verification, allowing any peer to inject forged sigs by
spoofing nodepubkey to a trusted validator's key.

C2: Add sender binding in onTrustedPeerMessage() — each export sig
blob's embedded validator pubkey must match the proposal sender's
nodepubkey. Reject the entire proposal's export sigs on any mismatch,
preventing a compromised validator from impersonating other validators
to single-handedly forge quorum.
2026-04-09 14:32:38 +07:00
Nicholas Dudfield
1b363b7eac fix: correct stale function name in ConsensusExtensionsTick comment 2026-04-09 14:03:07 +07:00
Nicholas Dudfield
9562b457cf chore: remove stale Peer-level RNG forwarders
All callsites now go through ce() consistently.
2026-03-23 09:57:53 +07:00
Nicholas Dudfield
724633ceb5 refactor(consensus): decouple CSF tests from xrpld.app via PeerTick.h
Move ConsensusExtensionsTick.h from xrpld/app/consensus/ to
xrpld/consensus/ — it's a pure template with no app-layer deps.
Extract Peer::Extensions::onTick() definition into test/csf/PeerTick.h
so Peer.h no longer includes from xrpld/app/.

Eliminates the test.csf > xrpld.app levelization edge.

Add --explain flag to levelization.py for tracing dependency edges.
2026-03-23 09:36:59 +07:00
Nicholas Dudfield
152d82e798 refactor(consensus): extract RNG/Export into ConsensusExtensions
Extract all Xahau consensus extension logic (RNG commit-reveal entropy
and Export validator multisign collection) from Consensus.h and
RCLCxAdaptor into a dedicated ConsensusExtensions class owned by
Application.

Implements all 10 lifecycle hooks from the design doc:
  onRoundStart, onTrustedPeerMessage, onTrustedPeerProposal,
  decoratePosition, decorateMessage, onTick, onPreBuild,
  onAcceptComplete, isSidecarSet, onAcquiredSidecarSet

Key design decisions:
- ConsensusTick<> template in ConsensusTypes.h keeps dependency
  direction clean (generic consensus defines contract, extensions
  implement it)
- extensionsTick<> shared template in ConsensusExtensionsTick.h
  ensures CSF test framework runs the same state machine as production
- ExportSigCollector ownership moved from global singleton to CE
- Sidecar acquisition routed through RCLConsensus mutex for thread
  safety (isExtensionSet + gotExtensionSet)
- RCLCxAdaptor reduced to thin ce() accessor + generic consensus
  interface methods

Files:
  new: ConsensusExtensions.h/.cpp, ConsensusExtensionsTick.h
  reduced: Consensus.h (-1060 lines), RCLConsensus.cpp (-1400 lines)
  updated: ConsensusTypes.h, Application.h/.cpp, NetworkOPs.cpp,
           PeerImp.cpp, Export.cpp, ExportSigCollector.h, Peer.h

7/7 testnet scenarios + 1463 consensus + 260 Export unit tests pass.
2026-03-19 20:23:19 +07:00
Nicholas Dudfield
0bb31ce7ce chore: add projected-source markers for consensus extension docs
Non-functional comment markers (//@@start, //@@end) for projected-source
documentation extraction.
2026-03-19 12:14:11 +07:00
Nicholas Dudfield
4cb3de0497 refactor(export): build xport wrapper via STObject then serialise to STTx
Replace the duplicated throwaway-STTx + real-STTx pattern with a
single STObject: set all fields including fee=0, serialise to compute
the fee, patch the fee, then serialise once into the final STTx.

20 lines shorter, no duplication.
2026-03-18 17:36:30 +07:00
Nicholas Dudfield
c6b315412d test(export): harden scenario tests with proper assertions
Replace warnings and logs with hard assertions across all export
scenario tests:

- export_helpers: add assert_hook_accepted(), assert_export_result(),
  assert_shadow_ticket() shared assertion helpers
- steady_state_export: assert hook ACCEPT + emitCount, ExportResult
  contents (signers, inner tx fields), shadow ticket exists
- retriable_export: assert ExportResult well-formed, shadow ticket
  created, payment not blocked
- export_degradation: assert export FAILS (not just log), no shadow
  ticket, payment still works
- export_unanimity: assert ExportResult + shadow ticket on success,
  absence on failure
2026-03-18 17:18:12 +07:00
Nicholas Dudfield
72395bec75 chore: clang-format 2026-03-18 17:12:41 +07:00
Nicholas Dudfield
8ed4d86f0f test(export): verify emitted ttEXPORT lifecycle end-to-end
testXportPayment now asserts the full emitted tx lifecycle:
- hook fires with ACCEPT, emitCount=1, returnCode=0
- sfHookEmissions present with 1 entry
- ltEMITTED_TXN in AffectedNodes
- emitted dir is not empty
- after close, emitted ttEXPORT appears in closed ledger

Also add FOCUSED_TEST env var gate for fast iteration during
development (set FOCUSED_TEST=1 to run only focused_test()).
2026-03-18 17:11:19 +07:00
Nicholas Dudfield
419fd16b9a chore: move export scenarios to export-suite.yml, add SuiteLogsWithOverrides
Move export scenario tests from suite.yml into their own
export-suite.yml file. The defaults already set CE+Export features
so individual test entries no longer need to repeat them.

Add SuiteLogsWithOverrides test utility: a Logs subclass that routes
specified journal partitions to stderr (always visible) while keeping
others on suite_.log (only on failure). Useful for debugging specific
subsystems during test development.
2026-03-18 17:09:47 +07:00
Nicholas Dudfield
a8097cd9a6 fix(export): compute emit fee before STTx construction
Mutating the fee via const_cast after STTx construction left a stale
cached getTransactionID(). When the emitted ttEXPORT was serialised
into the emitted directory and later deserialised, the round-tripped
txid differed from the original, causing tefNONDIR_EMIT in
Transactor::preclaim (the emitted dir entry was keyed with the stale
hash).

Build a throwaway STTx with fee=0 to calculate the fee size, then
construct the real STTx with the correct fee from the start.
2026-03-18 17:04:46 +07:00
Nicholas Dudfield
02a0552325 docs(export): clarify LLS semantics for retriable exports
Add comment explaining the three-state outcome table for exports
relative to LastLedgerSequence:
  ledger < LLS:  tesSUCCESS or terRETRY_EXPORT
  ledger == LLS: tesSUCCESS or tecEXPORT_EXPIRED
  ledger > LLS:  tefMAX_LEDGER (never reaches doApply)
2026-03-18 14:43:50 +07:00
Nicholas Dudfield
3698193b0a chore: clang-format 2026-03-18 14:21:00 +07:00
Nicholas Dudfield
de43ca2385 refactor(export): store multisigned tx as sfExportedTxn object in metadata
Use sfExportedTxn (OBJECT) instead of sfBlob (VL) for the multisigned
transaction in sfExportResult metadata. This renders as readable JSON
with all fields visible (Account, Signers, etc.) instead of an opaque
hex blob.

Also compute the tx hash directly via getHash(HashPrefix::transactionID)
on the STObject instead of serializing/deserializing through STTx.
2026-03-18 14:16:25 +07:00
Nicholas Dudfield
8c747a1916 feat(export): produce multisigned blob in export metadata
Export::doApply now builds the fully multisigned inner tx and stores
it as sfBlob in sfExportResult metadata. In standalone mode, the node
signs directly with its own validator keys (no consensus needed).

Key changes:
- ExportSigCollector stores actual multisign signatures, not just pubkeys
- RCLConsensus proposal attachment computes real multisign sigs over inner tx
- PeerImp harvests variable-length sig entries from proposals
- Export::doApply assembles Signers array, builds multisigned blob first,
  then uses its hash for the shadow ticket (getTransactionID includes all
  fields including Signers)
- Import skips OperationLimit and signing key checks for export callback
  path (sfTicketSequence present) — shadow ticket proves the relationship
- Full Export→XRPL→Import round-trip test: export on Xahau, submit
  multisigned blob to XRPL (with matching SignerList), build XPOP,
  import back, verify shadow ticket consumed
2026-03-18 13:54:29 +07:00
Nicholas Dudfield
cea110f29a feat: add XPOP test helper and XPOP_test suite
- src/test/jtx/xpop.h: test utilities for building XPOPs from Env ledgers
  (TestValidator, TestVLPublisher, TestXPOPContext, buildTestXPOP)
- src/test/app/XPOP_test.cpp: 4 tests (173 assertions)
  - LedgerProof construction from payment tx
  - XPOP v1 JSON structure verification
  - Merkle proof verification for multi-tx ledgers
  - Full Import round-trip: source Env payment → XPOP → dest Env Import → tesSUCCESS
2026-03-18 11:59:34 +07:00
Nicholas Dudfield
3ca056a94b feat: add XPOP test helper and XPOP_test suite
- src/test/jtx/xpop.h: test utilities for building XPOPs from Env ledgers
  (TestValidator, TestVLPublisher, buildTestXPOP)
- src/test/app/XPOP_test.cpp: 3 tests (133 assertions)
  - LedgerProof construction from payment tx
  - XPOP v1 JSON structure verification
  - Merkle proof verification for multi-tx ledgers
2026-03-18 11:41:16 +07:00
Nicholas Dudfield
705d8400db feat: add proof module for XPOP construction
New module at src/xrpld/app/proof/ with layered design:

- ProofBuilder: SHAMap merkle proof extraction (extractProofV1)
  Binary trie proof with 16-way branching, root hash verification.
- LedgerProof: proof-of-ledger (header fields + tx blob/meta + merkle proof)
  buildLedgerProof() extracts everything from a closed Ledger.
- XPOPv1: JSON format builder matching Import.cpp expectations
  buildXPOPv1() creates complete XPOP with validation signatures.

Designed for versioning: v1 JSON (current Import compat), future v2
binary proofs and account state proofs layer on the same core.
2026-03-18 11:29:29 +07:00
Nicholas Dudfield
655b751698 chore: regenerate hook/sfcodes.h for new sfields
Adds sfCancelTicketSequence (UINT32 101) and sfExportResult (OBJECT 98).
2026-03-18 11:06:04 +07:00
Nicholas Dudfield
f324081277 fix(import): verify XPOP tx hash matches shadow ticket
Shadow tickets store the exported transaction hash. Import now verifies
the XPOP's inner tx hash matches, preventing use of a different XPOP
with the same TicketSequence.
2026-03-18 10:52:19 +07:00
Nicholas Dudfield
24a284180a fix(export): address review findings from code audit
- Validate incoming export sig pubkeys against trusted UNL (PeerImp)
- Fix handleAcquiredRngSet misclassifying export sets when local map is null
- Add stale-entry cleanup (cleanupStale with 256-ledger timeout)
- Gate sig attachment on featureExport amendment (RCLConsensus)
- Fail with tefINTERNAL if ExportResult metadata can't be written
- Make export and cancel mutually exclusive (reject both in preflight)
- Remove dead ExportLimits.h include
2026-03-18 10:30:41 +07:00
Nicholas Dudfield
6f003cc983 feat(export): add SHAMap-based export sig convergence
Add deterministic export sig set convergence using CE infrastructure:
- exportSigSetHash in ExtendedPosition (flag 0x10)
- buildExportSigSet() builds SHAMap from collected sigs
- hasPendingExportSigs() gates convergence in phaseEstablish
- Parallel convergence gate alongside RNG sub-states
- handleAcquiredRngSet extended to merge export sig sets
- Tiered quorum: 80% with CE, 100% unanimity without CE

Scenario tests for all 3 feature combos:
- CE+Export, 1 node down: 4/5=80% → tesSUCCESS
- Export only, all up: 5/5=100% → tesSUCCESS
- Export only, 1 node down: 4/5≠100% → tecEXPORT_EXPIRED
2026-03-18 10:17:28 +07:00
Nicholas Dudfield
3a58020388 fix(export): tiered quorum threshold based on CE availability
Without CE: require unanimity (100% UNL) to avoid non-deterministic
quorum disagreement. With CE: use standard 80% quorum via
calculateQuorumThreshold (SHAMap convergence will ensure agreement).
Standalone/unit tests: require 1 sig.
2026-03-18 09:46:36 +07:00
Nicholas Dudfield
829441b52e fix(export): deduplicate export sigs across proposals within a round 2026-03-18 09:32:27 +07:00
Nicholas Dudfield
3a055663cc chore: add export-sig-attachment marker for projected-source 2026-03-18 09:26:33 +07:00
Nicholas Dudfield
985a194bdc feat(export): migrate to retriable ttEXPORT with proposal-based sigs
Replace the old ltEXPORTED_TXN + ttEXPORT_FINALIZE (validation-based
sigs, TxQ injection) approach with a retriable ttEXPORT that collects
validator signatures via TMProposeSet during consensus.

Added:
- terRETRY_EXPORT: keeps tx in retry set across ledger boundaries
- tecEXPORT_EXPIRED (200): LLS expiry frees sequence cleanly
- sfExportResult (OBJECT 98): signed export result in tx metadata
- ExportSigCollector: minimal thread-safe sig tracker
- Proposal sig attachment (RCLConsensus) + harvesting (PeerImp)
- exportSignatures field in TMProposeSet (ripple.proto)
- Metadata plumbing (TxMeta, ApplyViewImpl, ApplyStateTable)
- Hook xport() now emits ttEXPORT via normal emitted txn path

Removed:
- ttEXPORT_FINALIZE (type 90) pseudo-tx and Change::applyExportFinalize
- ltEXPORTED_TXN ledger entry and exportedDir/exportedTxn keylets
- ExportSignatureCollector (replaced by ExportSigCollector)
- TxQ export injection (quorum check + rawTxInsert)
- Validation-based export signing in RCLConsensus
- Application::getExportSignatureCollector

Verified on 5-node testnet: golden path (same-ledger finalization with
ExportResult in metadata), degraded path (tecEXPORT_EXPIRED on sub-quorum),
and hook xport() path (emitted ttEXPORT with shadow ticket creation).
2026-03-18 09:23:52 +07:00
Nicholas Dudfield
869f366d8a feat(export): add sfCancelTicketSequence for shadow ticket cancellation
Add sfCancelTicketSequence (UINT32 field 101) to ttEXPORT, allowing
users to cancel shadow tickets via a transaction. Both sfExportedTxn
and sfCancelTicketSequence are optional — at least one must be present.
This allows export, cancel, or both in a single transaction.

Test: create shadow ticket via export, cancel via sfCancelTicketSequence,
verify ticket is gone and owner reserve is freed.
2026-03-17 14:13:57 +07:00
Nicholas Dudfield
03936aa928 fix(export): require TicketSequence on exported transactions
Exported transactions must use TicketSequence (with Sequence=0)
because a bounced tx on the destination chain would jam sequential
sequence numbers. This is enforced in both the hook xport() API
and the Export transactor via ExportLedgerOps::validateTicketSequence().

Adds test: ttEXPORT rejects export without TicketSequence.
Updates existing test hooks to include TicketSequence in exported txns.
2026-03-17 12:30:55 +07:00
Nicholas Dudfield
6d180307ad feat(export): split Import into B2M and export callback paths
When the inner XPOP transaction has sfTicketSequence, Import now
takes the export callback path: consume the shadow ticket via
ExportLedgerOps::cancelShadowTicket() and return. No B2M balance
crediting, no account creation. Hooks fire normally and can inspect
the result via xpop_slot().

The B2M path is unchanged for non-ticket imports.

Also migrates the shadow ticket check in preclaim from the old
hookState namespace approach to keylet::shadowTicket().

Removes the unused shadowTicketNamespace constant.
2026-03-17 12:23:27 +07:00
Nicholas Dudfield
f2ca499c97 feat(export): add ltSHADOW_TICKET and xport_cancel hook API
Introduce shadow tickets for export replay protection:

- ltSHADOW_TICKET ledger entry: account-owned, keyed by
  account + ticket sequence. Fields: sfAccount, sfTicketSequence,
  sfTransactionHash, sfLedgerSequence, sfOwnerNode.

- ExportLedgerOps::createShadowTicket(): creates shadow ticket
  when exported tx has sfTicketSequence. Charges owner reserve.
  Called from both hook xport() path and Export transactor.

- ExportLedgerOps::cancelShadowTicket(): deletes shadow ticket,
  frees reserve. Used by xport_cancel hook API.

- xport_cancel(ticket_seq) hook API: allows hooks to cancel
  shadow tickets for exports that will never get a callback.

- InvariantCheck: add ltSHADOW_TICKET to valid entry types.

- Test: verify shadow ticket creation with correct fields and
  owner count bump via ttEXPORT with TicketSequence.
2026-03-17 12:13:41 +07:00
Nicholas Dudfield
bd68364f25 feat(export): add ttEXPORT user transaction and extract ExportLedgerOps
Rename the existing ttEXPORT pseudo-tx to ttEXPORT_FINALIZE (type 90)
to make room for a user-submittable ttEXPORT (type 91).

ttEXPORT allows non-hook users to submit export transactions directly,
creating the same ltEXPORTED_TXN entries that the hook xport() API
creates inline.

Extract shared logic into ExportLedgerOps.h:
- createExportedTxn(): creates ltEXPORTED_TXN, enforces directory cap
- validateNetworkID(): self-target and unconfigured guards
- validateExportAccount(): account ownership check

Both the hook API (HookAPI.cpp) and the Export transactor now call
into ExportLedgerOps, eliminating duplicated validation and ledger
mutation code.
2026-03-17 11:43:45 +07:00
Nicholas Dudfield
42a6407815 fix(export): reject exports when NETWORK_ID is unconfigured
If the node's NETWORK_ID is 0 (default/unconfigured) and the exported
transaction has no sfNetworkID field, we can't distinguish self-targeting
from legitimate cross-chain export. Reject to be safe.

Also adds exportTestConfig() helper and test for the unconfigured case.
2026-03-17 07:41:36 +07:00
Nicholas Dudfield
a387c853ab test(export): add NetworkID self-target guard test
Verify that xport() rejects exported transactions whose sfNetworkID
matches the local network. The hook builds a Payment with
NetworkID=21337 (matching the test env), and the guard correctly
returns EXPORT_FAILURE causing tecHOOK_REJECTED.

Also fix log level for the guard rejection to warn (not trace).
2026-03-17 07:31:35 +07:00
Nicholas Dudfield
9311e567d3 fix(export): reject exports targeting the local network
Explicitly forbid exported transactions whose sfNetworkID matches the
local network's ID. An exported txn re-executing on its origin chain
could cause exploits or logic issues.

The check is intentionally non-mandatory: XRPL mainnet (the primary
export target) doesn't use NetworkID, so absent = allowed.
2026-03-16 17:30:14 +07:00
Nicholas Dudfield
c26582bdf9 fix(export): move ExportLimits.h to xrpl/protocol
Both xrpld.overlay and xrpl.hook depend on xrpl.protocol, so placing
the header there avoids introducing a new xrpld.overlay > xrpl.hook
levelization dependency.
2026-03-16 15:58:58 +07:00
Nicholas Dudfield
417b999c7f chore(levelization): add xrpld.overlay > xrpl.hook dependency
New include of ExportLimits.h in PeerImp.cpp introduces this
module dependency (from feat(export) commit 89274b538).
2026-03-16 15:47:45 +07:00
Nicholas Dudfield
0205be4500 chore: add testnet scenario scripts
Entropy and export scenario scripts for local testnet validation.
2026-03-16 15:17:32 +07:00
Nicholas Dudfield
89274b5387 feat(export): wip export system limits
- max_export per hook: 4 → 2
- maxPendingExports: cap exported directory at 8 entries (tecDIR_FULL)
- clamp inbound signature processing in PeerImp to directory cap

The directory cap is the root DoS constraint: each pending export
requires every validator to sign and broadcast every round. Inbound
processing and signing throughput are transitively bounded by it.
2026-03-16 13:59:06 +07:00
Nicholas Dudfield
b65d9faf12 docs(consensus): add MERGE NOTE comments for upstream 86ef16dbeb resolution
Extends merge guidance to cover the empty-disputes bugfix (not yet in
sync-2.5.0): !disputes.empty() guard, stalled() j/clog params,
"should be rare" doc wording, debug→warn promotion, and auto-merged
testDisputes duplicate warning.
2026-03-11 10:45:04 +07:00
Nicholas Dudfield
aa1a7e5320 docs(consensus): add MERGE NOTE comments for sync-2.5.0 resolution
Inline comments at all 6 conflict points guiding the maintainer
through the expected merge conflicts when sync-2.5.0 lands:
ledgerMAX_CONSENSUS const, bootstrap params, calculateQuorumThreshold,
effectiveParms+stalled in haveConsensus, DisputedTx::stalled() j/clog
params, and testDisputes placement.
2026-03-11 10:09:02 +07:00
Nicholas Dudfield
6f0f17aad9 fix(consensus): cherry-pick upstream 86ef16dbeb empty-disputes stall fix
Cherry-pick of ripple/rippled@86ef16dbeb ("Fix: Don't flag consensus
as stalled prematurely (#5627)"). Not yet in any xahau sync branch.

Fixes false stall detection when there are no disputed transactions:
std::ranges::all_of on an empty set is vacuously true, so consensus
was incorrectly flagged as stalled. Adds !result_->disputes.empty()
guard.

Also adds diagnostic logging to DisputedTx::stalled() and the
stall detection path in haveConsensus(), and promotes the
"Need validated ledger" log from debug to warn.
2026-03-11 09:47:11 +07:00
Nicholas Dudfield
407bfa1467 feat(consensus): cherry-pick dd085e5d8 (upstream d22a5057b9) anti-stall mechanisms
Cherry-pick of ripple/rippled@d22a5057b9 / xahau dd085e5d8 ("Prevent
consensus from getting stuck in the establish phase (#5277)"), resolved
against our RNG pipeline and bootstrap fast-start changes.

Upstream adds three layered anti-stall mechanisms:
- Stateful per-dispute avalanche state machine (init→mid→late→stuck)
- Stall detection: declares consensus when all disputes individually settled
- Hard expiration: clamp(10× prev round, 15s, 120s) wall-clock safety net

Conflict resolution:
- ConsensusParms.h: kept both avalanche state machine (const members,
  avMIN_ROUNDS, avSTALLED_ROUNDS, getNeededWeight) and our bootstrap
  params (bootstrapRoundTimeSeed, bootstrapStableRoundsRequired).
  ledgerMAX_CONSENSUS left non-const for bootstrap override.
- Consensus.h: pass both stalled flag and effectiveParms to checkConsensus.
  Stall check uses original parms, bootstrap override only affects max
  consensus timeout.
- Consensus_test.cpp: kept all 12 RNG tests and new testDisputes test.
2026-03-11 09:36:38 +07:00
Nicholas Dudfield
f0dfcf6b81 fix(consensus): cap bootstrap ledgerMAX_CONSENSUS at 5s
Use an explicit 5s cap instead of dividing the default 15s.
5s is the sweet spot: long enough for peers to exchange proposals
and converge naturally, short enough to avoid wasted time.
Shorter values (e.g. 3.75s) cause nodes to hit reachedMax before
peers converge, cascading into slower subsequent rounds.
2026-03-10 14:30:20 +07:00
Nicholas Dudfield
503d2ebf98 feat(consensus): add XAHAUD_BOOTSTRAP_FAST_START for faster cold-start
Seed prevRoundTime_ to 3s instead of 15s on first round, override
idle interval to bypass closeTimeResolution (10-30s on early ledgers),
and halve ledgerMAX_CONSENSUS during bootstrap. Auto-disables after 3
consecutive rounds with UNL quorum participation.

Cuts 5-node testnet cold-start from ~28s to ~13s.

Also adds projected-source markers to TxQ, NetworkOPs, and Submit for
the transaction-submission documentation template.
2026-03-10 12:52:56 +07:00
Nicholas Dudfield
e52bc51384 refactor(consensus): extract shouldZeroEntropy() for quorum-gated entropy
Consolidate the repeated entropy fallback condition
(entropyFailed || no reveals || sub-quorum reveals) into a single
method. Fixes EntropyCount field reporting non-zero when the digest
was correctly zeroed due to sub-quorum reveals.
2026-03-10 08:42:10 +07:00
Nicholas Dudfield
91860db578 fix(consensus): require quorum-many reveals for non-zero entropy
Sub-quorum reveals (e.g. 3/4 threshold) were producing real entropy,
allowing a minority of validators to disproportionately influence the
output. Both injectEntropyPseudoTx and buildExplicitFinalProposalTxSet
now fall back to zero entropy when reveals < quorumThreshold().
2026-03-09 17:13:02 +07:00
Nicholas Dudfield
0b317a8e7a fix(consensus): skip rng pipeline during bootstrap convergence
When prevProposers < quorumThreshold, the network is still converging
and RNG can only produce zero entropy. Skip the commit/reveal pipeline
to avoid PIPELINE_TIMEOUT and conflict-wait delays that compound across
staggered startup rounds.
2026-03-09 16:27:36 +07:00
Nicholas Dudfield
dbd230b695 feat(rpc): add rng state to consensus_info response 2026-03-09 16:05:42 +07:00
Nicholas Dudfield
30cefcba85 chore: clang-format alignment fixes 2026-03-06 18:39:37 +07:00
Nicholas Dudfield
94edb5759d fix(export): gate pre-quorum on verified signature count
hasQuorum() and getExportsWithQuorum() were using raw signerMap.size()
which includes unverified signatures. TxQ could inject a ttEXPORT
pseudo-tx that then fails the stricter verified-signature check in
Change::applyExport(). Use verifiedSignatureCount() instead so TxQ
only injects when cryptographically verified quorum is actually met.

Also add cmake plumbing for enhanced logging: link date::date-tz when
available and enable BEAST_ENHANCED_LOGGING for Debug builds.
2026-03-06 18:38:54 +07:00
Nicholas Dudfield
ce57b6a3a0 fix(consensus): fix rng quorum to active UNL and demote rng log noise
Quorum fix:
- Rename expectedProposers_ → likelyParticipants_ to clarify role
- Fix commit quorum to 80% of active UNL snapshot (not shrinkable by
  recent proposer count, which was allowing 2/3 to pass as quorum)
- hasQuorumOfCommits() now uses simple threshold check only
- Add CSF test: persistent loss does not shrink quorum

Log level cleanup:
- Demote ~30 RNG/STALLDIAG per-peer/per-tick lines from info/debug to
  debug/trace across Consensus.h and RCLConsensus.cpp
- Principle: per-peer/per-tick → trace; state transitions → debug;
  milestones → info
- Reduces testnet log volume by ~93%
2026-03-06 18:36:43 +07:00
Nicholas Dudfield
fca5cad470 fix(log): catch tzdb exception in local-time formatter
date::current_zone() can throw if the timezone database is unavailable
or misconfigured (e.g. minimal container images). Fall back to UTC
formatting so enhanced logging does not make startup fatal.
2026-03-06 18:36:22 +07:00
Nicholas Dudfield
bb77c2090b consensus: gate RNG substates by amendment state 2026-03-06 14:09:06 +07:00
Nicholas Dudfield
90a94294e4 protocol: split export and consensus entropy amendments 2026-03-06 14:08:15 +07:00
Nicholas Dudfield
c2209b4472 docs(consensus): explain why seq=3 may mirror seq=2
Clarify inline that seq=3 publish can carry unchanged txSetHash while still providing extra entropySetHash delivery/fetch opportunity under packet loss or reordering.
2026-03-03 17:41:55 +07:00
Nicholas Dudfield
8fcb2ed336 docs(consensus): annotate implicit entropy injection rationale
Document why synthetic entropy pseudo-tx is canonically injected at onAccept/buildLCL and why explicit-final proposal remains experimental/default-off.
2026-03-03 17:31:04 +07:00
Nicholas Dudfield
fd1567d1ba consensus: document explicit-final tradeoffs and tighten rng diagnostics
Keep explicit final proposal as an opt-in experimental path with implicit mode as default.

Add inline rationale/TBD notes, extend stall diagnostics, and cover runtime-config + CSF txn-path behavior with tests.
2026-03-03 17:08:38 +07:00
Nicholas Dudfield
d32f34d3bf build(levelization): add fast python generator with CI parity check
Add Builds/levelization/levelization.py for fast local iteration and semantic comparison against canonical shell output via --compare-to.

Keep Builds/levelization/levelization.sh as canonical path, and update levelization workflow to fail if python output diverges from shell-generated results.

Also harden interactive-shell detection in levelization.sh for portability and document local usage in README.
2026-03-03 10:17:46 +07:00
Nicholas Dudfield
c491c5c82f refactor(consensus): reduce header fanout for faster iteration
Decouple RCLConsensus.h from Consensus.h by forward-declaring Consensus and storing Consensus<Adaptor> behind std::unique_ptr, moving thin wrappers out-of-line into RCLConsensus.cpp.

Also remove direct RCLConsensus.h include from NetworkOPs.h (forward declare), and add explicit includes in DatagramMonitor.h and ServerDefinitions.cpp to replace transitive dependencies.

Keep RNG fast-path behavior unchanged in Consensus.h; build and ripple.consensus.Consensus remain green.
2026-03-03 09:49:59 +07:00
Nicholas Dudfield
74817765ae consensus: restore full entropySet broadcast and document fanout tradeoffs 2026-03-03 08:32:09 +07:00
Nicholas Dudfield
fc23fa8535 consensus: reduce entropy-set proposal fanout
Keep entropy-set recovery path but elect a deterministic single broadcaster (lowest NodeID among tx-converged participants) instead of every proposer broadcasting entropySetHash.

This lowers steady-state proposal chatter while preserving liveness for lagging peers that need entropy-set fetch/merge.
2026-03-03 07:42:27 +07:00
Nicholas Dudfield
34c0f17b6b runtimeconfig: add rng_claim_drop_pct testing control
Expose rng_claim_drop_pct in runtime config (RPC + env) as a clamped 0-100 percentage used by RNG claim-drop testing.

Include RuntimeConfig RPC tests for round-trip and clamping behavior.
2026-03-03 07:20:32 +07:00
Nicholas Dudfield
765ad6a278 consensus: harden RNG set convergence under dropped claims
Track active RNG round sequence for fetched set validation so lagging observers can merge current-round commit sets instead of rejecting them as closed+1 out-of-round.

Refresh/re-publish commitSetHash after fetch-merge conflicts and publish entropySetHash in ConvergingReveal so peers can recover reveal sets.

Add inline tradeoff notes: extra proposal traffic is accepted to preserve consensus safety/liveness under packet loss or drop injection.
2026-03-03 07:14:46 +07:00
Nicholas Dudfield
f623ca89b9 chore(levelization): update loops result after format/merge 2026-03-02 17:01:47 +07:00
Nicholas Dudfield
e4865f09f9 Merge remote-tracking branch 'origin/dev' into feature-export-rng 2026-03-02 16:59:57 +07:00
Nicholas Dudfield
4c182e4738 consensus: guard commit-set conflicts and extend RNG CSF coverage 2026-03-02 16:59:41 +07:00
Nicholas Dudfield
d0c869c8a6 fix(consensus): tighten RNG acquired-set validation and observer quorum
Harden acquired RNG merge paths with strict entry typing, trusted key/node binding, round-sequence gating, reveal-to-commit linkage checks, and stale reveal/proof invalidation on commitment changes.

Adjust proposer expectation logic so non-proposing observers are not counted as expected committers, and add a CSF regression test covering observer self-commit exclusion.
2026-03-02 16:36:03 +07:00
Nicholas Dudfield
cac5efcd3c fix(consensus): harden acquired RNG set ingestion
Reject mixed commit/reveal maps, enforce per-entry type checks, bind node identity to trusted validator keys, and gate acquired entries to the active round.

Also verify acquired reveals against stored commitments and clear stale reveal/proof state when commitments change.
2026-03-02 16:18:55 +07:00
Nicholas Dudfield
514e60b71c fix(export): age and validate stashed tx data for signature checks 2026-03-02 15:54:53 +07:00
Nicholas Dudfield
2a34e32e05 fix(export): harden addSignature validation and verification 2026-03-02 15:46:07 +07:00
Nicholas Dudfield
b969024a25 fix(export): update duplicates and prevent phantom pending entries 2026-03-02 15:39:43 +07:00
Nicholas Dudfield
f30b9a4c3a fix(export): avoid stale-age poisoning from rejected signatures 2026-03-02 15:35:36 +07:00
Nicholas Dudfield
0e019fec4e fix(export): prune invalid early signatures when stashing tx data 2026-03-02 15:29:42 +07:00
Nicholas Dudfield
7e0c72fd22 fix(export): run stale signature cleanup during TxQ processing 2026-03-02 15:27:30 +07:00
Nicholas Dudfield
07d741cdd7 fix(export): harden collector duplicate and identity handling 2026-03-02 15:25:19 +07:00
Nicholas Dudfield
b99c38c09d test(consensus): add asymmetric delay reveal-timeout scenario 2026-03-02 15:11:01 +07:00
Nicholas Dudfield
64e50209ff fix(consensus): invalidate stale reveals when commitment changes
Add RNG regression tests for non-UNL data, reveal-without-commit, invalid reveal, and commitment-change stale-reveal handling in CSF consensus tests.
2026-03-02 15:04:35 +07:00
Nicholas Dudfield
b1ce2103ad test(csf): add RNG consensus hooks and edge-case tests 2026-03-02 14:28:34 +07:00
Nicholas Dudfield
50c4cf1df3 refactor: move xport_reserve and xport logic into HookAPI class
Move core xport_reserve and xport implementations from applyHook.cpp
DEFINE_HOOK_FUNCTION wrappers into the decoupled HookAPI class, following
the same pattern used for etxn_reserve and emit.
2026-03-02 14:10:03 +07:00
Nicholas Dudfield
6fc14f398d feat(rpc): add disconnect by ip:port [TESTNET] 2026-03-02 12:06:00 +07:00
Nicholas Dudfield
592a8600c7 fix: add missing <mutex> include for GCC compatibility 2026-02-27 16:42:10 +07:00
Nicholas Dudfield
e71768700a chore: update levelization after RuntimeConfig overlay dependency 2026-02-27 16:40:00 +07:00
Nicholas Dudfield
e598e405bd fix: harden RuntimeConfig validation and add startup diagnostics
- Error on unknown message_types instead of silently widening scope
- Make messageCategories optional so per-peer can override global filter
  to "all categories" (nullopt=inherit, empty set=explicitly all)
- Clamp send_drop_pct to 0-100% range
- Add STARTDIAG: logging for consensus startup diagnostics
- Add 3 test cases (11 total, 58 assertions)
2026-02-27 13:38:26 +07:00
Nicholas Dudfield
8af3ce2f5b fix: allow extended proposals in PeerImp and add message type filtering
- Fix convergence regression caused by 2.4.0 merge: replace
  stringIsUint256Sized(currenttxhash) with size() < uint256::size()
  to accept extended proposals (>32 bytes) containing RNG fields
- Add message_types filter to RuntimeConfig for targeting specific
  protocol message categories (proposal, validation, transaction, etc.)
- Add appliesTo() method and messageCategories set to ConfigVals
- Add category name mapping helpers in RPC handler
- Add 2 test cases for message type filtering (8 total)
2026-02-27 13:10:49 +07:00
Nicholas Dudfield
b67cb78b97 feat: add RuntimeConfig service with overlay artificial delays
Add a generic RuntimeConfig service for runtime-configurable parameters,
initially supporting artificial send delays and packet drops for testing
consensus behavior on local testnets.

- RuntimeConfig class with atomic fast-path gate (zero cost when inactive)
- Per-peer targeting via "*" (global) and "ip:port" keys with inheritance
- Pre-merged caching at write time for single-lookup read path
- Admin RPC handler `runtime_config` (set/clear/clear_all/get)
- Env var support: XAHAU_RUNTIME_CONFIG (JSON) or XAHAU_SEND_* vars
- PeerImp::send() integration with delay timer and probabilistic drops
- RPC handler test covering all operations and merge behavior
2026-02-27 09:46:19 +07:00
tequ
8cfee6c8a3 Merge fixAMMClawbackRounding amendment into featureAMMClawback amendment 2026-02-25 19:07:45 +10:00
yinyiqian1
8673599d2b fixAMMClawbackRounding: adjust last holder's LPToken balance (#5513)
Due to rounding, the LPTokenBalance of the last LP might not match the LP's trustline balance. This was fixed for `AMMWithdraw` in `fixAMMv1_1` by adjusting the LPTokenBalance to be the same as the trustline balance. Since `AMMClawback` is also performing a withdrawal, we need to adjust LPTokenBalance as well in `AMMClawback.`

This change includes:
1. Refactored `verifyAndAdjustLPTokenBalance` function in `AMMUtils`, which both`AMMWithdraw` and `AMMClawback` call to adjust LPTokenBalance.
2. Added the unit test `testLastHolderLPTokenBalance` to test the scenario.
3. Modify the existing unit tests for `fixAMMClawbackRounding`.
2026-02-25 19:07:45 +10:00
Nicholas Dudfield
0b1b82282e fix: reject single-signed exports and fix test hook SigningPubKey
Add single-sign rejection check in Change::applyExport() matching
rippled's multi-sign validation: SigningPubKey must be present but
empty, TxnSignature must not be present.

Fix Export_test.cpp hook to encode an empty VL blob for SigningPubKey
instead of 33 zero bytes (AI slop from export-uvtxn branch).
2026-02-25 14:55:55 +07:00
Nicholas Dudfield
d4c5a7e8ab fix: update copyright headers to 2026 XRPL Labs for new files 2026-02-25 14:38:40 +07:00
Nicholas Dudfield
82837864fa fix: extract calculateQuorumThreshold() and revert Import.cpp quorum change
Extract duplicated (n * 80 + 99) / 100 ceiling quorum formula into shared
calculateQuorumThreshold() in ConsensusParms.h, matching the standard
ValidatorList::calculateQuorum(). Used by ExportSignatureCollector,
Change.cpp, and RCLConsensus.cpp.

Revert Import.cpp quorum from ceiling back to original truncating formula
(totalValidatorCount * 0.8) since Import handles XPOP imports, not the
new Export feature. Added TODO for future upgrade.
2026-02-25 14:22:43 +07:00
Nicholas Dudfield
e1caee6459 fix: regenerate hook/sfcodes.h after sfHookExportCount field code change 2026-02-25 13:40:25 +07:00
Nicholas Dudfield
3206b4a4e1 fix: address @tequdev review comments (cbak, render, Change.cpp, markers)
- Remove unnecessary cbak() stubs from ConsensusEntropy test hooks and
  recompile WASM (cbak is optional per Guard.h validator)
- Restore RCLCxPeerPos::render() lost during merge (delegates to
  ConsensusProposal::render())
- Fix Change.cpp applyAmendment() fixInnerObjTemplate2 reversion:
  use STObject::makeInnerObject() and bracket assignment (fbcff932)
- Restore txq-export-quorum-check documentation marker in TxQ.cpp
2026-02-25 13:25:41 +07:00
tequ
ec65e622aa Merge fixAMMv1_3 amendment into featureAMM amendment 2026-02-25 16:20:43 +10:00
Gregory Tsipenyuk
65837f49e1 fix: Add AMMv1_3 amendment (#5203)
* Add AMM bid/create/deposit/swap/withdraw/vote invariants:
  - Deposit, Withdrawal invariants: `sqrt(asset1Balance * asset2Balance) >= LPTokens`.
  - Bid: `sqrt(asset1Balance * asset2Balance) > LPTokens` and the pool balances don't change.
  - Create: `sqrt(asset1Balance * assetBalance2) == LPTokens`.
  - Swap: `asset1BalanceAfter * asset2BalanceAfter >= asset1BalanceBefore * asset2BalanceBefore`
     and `LPTokens` don't change.
  - Vote: `LPTokens` and pool balances don't change.
  - All AMM and swap transactions: amounts and tokens are greater than zero, except on withdrawal if all tokens
    are withdrawn.
* Add AMM deposit and withdraw rounding to ensure AMM invariant:
  - On deposit, tokens out are rounded downward and deposit amount is rounded upward.
  - On withdrawal, tokens in are rounded upward and withdrawal amount is rounded downward.
* Add Order Book Offer invariant to verify consumed amounts. Consumed amounts are less than the offer.
* Fix Bid validation. `AuthAccount` can't have duplicate accounts or the submitter account.
2026-02-25 16:20:43 +10:00
Nicholas Dudfield
0c2e09050e fix: move sfHookExportCount to Xahau-reserved field code range
sfHookExportCount was at field code 23, colliding with the mainline
rippled UINT16 range. Move to 98 in the Xahau-reserved range.

Also reorder sfExportedTxn (90) before sfAmountEntry (91) for
consistency.
2026-02-25 12:05:28 +07:00
Nicholas Dudfield
83922d5c20 fix: restore XRPL_ASSERT and UNREACHABLE macros reverted during merge
The merge with origin/dev accidentally reverted 19 XRPL_ASSERT() calls
back to plain assert() and 1 UNREACHABLE() back to assert(0). These
macros provide descriptive diagnostic messages on failure and are the
project convention since the rippled 2.4.0 migration.

Files fixed:
- Consensus.h: 9 XRPL_ASSERT reversions
- RCLConsensus.cpp: 5 XRPL_ASSERT reversions
- BuildLedger.cpp: 3 XRPL_ASSERT reversions
- Change.cpp: 1 UNREACHABLE + 1 XRPL_ASSERT reversion
2026-02-25 11:55:07 +07:00
Nicholas Dudfield
6bae42ff01 fix: restore CLOG consensus logging removed during merge
The merge with origin/dev accidentally stripped all CLOG diagnostic
statements from the consensus code path. This restores the clog
parameter to internal Consensus.h functions (checkLedger, phaseOpen,
closeLedger, updateOurPositions, handleWrongLedger, leaveConsensus,
createDisputes) and re-adds all 46 CLOG statements that provide
per-round diagnostic detail for phase transitions, convergence
progress, dispute tracking, and pause decisions.

Also restores the origin/dev structure of Consensus.cpp by removing
the anonymous-namespace wrapper and forwarding overloads that were
merge artifacts.
2026-02-25 11:53:27 +07:00
Nicholas Dudfield
35e86d926e fix(consensus-entropy): align pseudo tx/sle formats and hook handling
Add missing ttEXPORT/ttCONSENSUS_ENTROPY pseudo transaction fields required by runtime logic and ensure corresponding ledger entries carry threading/sequence fields.

Handle ttEXPORT and ttCONSENSUS_ENTROPY in hook stakeholder routing to avoid Unknown transaction type assertion during ledger close.
2026-02-24 18:44:00 +07:00
Nicholas Dudfield
9c4ee9315d chore: update levelization results after merge 2026-02-24 15:56:36 +07:00
Nicholas Dudfield
0f17cf02aa chore: clang-format 2026-02-24 15:51:55 +07:00
Nicholas Dudfield
7753dc3cbe fix(invariants): exempt export and entropy pseudo-ledger entries
Handle ltEXPORTED_TXN and ltCONSENSUS_ENTROPY in LedgerEntryTypesMatch so creating/destroying these pseudo-ledger entries does not trigger XRP balance invariant violations.
2026-02-24 15:48:32 +07:00
Nicholas Dudfield
cc7f3c59ae merge: port export-rng onto post-2.4.0 tree restructure
Resolve the origin/dev post-2.4.0 sync conflicts across the xrpld path migration and macro-based protocol registration changes.

Re-apply export/RNG integration on top of the new structure, including consensus/build plumbing, tx/apply paths, peer ingest, and tests.

Regenerate hook headers and restore a green build via x-run-tests (Export_test build path).
2026-02-24 15:32:45 +07:00
RichardAH
e5b21f026e Merge pull request #692 from Xahau/sync-2.4.0-rebased
Sync: Ripple(d) 2.4.0
2026-02-24 16:07:09 +10:00
Nicholas Dudfield
e8c1b25ab4 fix: harden export signature trust model and quorum verification
- unify validator trust checks into isExportValidatorTrusted() preferring
  UNLReport with local trust fallback
- add last-line-of-defense sig verification in Change::applyExport()
  requiring 80% (ceil) verified trusted UNL signatures
- filter untrusted export signatures at ingestion in PeerImp
- fix Import quorum from floor(n*0.8) to ceil(n*80%) matching export side
2026-02-24 12:52:23 +07:00
Nicholas Dudfield
b9dd854595 refactor: unify featureExport + featureConsensusEntropy into featureExportRNG
Single amendment flag for both features. numFeatures 94 → 93.
Exclude featureExportRNG from default test set to prevent
ConsensusEntropy pseudo-tx injection from breaking existing tests.
2026-02-21 17:46:46 +07:00
Nicholas Dudfield
3bead8dcb6 merge: integrate origin/export-uvtxn into consensus-phase-entropy
Resolve 14 conflicts keeping both sides. Renumber TOO_LITTLE_ENTROPY
from -46 to -48 to avoid collision with export error codes.
Fix sfHookExportCount to soeOPTIONAL in InnerObjectFormats (only set
when featureExportRNG is enabled).
2026-02-21 17:41:37 +07:00
Nicholas Dudfield
908a78a1d9 fix: regenerate hook/extern.h to match hook_api.macro ordering 2026-02-20 10:11:37 +07:00
Nicholas Dudfield
a9e3dc41d4 fix: add featureExport stub for standalone guard_checker build 2026-02-20 10:05:58 +07:00
tequ
8d08d8f1db fix workflow 2026-02-20 09:11:47 +09:00
Nicholas Dudfield
fff46e3dd0 chore: clang-format 2026-02-20 08:22:49 +09:00
Nicholas Dudfield
52369f3ebb fix: resolve merge issues from dev sync
- Add missing getHookOn declaration and implementation (build blocker)
- Add else branches for HookOnOutgoing/HookOnIncoming in hsoUPDATE path
- Guard unprotected optional dereferences in hsoINSTALL path
- Reorder features.macro per review (HookOnV2/HooksUpdate2 before fixHookAPI20251128)
2026-02-20 08:22:15 +09:00
tequ
57c4e3c9cc Support new STIs for sto_* HookAPI (#657) 2026-02-20 08:18:45 +09:00
tequ
8e1ae86ce2 Add new keylets to util_keylet (#533) 2026-02-20 08:18:45 +09:00
tequ
bed5473540 Update CMake version to 3.25.3 in macOS workflow 2026-02-20 08:15:17 +09:00
tequ
de1f57819f Fix differences such as LedgerHash that occurred due to NetworkID in ltFeeSettings 2026-02-20 08:15:16 +09:00
tequ
6dd6393bb8 Add ltORACLE for Remarks target (#562) 2026-02-20 08:15:16 +09:00
tequ
73861a4bfe Conan Release Builder (2.4.0 sync) (#528) 2026-02-20 08:15:15 +09:00
tequ
5a2bf0e0b1 Add Remit test to AMM Account 2026-02-20 08:15:14 +09:00
tequ
5e9d961c5d Optimize AccountDelete and Creadentials tests, Update tests priority 2026-02-20 08:15:14 +09:00
tequ
73f14374b4 VoteBehavior::DefaultYes for new fix Amendments
- NFToken related fix Amendments remains as `DefaultNo`.
2026-02-20 08:15:13 +09:00
tequ
3380b7e2fd Add TSH processing for AMM, AMMClawback, Clawback, Oracle (#532)
* Add TSH processing for `AMM`, `AMMClawback`, `Oracle`, `Clawback`

* Add empty TSH processing for other transaction types

* Add AMMTsh tests
2026-02-20 08:14:39 +09:00
tequ
da6bd8cac1 Support Hook execution in simulate RPC (#531) 2026-02-20 08:10:52 +09:00
tequ
05f0186572 Supported::No for featurePermissionedDomains 2026-02-20 08:10:52 +09:00
tequ
d4db9276c6 Supported::No for featureDynamicNFT 2026-02-20 08:10:51 +09:00
tequ
a6824770fc Supported::No for featureCredentials 2026-02-20 08:10:50 +09:00
tequ
5678b8fbb1 Supported::No for featureMPTokensV1 2026-02-20 08:10:50 +09:00
tequ
bfeef7fd91 Supported::No for featureNFTokenMintOffer 2026-02-20 08:10:49 +09:00
tequ
b78c4abece Supported::No for featureDID 2026-02-20 08:10:49 +09:00
tequ
a9d1f2aa69 Supported::No for featureXChainBridge 2026-02-20 08:10:48 +09:00
tequ
3540148941 Combine AMM Amendments (#521)
* fixAMMv1_2
* fixAMMv1_1
* fixAMMOverflowOffer
* fixLPTokenTransfer
* suppress AMM test logs
* exclude `ltAMM` from `fixPreviousTxnID` Amendment
    - make `sfPreviousTxnID` and `sfPreviousTxnLgrSeq` required for ltAMM
2026-02-20 08:10:43 +09:00
tequ
8ec0e9bb22 Combine XChainBridge Amendments (#523) 2026-02-20 08:10:05 +09:00
tequ
7bfa595c6c Combine fixInnerObjTemplate Amendments (#524) 2026-02-20 08:10:05 +09:00
tequ
a57e819ac5 Disable instrumentation-build workflow (#530) 2026-02-20 08:10:01 +09:00
Denis Angell
825e923081 fix cmake & xrpl_core 2026-02-20 08:10:00 +09:00
tequ
30f8f7de00 fix release-builder, workflow building 2026-02-20 08:10:00 +09:00
tequ
0c236573fb remove checkpatterns workflow 2026-02-20 08:09:59 +09:00
tequ
293266519d Combine DID Amendments (#522)
fixEmptyDID -> featureDID
2026-02-20 08:09:58 +09:00
tequ
96e3851c51 Additional support for HookDefinition, HookState, ImportVLSequence at fixPreviousTxnID Amendment 2026-02-20 08:09:58 +09:00
Mark Travis
c9e886e5e3 Log detailed correlated consensus data together (#5302)
Combine multiple related debug log data points into a single
message. Allows quick correlation of events that
previously were either not logged or, if logged, strewn
across multiple lines, making correlation difficult.
The Heartbeat Timer and consensus ledger accept processing
each have this capability.

Also guarantees that log entries will be written if the
node is a validator, regardless of log severity level.
Otherwise, the level of these messages is at INFO severity.
2026-02-20 08:09:57 +09:00
Mark Travis
7eb3c96e8b fix: Acquire previously failed transaction set from network as new proposal arrives (#5318)
Reset the failure variable.
2026-02-20 08:09:57 +09:00
Bronek Kozicki
14edb034bb Fix Replace assert with XRPL_ASSERT (#5312) 2026-02-20 08:09:56 +09:00
Bronek Kozicki
8b45539f9c fix: Remove 'new parent hash' assert (#5313)
This assert is known to occasionally trigger, without causing errors
downstream. It is replaced with a log message.
2026-02-20 08:09:55 +09:00
Ed Hennis
a95832668c Add logging and improve counting of amendment votes from UNL (#5173)
* Add logging for amendment voting decision process
* When counting "received validations" to determine quorum, count the number of validators actually voting, not the total number of possible votes.
2026-02-20 08:09:55 +09:00
Olek
a9b36e1a40 fix: Switch Permissioned Domain to Supported::yes (#5287)
Switch Permissioned Domain feature's supported flag from Supported::no to Supported::yes for it to be votable.
2026-02-20 08:09:54 +09:00
Bart
dc5931fc36 docs: Clarifies default port of hosts (#5290)
The current comment in the example cfg file incorrectly mentions both "may" and "must". This change fixes this comment to clarify that the default port of hosts is 2459 and that specifying it is therefore optional. It further sets the default port to 2459 instead of the legacy 51235.
2026-02-20 08:09:54 +09:00
Mark Travis
59f91ab7ff Log proposals and validations (#5291)
Adds detailed log messages for each validation and proposal received from the network.
2026-02-20 08:09:53 +09:00
Bart
e58f1e9333 Support canonical ledger entry names (#5271)
This change enhances the filtering in the ledger, ledger_data, and account_objects methods by also supporting filtering by the canonical name of the LedgerEntryType using case-insensitive matching.
2026-02-20 08:09:52 +09:00
Ed Hennis
7e5b799eff refactor: Change recursive_mutex to mutex in DatabaseRotatingImp (#5276)
Rewrites the code so that the lock is not held during the callback. Instead it locks twice, once before, and once after. This is safe due to the structure of the code, but is checked after the second lock. This allows mutex_ to be changed back to a regular mutex.
2026-02-20 08:09:52 +09:00
Bart
eda5fe9b00 fix: Replace charge() by fee_.update() in OnMessage functions (#5269)
In PeerImpl.cpp, if the function is a message handler (onMessage) or called directly from a message handler, then it should use fee_, since when the handler returns (OnMessageEnd) then the charge function is called. If the function is not a message handler, such as a job queue item, it should remain charge.
2026-02-20 08:09:51 +09:00
Elliot Lee
d2f4539b38 docs: ensure build_type and CMAKE_BUILD_TYPE match (#5274) 2026-02-20 08:09:50 +09:00
code0xff
c2f5c9b07a chore: Fix small typos in protocol files (#5279) 2026-02-20 08:09:49 +09:00
Ed Hennis
c028804db6 docs: Add a summary of the git commit message rules (#5283) 2026-02-20 08:09:49 +09:00
Olek
e4330e6d14 fix: Amendment to add transaction flag checking functionality for Credentials (#5250)
CredentialCreate / CredentialAccept / CredentialDelete transactions will check sfFlags field in preflight() when the amendment is enabled.
2026-02-20 08:09:48 +09:00
Donovan Hide
76139db44e fix: Omit superfluous setCurrentThreadName call in GRPCServer.cpp (#5280) 2026-02-20 08:09:48 +09:00
Bronek Kozicki
eda065c41c fix: Do not allow creating Permissioned Domains if credentials are not enabled (#5275)
If the permissioned domains amendment XLS-80 is enabled before credentials XLS-70, then the permissioned domain users will not be able to match any credentials. The changes here prevent the creation of any permissioned domain objects if credentials are not enabled.
2026-02-20 08:09:47 +09:00
Mayukha Vadari
2b5932e420 fix: issues in simulate RPC (#5265)
Make `simulate` RPC easier to use:
* Prevent the use of `seed`, `secret`, `seed_hex`, and `passphrase` fields (to avoid confusing with the signing methods).
* Add autofilling of the `NetworkID` field.
2026-02-20 08:09:46 +09:00
Bart
baaa746d4f Updates Conan dependencies (#5256)
This PR updates several Conan dependencies:
* boost
* date
* libarchive
* libmysqlclient
* libpq
* lz4
* onetbb
* openssl
* sqlite3
* zlib
* zstd
2026-02-20 08:09:46 +09:00
Shawn Xie
923b91c883 Amendment fixFrozenLPTokenTransfer (#5227)
Prohibits LPToken holders from sending LPToken to others if they have been frozen by one of the assets in AMM pool.
2026-02-20 08:09:45 +09:00
Ed Hennis
3de1a4134b Improve git commit hash lookup (#5225)
- Also get the branch name.
- Use rev-parse instead of describe to get a clean hash.
- Return the git hash and branch name in server_info for admin
  connections.
- Include git hash and branch name on separate lines in --version.
2026-02-20 08:09:45 +09:00
Mayukha Vadari
19ccbaf7b2 Add RPC "simulate" to execute a dry run of a transaction (#5069)
- Spec: https://github.com/XRPLF/XRPL-Standards/tree/master/XLS-0069d-simulate
- Also update signing methods to autofill fees better and properly handle transactions that require a non-standard fee.
2026-02-20 08:09:44 +09:00
Olek
5d7f70f9df Fix CI unit tests (#5196)
- Add retries for rpc client
- Add dynamic port allocation for rpc servers
2026-02-20 08:09:43 +09:00
Michael Legleux
3eb092a914 Update secp256k1 library to 0.6.0 (#5254) 2026-02-20 08:09:43 +09:00
Bronek Kozicki
86d7d61a3f Add [validator_list_threshold] to validators.txt to improve UNL security (#5112) 2026-02-20 08:09:42 +09:00
Bronek Kozicki
923466e023 Switch from assert to XRPL_ASSERT (#5245) 2026-02-20 08:09:42 +09:00
tequ
0bd5bcc692 Add missing space character to a log message (#5251) 2026-02-20 08:09:41 +09:00
Bronek Kozicki
979fb40073 Cleanup API-CHANGELOG.md (#5207) 2026-02-20 08:09:40 +09:00
Ed Hennis
b9e2341b69 test: Unit tests to recreate invalid index logic error (#5242)
* One hits the global cache, one does not.
* Also some extra checking.

Co-authored-by: Bronek Kozicki <brok@incorrekt.com>
2026-02-20 08:09:40 +09:00
Sergey Kuznetsov
e4d4d5a5a6 fix: Error consistency in LedgerEntry::parsePermissionedDomains() (#5252)
Update errors for parsing permissioned domains in the LedgerEntry handler to make them consistent with other parsers.
2026-02-20 08:09:39 +09:00
Ed Hennis
64b6bd2905 fix: Use consistent CMake settings for all modules (#5228)
* Resolves an issue introduced in #5111, which inadvertently removed the
  -Wno-maybe-uninitialized compiler option from some xrpl.libxrpl
  modules. This resulted in new "may be used uninitialized" build
  warnings, first noticed in the "protocol" module. When compiling with
  derr=TRUE, those warnings became errors, which made the build fail.
* Github CI actions will build with the assert and werr options turned
  on. This will cause CI jobs to fail if a developer introduces a new
  compiler warning, or causes an assert to fail in release builds.
* Includes the OS and compiler version in the linux dependencies jobs in
  the "check environment" step.
* Translates the `unity` build option into `CMAKE_UNITY_BUILD` setting.
2026-02-20 08:09:39 +09:00
Valentin Balaschenko
ad2f0eaafc Fix levelization script to ignore commented includes (#5194)
Check to ignore single-line comments during dependency analysis.
2026-02-20 08:09:38 +09:00
tequ
08db1de928 Fix the flag processing of NFTokenModify (#5246)
Adds checks for invalid flags.
2026-02-20 08:09:38 +09:00
Mayukha Vadari
1cdcfca337 Fix failing assert in connect RPC (#5235) 2026-02-20 08:09:37 +09:00
Olek
959f7cd21c Permissioned Domains (XLS-80d) (#5161) 2026-02-20 08:09:34 +09:00
tequ
9c17813764 XLS-46: DynamicNFT (#5048)
This Amendment adds functionality to update the URI of NFToken objects as described in the XLS-46d: Dynamic Non Fungible Tokens (dNFTs) spec.
2026-02-20 08:05:50 +09:00
Shawn Xie
eb9ec41b78 prefix Uint384 and Uint512 with Hash in server_definitions (#5231) 2026-02-20 08:05:50 +09:00
Mayukha Vadari
17a484ae4a refactor: add rpcName to LEDGER_ENTRY macro (#5202)
The LEDGER_ENTRY macro now takes an additional parameter, which makes it easier to avoid missing including the new field in jss.h and to the list of account_objects/ledger_data filters.
2026-02-20 08:05:49 +09:00
Michael Legleux
67b87e4593 fix: Add header for set_difference (#5197)
Fix `error C2039: 'set_difference': is not a member of 'std'`
2026-02-20 08:05:48 +09:00
Mayukha Vadari
af13ac80ca fix: allow overlapping types in Expected (#5218)
For example, Expected<std::uint32_t, Json::Value>, will now build even though there is animplicit conversion from unsigned int to Json::Value.
2026-02-20 08:05:48 +09:00
Gregory Tsipenyuk
dd86d7c4b5 Add MPTIssue to STIssue (#5200)
Replace Issue in STIssue with Asset. STIssue with MPTIssue is only used in MPT tests.
Will be used in Vault and in transactions with STIssue fields once MPT is integrated into DEX.
2026-02-20 08:05:47 +09:00
Bronek Kozicki
8b9ce5f60d Antithesis instrumentation improvements (#5213)
* Rename ASSERT to XRPL_ASSERT
* Upgrade to Anthithesis SDK 0.4.4, and use new 0.4.4 features
  * automatic cast to bool, like assert
* Add instrumentation workflow to verify build with instrumentation enabled
2026-02-20 08:05:47 +09:00
John Freeman
476f6e04e7 Enforce levelization in libxrpl with CMake (#5111)
Adds two CMake functions:

* add_module(library subdirectory): Declares an OBJECT "library" (a CMake abstraction for a collection of object files) with sources from the given subdirectory of the given library, representing a module. Isolates the module's headers by creating a subdirectory in the build directory, e.g. .build/tmp123, that contains just a symlink, e.g. .build/tmp123/basics, to the module's header directory, e.g. include/xrpl/basics, in the source directory, and putting .build/tmp123 (but not include/xrpl) on the include path of the module sources. This prevents the module sources from including headers not explicitly linked to the module in CMake with target_link_libraries.
* target_link_modules(library scope modules...): Links the library target to each of the module targets, and removes their sources from its source list (so they are not compiled and linked twice).

Uses these functions to separate and explicitly link modules in libxrpl:

    Level 01: beast
    Level 02: basics
    Level 03: json, crypto
    Level 04: protocol
    Level 05: resource, server
2026-02-20 08:05:43 +09:00
Mayukha Vadari
5c1e54eec1 refactor: clean up LedgerEntry.cpp (#5199)
Refactors LedgerEntry to make it easier to read and understand.
2026-02-20 08:03:00 +09:00
Ed Hennis
7e4c52c869 test: Add more test cases for Base58 parser (#5174)
---------
Co-authored-by: John Freeman <jfreeman08@gmail.com>
2026-02-20 08:03:00 +09:00
Ed Hennis
ed9046b4fc test: Check for some unlikely null dereferences in tests (#5004) 2026-02-20 08:02:59 +09:00
Bronek Kozicki
01c1f87eca Add Antithesis intrumentation (#5042)
* Copy Antithesis SDK version 0.4.0 to directory external/
* Add build option `voidstar` to enable instrumentation with Antithesis SDK
* Define instrumentation macros ASSERT and UNREACHABLE in terms of regular C assert
* Replace asserts with named ASSERT or UNREACHABLE
* Add UNREACHABLE to LogicError
* Document instrumentation macros in CONTRIBUTING.md
2026-02-20 08:02:59 +09:00
Valentin Balaschenko
c764643d52 Reduce the peer charges for well-behaved peers:
- Fix an erroneous high fee penalty that peers could incur for sending
  older transactions.
- Update to the fees charged for imposing a load on the server.
- Prevent the relaying of internal pseudo-transactions.
  - Before: Pseudo-transactions received from a peer will fail the signature
    check, even if they were requested (using TMGetObjectByHash), because
    they have no signature. This causes the peer to be charge for an
    invalid signature.
  - After: Pseudo-transactions, are put into the global cache
    (TransactionMaster) only. If the transaction is not part of
    a TMTransactions batch, the peer is charged an unwanted data fee.
    These fees will not be a problem in the normal course of operations,
    but should dissuade peers from behaving badly by sending a bunch of
    junk.
- Improve logging: include the reason for fees charged to a peer.

Co-authored-by: Ed Hennis <ed@ripple.com>
2026-02-20 08:02:58 +09:00
tequ
9b5937a1a1 update actions/upload-artifact to v4
https://github.blog/changelog/2024-04-16-deprecation-notice-v3-of-the-artifact-actions/
2026-02-20 08:02:57 +09:00
tequ
794a146116 levelization 2026-02-20 08:02:49 +09:00
tequ
5736d83aa1 Update ServerDefinition 2026-02-20 08:00:00 +09:00
Elliot Lee
8a2d0b1aa4 refactor(AMMClawback): move tfClawTwoAssets check (#5201)
Move tfClawTwoAssets check to preflight and return
error temINVALID_FLAG

---------

Co-authored-by: yinyiqian1 <yqian@ripple.com>
2026-02-20 07:58:14 +09:00
Elliot Lee
12fea12674 Add a new serialized type: STNumber (#5121)
`STNumber` lets objects and transactions contain multiple fields for
quantities of XRP, IOU, or MPT without duplicating information about the
"issue" (represented by `STIssue`). It is a straightforward serialization of
the `Number` type that uniformly represents those quantities.

---------

Co-authored-by: John Freeman <jfreeman08@gmail.com>
Co-authored-by: Howard Hinnant <howard.hinnant@gmail.com>
2026-02-20 07:57:00 +09:00
Olek
e1ea2d61ea fix: check for valid ammID field in amm_info RPC (#5188) 2026-02-20 07:56:26 +09:00
Mayukha Vadari
fb50a0c90d fix: include index in server_definitions RPC (#5190) 2026-02-20 07:56:25 +09:00
Bronek Kozicki
14af5e67fc Fix ledger_entry crash on invalid credentials request (#5189) 2026-02-20 07:56:25 +09:00
Shawn Xie
780500bc35 Replace Uint192 with Hash192 in server_definitions response (#5177) 2026-02-20 07:56:24 +09:00
Bronek Kozicki
c8d7f35a59 Fix potential deadlock (#5124)
* 2.2.2 changed functions acquireAsync and NetworkOPsImp::recvValidation to add an item to a collection under lock, unlock, do some work, then lock again to do remove the item. It will deadlock if an exception is thrown while adding the item - before unlocking.
* Replace ScopedUnlock with scope_unlock.
2026-02-20 07:56:24 +09:00
Olek
2867b6eac5 Introduce Credentials support (XLS-70d): (#5103)
Amendment:
    - Credentials

    New Transactions:
    - CredentialCreate
    - CredentialAccept
    - CredentialDelete

    Modified Transactions:
    - DepositPreauth
    - Payment
    - EscrowFinish
    - PaymentChannelClaim
    - AccountDelete

    New Object:
    - Credential

    Modified Object:
    - DepositPreauth

    API updates:
    - ledger_entry
    - account_objects
    - ledger_data
    - deposit_authorized

    Read full spec: https://github.com/XRPLF/XRPL-Standards/tree/master/XLS-0070d-credentials
2026-02-20 07:56:21 +09:00
Gregory Tsipenyuk
879bfb453e Fix token comparison in Payment (#5172)
* Checks only Currency or MPT Issuance ID part of the Asset object.
* Resolves temREDUNDANT regression detected in testing.
2026-02-20 07:51:04 +09:00
Gregory Tsipenyuk
6eddc7095b Add fixAMMv1_2 amendment (#5176)
* Add reserve check on AMM Withdraw
* Try AMM max offer if changeSpotPriceQuality() fails
2026-02-20 07:51:01 +09:00
Gregory Tsipenyuk
a289acc691 Fix unity build (#5179) 2026-02-20 07:50:28 +09:00
yinyiqian1
05ca77b23c Add AMMClawback Transaction (XLS-0073d) (#5142)
Amendment:
- AMMClawback

New Transactions:
- AMMClawback

Modified Transactions:
- AMMCreate
- AMMDeposit
2026-02-20 07:50:19 +09:00
Valentin Balaschenko
d2e41693c5 docs: Add protobuf dependencies to linux setup instructions (#5156) 2026-02-20 07:45:23 +09:00
yinyiqian1
bfabc979bb fix: reject invalid markers in account_objects RPC calls (#5046) 2026-02-20 07:45:22 +09:00
Bob Conan
2a4c068f81 Update RELEASENOTES.md (#5154)
fix the typo "concensus" -> "consensus"
2026-02-20 07:45:21 +09:00
Gregory Tsipenyuk
0cd8805e36 Introduce MPT support (XLS-33d): (#5143)
Amendment:
- MPTokensV1

New Transactions:
- MPTokenIssuanceCreate
- MPTokenIssuanceDestroy
- MPTokenIssuanceSet
- MPTokenAuthorize

Modified Transactions:
- Payment
- Clawback

New Objects:
- MPTokenIssuance
- MPToken

API updates:
- ledger_entry
- account_objects
- ledger_data

Other:
- Add += and -= operators to ValueProxy

Read full spec: https://github.com/XRPLF/XRPL-Standards/tree/master/XLS-0033d-multi-purpose-tokens

---------
Co-authored-by: Shawn Xie <shawnxie920@gmail.com>
Co-authored-by: Howard Hinnant <howard.hinnant@gmail.com>
Co-authored-by: Ed Hennis <ed@ripple.com>
Co-authored-by: John Freeman <jfreeman08@gmail.com>
2026-02-20 07:45:19 +09:00
John Freeman
019940e907 Consolidate definitions of fields, objects, transactions, and features (#5122) 2026-02-20 07:37:27 +09:00
John Freeman
02749feea8 Reformat code with clang-format-18 2026-02-20 07:26:21 +09:00
John Freeman
0788de203f Update pre-commit hook 2026-02-20 07:25:23 +09:00
John Freeman
7454c46037 Update clang-format settings 2026-02-20 07:25:22 +09:00
John Freeman
202f2f03fb Update clang-format workflow 2026-02-20 07:25:21 +09:00
Chenna Keshava B S
282b7b73c5 Expand Error Message for rpcInternal (#4959)
Validator operators have been confused by the rpcInternal error, which can occur if the server is not running in another process.
2026-02-20 07:25:21 +09:00
Elliot Lee
cf80df3eca docs: clean up API-CHANGELOG.md (#5064)
Move the newest information to the top, i.e., use reverse chronological order within each of the two sections ("API Versions" and "XRP Ledger server versions")
2026-02-20 07:25:20 +09:00
Denis Angell
47a0018f30 feat(SQLite): allow configurable database pragma values (#5135)
Make page_size and journal_size_limit configurable values in rippled.cfg
2026-02-20 07:25:20 +09:00
Vlad
ea53a31081 refactor: re-order PRAGMA statements (#5140)
The page_size will soon be made configurable with #5135, making this
re-ordering necessary.

When opening SQLite connection, there are specific pragmas set with
commonPragmas.

In particular, PRAGMA journal_mode creates journal file and locks the
page_size; as of this commit, this sets the page size to the default
value of 4096. Coincidentally, the hardcoded page_size was also 4096, so
no issue was noticed.
2026-02-20 07:25:19 +09:00
Chenna Keshava B S
8491fd37e8 fix(book_changes): add "validated" field and reduce RPC latency (#5096)
Update book_changes RPC to reduce latency, add "validated" field, and accept shortcut strings (current, closed, validated) for ledger_index.

`"validated": true` indicates that the transaction has been included in a validated ledger so the result of the transaction is immutable.

Fix #5033

Fix #5034

Fix #5035

Fix #5036

---------

Co-authored-by: Bronek Kozicki <brok@incorrekt.com>
2026-02-20 07:25:18 +09:00
luozexuan
fbcb7376ff chore: fix typos in comments (#5094)
Signed-off-by: luozexuan <fetchcode@139.com>
2026-02-20 07:25:18 +09:00
Ed Hennis
dfaaac19c6 test: Retry RPC commands to try to fix MacOS CI jobs (#5120)
* Retry some failed RPC connections / commands in unit tests
* Remove orphaned `getAccounts` function

Co-authored-by: John Freeman <jfreeman08@gmail.com>
2026-02-20 07:25:17 +09:00
John Freeman
c5a971317d docs: Update options documentation (#5083)
Co-authored-by: Elliot Lee <github.public@intelliot.com>
2026-02-20 07:25:17 +09:00
John Freeman
725c5beb3f refactor: Remove dead headers (#5081) 2026-02-20 07:25:16 +09:00
John Freeman
5a0882d17a refactor: Remove reporting mode (#5092) 2026-02-20 07:25:15 +09:00
Scott Schurr
7465bf594c Address rare corruption of NFTokenPage linked list (#4945)
* Add fixNFTokenPageLinks amendment:

It was discovered that under rare circumstances the links between
NFTokenPages could be removed.  If this happens, then the
account_objects and account_nfts RPC commands under-report the
NFTokens owned by an account.

The fixNFTokenPageLinks amendment does the following to address
the problem:

- It fixes the underlying problem so no further broken links
  should be created.
- It adds Invariants so, if such damage were introduced in the
  future, an invariant would stop it.
- It adds a new FixLedgerState transaction that repairs
  directories that were damaged in this fashion.
- It adds unit tests for all of it.
2026-02-20 07:25:15 +09:00
Bronek Kozicki
96ce8c5b74 Factor out Transactor::trapTransaction (#5087) 2026-02-20 07:25:14 +09:00
John Freeman
4b7370c0d2 Remove shards (#5066) 2026-02-20 07:25:13 +09:00
Bronek Kozicki
59151959a1 Update gcovr EXCLUDE (#5084) 2026-02-20 07:25:13 +09:00
Bronek Kozicki
380e3839c2 Fix crash inside OverlayImpl loops over ids_ (#5071) 2026-02-20 07:25:12 +09:00
Ed Hennis
7a68da9c4b docs: Document the process for merging pull requests (#5010) 2026-02-20 07:25:12 +09:00
Scott Schurr
b2505dbd5f Remove unused constants from resource/Fees.h (#4856) 2026-02-20 07:25:11 +09:00
Mayukha Vadari
2aac65330b fix: change error for invalid feature param in feature RPC (#5063)
* Returns an "Invalid parameters" error if the `feature` parameter is provided and is not a string.
2026-02-20 07:25:11 +09:00
Ed Hennis
3570f3410d Ensure levelization sorting is ASCII-order across platforms (#5072) 2026-02-20 07:25:10 +09:00
Ed Hennis
1f1a52b5f0 fix: Fix NuDB build error via Conan patch (#5061)
* Includes updated instructions in BUILD.md.
2026-02-20 07:25:09 +09:00
yinyiqian1
cdf3e82f6e Disallow filtering account_objects by unsupported types (#5056)
* `account_objects` returns an invalid field error if `type` is not supported.
  This includes objects an account can't own, or which are unsupported by `account_objects`
* Includes:
  * Amendments
  * Directory Node
  * Fee Settings
  * Ledger Hashes
  * Negative UNL
2026-02-20 07:24:41 +09:00
Scott Schurr
e22f8749db chore: Add comments to SignerEntries.h (#5059) 2026-02-20 07:20:18 +09:00
Scott Schurr
2091d129d4 chore: Rename two files from Directory* to Dir*: (#5058)
The names of the files should reflect the name of the Dir class.

Co-authored-by: Zack Brunson <Zshooter@gmail.com>
Co-authored-by: Ed Hennis <ed@ripple.com>
2026-02-20 07:20:17 +09:00
Denis Angell
ce7dbaf5ab Update BUILD.md after PR #5052 (#5067)
* Document the need to specify "xrpld" and "tests" to build and test rippled.
2026-02-20 07:20:17 +09:00
John Freeman
7ae3065a36 Add xrpld build option and Conan package test (#5052)
* Make xrpld target optional

* Add job to test Conan recipe

* [fold] address review comments

* [fold] Enable tests in workflows

* [fold] Rename with_xrpld option

* [fold] Fix grep expression
2026-02-20 07:20:16 +09:00
dashangcun
e9d75a6cc7 chore: remove repeat words (#5053)
Signed-off-by: dashangcun <jchaodaohang@foxmail.com>
Co-authored-by: dashangcun <jchaodaohang@foxmail.com>
Co-authored-by: Zack Brunson <Zshooter@gmail.com>
2026-02-20 07:20:16 +09:00
yinyiqian1
eccc2fc522 fix CTID in tx command returns invalidParams on lowercase hex (#5049)
* fix CTID in tx command returns invalidParams on lowercase hex

* test mixed case and change auto to explicit type

* add header cctype because std::tolower is called

* remove unused local variable

* change test case comment from 'lowercase' to 'mixed case'

---------

Co-authored-by: Zack Brunson <Zshooter@gmail.com>
2026-02-20 07:20:15 +09:00
Ed Hennis
61e8f0956f Invariant: prevent a deleted account from leaving (most) artifacts on the ledger. (#4663)
* Add feature / amendment "InvariantsV1_1"

* Adds invariant AccountRootsDeletedClean:

* Checks that a deleted account doesn't leave any directly
  accessible artifacts behind.
* Always tests, but only changes the transaction result if
  featureInvariantsV1_1 is enabled.
* Unit tests.

* Resolves #4638

* [FOLD] Review feedback from @gregtatcam:

* Fix unused variable warning
* Improve Invariant test const correctness

* [FOLD] Review feedback from @mvadari:

* Centralize the account keylet function list, and some optimization

* [FOLD] Some structured binding doesn't work in clang

* [FOLD] Review feedback 2 from @mvadari:

* Clean up and clarify some comments.

* [FOLD] Change InvariantsV1_1 to unsupported

* Will allow multiple PRs to be merged over time using the same amendment.

* fixup! [FOLD] Change InvariantsV1_1 to unsupported

* [FOLD] Update and clarify some comments. No code changes.

* Move CMake directory

* Rearrange sources

* Rewrite includes

* Recompute loops

* Fix merge issue and formatting

---------

Co-authored-by: Pretty Printer <cpp@ripple.com>
2026-02-20 07:20:14 +09:00
yinyiqian1
66aa252a52 fix "account_nfts" with unassociated marker returning issue (#5045)
* fix "account_nfts" with unassociated marker returning issue

* create unit test for fixing nft page invalid marker not returning error

add more test

change test name

create unit test

* fix "account_nfts" with unassociated marker returning issue

* fix "account_nfts" with unassociated marker returning issue

* fix "account_nfts" with unassociated marker returning issue

* fix "account_nfts" with unassociated marker returning issue

* fix "account_nfts" with unassociated marker returning issue

* fix "account_nfts" with unassociated marker returning issue

* fix "account_nfts" with unassociated marker returning issue

* fix "account_nfts" with unassociated marker returning issue

* [FOLD] accumulated review suggestions

* move BEAST check out of lambda function

---------

Authored-by: Scott Schurr <scott@ripple.com>
2026-02-20 07:20:14 +09:00
Scott Schurr
fbcff93251 fixInnerObjTemplate2 amendment (#5047)
* fixInnerObjTemplate2 amendment:

Apply inner object templates to all remaining (non-AMM)
inner objects.

Adds a unit test for applying the template to sfMajorities.
Other remaining inner objects showed no problems having
templates applied.

* Move CMake directory

* Rearrange sources

* Rewrite includes

* Recompute loops

---------

Co-authored-by: Pretty Printer <cpp@ripple.com>
2026-02-20 07:20:13 +09:00
tequ
e95edcba48 fix for xahaud codebase 2026-02-20 07:11:14 +09:00
Pretty Printer
d25b5dcd56 Recompute loops 2026-02-20 07:11:13 +09:00
Pretty Printer
e618806999 Rewrite includes 2026-02-20 07:11:13 +09:00
Pretty Printer
bfafa2bb39 Rearrange sources 2026-02-20 07:11:12 +09:00
Pretty Printer
e47b1c1b3b Move CMake directory 2026-02-20 07:11:11 +09:00
John Freeman
4564054423 Add bin/physical.sh (#4997) 2026-02-20 07:11:10 +09:00
John Freeman
8e221bcd68 Prepare to rearrange sources: (#4997)
- Remove CMake module "MultiConfig".
- Update clang-format configuration, CodeCov configuration,
  levelization script.
- Replace source lists in CMake with globs.
2026-02-20 07:11:10 +09:00
Bronek Kozicki
a13f367b75 Change order of checks in amm_info: (#4924)
* Change order of checks in amm_info

* Change amm_info error message in API version 3

* Change amm_info error tests
2026-02-20 07:11:09 +09:00
Scott Schurr
10435a40e6 Add the fixEnforceNFTokenTrustline amendment: (#4946)
Fix interactions between NFTokenOffers and trust lines.

Since the NFTokenAcceptOffer does not check the trust line that
the issuer receives as a transfer fee in the NFTokenAcceptOffer,
if the issuer deletes the trust line after NFTokenCreateOffer,
the trust line is created for the issuer by the
NFTokenAcceptOffer.  That's fixed.

Resolves #4925.
2026-02-20 07:11:09 +09:00
Chenna Keshava B S
b7fbe4eb99 Replaces the usage of boost::string_view with std::string_view (#4509) 2026-02-20 07:11:08 +09:00
Elliot Lee
316718312b docs: explain how to find a clang-format patch generated by CI (#4521) 2026-02-20 07:11:07 +09:00
tequ
9465fccbc1 XLS-52d: NFTokenMintOffer (#4845) 2026-02-20 07:10:42 +09:00
todaymoon
92d3125bca chore: remove repeat words (#5041) 2026-02-20 07:10:41 +09:00
Alex Kremer
1b1420d51b Expose all amendments known by libxrpl (#5026) 2026-02-20 07:10:41 +09:00
Scott Schurr
0a2a5479c6 fixReducedOffersV2: prevent offers from blocking order books: (#5032)
Fixes issue #4937.

The fixReducedOffersV1 amendment fixed certain forms of offer
modification that could lead to blocked order books.  Reduced
offers can block order books if the effective quality of the
reduced offer is worse than the quality of the original offer
(from the perspective of the taker). It turns out that, for
small values, the quality of the reduced offer can be
significantly affected by the rounding mode used during
scaling computations.

Issue #4937 identified an additional code path that modified
offers in a way that could lead to blocked order books.  This
commit changes the rounding in that newly located code path so
the quality of the modified offer is never worse than the
quality of the offer as it was originally placed.

It is possible that additional ways of producing blocking
offers will come to light.  Therefore there may be a future
need for a V3 amendment.
2026-02-20 07:10:40 +09:00
Chenna Keshava B S
b3a41ee9de Additional unit tests for testing deletion of trust lines (#4886) 2026-02-20 07:10:39 +09:00
Olek
4cebe4c0ea Fix conan typo: (#5044)
Add missed coma in 'exportes_sources'
2026-02-20 07:10:39 +09:00
Bronek Kozicki
0c3909af4e Add new command line option to make replaying transactions easier: (#5027)
* Add trap_tx_hash command line option

This new option can be used only if replay is also enabled. It takes a transaction hash from the ledger loaded for replay, and will cause a specific line to be hit in Transactor.cpp, right before the selected transaction is applied.
2026-02-20 07:10:38 +09:00
John Freeman
afc7af4ab9 Fix compatibility with Conan 2.x: (#5001)
Closes #4926, #4990
2026-02-20 07:10:38 +09:00
J. Scott Branson
ff2a603745 Update SQLite3 max_page_count to match current defaults (#5114)
When rippled initiates a connection to SQLite3, rippled sends a "PRAGMA"
statement defining the maximum number of pages allowed in the database.
Update the max_page_count so it is consistent with the default for newer
versions of SQLite3. Increasing max_page_count is critical for keeping
full history servers online.

Fix #5102
2026-02-20 07:10:37 +09:00
Valentin Balaschenko
8a054c36f0 Track latencies of certain code blocks, and log if they take too long 2026-02-20 07:10:36 +09:00
John Freeman
bf0705775a Use error codes throughout fast Base58 implementation 2026-02-20 07:10:36 +09:00
Mayukha Vadari
469b13dd71 Improve error handling in some RPC commands 2026-02-20 07:10:35 +09:00
Alex Kremer
fad6dfe1e3 Add xrpl.libpp as an exported lib in conan (#5022) 2026-02-20 07:07:02 +09:00
Gregory Tsipenyuk
0eb5cbf78c Fix Oracle's token pair deterministic order: (#5021)
Price Oracle data-series logic uses `unordered_map` to update the Oracle object.
This results in different servers disagreeing on the order of that hash table.
Consequently, the generated ledgers will have different hashes.
The fix uses `map` instead to guarantee the order of the token pairs
in the data-series.
2026-02-20 07:07:02 +09:00
Gregory Tsipenyuk
3d87543300 Fix last Liquidity Provider withdrawal:
Due to the rounding, LPTokenBalance of the last
Liquidity Provider (LP), might not match this LP's
trustline balance. This fix sets LPTokenBalance on
last LP withdrawal to this LP's LPToken trustline
balance.
2026-02-20 07:07:01 +09:00
Gregory Tsipenyuk
8f50c787c1 Fix offer crossing via single path AMM with transfer fee:
Single path AMM offer has to factor in the transfer in rate
when calculating the upper bound quality and the quality function
because single path AMM's offer quality is not constant.
This fix factors in the transfer fee in
BookStep::adjustQualityWithFees().
2026-02-20 07:07:01 +09:00
Gregory Tsipenyuk
0637543c2a Fix adjustAmountsByLPTokens():
The fix is to return the actual adjusted lp tokens and amounts
by the function.
2026-02-20 07:07:00 +09:00
Gregory Tsipenyuk
a9a04921b5 Add the fixAMMOfferRounding amendment: (#4983)
* Fix AMM offer rounding and low quality LOB offer blocking AMM:

A single-path AMM offer with account offer on DEX, is always generated
starting with the takerPays first, which is rounded up, and then
the takerGets, which is rounded down. This rounding ensures that the pool's
product invariant is maintained. However, when one of the offer's side
is XRP, this rounding can result in the AMM offer having a lower
quality, potentially causing offer generation to fail if the quality
is lower than the account's offer quality.

To address this issue, the proposed fix adjusts the offer generation process
to start with the XRP side first and always rounds it down. This results
in a smaller offer size, improving the offer's quality. Regardless if the offer
has XRP or not, the rounding is done so that the offer size is minimized.
This change still ensures the product invariant, as the other generated
side is the exact result of the swap-in or swap-out equations.

If a liquidity can be provided by both AMM and LOB offer on offer crossing
then AMM offer is generated so that it matches LOB offer quality. If LOB
offer quality is less than limit quality then generated AMM offer quality
is also less than limit quality and the offer doesn't cross. To address
this issue, if LOB quality is better than limit quality then use LOB
quality to generate AMM offer. Otherwise, don't use the quality to generate
AMM offer. In this case, limitOut() function in StrandFlow limits
the out amount to match strand's quality to limit quality and consume
maximum AMM liquidity.
2026-02-20 07:06:59 +09:00
Gregory Tsipenyuk
02c2936e62 Price Oracle: validate input parameters and extend test coverage: (#5013)
* Price Oracle: validate input parameters and extend test coverage:

Validate trim, time_threshold, document_id are valid
Int, UInt, or string convertible to UInt. Validate base_asset
and quote_asset are valid currency. Update error codes.
Extend Oracle and GetAggregatePrice unit-tests.
Denote unreachable coverage code.

* Set one-line LCOV_EXCL_LINE

* Move ledger_entry tests to LedgerRPC_test.cpp

* Add constants for "None"

* Fix LedgerRPC test

---------

Co-authored-by: Scott Determan <scott.determan@yahoo.com>
2026-02-20 07:06:59 +09:00
Michael Legleux
6e51de48a1 Add external directory to Conan recipe's exports (#5006) 2026-02-20 07:06:58 +09:00
John Freeman
fb531107a9 Add missing includes (#5011) 2026-02-20 07:06:57 +09:00
seelabs
39b861c78a Remove flow assert: (#5009)
Rounding in the payment engine is causing an assert to sometimes fire
with "dust" amounts. This is causing issues when running debug builds of
rippled. This issue will be addressed, but the assert is no longer
serving its purpose.
2026-02-20 07:06:57 +09:00
seelabs
c9b825a09a fix amendment: AMM swap should honor invariants: (#5002)
The AMM has an invariant for swaps where:
new_balance_1*new_balance_2 >= old_balance_1*old_balance_2

Due to rounding, this invariant could sometimes be violated (although by
very small amounts).

This patch introduces an amendment `fixAMMRounding` that changes the
rounding to always favor the AMM. Doing this should maintain the
invariant.

Co-authored-by: Bronek Kozicki
Co-authored-by: thejohnfreeman
2026-02-20 07:06:56 +09:00
seelabs
88d3428249 Add global access to the current ledger rules:
It can be difficult to make transaction breaking changes to low level
code because the low level code does not have access to a ledger and the
current activated amendments in that ledger (the "rules"). This patch
adds global access to the current ledger rules as a `std::optional`. If
the optional is not seated, then there is no active transaction.
2026-02-20 07:06:56 +09:00
Snoppy
15f41c9777 chore: fix typos (#4958) 2026-02-20 07:06:55 +09:00
Ed Hennis
de142a1211 test: Add RPC error checking support to unit tests (#4987) 2026-02-20 07:06:54 +09:00
John Freeman
2187676f36 Ignore more commits 2026-02-20 07:06:54 +09:00
John Freeman
1cdca0a832 Address compiler warnings 2026-02-20 07:06:53 +09:00
John Freeman
384d8ed09b Add markers around source lists 2026-02-20 07:06:52 +09:00
John Freeman
8ea5a11f97 Fix source lists 2026-02-20 07:06:52 +09:00
Pretty Printer
3916c44793 Rewrite includes
$ find src/ripple/ src/test/ -type f -exec sed -i 's:include\s*["<]ripple/\(.*\)\.h\(pp\)\?[">]:include <ripple/\1.h>:' {} +
2026-02-20 07:06:51 +09:00
Pretty Printer
5a227dc719 Format formerly .hpp files 2026-02-20 07:06:51 +09:00
Pretty Printer
0345a2645d Rename .hpp to .h 2026-02-20 07:06:50 +09:00
John Freeman
07cd7f38b7 Simplify protobuf generation 2026-02-20 07:06:49 +09:00
Pretty Printer
da1d20d6d5 Consolidate external libraries 2026-02-20 07:06:49 +09:00
John Freeman
e76014f421 Remove unused files 2026-02-20 07:06:48 +09:00
Ed Hennis
723f0e7602 fix: Remove redundant STAmount conversion in test (#4996) 2026-02-20 07:06:47 +09:00
Scott Determan
eb441c6987 fix: resolve database deadlock: (#4989)
The `rotateWithLock` function holds a lock while it calls a callback
function that's passed in by the caller. This is a problematic design
that needs to be used very carefully. In this case, at least one caller
passed in a callback that eventually relocks the mutex on the same
thread, causing UB (a deadlock was observed). The caller was from
SHAMapStoreImpl, and it called `clearCaches`. This `clearCaches` can
potentially call `fetchNodeObject`, which tried to relock the mutex.

This patch resolves the issue by changing the mutex type to a
`recursive_mutex`. Ideally, the code should be rewritten so it doesn't
hold the mutex during the callback and the mutex should be changed back
to a regular mutex.

Co-authored-by: Ed Hennis <ed@ripple.com>
2026-02-20 07:06:47 +09:00
Michael Legleux
4366e5b111 fix Conan component reference typo 2026-02-20 07:06:46 +09:00
Bronek Kozicki
3c7241c5bc Remove unused lambdas from MultiApiJson_test 2026-02-20 07:06:46 +09:00
Chenna Keshava B S
76e376c75e test: verify the rounding behavior of equal-asset AMM deposits (#4982)
* Specifically, test using tfLPToken flag
2026-02-20 07:06:45 +09:00
John Freeman
14b1ac7068 test: Add tests to raise coverage of AMM (#4971)
---------

Co-authored-by: Howard Hinnant <howard.hinnant@gmail.com>
Co-authored-by: Mark Travis <mtravis@ripple.com>
Co-authored-by: Bronek Kozicki <brok@incorrekt.com>
Co-authored-by: Mayukha Vadari <mvadari@gmail.com>
Co-authored-by: Chenna Keshava <ckeshavabs@gmail.com>
2026-02-20 07:06:44 +09:00
Bronek Kozicki
afd71b9241 test: Unit test for AMM offer overflow (#4986) 2026-02-20 07:06:44 +09:00
Mayukha Vadari
11b8f3ec1a fix amendment to add PreviousTxnID/PreviousTxnLgrSequence (#4751)
This amendment, `fixPreviousTxnID`, adds `PreviousTxnID` and
`PreviousTxnLgrSequence` as fields to all ledger objects that did
not already have them included (`DirectoryNode`, `Amendments`,
`FeeSettings`, `NegativeUNL`, and `AMM`). This makes it much easier
to go through the history of these ledger objects.
2026-02-20 07:06:43 +09:00
Ed Hennis
f017a2af17 chore: Default validator-keys-tool to master branch: (#4943)
* master is the default branch for that project. There's no point in
  using develop.
2026-02-20 07:06:42 +09:00
Scott Determan
7a0235672b fixXChainRewardRounding: round reward shares down: (#4933)
When calculating reward shares, the amount should always be rounded
down. If the `fixUniversalNumber` amendment is not active, this works
correctly. If it is not active, then the amount is incorrectly rounded
up. This patch introduces an amendment so it will be rounded down.
2026-02-20 07:06:42 +09:00
Mark Travis
f756587680 Don't reach consensus as quickly if no other proposals seen: (#4763)
This fixes a case where a peer can desync under a certain timing
circumstance--if it reaches a certain point in consensus before it receives
proposals. 

This was noticed under high transaction volumes. Namely, when we arrive at the
point of deciding whether consensus is reached after minimum establish phase
duration but before having received any proposals. This could be caused by
finishing the previous round slightly faster and/or having some delay in
receiving proposals. Existing behavior arrives at consensus immediately after
the minimum establish duration with no proposals. This causes us to desync
because we then close a non-validated ledger. The change in this PR causes us to
wait for a configured threshold before making the decision to arrive at
consensus with no proposals. This allows validators to catch up and for brief
delays in receiving proposals to be absorbed. There should be no drawback since,
with no proposals coming in, we needn't be in a huge rush to jump ahead.
2026-02-20 07:06:41 +09:00
Bronek Kozicki
5be2f0a6da Write improved forAllApiVersions used in NetworkOPs (#4833) 2026-02-20 07:06:41 +09:00
Bronek Kozicki
a271c63a2b Enforce no duplicate slots from incoming connections: (#4944)
We do not currently enforce that incoming peer connection does not have
remote_endpoint which is already used (either by incoming or outgoing
connection), hence already stored in slots_. If we happen to receive a
connection from such a duplicate remote_endpoint, it will eventually result in a
crash (when disconnecting) or weird behavior (when updating slot state), as a
result of an apparently matching remote_endpoint in slots_ being used by a
different connection.
2026-02-20 07:06:40 +09:00
Mayukha Vadari
92c84471fd fixEmptyDID: fix amendment to handle empty DID edge case: (#4950)
This amendment fixes an edge case where an empty DID object can be
created. It adds an additional check to ensure that DIDs are
non-empty when created, and returns a `tecEMPTY_DID` error if the DID
would be empty.
2026-02-20 07:06:39 +09:00
Ed Hennis
d12ae81070 test: Env unit test RPC errors return a unique result: (#4877)
* telENV_RPC_FAILED is a new code, reserved exclusively
  for unit tests when RPC fails. This will
  make those types of errors distinct and easier to test
  for when expected and/or diagnose when not.
* Output RPC command result when result is not expected.
2026-02-20 07:06:39 +09:00
Bronek Kozicki
f3065cbd6d Upgrade to xxhash 0.8.2 as a Conan requirement, enable SIMD hashing (#4893)
We are currently using old version 0.6.2 of `xxhash`, as a verbatim copy and paste of its header file `xxhash.h`. Switch to the more recent version 0.8.2. Since this version is in Conan Center (and properly protects its ABI by keeping the state object incomplete), add it as a Conan requirement. Switch to the SIMD instructions (in the new `XXH3` family) supported by the new version.
2026-02-20 07:06:35 +09:00
Michael Legleux
21ec4907c7 Install more public headers (#4940)
Fixes some mistakes in #4885
2026-02-20 07:06:14 +09:00
Scott Determan
535728e1c4 fix: order book update variable swap: (#4890)
This is likely the result of a typo when the code was simplified.
2026-02-20 07:06:14 +09:00
John Freeman
b7bd3e33ae Embed patched recipe for RocksDB 6.29.5 (#4947) 2026-02-20 07:06:13 +09:00
Gregory Tsipenyuk
72bbeda46e build: add STCurrency.h to xrpl_core to fix clio build (#4939) 2026-02-20 07:06:13 +09:00
Mayukha Vadari
db64da58bd feat: add user version of feature RPC (#4781)
* uses same formatting as admin RPC
* hides potentially sensitive data
2026-02-20 07:06:12 +09:00
Scott Determan
85b46dbf0f Fast base58 codec: (#4327)
This algorithm is about an order of magnitude faster than the existing
algorithm (about 10x faster for encoding and about 15x faster for
decoding - including the double hash for the checksum). The algorithms
use gcc's int128 (fast MS version will have to wait, in the meantime MS
falls back to the slow code).
2026-02-20 07:06:11 +09:00
Chenna Keshava B S
7b562e2312 Remove default ctors from SecretKey and PublicKey: (#4607)
* It is now an invariant that all constructed Public Keys are valid,
  non-empty and contain 33 bytes of data.
* Additionally, the memory footprint of the PublicKey class is reduced.
  The size_ data member is declared as static.
* Distinguish and identify the PublisherList retrieved from the local
  config file, versus the ones obtained from other validators.
* Fixes #2942
2026-02-20 07:05:25 +09:00
Gregory Tsipenyuk
a3f459dff4 fix compile error on gcc 13: (#4932)
The compilation fails due to an issue in the initializer list
of an optional argument, which holds a vector of pairs.
The code compiles correctly on earlier gcc versions, but fails on gcc 13.
2026-02-20 07:01:31 +09:00
Gregory Tsipenyuk
37ae1d8a4f Price Oracle (XLS-47d): (#4789) (#4789)
Implement native support for Price Oracles.

 A Price Oracle is used to bring real-world data, such as market prices,
 onto the blockchain, enabling dApps to access and utilize information
 that resides outside the blockchain.

 Add Price Oracle functionality:
 - OracleSet: create or update the Oracle object
 - OracleDelete: delete the Oracle object

 To support this functionality add:
 - New RPC method, `get_aggregate_price`, to calculate aggregate price for a token pair of the specified oracles
 - `ltOracle` object

 The `ltOracle` object maintains:
 - Oracle Owner's account
 - Oracle's metadata
 - Up to ten token pairs with the scaled price
 - The last update time the token pairs were updated

 Add Oracle unit-tests
2026-02-20 07:01:17 +09:00
Mayukha Vadari
d424910e81 feat(rpc): add server_definitions method (#4703)
Add a new RPC / WS call for `server_definitions`, which returns an
SDK-compatible `definitions.json` (binary enum definitions) generated by
the server. This enables clients/libraries to dynamically work with new
fields and features, such as ones that may become available on side
chains. Clients query `server_definitions` on a node from the network
they want to work with, and immediately know how to speak that node's
binary "language", even if new features are added to it in the future
(as long as there are no new serialized types that the software doesn't
know how to serialize/deserialize).

Example:

```js
> {"command": "server_definitions"}
< {
    "result": {
        "FIELDS": [
            [
                "Generic",
                {
                    "isSerialized": false,
                    "isSigningField": false,
                    "isVLEncoded": false,
                    "nth": 0,
                    "type": "Unknown"
                }
            ],
            [
                "Invalid",
                {
                    "isSerialized": false,
                    "isSigningField": false,
                    "isVLEncoded": false,
                    "nth": -1,
                    "type": "Unknown"
                }
            ],
            [
                "ObjectEndMarker",
                {
                    "isSerialized": false,
                    "isSigningField": true,
                    "isVLEncoded": false,
                    "nth": 1,
                    "type": "STObject"
                }
            ],
        ...
```

Close #3657

---------

Co-authored-by: Richard Holland <richard.holland@starstone.co.nz>
2026-02-20 07:00:28 +09:00
Gregory Tsipenyuk
1fde2a507c fix: improper handling of large synthetic AMM offers:
A large synthetic offer was not handled correctly in the payment engine.
This patch fixes that issue and introduces a new invariant check while
processing synthetic offers.
2026-02-20 07:00:28 +09:00
Ed Hennis
87154a3ed0 test: guarantee proper lifetime for temporary Rules object: (#4917)
* Commit 01c37fe introduced a change to the STTx unit test where a local
  "defaultRules" object was created with a temporary inline "presets"
  value provided to the ctor. Rules::Impl stores a const ref to the
  presets provided to the ctor.  This particular call provided an inline
  temp variable, which goes out of scope as soon as the object is
  created. On Windows, attempting to use the presets (e.g. via the
  enabled() function) causes an access violation, which crashes the test
  run.
* An audit of the code indicates that all other instances of Rules use
  the Application's config.features list, which will have a sufficient
  lifetime.
2026-02-20 07:00:27 +09:00
Gregory Tsipenyuk
c9f4bc8e92 fixInnerObjTemplate: set inner object template (#4906)
Add `STObject` constructor to explicitly set the inner object template.
This allows certain AMM transactions to apply in the same ledger:

There is no issue if the trading fee is greater than or equal to 0.01%.
If the trading fee is less than 0.01%, then:
- After AMM create, AMM transactions must wait for one ledger to close
  (3-5 seconds).
- After one ledger is validated, all AMM transactions succeed, as
  appropriate, except for AMMVote.
- The first AMMVote which votes for a 0 trading fee in a ledger will
  succeed. Subsequent AMMVote transactions which vote for a 0 trading
  fee will wait for the next ledger (3-5 seconds). This behavior repeats
  for each ledger.

This has no effect on the ultimate correctness of AMM. This amendment
will allow the transactions described above to succeed as expected, even
if the trading fee is 0 and the transactions are applied within one
ledger (block).
2026-02-20 07:00:26 +09:00
Chenna Keshava B S
eba2f5a439 feat: allow port_grpc to be specified in [server] stanza (#4728)
Prior to this commit, `port_grpc` could not be added to the [server]
stanza. Instead of validating gRPC IP/Port/Protocol information in
ServerHandler, validate grpc port info in GRPCServer constructor. This
should not break backwards compatibility.

gRPC-related config info must be in a section (stanza) called
[port_gprc].

* Close #4015 - That was an alternate solution. It was decided that with
  relaxed validation, it is not necessary to rename port_grpc.
* Fix #4557
2026-02-20 07:00:26 +09:00
Michael Legleux
eec05dae5f build: add headers needed in Conan package for libxrpl (#4885)
These headers are required in the xrpl Conan package in order for
xbridge witness server (xbwd) to build. This change to libxrpl may help
any dependents of libxrpl. This addition does not change any C++ code.
2026-02-20 07:00:25 +09:00
Shawn Xie
ca2627635a fixNFTokenReserve: ensure NFT tx fails when reserve is not met (#4767)
Without this amendment, an NFTokenAcceptOffer transaction can succeed
even when the NFToken recipient does not have sufficient reserves for
the new NFTokenPage. This allowed accounts to accept NFT sell offers
without having a sufficient reserve. (However, there was no issue in
brokered mode or when a buy offer is involved.)

Instead, the transaction should fail with `tecINSUFFICIENT_RESERVE` as
appropriate. The `fixNFTokenReserve` amendment adds checks in the
NFTokenAcceptOffer transactor to check if the OwnerCount changed. If it
did, then it checks the new reserve requirement.

Fix #4679
2026-02-20 07:00:25 +09:00
tequ
d5e4883fb3 bad merge: RPCCall_test, Transaction_test 2026-02-20 07:00:24 +09:00
Ed Hennis
85838140fb Fix cahce bug introduced in 2.0.1
Partially chery-picked from f419c18056
2026-02-20 07:00:23 +09:00
John Freeman
950f869715 fix(libxrpl): change library names in Conan recipe (#4831)
Use consistent platform-agnostic library names on all platforms.

Fix an issue that prevents dependents like validator-keys-tool from
linking to libxrpl on Windows.

It is bad practice to change the binary base name depending on the
platform. CMake already manipulates the base name into a final name that
fits the conventions of the platform. Linkers accept base names on the
command line and then look for conventional names on disk.
2026-02-20 07:00:23 +09:00
Bronek Kozicki
167f0a2d34 test: add unit test for redundant payment (#4860)
If the payee and payer are the same account, then the transaction fails
in preflight with temREDUNDANT.
2026-02-20 07:00:22 +09:00
Bronek Kozicki
eaa6ef5469 test: improve code coverage reporting (#4849)
* Speed up the generation of coverage reports by using multiple cores.

* Add codecov step to coverage workflow.
2026-02-20 07:00:19 +09:00
Chenna Keshava B S
f8856e2eb1 docs: update help message about unit test-suite pattern matching (#4846)
Update the "rippled --help" message for the "-u" parameter. This
documents the unit test name pattern matching rule implemented by #4634.

Fix #4800
2026-02-20 06:59:51 +09:00
Elliot Lee
d9562b4164 docs: add Performance type to PR template (#4875) 2026-02-20 06:59:50 +09:00
Bronek Kozicki
b7d53be99f test: add DeliverMax to more JSONRPC tests (#4826)
Minor change in unit tests to improve testing scope.
2026-02-20 06:59:49 +09:00
John Freeman
8d012d03e8 fix: change default send_queue_limit to 500 (#4867)
Clients subscribed to `transactions` over WebSocket are being
disconnected because the traffic exceeds the default `send_queue_limit`
of 100.

This commit changes the default configuration, not the default in code.

Fix #4866
2026-02-20 06:59:49 +09:00
Ed Hennis
f1b86fecbb Improve lifetime management of ledger objects (SLEs) to prevent runaway memory usage: (#4822)
* Add logging for Application.cpp sweep()
* Improve lifetime management of ledger objects (`SLE`s)
* Only store SLE digest in CachedView; get SLEs from CachedSLEs
* Also force release of last ledger used for path finding if there are
  no path finding requests to process
* Count more ST objects (derive from `CountedObject`)
* Track CachedView stats in CountedObjects
* Rename the CachedView counters
* Fix the scope of the digest lookup lock

Before this patch, if you asked "is it caching?" It was always caching.
2026-02-20 06:59:48 +09:00
Ed Hennis
f44b737fce WebSocket should only call async_close once (#4848)
Prevent WebSocket connections from trying to close twice.

The issue only occurs in debug builds (assertions are disabled in
release builds, including published packages), and when the WebSocket
connections are unprivileged. The assert (and WRN log) occurs when a
client drives up the resource balance enough to be forcibly disconnected
while there are still messages pending to be sent.

Thanks to @lathanbritz for discovering this issue in #4822.
2026-02-20 06:59:48 +09:00
Hussein Badakhchani
3f489f9c19 fix typo: 'of' instead of 'on' (#4821)
Co-authored-by: Hussein Badakhchani <hoos@alsoug.com>
2026-02-20 06:59:47 +09:00
Bronek Kozicki
c7d6d34372 Workarounds for gcc-13 compatibility (#4817)
Workaround for compilation errors with gcc-13 and other compilers
relying on `libstdc++` version 13. This is temporary until actual fix is
available for us to use: https://github.com/boostorg/beast/pull/2682

Some boost.beast files (which we do use) rely on an old gcc-12 behaviour
where `#include <cstdint>` was not needed even though types from this
header were used. This was broken by a change in libstdc++ version 13:
https://gcc.gnu.org/gcc-13/porting_to.html#header-dep-changes

The necessary fix was implemented in boost.beast, however it is not yet
available. Until it is available, we can use this workaround to enable
compilation of `rippled` with gcc-13, clang-16, etc.
2026-02-20 06:59:46 +09:00
Bronek Kozicki
54e3952297 APIv2: show DeliverMax in submit, submit_multisigned (#4827)
Show `DeliverMax` instead of `Amount` in output from `submit`,
`submit_multisigned`, `sign`, and `sign_for`.

Fix #4829
2026-02-20 06:59:46 +09:00
Bronek Kozicki
5ffda742f5 APIv2: consistently return ledger_index as integer (#4820)
For api_version 2, always return ledger_index as integer in JSON output.

api_version 1 retains prior behavior.
2026-02-20 06:59:45 +09:00
Bronek Kozicki
2730a0e329 Fix 2.0 regression in tx method with binary output (#4812)
* Fix binary output from tx method

* Formatting fix

* Minor test improvement

* Minor test improvements
2026-02-20 06:59:45 +09:00
Bronek Kozicki
3c25e7eca6 Promote API version 2 to supported (#4803)
* Promote API version 2 to supported

* Switch command line to API version 1

* Fix LedgerRequestRPC test

* Remove obsolete tx_account method

This method is not implemented, the only parts which are removed are related to command-line parsing

* Fix RPCCall test

* Reduce diff size, small test improvements

* Minor fixes

* Support for the mold linker

* [fold] handle case where both mold and gold are installed

* [fold] Use first non-default linker

* Fix TransactionEntry_test

* Fix AccountTx_test

---------

Co-authored-by: seelabs <scott.determan@yahoo.com>
2026-02-20 06:59:44 +09:00
Scott Determan
c3489123ff Support for the mold linker (#4807) 2026-02-20 06:59:44 +09:00
Bronek Kozicki
dc8d496e1c Unify JSON serialization format of transactions (#4775)
* Remove include <ranges>

* Formatting fix

* Output for subscriptions

* Output from sign, submit etc.

* Output from ledger

* Output from account_tx

* Output from transaction_entry

* Output from tx

* Store close_time_iso in API v2 output

* Add small APIv2 unit test for subscribe

* Add unit test for transaction_entry

* Add unit test for tx

* Remove inLedger from API version 2

* Set ledger_hash and ledger_index

* Move isValidated from RPCHelpers to LedgerMaster

* Store closeTime in LedgerFill

* Time formatting fix

* additional tests for Subscribe unit tests

* Improved comments

* Rename mInLedger to mLedgerIndex

* Minor fixes

* Set ledger_hash on closed ledger, even if not validated

* Update API-CHANGELOG.md

* Add ledger_hash, ledger_index to transaction_entry

* Fix validated and close_time_iso in account_tx

* Fix typos

* Improve getJson for Transaction and STTx

* Minor improvements

* Replace class enum JsonOptions with struct

We may consider turning this into a general-purpose template and using it elsewhere

* simplify the extraction of transactionID from Transaction object

* Remove obsolete comments

* Unconditionally set validated in account_tx output

* Minor improvements

* Minor fixes

---------

Co-authored-by: Chenna Keshava <ckeshavabs@gmail.com>
2026-02-20 06:58:33 +09:00
Scott Determan
a70bbc1fe1 fix: check for valid public key in attestations (#4798) 2026-02-20 06:55:03 +09:00
pwang200
ef978f407d Fix unit test api_version to enable api_version 2 (#4785)
The command line API still uses `apiMaximumSupportedVersion`.
The unit test RPCs use `apiMinimumSupportedVersion` if unspecified.

Context:
- #4568
- #4552
2026-02-20 06:55:02 +09:00
Gregory Tsipenyuk
48443977bd fixFillOrKill: fix offer crossing with tfFillOrKill (#4694)
Introduce the `fixFillOrKill` amendment.

Fix an edge case occurring when an offer with `tfFillOrKill` set (but
without `tfSell` set) fails to cross an offer with a better rate. If
`tfFillOrKill` is set, then the owner must receive the full TakerPays.
Without this amendment, an offer fails if the entire `TakerGets` is not
spent. With this amendment, when `tfSell` is not set, the entire
`TakerGets` does not have to be spent.

For details about OfferCreate, see: https://xrpl.org/offercreate.html

Fix #4684

---------

Co-authored-by: Scott Schurr <scott@ripple.com>
2026-02-20 06:55:01 +09:00
Bronek Kozicki
63415cf534 fix: remove include <ranges> (#4788)
Remove dependency on `<ranges>` header, since it is not implemented by
all compilers which we want to support.

This code change only affects unit tests.

Resolve https://github.com/XRPLF/rippled/issues/4787
2026-02-20 06:55:01 +09:00
Bronek Kozicki
6211c36712 APIv2: remove tx_history and ledger_header (#4759)
Remove `tx_history` and `ledger_header` methods from API version 2.

Update `RPC::Handler` to allow for methods (or method implementations)
to be API version specific. This partially resolves #4727. We can now
store multiple handlers with the same name, as long as they belong to
different (non-overlapping) API versions. This necessarily impacts the
handler lookup algorithm and its complexity; however, there is no
performance loss on x86_64 architecture, and only minimal performance
loss on arm64 (around 10ns). This design change gives us extra
flexibility evolving the API in the future, including other parts of

In API version 2, `tx_history` and `ledger_header` are no longer
recognised; if they are called, `rippled` will return error
`unknownCmd`

Resolve #3638

Resolve #3539
2026-02-20 06:55:00 +09:00
Mark Travis
6441add2c9 docs: clarify definition of network health (#4729)
Update the documentation to describe network health with more nuance as
well as context about related factors.
2026-02-20 06:55:00 +09:00
Bronek Kozicki
24e27375a4 APIv2(DeliverMax): add alias for Amount in Payment transactions (#4733)
Using the "Amount" field in Payment transactions can cause incorrect
interpretation. There continue to be problems from the use of this
field. "Amount" is rarely the correct field to use; instead,
"delivered_amount" (or "DeliveredAmount") should be used.

Rename the "Amount" field to "DeliverMax", a less misleading name. With
api_version: 2, remove the "Amount" field from Payment transactions.

- Input: "DeliverMax" in `tx_json` is an alias for "Amount"
  - sign
  - submit (in sign-and-submit mode)
  - submit_multisigned
  - sign_for
- Output: Add "DeliverMax" where transactions are provided by the API
  - ledger
  - tx
  - tx_history
  - account_tx
  - transaction_entry
  - subscribe (transactions stream)
- Output: Remove "Amount" from API version 2

Fix #3484

Fix #3902
2026-02-20 06:54:59 +09:00
Mayukha Vadari
74432a37bb DID: Decentralized identifiers (DIDs) (XLS-40): (#4636)
Implement native support for W3C DIDs.

Add a new ledger object: `DID`.

Add two new transactions:
1. `DIDSet`: create or update the `DID` object.
2. `DIDDelete`: delete the `DID` object.

This meets the requirements specified in the DID v1.0 specification
currently recommended by the W3C Credentials Community Group.

The DID format for the XRP Ledger conforms to W3C DID standards.
The objects can be created and owned by any XRPL account holder.
The transactions can be integrated by any service, wallet, or application.
2026-02-20 06:54:47 +09:00
Scott Schurr
ca591b7c1d refactor(peerfinder): use LogicError in PeerFinder::Logic (#4562)
It might be possible for the server code to indirect through certain
`end()` iterators. While a debug build would catch this problem with
`assert()`s, a release build would crash. If there are problems in this
area in the future, it is best to get a definitive indication of the
nature of the error regardless of whether it's a debug or release build.
To accomplish this, these `assert`s are converted into `LogicError`s
that will produce a reasonable error message when they fire.
2026-02-20 06:48:56 +09:00
Ed Hennis
9b6c7c647a fix(PathRequest): remove incorrect assert (#4743)
The assert is saying that the only reason `pathFinder` would be null is
if the request was aborted (connection dropped, etc.). That's what
`continueCallback()` checks. But that is very clearly not true if you
look at `getPathFinder`, which calls `findPaths`, which can return false
for many reasons.

Fix #4744
2026-02-20 06:48:55 +09:00
Ed Hennis
4d43e109a5 docs(API-CHANGELOG): add XRPFees change (#4741)
* Add a new API Changelog section for release 1.10.
* Mark `jss::fee_ref` as deprecated.
* Fix a copy-paste error in one of the unit tests.
2026-02-20 06:48:54 +09:00
Florent
b8398cf004 docs(rippled-example.cfg): add P2P link compression (#4753)
P2P link compression is a feature added in 1.6.0 by #3287.

https://xrpl.org/enable-link-compression.html

If the default changes in the future - for example, as currently
proposed by #4387 - the comment will be updated at that time.

Fix #4656
2026-02-20 06:48:54 +09:00
Denis Angell
26fc5422f9 fixDisallowIncomingV1: allow issuers to authorize trust lines (#4721)
Context: The `DisallowIncoming` amendment provides an option to block
incoming trust lines from reaching your account. The
asfDisallowIncomingTrustline AccountSet Flag, when enabled, prevents any
incoming trust line from being created. However, it was too restrictive:
it would block an issuer from authorizing a trust line, even if the
trust line already exists. Consider:

1. Issuer sets asfRequireAuth on their account.
2. User sets asfDisallowIncomingTrustline on their account.
3. User submits tx to SetTrust to Issuer.

At this point, without `fixDisallowIncomingV1` active, the issuer would
not be able to authorize the trust line.

The `fixDisallowIncomingV1` amendment, once activated, allows an issuer
to authorize a trust line even after the user sets the
asfDisallowIncomingTrustline flag, as long as the trust line already
exists.
2026-02-20 06:48:53 +09:00
Scott Determan
cbb15ab000 refactor: reduce boilerplate in applySteps: (#4710)
When a new transactor is added, there are several places in applySteps
that need to be modified. This patch refactors the code so only one
function needs to be modified.
2026-02-20 06:48:53 +09:00
Rome Reginelli
35898efb13 refactor: reunify transaction common fields: (#4715)
Make transactions and pseudo-transactions share the same commonFields
again. This regularizes the code in a nice way.

While this technically allows pseudo-transactions to have a
TicketSequence field, pseudo-transactions are only ever constructed by
code paths that don't add such a field, so this is not a transaction
processing change. It may be possible to add a separate check to ensure
TicketSequence (and other fields that don't make sense on
pseudo-transactions) are never added to pseudo-transactions, but that
should not be necessary. (TicketSequence is not the only common field
that can not and does not appear in pseudo-transactions.) Note:
TicketSequence is already documented as a common field.

Related: #4637

Fix #4714
2026-02-20 06:48:52 +09:00
Chenna Keshava B S
3517ccb604 docs(BUILD.md): require GCC 11 or higher (#4700)
Update minimum compiler requirement for building the codebase. The
feature "using enum" is required. This feature was introduced in C++20.

Updating the C++ compiler to version 11 or later fixes this error:

```
Building CXX object CMakeFiles/xrpl_core.dir/src/ripple/protocol/impl/STAmount.cpp.o
/build/ripple/binary/src/ripple/protocol/impl/STAmount.cpp: In lambda function:
/build/ripple/binary/src/ripple/protocol/impl/STAmount.cpp:1577:15: error: expected nested-name-specifier before 'enum'
 1577 |         using enum Number::rounding_mode;
      |               ^~~~
```

Fix #4693
2026-02-20 06:48:51 +09:00
Scott Determan
95684df001 fix(XLS-38): disallow the same bridge on one chain: (#4720)
Modify the `XChainBridge` amendment.

Before this patch, two door accounts on the same chain could could own
the same bridge spec (of course, one would have to be the issuer and one
would have to be the locker). While this is silly, it does not violate
any bridge invariants. However, on further review, if we allow this then
the `claim` transactions would need to change. Since it's hard to see a
use case for two doors to own the same bridge, this patch disallows
it. (The transaction will return tecDUPLICATE).
2026-02-20 06:48:51 +09:00
Scott Schurr
6c1a17a8bd fix: stabilize voting threshold for amendment majority mechanism (#4410)
Amendment "flapping" (an amendment repeatedly gaining and losing
majority) usually occurs when an amendment is on the verge of gaining
majority, and a validator not in favor of the amendment goes offline or
loses sync. This fix makes two changes:

1. The number of validators in the UNL determines the threshold required
   for an amendment to gain majority.
2. The AmendmentTable keeps a record of the most recent Amendment vote
   received from each trusted validator (and, with `trustChanged`, stays
   up-to-date when the set of trusted validators changes). If no
   validation arrives from a given validator, then the AmendmentTable
   assumes that the previously-received vote has not changed.

In other words, when missing an `STValidation` from a remote validator,
each server now uses the last vote seen. There is a 24 hour timeout for
recorded validator votes.

These changes do not require an amendment because they do not impact
transaction processing, but only the threshold at which each individual
validator decides to propose an EnableAmendment pseudo-transaction.

Fix #4350
2026-02-20 06:48:50 +09:00
Ed Hennis
1cdbf27643 fix(build): uint is not defined on Windows platform (#4731)
Fix the Windows build by using `unsigned int` (instead of `uint`).

The error, introduced by #4618, looks something like:
  rpc\impl\RPCHelpers.h(299,5): error C2061: syntax error: identifier
  'uint' (compiling source file app\ledger\Ledger.cpp)
2026-02-20 06:48:50 +09:00
Nik Bougalis
b0a82fd8e4 Eliminate the built-in SNTP support (fixes #4207): (#4628) 2026-02-20 06:48:49 +09:00
John Freeman
9bfea81de4 fix: accept all valid currency codes in API (#4566)
A few methods, including `book_offers`, take currency codes as
parameters. The XRPL doesn't care if the letters in those codes are
lowercase or uppercase, as long as they come from an alphabet defined
internally. rippled doesn't care either, when they are submitted in a
hex representation. When they are submitted in an ASCII string
representation, rippled, but not XRPL, is more restrictive, preventing
clients from interacting with some currencies already in the XRPL.

This change gets rippled out of the way and lets clients submit currency
codes in ASCII using the full alphabet.

Fixes #4112
2026-02-20 06:48:48 +09:00
Bronek Kozicki
f170d4675e chore: add .build to .gitignore (#4722)
Currently, the `BUILD.md` instructions suggest using `.build` as the
build directory, so this change helps to reduce confusion.

An alternative would be to instruct developers to add `/.build/` to
`.git/info/exclude` or to user-level `.gitignore` (although the latter
is very intrusive). However, it is being added here because it is a good
practice to have a sensible default that's consistent with the build
instructions.
2026-02-20 06:48:48 +09:00
ForwardSlashBack
f6a016281f Fix typo in BUILD.md (#4718)
Co-authored-by: Chenna Keshava B S <21219765+ckeshava@users.noreply.github.com>
2026-02-20 06:48:47 +09:00
Peter Chen
d5fde211a3 APIv2(gateway_balances, channel_authorize): update errors (#4618)
gateway_balances
* When `account` does not exist in the ledger, return `actNotFound`
  * (Previously, a normal response was returned)
  * Fix #4290
* When required field(s) are missing, return `invalidParams`
  * (Previously, `invalidHotWallet` was incorrectly returned)
  * Fix #4548

channel_authorize
* When the specified `key_type` is invalid, return `badKeyType`
  * (Previously, `invalidParams` was returned)
  * Fix #4289

Since these are breaking changes, they apply only to API version 2.

Supersedes #4577
2026-02-20 06:48:47 +09:00
John Freeman
810b464fe0 build: use Boost 1.82 and link Boost.Json (#4632)
Add Boost::json to the list of linked Boost libraries.

This seems to be required for macOS.
2026-02-20 06:48:46 +09:00
Chenna Keshava B S
1b87026411 docs(overlay): add URL of blog post and clarify wording (#4635) 2026-02-20 06:48:45 +09:00
Elliot Lee
b63b822e21 docs(RELEASENOTES): update 1.12.0 notes to match dev blog (#4691)
* Reorganize some changelog entries
* Add note about portable binaries
* Dev blog: https://xrpl.org/blog
2026-02-20 06:48:45 +09:00
John Freeman
c7e6d475e4 Update secp256k1 to 0.3.2 (#4653)
Copy the new code to `src/secp256k1` without changes:
`src/secp256k1` is identical to bitcoin-core/secp256k1@acf5c55 (v0.3.2).

We could consider changing to a Git submodule, though that would require
changes to the build instructions because we are not using submodules
anywhere else.
2026-02-20 06:48:44 +09:00
Chenna Keshava B S
4812f6abb4 docs: fix comment for LedgerHistory::fixIndex return value (#4574)
`LedgerHistory::fixIndex` returns `false` if a repair was performed.

Fix #4572
2026-02-20 06:48:43 +09:00
Ed Hennis
1b6a2a39ff fix: remove unused variable causing clang 14 build errors (#4672)
Removed the unused variable `none` from `Writer.cpp` which was causing
build errors on clang version 14.
2026-02-20 06:48:43 +09:00
Elliot Lee
dfc722aa05 docs(BUILD): make it easier to find environment.md (#4507)
Make the instructions a bit easier to follow. Users on different
platforms can look for their platform name to find relevant information.
2026-02-20 06:48:42 +09:00
Michael Legleux
f16f8d00a4 Revert CMake changes (#4707)
This was likely put back when #4292 was rebased.
2026-02-20 06:48:42 +09:00
Scott Determan
babc37072f Change XChainBridge amendment to Supported::yes (#4709) 2026-02-20 06:48:41 +09:00
Scott Determan
7c46fc37a4 Fix Windows build by removing two unused declarations (#4708)
Remove the `verify` and `message` function declarations. The explicit
instantiation requests could not be completed because there were no
implementations for those two member functions. It is helpful that the
Microsoft (MSVC) compiler on Windows appears to be strict when it comes
to template instantiation.

This resolves the warning:

  XChainAttestations.h(450): warning C4661: 'bool
  ripple::XChainAttestationsBase<ripple::XChainClaimAttestation>::verify(void)
  const': no suitable definition provided for explicit template
  instantiation request
2026-02-20 06:48:40 +09:00
Howard Hinnant
8894f1f4f5 Revert ThreadName due to problems on Windows (#4702)
* Revert "Remove CurrentThreadName.h from RippledCore.cmake (#4697)"

This reverts commit 3b5fcd587313f5ebc762bc21c6a4ec3e6c275e83.

* Revert "Introduce replacement for getting and setting thread name: (#4312)"

This reverts commit 36cb5f90e233f975eb3f80d819b2fbadab0a9387.
2026-02-20 06:48:40 +09:00
Scott Determan
8a14bd4cfe XChainBridge: Introduce sidechain support (XLS-38): (#4292)
A bridge connects two blockchains: a locking chain and an issuing
chain (also called a mainchain and a sidechain). Both are independent
ledgers, with their own validators and potentially their own custom
transactions. Importantly, there is a way to move assets from the
locking chain to the issuing chain and a way to return those assets from
the issuing chain back to the locking chain: the bridge. This key
operation is called a cross-chain transfer. A cross-chain transfer is
not a single transaction. It happens on two chains, requires multiple
transactions, and involves an additional server type called a "witness".

A bridge does not exchange assets between two ledgers. Instead, it locks
assets on one ledger (the "locking chain") and represents those assets
with wrapped assets on another chain (the "issuing chain"). A good model
to keep in mind is a box with an infinite supply of wrapped assets.
Putting an asset from the locking chain into the box will release a
wrapped asset onto the issuing chain. Putting a wrapped asset from the
issuing chain back into the box will release one of the existing locking
chain assets back onto the locking chain. There is no other way to get
assets into or out of the box. Note that there is no way for the box to
"run out of" wrapped assets - it has an infinite supply.

Co-authored-by: Gregory Popovitch <greg7mdp@gmail.com>
2026-02-20 06:48:10 +09:00
Peter Chen
0ed1da79ce APIv2(account_tx, noripple_check): return error on invalid input (#4620)
For the `account_tx` and `noripple_check` methods, perform input
validation for optional parameters such as "binary", "forward",
"strict", "transactions". Previously, when these parameters had invalid
values (e.g. not a bool), no error would be returned. Now, it returns an
`invalidParams` error.

* This updates the behavior to match Clio
  (https://github.com/XRPLF/clio).
* Since this is potentially a breaking change, it only applies to
  requests specifying api_version: 2.
* Fix #4543.
2026-02-20 06:41:31 +09:00
Howard Hinnant
f44998cd8c Remove CurrentThreadName.h from RippledCore.cmake (#4697)
(File was already removed from the source)
2026-02-20 06:41:30 +09:00
Mayukha Vadari
a865e93312 refactor: simplify TxFormats common fields logic (#4637)
Minor refactor to `TxFormats.cpp`:
- Rename `commonFields` to `pseudoCommonFields` (since it is the common fields
  that all pseudo-transactions need)
- Add a new static variable, `commonFields`, which represents all the common
  fields that non-pseudo transactions need (essentially everything that
  `pseudoCommonFields` contains, plus `sfTicketSequence`)

This makes it harder to accidentally leave out `sfTicketSequence` in a new
transaction.
2026-02-20 06:41:30 +09:00
Peter Chen
ce09fedc2c APIv2(ledger_entry): return invalidParams for bad parameters (#4630)
- Verify "check", used to retrieve a Check object, is a string.
- Verify "nft_page", used to retrieve an NFT Page, is a string.
- Verify "index", used to retrieve any type of ledger object by its
  unique ID, is a string.
- Verify "directory", used to retrieve a DirectoryNode, is a string or
  an object.

This change only impacts api_version 2 since it is a breaking change.

https://xrpl.org/ledger_entry.html

Fix #4550
2026-02-20 06:41:29 +09:00
Mark Pevec
f3de28f9b7 docs(rippled-example.cfg): clarify ssl_cert vs ssl_chain (#4667)
Clarify usage of ssl_cert vs ssl_chain
2026-02-20 06:41:28 +09:00
Howard Hinnant
dd9b0953b8 Introduce replacement for getting and setting thread name: (#4312)
* In namespace ripple, introduces get_name function that takes a
  std:🧵:native_handle_type and returns a std::string.
* In namespace ripple, introduces get_name function that takes a
  std::thread or std::jthread and returns a std::string.
* In namespace ripple::this_thread, introduces get_name function
  that takes no parameters and returns the name of the current
  thread as a std::string.
* In namespace ripple::this_thread, introduces set_name function
  that takes a std::string_view and sets the name of the current
  thread.
* Intended to replace the beast utilities setCurrentThreadName
  and getCurrentThreadName.
2026-02-20 06:41:28 +09:00
John Freeman
2ab5d729cc Update dependencies (#4595)
Use the most recent versions in ConanCenter.

* Due to a bug in Clang 16, you may get a compile error:
  "call to 'async_teardown' is ambiguous"
  * A compiler flag workaround is documented in `BUILD.md`.
* At this time, building this with gcc 13 may require editing some files
  in `.conan/data`
  * A patch to support gcc13 may be added in a later PR.

---------

Co-authored-by: Scott Schurr <scott@ripple.com>
2026-02-20 06:41:27 +09:00
Gregory Tsipenyuk
b2b126b51a amm_info: fetch by amm account id; add AMM object entry (#4682)
- Update amm_info to fetch AMM by amm account id.
  - This is an additional way to retrieve an AMM object.
  - Alternatively, AMM can still be fetched by the asset pair as well.
- Add owner directory entry for AMM object.

Context:

- Add back the AMM object directory entry, which was deleted by #4626.
  - This fixes `account_objects` for `amm` type.
2026-02-20 06:41:27 +09:00
Rome Reginelli
4713dcf8c8 AMMBid: use tecINTERNAL for 'impossible' errors (#4674)
Modify two error cases in AMMBid transactor to return `tecINTERNAL` to
more clearly indicate that these errors should not be possible unless
operating in unforeseen circumstances. It likely indicates a bug.

The log level has been updated to `fatal()` since it indicates a
(potentially network-wide) unexpected condition when either of these
errors occurs.

Details:

The two specific transaction error cases changed are:

- `tecAMM_BALANCE` - In this case, this error (total LP Tokens
  outstanding is lower than the amount to be burned for the bid) is a
  subset of the case where the user doesn't have enough LP Tokens to pay
  for the bid. When this case is reached, the bidder's LP Tokens balance
  has already been checked first. The user's LP Tokens should always be
  a subset of total LP Tokens issued, so this should be impossible.
- `tecINSUFFICIENT_PAYMENT` - In this case, the amount to be refunded as
  a result of the bid is greater than the price paid for the auction
  slot. This should never occur unless something is wrong with the math
  for calculating the refund amount.

Both error cases in question are "defense in depth" measures meant to
protect against making things worse if the code has already reached a
state that is supposed to be impossible, likely due to a bug elsewhere.

Such "shouldn't ever occur" checks should use an error code that
categorically indicates a larger problem. This is similar to how
`tecINVARIANT_FAILED` is a warning sign that something went wrong and
likely could've been worse, but since there isn't an Invariant Check
applying here, `tecINTERNAL` is the appropriate error code.

This is "debatably" a transaction processing change since it could
hypothetically change how transactions are processed if there's a bug we
don't know about.
2026-02-20 06:41:26 +09:00
Ikko Eltociear Ashimine
b5beacfc54 refactor: fix typo in FeeUnits.h (#4644)
covert -> convert
2026-02-20 06:41:25 +09:00
Arihant Kothari
e9ac727cc1 test: add forAllApiVersions helper function (#4611)
Introduce a new variadic template helper function, `forAllApiVersions`,
that accepts callables to execute a set of functions over a range of
versions - from RPC::apiMinimumSupportedVersion to RPC::apiBetaVersion.
This avoids the duplication of code.

Context: #4552
2026-02-20 06:41:25 +09:00
Mayukha Vadari
d5b6f7ef1a add view updates for account SLEs (#4629)
Signed-off-by: Manoj Doshi <mdoshi@ripple.com>
2026-02-20 06:41:24 +09:00
John Freeman
8aa90a1f3d Fix the package recipe for consumers of libxrpl (#4631)
- "Rename" the type `LedgerInfo` to `LedgerHeader` (but leave an alias
  for `LedgerInfo` to not yet disturb existing uses). Put it in its own
  public header, named after itself, so that it is more easily found.
- Move the type `Fees` and NFT serialization functions into public
  (installed) headers.
- Compile the XRPL and gRPC protocol buffers directly into `libxrpl` and
  install their headers. Fix the Conan recipe to correctly export these
  types.

Addresses change (2) in
https://github.com/XRPLF/XRPL-Standards/discussions/121.

For context: This work supports Clio's dependence on libxrpl. Clio is
just an example consumer. These changes should benefit all current and
future consumers.

---------

Co-authored-by: cyan317 <120398799+cindyyan317@users.noreply.github.com>
Signed-off-by: Manoj Doshi <mdoshi@ripple.com>
2026-02-20 06:41:24 +09:00
John Freeman
7750032b50 Fix package definition for Conan (#4485)
Fix the libxrpl library target for consumers using Conan.

* Fix installation issues and update includes.
* Update requirements in the Conan package info.
  * libxrpl requires openssl::crypto.

(Conan is a software package manager for C++.)
2026-02-20 06:41:23 +09:00
Alphonse Noni Mousse
c9ddee83c2 refactor: improve checking of path lengths (#4519)
Improve the checking of the path lengths during Payments. Previously,
the code that did the check of the payment path lengths was sometimes
executed, but without any effect. This changes it to only check when it
matters, and to not make unnecessary copies of the path vectors.

Signed-off-by: Manoj Doshi <mdoshi@ripple.com>
2026-02-20 06:41:22 +09:00
Alphonse N. Mousse
8b3c65cf76 refactor: use C++20 function std::popcount (#4389)
- Replace custom popcnt16 implementation with std::popcount from C++20
- Maintain compatibility with older compilers and MacOS by providing a
  conditional compilation fallback to __builtin_popcount and a lookup
  table method
- Move and inline related functions within SHAMapInnerNode for
  performance and readability

Signed-off-by: Manoj Doshi <mdoshi@ripple.com>
2026-02-20 06:41:22 +09:00
Gregory Tsipenyuk
200a635499 fix(AMM): prevent orphaned objects, inconsistent ledger state: (#4626)
When an AMM account is deleted, the owner directory entries must be
deleted in order to ensure consistent ledger state.

* When deleting AMM account:
  * Clean up AMM owner dir, linking AMM account and AMM object
  * Delete trust lines to AMM
* Disallow `CheckCreate` to AMM accounts
  * AMM cannot cash a check
* Constrain entries in AuthAccounts array to be accounts
  * AuthAccounts is an array of objects for the AMMBid transaction
* SetTrust (TrustSet): Allow on AMM only for LP tokens
  * If the destination is an AMM account and the trust line doesn't
    exist, then:
    * If the asset is not the AMM LP token, then fail the tx with
      `tecNO_PERMISSION`
    * If the AMM is in empty state, then fail the tx with `tecAMM_EMPTY`
      * This disallows trustlines to AMM in empty state
* Add AMMID to AMM root account
  * Remove lsfAMM flag and use sfAMMID instead
* Remove owner dir entry for ltAMM
* Add `AMMDelete` transaction type to handle amortized deletion
  * Limit number of trust lines to delete on final withdraw + AMMDelete
  * Put AMM in empty state when LPTokens is 0 upon final withdraw
  * Add `tfTwoAssetIfEmpty` deposit option in AMM empty state
  * Fail all AMM transactions in AMM empty state except special deposit
  * Add `tecINCOMPLETE` to indicate that not all AMM trust lines are
    deleted (i.e. partial deletion)
    * This is handled in Transactor similar to deleted offers
  * Fail AMMDelete with `tecINTERNAL` if AMM root account is nullptr
  * Don't validate for invalid asset pair in AMMDelete
* AMMWithdraw deletes AMM trust lines and AMM account/object only if the
  number of trust lines is less than max
  * Current `maxDeletableAMMTrustLines` = 512
  * Check no directory left after AMM trust lines are deleted
  * Enable partial trustline deletion in AMMWithdraw
* Add `tecAMM_NOT_EMPTY` to fail any transaction that expects an AMM in
  empty state
* Clawback considerations
  * Disallow clawback out of AMM account
  * Disallow AMM create if issuer can claw back

This patch applies to the AMM implementation in #4294.

Acknowledgements:
Richard Holland and Nik Bougalis for responsibly disclosing this issue.

Bug Bounties and Responsible Disclosures:
We welcome reviews of the project code and urge researchers to
responsibly disclose any issues they may find.

To report a bug, please send a detailed report to:

    bugs@xrpl.org

Signed-off-by: Manoj Doshi <mdoshi@ripple.com>
2026-02-20 06:40:58 +09:00
RichardAH
802d9096d8 feat: support Concise Transaction Identifier (CTID) (XLS-37) (#4418)
* add CTIM to tx rpc

---------

Co-authored-by: Rome Reginelli <mduo13@gmail.com>
Co-authored-by: Elliot Lee <github.public@intelliot.com>
Co-authored-by: Denis Angell <dangell@transia.co>
2026-02-20 06:38:40 +09:00
Gregory Tsipenyuk
233f060b0b Introduce AMM support (XLS-30d): (#4294)
Add AMM functionality:
- InstanceCreate
- Deposit
- Withdraw
- Governance
- Auctioning
- payment engine integration

To support this functionality, add:
- New RPC method, `amm_info`, to fetch pool and LPT balances
- AMM Root Account
- trust line for each IOU AMM token
- trust line to track Liquidity Provider Tokens (LPT)
- `ltAMM` object

The `ltAMM` object tracks:
- fee votes
- auction slot bids
- AMM tokens pair
- total outstanding tokens balance
- `AMMID` to AMM `RootAccountID` mapping

Add new classes to facilitate AMM integration into the payment engine.
`BookStep` uses these classes to infer if AMM liquidity can be consumed.

The AMM formula implementation uses the new Number class added in #4192.
IOUAmount and STAmount use Number arithmetic.

Add AMM unit tests for all features.

AMM requires the following amendments:
- featureAMM
- fixUniversalNumber
- featureFlowCross

Notes:
- Current trading fee threshold is 1%
- AMM currency is generated by: 0x03 + 152 bits of sha256{cur1, cur2}
- Current max AMM Offers is 30

---------

Co-authored-by: Howard Hinnant <howard.hinnant@gmail.com>
2026-02-20 06:38:25 +09:00
Arihant Kothari
ac415e24fb APIv2(ledger_entry): return "invalidParams" when fields missing (#4552)
Improve error handling for ledger_entry by returning an "invalidParams"
error when one or more request fields are specified incorrectly, or one
or more required fields are missing.

For example, if none of of the following fields is provided, then the
API should return an invalidParams error:
* index, account_root, directory, offer, ripple_state, check, escrow,
  payment_channel, deposit_preauth, ticket

Prior to this commit, the API returned an "unknownOption" error instead.
Since the error was actually due to invalid parameters, rather than
unknown options, this error was misleading.

Since this is an API breaking change, the "invalidParams" error is only
returned for requests using api_version: 2 and above. To maintain
backward compatibility, the "unknownOption" error is still returned for
api_version: 1.

Related: #4573

Fix #4303
2026-02-19 14:14:30 +09:00
Chenna Keshava B S
706b0d2e31 refactor: change the return type of mulDiv to std::optional (#4243)
- Previously, mulDiv had `std::pair<bool, uint64_t>` as the output type.
  - This is an error-prone interface as it is easy to ignore when
    overflow occurs.
- Using a return type of `std::optional` should decrease the likelihood
  of ignoring overflow.
  - It also allows for the use of optional::value_or() as a way to
    explicitly recover from overflow.
- Include limits.h header file preprocessing directive in order to
  satisfy gcc's numeric_limits incomplete_type requirement.

Fix #3495

---------

Co-authored-by: John Freeman <jfreeman08@gmail.com>
2026-02-19 14:06:18 +09:00
Peter Chen
4085036e25 APIv2(account_info): handle invalid "signer_lists" value (#4585)
When requesting `account_info` with an invalid `signer_lists` value, the
API should return an "invalidParams" error.

`signer_lists` should have a value of type boolean. If it is not a
boolean, then it is invalid input. The response now indicates that.

* This is an API breaking change, so the change is only reflected for
  requests containing `"api_version": 2`
* Fix #4539
2026-02-19 14:05:56 +09:00
Chenna Keshava B S
21cacbfd5f fix: Update Handler::Condition enum values #3417 (#4239)
- Use powers of two to clearly indicate the bitmask
- Replace bitmask with explicit if-conditions to better indicate predicates

Change enum values to be powers of two (fix #3417) #4239

Implement the simplified condition evaluation
removes the complex bitwise and(&) operator
Implement the second proposed solution in Nik Bougalis's comment - Software does not distinguish between different Conditions (Version: 1.5) #3417 (comment)
I have tested this code change by performing RPC calls with the commands server_info, server_state, peers and validation_info. These commands worked as expected.
2026-02-19 14:05:55 +09:00
Peter Chen
e0566cc22a APIv2: add error messages for account_tx (#4571)
Certain inputs for the AccountTx method should return an error. In other
words, an invalid request from a user or client now results in an error
message.

Since this can change the response from the API, it is an API breaking
change. This commit maintains backward compatibility by keeping the
existing behavior for existing requests. When clients specify
"api_version": 2, they will be able to get the updated error messages.

Update unit tests to check the error based on the API version.

* Fix #4288
* Fix #4545
2026-02-19 14:05:55 +09:00
Ed Hennis
6e1a763806 Fix build references to deleted ServerHandlerImp: (#4592)
* Commits 0b812cd (#4427) and 11e914f (#4516) conflict. The first added
  references to `ServerHandlerImp` in files outside of that class's
  organizational unit (which is technically incorrect). The second
  removed `ServerHandlerImp`, but was not up to date with develop. This
  results in the build failing.
* Fixes the build by changing references to `ServerHandlerImp` to
  the more correct `ServerHandler`.
2026-02-19 14:05:54 +09:00
Scott Schurr
ff17f3190a refactor: rename ServerHandlerImp to ServerHandler (#4516)
Rename `ServerHandlerImp` to `ServerHandler`. There was no other
ServerHandler definition despite the existence of a header suggesting
that there was.

This resolves a piece of historical confusion in the code, which was
identified during a code review.

The changes in the diff may look more extensive than they actually are.
The contents of `impl/ServerHandlerImp.h` were merged into
`ServerHandler.h`, making the latter file appear to have undergone
significant modifications. However, this is a non-breaking refactor that
only restructures code.
2026-02-19 14:05:54 +09:00
Chenna Keshava B S
9554f1efb9 fix: remove deprecated fields in ledger method (#4244)
Remove deprecated fields from the ledger command:
* accepted
* hash (use ledger_hash instead)
* seqNum (use ledger_index instead)
* totalCoins (use total_coins instead)

Update SHAMapStore unit tests to use `jss:ledger_hash` instead of the
deprecated `hash` field.

Fix #3214
2026-02-19 14:05:53 +09:00
Denis Angell
154d07a5cc refactor: replace hand-rolled lexicalCast (#4473)
Replace hand-rolled code with std::from_chars for better
maintainability.

The C++ std::from_chars function is intended to be as fast as possible,
so it is unlikely to be slower than the code it replaces. This change is
a net gain because it reduces the amount of hand-rolled code.
2026-02-19 14:05:52 +09:00
Howard Hinnant
1fa8f8f087 refactor: remove TypedField's move constructor (#4567)
Apply a minor cleanup in `TypedField`:
* Remove a non-working and unused move constructor.
* Constrain the remaining constructor to not be overly generic enough as
  to be used as a copy or move constructor.
2026-02-19 14:03:45 +09:00
drlongle
045b07f979 Add RPC/WS ports to server_info (#4427)
Enhance the /crawl endpoint by publishing WebSocket/RPC ports in the
server_info response. The function processing requests to the /crawl
endpoint actually calls server_info internally, so this change enables a
server to advertise its WebSocket/RPC port(s) to peers via the /crawl
endpoint. `grpc` and `peer` ports are included as well.

The new `ports` array contains objects, each containing a `port` for the
listening port (number string), and an array `protocol` listing the
supported protocol(s).

This allows crawlers to build a richer topology without needing to
port-scan nodes. For non-admin users (including peers), the info about
*admin* ports is excluded.

Also increase test coverage for RPC ServerInfo.

Fix #2837.
2026-02-19 14:03:45 +09:00
Scott Schurr
711297aa81 fixReducedOffersV1: prevent offers from blocking order books: (#4512)
Curtail the occurrence of order books that are blocked by reduced offers
with the implementation of the fixReducedOffersV1 amendment.

This commit identifies three ways in which offers can be reduced:

1. A new offer can be partially crossed by existing offers, so the new
   offer is reduced when placed in the ledger.

2. An in-ledger offer can be partially crossed by a new offer in a
   transaction. So the in-ledger offer is reduced by the new offer.

3. An in-ledger offer may be under-funded. In this case the in-ledger
   offer is scaled down to match the available funds.

Reduced offers can block order books if the effective quality of the
reduced offer is worse than the quality of the original offer (from the
perspective of the taker). It turns out that, for small values, the
quality of the reduced offer can be significantly affected by the
rounding mode used during scaling computations.

This commit adjusts some rounding modes so that the quality of a reduced
offer is always at least as good (from the taker's perspective) as the
original offer.

The amendment is titled fixReducedOffersV1 because additional ways of
producing reduced offers may come to light. Therefore, there may be a
future need for a V2 amendment.
2026-02-19 14:03:41 +09:00
Ed Hennis
bb70739fc6 Enable the Beta RPC API (v2) for all unit tests: (#4573)
* Enable api_version 2, which is currently in beta. It is expected to be
  marked stable by the next stable release.
* This does not change any defaults.
* The only existing tests changed were one that set the same flag, which
  was now redundant, and a couple that tested versioning explicitly.
2026-02-19 13:52:52 +09:00
Nicholas Dudfield
02990eb4ee Merge remote-tracking branch 'origin/dev' into consensus-phase-entropy
# Conflicts:
#	hook/extern.h
#	src/ripple/app/hook/hook_api.macro
#	src/ripple/protocol/Feature.h
#	src/ripple/protocol/impl/Feature.cpp
2026-02-19 10:57:40 +07:00
Nicholas Dudfield
ce76632322 Merge remote-tracking branch 'origin/dev' into export-uvtxn
# Conflicts:
#	Builds/CMake/RippledCore.cmake
#	hook/extern.h
#	src/ripple/app/hook/Guard.h
#	src/ripple/app/hook/applyHook.h
#	src/ripple/app/hook/guard_checker.cpp
#	src/ripple/app/tx/impl/Change.cpp
#	src/ripple/app/tx/impl/SetHook.cpp
#	src/ripple/protocol/Feature.h
#	src/ripple/protocol/impl/Feature.cpp
#	src/ripple/protocol/jss.h
2026-02-19 10:12:55 +07:00
Nicholas Dudfield
9eac54d690 Merge remote-tracking branch 'origin/dev' into consensus-phase-entropy
# Conflicts:
#	src/ripple/app/hook/Guard.h
#	src/ripple/app/hook/applyHook.h
#	src/ripple/app/tx/impl/SetHook.cpp
2026-02-17 10:12:46 +07:00
Nicholas Dudfield
24e4ac16ad docs(consensus): add extraction markers for remaining RNG sections
Add @@start/@@end comment markers to pseudo-tx submission filtering,
fast-polling, local testnet resource bucketing, and test environment
gating. No logic changes.
2026-02-13 12:52:14 +07:00
Nicholas Dudfield
94ce15d233 docs(consensus): add extraction markers for guided code review
Add @@start/@@end comment markers to key RNG pipeline sections for
automated documentation extraction. No logic changes.
2026-02-13 12:47:22 +07:00
Nicholas Dudfield
8f331a538e fix(consensus): harden proposal parser and guard dice(0) UB
Address findings from code review:

- dice(0): add early return with INVALID_ARGUMENT before modulo
  operation to prevent undefined behavior
- fromSerialIter: return std::optional to safely reject malformed
  payloads (truncated, unknown flag bits, trailing bytes) instead
  of throwing
- Update all callers (PeerImp, RCLConsensus, tests) for optional
- Add unit tests for dice(0) error code and 7 malformed wire cases
2026-02-12 16:18:30 +07:00
Nicholas Dudfield
7425ab0a39 fix(consensus): avoid structured bindings in lambda captures
clang-14 (CI) does not implement P2036R3 — structured bindings cannot
be captured by lambdas. Use explicit .first/.second instead.
2026-02-10 19:02:42 +07:00
Nicholas Dudfield
c5292bfe0d fix(test): use large dice range to avoid deterministic collision
Standalone synthetic entropy produces identical dice(6) results for
consecutive calls due to hash collision mod 6. Switch to dice(1000000)
and add diagnostic output for return code debugging.
2026-02-10 18:53:24 +07:00
Nicholas Dudfield
79b2f9f410 feat(hooks): add consensus entropy definitions to hook headers
Add dice/random externs, TOO_LITTLE_ENTROPY error code, sfEntropyCount
field code, and ttCONSENSUS_ENTROPY transaction type to hook SDK headers.
2026-02-10 18:53:16 +07:00
Nicholas Dudfield
e8358a82b1 feat(hooks): register dice/random with WasmEdge and add hook API tests
- ADD_HOOK_FUNCTION for dice/random (was defined+declared but not registered)
- Relax fairRng() seq check to allow previous ledger entropy (open ledger)
- Add hook tests: dice range, random fill, consecutive calls differ
- TODO: open-ledger entropy semantics need further thought
2026-02-10 17:58:52 +07:00
Nicholas Dudfield
d850e740e1 feat(consensus): standalone synthetic entropy and ConsensusEntropy test
Generate deterministic entropy in standalone mode so Hook APIs (dice/random)
work for testing. Add test suite verifying SLE creation on ledger close.
2026-02-10 17:27:57 +07:00
Nicholas Dudfield
61a166bcb0 feat(hooks): add dice() and random() hook APIs for consensus entropy
Port the Hook API surface from the tt-rng branch, adapted to use our
commit-reveal consensus entropy (ltCONSENSUS_ENTROPY / sfDigest).

Hook APIs:
- dice(sides): returns random int [0, sides) from consensus entropy
- random(write_ptr, write_len): fills buffer with 1-512 random bytes

Internal fairRng() derives per-execution entropy by hashing: ledger
seq + tx ID + hook hash + account + chain position + execution phase
+ consensus entropy + incrementing call counter. This ensures each
call within a single hook execution returns different values.

Quality gate: fairRng returns empty (TOO_LITTLE_ENTROPY) if fewer
than 5 validators contributed, preventing weak entropy from being
consumed by hooks.

Also adds sfEntropyCount and sfLedgerSequence to the consensus
entropy SLE and pseudo-tx, enabling the freshness and quality
checks needed by the Hook API.
2026-02-10 17:12:27 +07:00
Nicholas Dudfield
41a41ec625 feat(consensus): intersect expected proposers with UNL Report and adaptive quorum
setExpectedProposers() now filters incoming proposers against the
on-chain UNL Report, preventing non-UNL nodes from inflating the
expected set and causing unnecessary timeouts.

quorumThreshold() uses expectedProposers_.size() (recent proposers ∩
UNL) when available, falling back to full UNL Report count on cold
boot. This adapts to actual network conditions rather than relying
on a potentially stale UNL Report that over-counts offline validators.

Renamed activeUNLNodeIds_/cacheActiveUNL/isActiveUNLMember to
unlReportNodeIds_/cacheUNLReport/isUNLReportMember to make the
on-chain data source explicit.
2026-02-10 16:14:47 +07:00
Nicholas Dudfield
bc98c589b7 docs(consensus): fix stale quorum comment in phaseEstablish
Update inline comment to reflect that hasQuorumOfCommits() checks
expected proposers first, with 80% of active UNL as fallback.
2026-02-06 16:56:01 +07:00
Nicholas Dudfield
4f009e4698 fix(consensus): proceed with partial commitSet on timeout instead of zero entropy
When expected proposers don't all arrive before rngPIPELINE_TIMEOUT,
check if we still have quorum (80% of UNL). If so, build the commitSet
with available commits and continue to reveals. Only fall back to zero
entropy when truly below quorum.

Previously any missing expected proposer caused a full timeout with zero
entropy for that round. Now: kill 3 of 20 nodes → one 3s timeout round
per kill but entropy preserved (17/16 quorum met).
2026-02-06 16:40:33 +07:00
Nicholas Dudfield
b6811a6f59 feat(consensus): deterministic commitSets via expected proposers and seq=0 proofs
Wait for commits from last round's proposers (falling back to activeUNL
on cold boot) instead of 80% of UNL. This ensures all nodes build the
commitSet at the same moment with the same entries.

Split proof storage: commitProofs_ (seq=0 only, deterministic) and
proposalProofs_ (latest with reveal, for entropySet). Previously the
proof blob contained whichever proposeSeq was last seen, causing
identical commits to produce different SHAMap hashes across nodes.

20-node testnet: all nodes now produce identical commitSet hashes.
2026-02-06 16:27:10 +07:00
Nicholas Dudfield
ae88fd3d24 feat(consensus): add dedicated reveal-phase timeout measured from phase entry
Previously rngPIPELINE_TIMEOUT (3s) was measured from round start,
meaning txSet convergence could eat into the reveal budget. Now reveals
get their own rngREVEAL_TIMEOUT (1.5s) measured from the moment we
enter ConvergingReveal, ensuring consistent time for reveal collection
regardless of how long txSet convergence took.
2026-02-06 16:00:40 +07:00
Nicholas Dudfield
db3ed0c2eb fix(consensus): wait for all committers' reveals and fix local testnet resource charging
- Change hasMinimumReveals() to wait for reveals from ALL committers
  (pendingCommits_.size()) instead of 80% quorum. The commit set is
  deterministic, so we know exactly which reveals to expect.
  rngPIPELINE_TIMEOUT remains the safety valve for crash/partition.
  Fixes reveal-set non-determinism causing entropy divergence on
  15-node testnets.

- Resource manager: preserve port for loopback addresses so local
  testnet nodes each get their own resource bucket instead of all
  sharing one on 127.0.0.1 (causing rate-limit disconnections).

- Make RNG fast-poll interval configurable via XAHAU_RNG_POLL_MS
  env var (default 250ms) for testnet tuning.
2026-02-06 15:22:42 +07:00
Nicholas Dudfield
960808b172 fix(consensus): skip RNG wait when quorum is impossible and base threshold on active UNL
When fewer participants are present than the quorum threshold, skip the
RNG commit wait immediately instead of waiting the full pipeline timeout.
Also base the quorum on activeUNLNodeIds_ (UNL Report with fallback)
instead of the full trusted key set, so the denominator reflects who is
actually active on the network.
2026-02-06 14:34:28 +07:00
Nicholas Dudfield
a9dffd38ff fix(consensus): shorten RNG pipeline timeout to 3s for faster recovery
Add rngPIPELINE_TIMEOUT (3s) to replace ledgerMAX_CONSENSUS (10s) in
the commit/reveal quorum gates. Late-joining nodes enter as
proposing=false and cannot contribute commitments until promoted, so
waiting beyond a few seconds just delays the ZERO-entropy fallback and
penalizes recovery. Add inline comments documenting the late-joiner
constraint and SHAMap sync's role as a dropped-proposal safety net.
2026-02-06 14:04:53 +07:00
Nicholas Dudfield
382e6fa673 fix(consensus): verify reveals match commitments and cache UNL for observers
Prevent grinding attacks by verifying sha512Half(reveal, pubKey, seq)
matches the stored commitment before accepting a reveal. Also move
cacheActiveUNL() into startRound so non-proposing nodes (exchanges,
block explorers) correctly accept RNG data instead of diverging with
zero entropy.
2026-02-06 13:28:55 +07:00
Nicholas Dudfield
2905b0509c perf(consensus): gate RNG SHAMap fetches on sub-state
During ConvergingTx all RNG data arrives via proposal leaves, so
fetching a peer's commitSet before we've built our own just generates
unnecessary traffic. Only fetch commitSetHash once in ConvergingCommit+,
and entropySetHash once in ConvergingReveal.
2026-02-06 13:18:53 +07:00
Nicholas Dudfield
4911c1bf52 feat(consensus): embed proposal signature proofs in RNG SHAMap entries
Prevents spoofed SHAMap entries by embedding verifiable proof blobs
(proposal signature + metadata) in each commit/reveal entry via sfBlob.

- Store ProposalProof in harvestRngData (peers) and propose() (self)
- serializeProof: pack proposeSeq/closeTime/prevLedger/position/sig
- verifyProof: reconstruct signingHash, verify against public key
- Embed proofs in buildCommitSet/buildEntropySet via sfBlob field
- Verify proofs in handleAcquiredRngSet (both diff and visitLeaves paths)
- Add stall fix: gate ConvergingTx on timeout when commits unavailable
- Clear proposalProofs_ in clearRngState
2026-02-06 11:47:42 +07:00
Nicholas Dudfield
1744d21410 docs(consensus): explain union convergence model for RNG sets 2026-02-06 11:17:52 +07:00
Nicholas Dudfield
34ff53f65d feat(consensus): add UNL enforcement for RNG commit-reveal pipeline
Cache active UNL NodeIDs per round from UNL Report (in-ledger),
falling back to getTrustedMasterKeys() on fresh testnets.
Reject non-UNL validators at all entry points: harvestRngData,
buildCommitSet, buildEntropySet, and handleAcquiredRngSet.
2026-02-06 11:12:03 +07:00
Nicholas Dudfield
893f8d5a10 feat(consensus): replace fake hashes with real SHAMap-backed commitSet/entropySet
Build real ephemeral (unbacked) SHAMaps for commitSet and entropySet using
ttCONSENSUS_ENTROPY entries with tfEntropyCommit/tfEntropyReveal flags.
Reuse InboundTransactions pipeline for peer fetch/diff/merge with no new
classes. Encode NodeID in sfAccount to avoid master-vs-signing key mismatch.
Add isPseudoTx guard in ConsensusTransSetSF to prevent pseudo-tx submission.
Route acquired RNG sets via isRngSet/gotRngSet in NetworkOPs mapComplete.
2026-02-06 10:38:06 +07:00
Nicholas Dudfield
3e5389d652 feat(consensus): add 250ms fast-poll for RNG sub-state transitions
During ConvergingCommit and ConvergingReveal sub-states, poll at 250ms
instead of the default 1s ledgerGRANULARITY. This reduces total RNG
pipeline overhead from ~3s to ~500ms while keeping the normal heartbeat
interval unchanged for all other consensus phases.
2026-02-06 09:21:42 +07:00
Nicholas Dudfield
c44dea3acf fix(consensus): resolve commit-reveal pipeline bugs enabling non-zero entropy
Three critical fixes that unblock the RNG commit-reveal pipeline:

- Remove entropy secret regeneration in ConvergingTx->ConvergingCommit
  transition that was overwriting the onClose() secret, breaking reveal
  verification against the original commitment
- Change ExtendedPosition operator== to compare txSetHash only, preventing
  deadlock where nodes transitioning sub-states at different times would
  break haveConsensus() for all peers
- Self-seed own commitment and reveal into pending collections so the
  node counts toward its own quorum checks

Also adds ExtendedPosition_test with signing, suppression, serialization
round-trip and equality tests, iterator safety fix in BuildLedger, wire
compatibility early-return, and RNG debug logging throughout the pipeline.
2026-02-06 09:03:26 +07:00
Nicholas Dudfield
a6dd54fa48 feat(consensus): add featureConsensusEntropy amendment gating
- Register ConsensusEntropy amendment (Supported::yes, DefaultNo)
- Gate entropy pseudo-tx injection behind amendment in doAccept()
- Gate preflight with temDISABLED when amendment not enabled
- Bump numFeatures 90 -> 91
- Exclude featureConsensusEntropy from default test environment to
  avoid breaking existing test transaction count assumptions
2026-02-06 07:29:48 +07:00
Nicholas Dudfield
28bd0a22d3 feat(consensus): add entropy injection, tx ordering, and dispatch registration
- Implement injectEntropyPseudoTx() to combine reveals into final
  entropy hash and inject as pseudo-tx into CanonicalTXSet in doAccept()
- Modify BuildLedger applyTransactions() to apply entropy tx FIRST
  before all other transactions to prevent front-running
- Remove redundant explicit threading in applyConsensusEntropy() as
  sfPreviousTxnID/sfPreviousTxnLgrSeq are set automatically by
  ApplyStateTable::threadItem()
- Register ttCONSENSUS_ENTROPY in applySteps.cpp dispatch tables
  (preflight, preclaim, calculateBaseFee, apply)
- Add ltCONSENSUS_ENTROPY to InvariantCheck.cpp valid type whitelist
2026-02-06 05:43:19 +07:00
Nicholas Dudfield
960fffcf82 feat(consensus): add ttCONSENSUS_ENTROPY pseudo-transaction protocol layer
Add protocol definitions for consensus-derived entropy pseudo-transaction:
- ttCONSENSUS_ENTROPY = 105 transaction type
- ltCONSENSUS_ENTROPY = 0x0058 ledger entry type
- keylet::consensusEntropy() singleton keylet (namespace 'X')
- applyConsensusEntropy() handler in Change.cpp
- Added to isPseudoTx() in STTx.cpp

The entropy value is stored in sfDigest field of the singleton ledger object.
This provides the protocol foundation for same-ledger entropy injection.
2026-02-05 17:26:31 +07:00
Nicholas Dudfield
e7867c07a1 feat(consensus): add RNG sub-state gating logic in phaseEstablish
- Add clearRngState() call in startRoundInternal
- Reset estState_ in closeLedger when entering establish phase
- Implement three-phase RNG checkpoint gating:
  - ConvergingTx: wait for quorum commits, build commitSet
  - ConvergingCommit: reveal entropy, transition immediately
  - ConvergingReveal: wait for reveals or timeout, build entropySet
- Use if constexpr for test framework compatibility
2026-02-05 16:53:00 +07:00
Nicholas Dudfield
a828e8a44d feat(consensus): add RNG wire protocol and harvest logic
- Serialize full ExtendedPosition in share() and propose()
- Deserialize ExtendedPosition in PeerImp using fromSerialIter()
- Add harvestRngData() to collect commits/reveals from peer proposals
- Conditionally call harvest via if constexpr for test compatibility
2026-02-05 16:41:13 +07:00
Nicholas Dudfield
bb33e7cf64 feat(consensus): add ExtendedPosition for RNG entropy support
Introduce data structures for consensus-derived randomness using
commit-reveal scheme:

- Add ExtendedPosition struct with consensus targets (txSetHash,
  commitSetHash, entropySetHash) and pipelined leaves (myCommitment,
  myReveal)
- operator== excludes leaves to allow convergence with unique leaves
- add() includes ALL fields to prevent signature stripping attacks
- Add EstablishState enum for sub-phases: ConvergingTx, ConvergingCommit,
  ConvergingReveal
- Update Consensus template to use Adaptor::Position_t
- Add Position_t typedef to RCLConsensus::Adaptor and test CSF Peer

This is the foundational data structure work for the RNG implementation.
The gating logic and entropy computation will follow.
2026-02-05 16:20:54 +07:00
Nicholas Dudfield
7e8e0654cd chore: add documentation markers for pr-description-outline 2026-01-28 15:00:14 +07:00
Nicholas Dudfield
38af0626e0 chore: add documentation markers for pr-description 2026-01-28 14:50:56 +07:00
Nicholas Dudfield
8500e86f57 chore: remove projected-source documentation markers 2026-01-28 11:30:47 +07:00
Nicholas Dudfield
1fc4fd9bfd chore: regenerate hook headers for export feature 2026-01-28 11:27:13 +07:00
Nicholas Dudfield
e4875e5398 refactor: remove ttEXPORT_SIGN and UVTx infrastructure
- Delete ExportSign.cpp/h transactor (ttEXPORT_SIGN no longer used)
- Remove isUVTx() function and all UVTx checks from Transactor/TxQ
- Remove ttEXPORT_SIGN from TxFormats enum and format definition
- Remove jss::ExportSign
- Move signPendingExports() to ExportSignatureCollector

Export signatures are now collected ephemerally via TMValidation
messages, not via ttEXPORT_SIGN transactions.
2026-01-28 10:59:45 +07:00
Nicholas Dudfield
5b1b142be0 chore: remove stray iostream includes 2026-01-28 10:34:11 +07:00
Nicholas Dudfield
5ba832204a test: remove unused scaffolding from Export_test
- Remove accept_wasm and emit_wasm hooks (not export-related)
- Remove testBasicSetup, testEmitPayment, testXportPayment
- Keep only testXportPaymentWithValidator which tests the export flow
2026-01-28 10:31:49 +07:00
Nicholas Dudfield
1257b3a65c Merge remote-tracking branch 'origin/dev' into export-uvtxn 2026-01-28 10:21:37 +07:00
Nicholas Dudfield
6013ed2cb6 refactor: remove vestigial on-ledger export signature code
- Remove makeExportSignTxns() function (signatures now via TMValidation)
- Simplify ExportSign::doApply() to no-op (ttEXPORT_SIGN kept for protocol)
- Remove sfSigners from ltEXPORTED_TXN format (collected in memory now)
- Remove unused OpenView include and forward declaration
- Remove vestigial comment in TxQ about makeExportSignTxns
2026-01-28 10:19:14 +07:00
Nicholas Dudfield
034010716e feat: add signature verification cache for export signatures
Add cryptographic verification of export signatures as they arrive:
- stashTxnData() caches serialized txn for verification
- verifyAndAddSignature() verifies against cached data, rejects invalid
- isSignatureVerified() / verifySignature() for Transactor fallback
- Cleanup methods updated to clear verification cache

Also removes leftover debug std::cerr from OpenView, STObject, and tests.
2026-01-27 18:03:55 +07:00
Nicholas Dudfield
b28793b0fa chore: clean up export debug logging
- remove DBG_EXPORT macros and all usages
- remove [EXPORT-TRACE] and [EXPORT-TIMING] debug prefixes
- adjust log levels (verbose logs to trace, summaries to debug)
- upgrade "quorum reached" to info level (important event)
- standardize log prefixes to use "Export:"
- re-enable relay loop in OpenLedger.cpp
- remove reentrant call detection debug code
2026-01-27 16:21:02 +07:00
Nicholas Dudfield
4bce392c31 feat: continuous signature broadcasting for export robustness
Validators now sign ALL pending ltEXPORTED_TXN entries every ledger
(not just those from the current ledger). Signatures are cached in
ExportSignatureCollector and re-broadcast until the export is finalized.

Changes:
- Add hasSignatureFrom() and getSignatureFrom() to collector for
  checking/retrieving cached signatures
- signPendingExports() now iterates ALL pending exports, uses cached
  signature if available, otherwise signs fresh
- Signatures keep broadcasting until ltEXPORTED_TXN is deleted

This ensures:
- Late validators can contribute (sign when they come online)
- Network partitions self-heal (signatures propagate on reconnect)
- Node restarts recover (re-sign from ledger state)

The ltEXPORTED_TXN acts as a "ticket" - signatures only valid while it
exists. No explicit expiry check needed; ledger state is the gatekeeper.
2026-01-26 18:25:24 +07:00
Nicholas Dudfield
244a28b981 feat: implement ephemeral export signature collection
Replace on-ledger ttEXPORT_SIGN transactions with ephemeral signature
collection via TMValidation messages. This eliminates O(n²) metadata
bloat from accumulating signatures on-ledger.

Changes:
- Add ExportSignatureCollector for in-memory signature storage with
  quorum tracking (80% UNL threshold)
- Extend TMValidation protobuf with exportSignatures field
- Sign pending exports during validate() and broadcast via validation
- Extract signatures from received TMValidation in PeerImp
- TxQ checks quorum from memory instead of ledger
- Inject ttEXPORT when quorum reached (can be ledger N+1 or N+2)
- Clean up collector after ttEXPORT processed

Includes [EXPORT-TIMING] debug logging for timing analysis.
2026-01-26 17:54:17 +07:00
Nicholas Dudfield
f2838351c9 chore: add [EXPORT-TRACE] debug logging for export flow tracing
adds step-by-step trace logging with [EXPORT-TRACE] prefix to track
the complete export transaction lifecycle:
- STEP-1: xport() creates ltEXPORTED_TXN
- STEP-2a: rawTxInsert ttEXPORT_SIGN in callback
- STEP-2b: doApply ttEXPORT_SIGN
- STEP-3a: rawTxInsert ttEXPORT
- STEP-4: doApply ttEXPORT (cleanup)

filter with: grep '\[EXPORT-TRACE\]'
2026-01-23 08:10:33 +07:00
Nicholas Dudfield
dae082d6a5 chore: format files with clang-format 2026-01-22 16:42:05 +07:00
Nicholas Dudfield
619a4a68f7 fix: resolve export feature bugs and add comprehensive tests
- fix Guard.h: add import_whitelist_2 to signature lookup chain
  (was causing "Function type is inconsistent" errors for xport APIs)
- fix InvariantCheck.cpp: add ltEXPORTED_TXN to valid ledger entry types
  (was causing "invalid ledger entry type added" invariant failures)
- add SetHook.cpp: TODO comment documenting API version confusion

- add Export_test.cpp: comprehensive test suite for export feature
  - testBasicSetup: verify hook installation works
  - testEmitPayment: verify emit() flow works
  - testXportPayment: verify xport() creates ltEXPORTED_TXN
  - includes DebugLogs helper for per-partition log levels
  - parameterized runXportTest helper for future validator tests

Note: validator signing flow (ttEXPORT_SIGN) still needs debugging -
causes internal error on env.close() when validator config enabled.
2026-01-22 09:51:50 +07:00
Nicholas Dudfield
4a6db8bb05 Merge remote-tracking branch 'origin/dev' into export-uvtxn 2026-01-22 08:07:58 +07:00
Nicholas Dudfield
c86479bc58 fix: correct xport api signature and sfExportedTxn type usage
- Fix xport hook API whitelist to declare 4 args (I32, I32, I32, I32)
  instead of 2, matching the actual implementation signature
- Fix TxQ.cpp to use emplace_back with STObject for sfExportedTxn
  instead of setFieldVL, since sfExportedTxn is OBJECT type not VL.
  The previous code would throw "Wrong field type" at runtime.
2026-01-22 07:41:12 +07:00
Nicholas Dudfield
dc6a2dc6ff refactor: separate ExportSign transactor from Change
Move ttEXPORT_SIGN handling to dedicated ExportSign transactor class,
following the same pattern as ttENTROPY/Entropy from the RNG feature.
UVTxns (signed validator transactions) should not be mixed with
pseudo-transactions in the Change transactor.

- Create ExportSign.h/cpp with preflight, preclaim, doApply
- Route ttEXPORT_SIGN through ExportSign in applySteps.cpp
- Remove UVTx branches from Change transactor
- Add documentation markers to View.h for inUNLReport functions
2026-01-22 07:41:12 +07:00
Nicholas Dudfield
c01b9a657b feat: implement uvtxn pattern for ttEXPORT_SIGN
Port the UNL Validator Transaction (UVTxn) pattern from the RNG feature
to allow validators to submit signed ttEXPORT_SIGN transactions without
requiring a funded account.

Changes:
- Add isUVTx() to identify UVTxn transaction types
- Add inUNLReport() templates to check validator UNLReport membership
- Add getValidationSecretKey() to Application for signing
- Modify Transactor for UVTxn bypasses (fee, seq, signature checks)
- Add makeExportSignTxns() to generate validator signatures
- Hook into RCLConsensus to submit ttEXPORT_SIGN during accept
- Update applySteps.cpp routing for ttEXPORT_SIGN
- Remove direct ttEXPORT_SIGN injection from TxQ::accept

Note: Currently uses Change transactor with UVTx branches.
May refactor to dedicated ExportSign transactor class.
2026-01-20 13:44:38 +07:00
Nicholas Dudfield
652b181b5d chore: clang format 2026-01-20 12:44:14 +07:00
RichardAH
8329d78f32 Update src/ripple/app/tx/impl/Import.cpp
Co-authored-by: tequ <git@tequ.dev>
2025-12-21 13:42:46 +10:00
RichardAH
bf4579c1d1 Update src/ripple/app/tx/impl/Change.cpp
Co-authored-by: tequ <git@tequ.dev>
2025-12-21 13:42:37 +10:00
RichardAH
73e099eb23 Update src/ripple/app/hook/impl/applyHook.cpp
Co-authored-by: tequ <git@tequ.dev>
2025-12-21 13:42:29 +10:00
RichardAH
2e311b4259 Update src/ripple/app/hook/applyHook.h
Co-authored-by: tequ <git@tequ.dev>
2025-12-21 13:42:20 +10:00
RichardAH
7c8e940091 Merge branch 'dev' into export 2025-12-19 13:27:02 +10:00
Richard Holland
9b90c50789 featureExport compiling, untested 2025-12-19 14:19:17 +11:00
Richard Holland
a18e2cb2c6 remainder of the export feature... untested uncompiled 2025-12-14 19:04:37 +11:00
Richard Holland
be5f425122 change symbol name to xport 2025-12-14 13:27:44 +11:00
Richard Holland
fc6f4762da export hook apis, untested 2025-12-13 15:46:08 +11:00
372 changed files with 50148 additions and 6001 deletions

37
.codecov.yml Normal file
View File

@@ -0,0 +1,37 @@
codecov:
require_ci_to_pass: true
comment:
behavior: default
layout: reach,diff,flags,tree,reach
show_carryforward_flags: false
coverage:
range: "60..80"
precision: 1
round: nearest
status:
project:
default:
target: 60%
threshold: 2%
patch:
default:
target: auto
threshold: 2%
changes: false
github_checks:
annotations: true
parsers:
cobertura:
partials_as_hits: true
handle_missing_conditions : true
slack_app: false
ignore:
- "src/test/"
- "include/xrpl/beast/test/"
- "include/xrpl/beast/unit_test/"

View File

@@ -1,8 +1,25 @@
# This feature requires Git >= 2.24
# To use it by default in git blame:
# git config blame.ignoreRevsFile .git-blame-ignore-revs
# Format first-party source according to .clang-format
50760c693510894ca368e90369b0cc2dabfd07f3
e2384885f5f630c8f0ffe4bf21a169b433a16858
241b9ddde9e11beb7480600fd5ed90e1ef109b21
760f16f56835663d9286bd29294d074de26a7ba6
0eebe6a5f4246fced516d52b83ec4e7f47373edd
# Reintroduce Clang-Format & Levelization
da1d20d6d5d862716125d60899b80fab5302954a
# Consolidate external libraries
da1d20d6d5d862716125d60899b80fab5302954a
# Rename .hpp to .h
0345a2645d0f5ad900f4fbbcaff96040d3a887fc
# Format formerly .hpp files
5a227dc719016e10045e17c9396ad401118044f1
# Rewrite includes
e61880699997398f5a746e6c4034edc7632661f5
# Move CMake directory (#4997)
e47b1c1b3b97c3f6d11858ee02f463596e29e7f0
# Rearrange sources (#4997)
bfafa2bb39e562901736d656806bd700c3699a2f
# Rewrite includes (#4997)
e61880699997398f5a746e6c4034edc7632661f5
# Recompute loops (#4997)
d25b5dcd568bb96c18e347d55fac10fe901a1bfb
# Reformat code with clang-format-18
02749feea88ce61c1f7eeb2d61a57d8ecf07ab11

12
.githooks/pre-commit Executable file
View File

@@ -0,0 +1,12 @@
#!/bin/bash
# Pre-commit hook that runs the suspicious patterns check on staged files
# Get the repository's root directory
repo_root=$(git rev-parse --show-toplevel)
# Run the suspicious patterns script in pre-commit mode
"$repo_root/suspicious_patterns.sh" --pre-commit
# Exit with the same code as the script
exit $?

4
.githooks/setup.sh Normal file
View File

@@ -0,0 +1,4 @@
#!/bin/bash
echo "Configuring git to use .githooks directory..."
git config core.hooksPath .githooks

View File

@@ -2,6 +2,14 @@ name: build
description: 'Builds the project with ccache integration'
inputs:
cmake-target:
description: 'CMake target to build'
required: false
default: all
cmake-args:
description: 'Additional CMake arguments'
required: false
default: null
generator:
description: 'CMake generator to use'
required: true
@@ -20,6 +28,10 @@ inputs:
description: 'C++ compiler to use'
required: false
default: ''
gcov:
description: 'Gcov to use'
required: false
default: ''
compiler-id:
description: 'Unique identifier: compiler-version-stdlib[-gccversion] (e.g. clang-14-libstdcxx-gcc11, gcc-13-libstdcxx)'
required: false
@@ -41,10 +53,11 @@ inputs:
required: false
default: 'dev'
stdlib:
description: 'C++ standard library to use'
description: 'C++ standard library to use (default = compiler default, e.g. GCC always uses libstdc++)'
required: true
type: choice
options:
- default
- libstdcxx
- libcxx
clang_gcc_toolchain:
@@ -87,11 +100,6 @@ runs:
export CCACHE_CONFIGPATH="$HOME/.config/ccache/ccache.conf"
echo "CCACHE_CONFIGPATH=$CCACHE_CONFIGPATH" >> $GITHUB_ENV
# Keep config separate from cache_dir so configs aren't swapped when CCACHE_DIR changes between steps
mkdir -p ~/.config/ccache
export CCACHE_CONFIGPATH="$HOME/.config/ccache/ccache.conf"
echo "CCACHE_CONFIGPATH=$CCACHE_CONFIGPATH" >> $GITHUB_ENV
# Configure ccache settings AFTER cache restore (prevents stale cached config)
ccache --set-config=max_size=${{ inputs.ccache_max_size }}
ccache --set-config=hash_dir=${{ inputs.ccache_hash_dir }}
@@ -122,6 +130,10 @@ runs:
export CXX="${{ inputs.cxx }}"
fi
if [ -n "${{ inputs.gcov }}" ]; then
ln -sf /usr/bin/${{ inputs.gcov }} /usr/local/bin/gcov
fi
# Create wrapper toolchain that overlays ccache on top of Conan's toolchain
# This enables ccache for the main app build without affecting Conan dependency builds
if [ "${{ inputs.ccache_enabled }}" = "true" ]; then
@@ -185,7 +197,8 @@ runs:
-DCMAKE_TOOLCHAIN_FILE:FILEPATH=${TOOLCHAIN_FILE} \
-DCMAKE_BUILD_TYPE=${{ inputs.configuration }} \
-Dtests=TRUE \
-Dxrpld=TRUE
-Dxrpld=TRUE \
${{ inputs.cmake-args }}
- name: Show ccache config before build
if: inputs.ccache_enabled == 'true'
@@ -209,7 +222,7 @@ runs:
VERBOSE_FLAG="-- -v"
fi
cmake --build . --config ${{ inputs.configuration }} --parallel $(nproc) ${VERBOSE_FLAG}
cmake --build . --config ${{ inputs.configuration }} --parallel $(nproc) --target ${{ inputs.cmake-target }} ${VERBOSE_FLAG}
- name: Show ccache statistics
if: inputs.ccache_enabled == 'true'

View File

@@ -0,0 +1,107 @@
name: Check Genesis Hooks
on:
push:
pull_request:
jobs:
check-genesis-hooks:
runs-on: ubuntu-24.04
env:
CLANG_VERSION: 18
name: Verify xahau.h is in sync with genesis hooks
steps:
- name: Checkout repository
uses: actions/checkout@v6
# Install binaryen from GitHub Releases (pinned to version 100)
- name: Install binaryen (version 100)
run: |
curl -LO https://github.com/WebAssembly/binaryen/releases/download/version_100/binaryen-version_100-x86_64-linux.tar.gz
tar -xzf binaryen-version_100-x86_64-linux.tar.gz
sudo cp binaryen-version_100/bin/* /usr/local/bin/
wasm-opt --version
- name: Install clang-format
run: |
codename=$( lsb_release --codename --short )
sudo tee /etc/apt/sources.list.d/llvm.list >/dev/null <<EOF
deb http://apt.llvm.org/${codename}/ llvm-toolchain-${codename}-${CLANG_VERSION} main
deb-src http://apt.llvm.org/${codename}/ llvm-toolchain-${codename}-${CLANG_VERSION} main
EOF
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add
sudo apt-get update
sudo apt-get install clang-format-${CLANG_VERSION}
clang-format --version
# Install wasienv (WebAssembly SDK)
- name: Install wasienv
run: |
# Download install.sh
curl -o /tmp/wasienv-install.sh https://raw.githubusercontent.com/wasienv/wasienv/master/install.sh
# Replace /bin to /local/bin
sed -i 's|/bin|/local/bin|g' /tmp/wasienv-install.sh
# Execute the installed script
bash /tmp/wasienv-install.sh
# Add wasienv to PATH for subsequent steps
- name: Setup wasienv
run: |
echo "$HOME/.wasienv/bin" >> $GITHUB_PATH
wasmcc -v || true
# Build and install hook-cleaner tool
- name: Build and install hook-cleaner
run: |
git clone https://github.com/richardah/hook-cleaner-c.git /tmp/hook-cleaner
cd /tmp/hook-cleaner
make
cp hook-cleaner /usr/local/bin/
chmod +x /usr/local/bin/hook-cleaner
# Build and install guard_checker tool
- name: Build and install guard_checker
run: |
cd include/xrpl/hook
make
cp guard_checker /usr/local/bin/
chmod +x /usr/local/bin/guard_checker
# Verify all required tools are available
- name: Verify required tools
run: |
echo "Checking tool availability..."
command -v wasmcc || (echo "Error: wasmcc not found" && exit 1)
command -v wasm-opt || (echo "Error: wasm-opt not found" && exit 1)
command -v hook-cleaner || (echo "Error: hook-cleaner not found" && exit 1)
command -v guard_checker || (echo "Error: guard_checker not found" && exit 1)
command -v xxd || (echo "Error: xxd not found" && exit 1)
command -v clang-format || (echo "Error: clang-format not found" && exit 1)
echo "All tools verified successfully"
# Execute build script to regenerate xahau.h
- name: Run build_xahau_h.sh
run: |
cd hook/genesis
./build_xahau_h.sh
# Check if xahau.h has changed (fail if out of sync)
- name: Verify xahau.h is in sync
run: |
if ! git diff --exit-code include/xrpl/hook/xahau.h; then
echo ""
echo "❌ ERROR: xahau.h is out of sync with genesis hooks"
echo ""
echo "The generated xahau.h differs from the committed version."
echo "Please run the following command and commit the changes:"
echo ""
echo " cd hook/genesis && ./build_xahau_h.sh"
echo ""
echo "Diff:"
git diff include/xrpl/hook/xahau.h
exit 1
fi
echo "✅ xahau.h is in sync with genesis hooks"

View File

@@ -20,7 +20,7 @@ jobs:
sudo apt-get update
sudo apt-get install clang-format-${CLANG_VERSION}
- name: Format first-party sources
run: find include src -type f \( -name '*.cpp' -o -name '*.hpp' -o -name '*.h' -o -name '*.ipp' \) -not -path "src/magic/magic_enum.h" -exec clang-format-${CLANG_VERSION} -i {} +
run: find include src -type f \( -name '*.cpp' -o -name '*.hpp' -o -name '*.h' -o -name '*.ipp' \) -exec clang-format-${CLANG_VERSION} -i {} +
- name: Check for differences
id: assert
run: |

View File

@@ -0,0 +1,127 @@
name: Formal Verification (Lean)
on:
push:
branches: ["feature-export-rng-lean"]
pull_request:
branches: ["**"]
types: [opened, synchronize, reopened]
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
lean-consensus:
name: Lean/C++ drift checks
runs-on: [self-hosted, macOS]
env:
BUILD_DIR: .build-formal
CMAKE_BUILD_DIR: .build-formal-cmake
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Add Homebrew to PATH
run: |
echo "/opt/homebrew/bin" >> "$GITHUB_PATH"
echo "/opt/homebrew/sbin" >> "$GITHUB_PATH"
- name: Install core tools
run: |
brew install coreutils
echo "Num proc: $(nproc)"
- name: Setup toolchain (mise)
uses: jdx/mise-action@v3.6.1
with:
cache: false
install: true
mise_toml: |
[tools]
cmake = "3.25.3"
python = "3.12"
pipx = "latest"
conan = "2"
ninja = "latest"
- name: Install tools via mise
run: |
mise install
mise reshim
echo "$HOME/.local/share/mise/shims" >> "$GITHUB_PATH"
- name: Install Lean toolchain
run: |
toolchain="$(cat formal_verification/lean-toolchain)"
curl -sSfL https://raw.githubusercontent.com/leanprover/elan/master/elan-init.sh \
| sh -s -- -y --default-toolchain "$toolchain"
echo "$HOME/.elan/bin" >> "$GITHUB_PATH"
"$HOME/.elan/bin/lake" --version
"$HOME/.elan/bin/lean" --version
- name: Build Lean proofs
run: |
cd formal_verification
"$HOME/.elan/bin/lake" build XahauConsensus:static
- name: Detect compiler version
id: detect-compiler
run: |
compiler_version=$(clang --version | grep -oE 'version [0-9]+' | grep -oE '[0-9]+')
echo "compiler_version=${compiler_version}" >> "$GITHUB_OUTPUT"
echo "Detected Apple Clang version: ${compiler_version}"
- name: Configure Conan profile
run: |
mkdir -p ~/.conan2/profiles
cat > ~/.conan2/profiles/default <<EOF
[settings]
arch=armv8
build_type=Debug
compiler=apple-clang
compiler.cppstd=20
compiler.libcxx=libc++
compiler.version=${{ steps.detect-compiler.outputs.compiler_version }}
os=Macos
[conf]
tools.build:cxxflags=["-Wno-missing-template-arg-list-after-template-kw"]
EOF
conan profile show
- name: Export custom Conan recipes
run: |
conan export external/snappy --version 1.1.10 --user xahaud --channel stable
conan export external/soci --version 4.0.3 --user xahaud --channel stable
conan export external/wasmedge --version 0.11.2 --user xahaud --channel stable
- name: Install Conan dependencies
env:
CONAN_REQUEST_TIMEOUT: 180
run: |
conan install . \
--output-folder "$BUILD_DIR" \
--build missing \
--settings build_type=Debug \
-o '&:tests=True' \
-o '&:xrpld=True' \
-o '&:formal_verification=True'
- name: Configure formal build
run: |
cmake -S . -B "$CMAKE_BUILD_DIR" -G Ninja \
-DCMAKE_TOOLCHAIN_FILE="$PWD/$BUILD_DIR/build/generators/conan_toolchain.cmake" \
-DCMAKE_BUILD_TYPE=Debug \
-Dtests=ON \
-Dxrpld=ON \
-Dformal_verification=ON
- name: Build formal-enabled rippled
run: |
cmake --build "$CMAKE_BUILD_DIR" --target rippled --parallel "$(nproc)"
- name: Run Lean/C++ drift checks
run: |
"$CMAKE_BUILD_DIR/rippled" --unittest=LeanConsensus --unittest-log

View File

@@ -10,7 +10,7 @@ jobs:
steps:
- uses: actions/checkout@v3
- name: Check levelization
run: Builds/levelization/levelization.sh
run: python Builds/levelization/levelization.py
- name: Check for differences
id: assert
run: |
@@ -40,7 +40,7 @@ jobs:
To fix it, you can do one of two things:
1. Download and apply the patch generated as an artifact of this
job to your repo, commit, and push.
2. Run './Builds/levelization/levelization.sh' in your repo,
2. Run 'python Builds/levelization/levelization.py' in your repo,
commit, and push.
See Builds/levelization/README.md for more info.

View File

@@ -18,6 +18,10 @@ jobs:
generator: bash ./hook/generate_sfcodes.sh
- target: hook/tts.h
generator: ./hook/generate_tts.sh
- target: hook/ls_flags.h
generator: ./hook/generate_lsflags.sh
- target: hook/tx_flags.h
generator: ./hook/generate_txflags.sh
runs-on: ubuntu-24.04
env:
CLANG_VERSION: 18

View File

@@ -57,8 +57,9 @@ jobs:
"cc": "gcc-11",
"cxx": "g++-11",
"compiler_version": 11,
"stdlib": "libstdcxx",
"configuration": "Debug"
"stdlib": "default",
"configuration": "Debug",
"job_type": "build"
},
{
"compiler_id": "gcc-13-libstdcxx",
@@ -66,8 +67,20 @@ jobs:
"cc": "gcc-13",
"cxx": "g++-13",
"compiler_version": 13,
"stdlib": "libstdcxx",
"configuration": "Debug"
"stdlib": "default",
"configuration": "Debug",
"job_type": "build"
},
{
"compiler_id": "gcc-13-libstdcxx",
"compiler": "gcc",
"cc": "gcc-13",
"cxx": "g++-13",
"gcov": "gcov-13",
"compiler_version": 13,
"stdlib": "default",
"configuration": "Debug",
"job_type": "coverage"
},
{
"compiler_id": "clang-14-libstdcxx-gcc11",
@@ -77,7 +90,8 @@ jobs:
"compiler_version": 14,
"stdlib": "libstdcxx",
"clang_gcc_toolchain": 11,
"configuration": "Debug"
"configuration": "Debug",
"job_type": "build"
},
{
"compiler_id": "clang-16-libstdcxx-gcc13",
@@ -87,7 +101,8 @@ jobs:
"compiler_version": 16,
"stdlib": "libstdcxx",
"clang_gcc_toolchain": 13,
"configuration": "Debug"
"configuration": "Debug",
"job_type": "build"
},
{
"compiler_id": "clang-17-libcxx",
@@ -96,7 +111,8 @@ jobs:
"cxx": "clang++-17",
"compiler_version": 17,
"stdlib": "libcxx",
"configuration": "Debug"
"configuration": "Debug",
"job_type": "build"
},
{
# Clang 18 - testing if it's faster than Clang 17 with libc++
@@ -107,14 +123,16 @@ jobs:
"cxx": "clang++-18",
"compiler_version": 18,
"stdlib": "libcxx",
"configuration": "Debug"
"configuration": "Debug",
"job_type": "build"
}
]
# Minimal matrix for PRs and feature branches
minimal_matrix = [
full_matrix[1], # gcc-13 (middle-ground gcc)
full_matrix[2] # clang-14 (mature, stable clang)
full_matrix[2], # gcc-13 coverage
full_matrix[3] # clang-14 (mature, stable clang)
]
# Determine which matrix to use based on the target branch
@@ -189,14 +207,21 @@ jobs:
# Select the appropriate matrix
if use_full:
if force_full:
print(f"Using FULL matrix (6 configs) - forced by [ci-nix-full-matrix] tag")
print(f"Using FULL matrix (7 configs) - forced by [ci-nix-full-matrix] tag")
else:
print(f"Using FULL matrix (6 configs) - targeting main branch")
print(f"Using FULL matrix (7 configs) - targeting main branch")
matrix = full_matrix
else:
print(f"Using MINIMAL matrix (2 configs) - feature branch/PR")
print(f"Using MINIMAL matrix (3 configs) - feature branch/PR")
matrix = minimal_matrix
# Add runs_on based on job_type
for entry in matrix:
if entry.get("job_type") == "coverage":
entry["runs_on"] = '["self-hosted", "generic", 24.04]'
else:
entry["runs_on"] = '["self-hosted", "generic", 20.04]'
# Output the matrix as JSON
output = json.dumps({"include": matrix})
with open(os.environ['GITHUB_OUTPUT'], 'a') as f:
@@ -204,7 +229,10 @@ jobs:
build:
needs: matrix-setup
runs-on: [self-hosted, generic, 20.04]
runs-on: ${{ fromJSON(matrix.runs_on) }}
permissions:
id-token: write
contents: read
container:
image: ubuntu:24.04
volumes:
@@ -233,7 +261,7 @@ jobs:
apt-get install -y software-properties-common
add-apt-repository ppa:ubuntu-toolchain-r/test -y
apt-get update
apt-get install -y python3 python-is-python3 pipx
apt-get install -y git python3 python-is-python3 pipx
pipx ensurepath
apt-get install -y cmake ninja-build ${{ matrix.cc }} ${{ matrix.cxx }} ccache
apt-get install -y perl # for openssl build
@@ -304,6 +332,12 @@ jobs:
pipx install "conan>=2.0,<3"
echo "$HOME/.local/bin" >> $GITHUB_PATH
# Install gcovr for coverage jobs
if [ "${{ matrix.job_type }}" = "coverage" ]; then
pipx install "gcovr>=7,<9"
apt-get install -y curl lcov
fi
- name: Check environment
run: |
echo "PATH:"
@@ -313,6 +347,13 @@ jobs:
which ${{ matrix.cc }} && ${{ matrix.cc }} --version || echo "${{ matrix.cc }} not found"
which ${{ matrix.cxx }} && ${{ matrix.cxx }} --version || echo "${{ matrix.cxx }} not found"
which ccache && ccache --version || echo "ccache not found"
# Check gcovr for coverage jobs
if [ "${{ matrix.job_type }}" = "coverage" ]; then
which gcov && gcov --version || echo "gcov not found"
which gcovr && gcovr --version || echo "gcovr not found"
fi
echo "---- Full Environment ----"
env
@@ -340,6 +381,7 @@ jobs:
gha_cache_enabled: 'false' # Disable caching for self hosted runner
- name: Build
if: matrix.job_type == 'build'
uses: ./.github/actions/xahau-ga-build
with:
generator: Ninja
@@ -354,7 +396,27 @@ jobs:
clang_gcc_toolchain: ${{ matrix.clang_gcc_toolchain || '' }}
ccache_max_size: '100G'
- name: Build (Coverage)
if: matrix.job_type == 'coverage'
uses: ./.github/actions/xahau-ga-build
with:
generator: Ninja
configuration: ${{ matrix.configuration }}
build_dir: ${{ env.build_dir }}
cc: ${{ matrix.cc }}
cxx: ${{ matrix.cxx }}
gcov: ${{ matrix.gcov }}
compiler-id: ${{ matrix.compiler_id }}
cache_version: ${{ env.CACHE_VERSION }}
main_branch: ${{ env.MAIN_BRANCH_NAME }}
stdlib: ${{ matrix.stdlib }}
# Coverage builds are slower due to instrumentation; use fewer parallel jobs to avoid flakiness
cmake-args: '-Dcoverage=ON -Dcoverage_format=xml -Dcoverage_test_parallelism=$(($(nproc)/2)) -DCODE_COVERAGE_VERBOSE=ON -DCMAKE_CXX_FLAGS="-O0" -DCMAKE_C_FLAGS="-O0"'
cmake-target: 'coverage'
ccache_max_size: '100G'
- name: Set artifact name
if: matrix.job_type == 'build'
id: set-artifact-name
run: |
ARTIFACT_NAME="build-output-nix-${{ github.run_id }}-${{ matrix.compiler }}-${{ matrix.configuration }}"
@@ -367,6 +429,7 @@ jobs:
ls -la ${{ env.build_dir }} || echo "Build directory not found or empty"
- name: Run tests
if: matrix.job_type == 'build'
run: |
# Ensure the binary exists before trying to run
if [ -f "${{ env.build_dir }}/rippled" ]; then
@@ -375,3 +438,42 @@ jobs:
echo "Error: rippled executable not found in ${{ env.build_dir }}"
exit 1
fi
# Coverage-specific steps
- name: Move coverage report
if: matrix.job_type == 'coverage'
shell: bash
run: |
mv "${{ env.build_dir }}/coverage.xml" ./
- name: Archive coverage report
if: matrix.job_type == 'coverage'
uses: actions/upload-artifact@v4
with:
name: coverage.xml
path: coverage.xml
retention-days: 30
- name: Upload coverage report
if: matrix.job_type == 'coverage'
uses: codecov/codecov-action@v5
with:
files: coverage.xml
fail_ci_if_error: true
disable_search: true
verbose: true
plugins: noop
use_oidc: true
- name: Export server definitions
if: matrix.job_type == 'build' && matrix.compiler_id == 'gcc-13-libstdcxx'
run: |
${{ env.build_dir }}/rippled --definitions | python3 -m json.tool > server_definitions.json
- name: Upload server definitions
if: matrix.job_type == 'build' && matrix.compiler_id == 'gcc-13-libstdcxx'
uses: actions/upload-artifact@v7
with:
name: server-definitions
path: server_definitions.json
archive: false

10
.gitignore vendored
View File

@@ -53,6 +53,9 @@ Builds/levelization/results/paths.txt
Builds/levelization/results/includes/
Builds/levelization/results/includedby/
# Python
__pycache__
# Ignore tmp directory.
tmp
@@ -124,5 +127,12 @@ bld.rippled/
generated
.vscode
# AI docs (local working documents)
.ai-docs/
# Local formal-methods workspace; kept as a separate repository and optionally
# symlinked here for navigation.
formal/lean/xahau_consensus
# Suggested in-tree build directory
/.build/

4
.testnet/.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
output/
__pycache__/
scenarios/odd-cases/
scenarios/suite-experiments.yml

View File

@@ -0,0 +1,29 @@
"""Scenario: ConsensusEntropy amendment crashes non-supporting node.
Votes ConsensusEntropy accept on all nodes except n4, then waits for n4
to crash as the amendment activates without its support.
x-testnet run --scenario-script consensus_entropy_crash.py
"""
from helpers import CONSENSUS_ENTROPY_FEATURE
async def scenario(ctx, log):
await ctx.wait_for_ledger_close()
ctx.feature(CONSENSUS_ENTROPY_FEATURE, vetoed=False, exclude_nodes=[4])
log("Waiting for ConsensusEntropy to be voted for...")
await ctx.wait_for_feature(
CONSENSUS_ENTROPY_FEATURE,
check=lambda s: not s.get("vetoed"),
exclude_nodes=[4],
timeout=60,
)
log("Waiting for n4 to crash...")
op = await ctx.wait_for_nodes_down(nodes=[4], timeout=600)
ctx.assert_log("unsupported amendments activated", since=op.started, nodes=[4])
ctx.assert_exit_status(0, nodes=[4])
log("PASS: n4 shut down due to unsupported amendment")

View File

@@ -0,0 +1,52 @@
""":descr: entropy stays valid under transaction load"""
from __future__ import annotations
from helpers import require_entropy, get_entropy_tx, assert_valid_entropy
variants = [
{"label": "light", "min_txns": 5, "max_txns": 10},
{"label": "heavy", "min_txns": 50, "max_txns": 60},
{"label": "super_heavy", "min_txns": 90, "max_txns": 120},
]
async def scenario(ctx, log, *, min_txns=5, max_txns=10, **_):
await require_entropy(ctx, log)
gen = ctx.txn_generator(min_txns=min_txns, max_txns=max_txns)
await gen.start()
await gen.wait_until_ready()
log(f"Transaction generator ready ({min_txns}-{max_txns} txns/ledger)")
# Wait for pipeline warmup + a few txn-bearing ledgers.
await ctx.wait_for_ledgers(3, node_id=0, timeout=60)
start_seq = ctx.validated_ledger_index(0)
await ctx.wait_for_ledgers(10, node_id=0, timeout=120)
end_seq = ctx.validated_ledger_index(0)
log(f"Inspecting ledgers {start_seq + 1}{end_seq}")
digests = set()
total_user_txns = 0
for seq in range(start_seq + 1, end_seq + 1):
ce, user_txns = get_entropy_tx(ctx, seq)
digest, count = assert_valid_entropy(ce, seq, seen_digests=digests)
total_user_txns += len(user_txns)
log(
f" Ledger {seq}: EntropyCount={count} "
f"user_txns={len(user_txns)} Digest={digest[:16]}..."
)
await gen.stop()
log(
f"Verified {end_seq - start_seq} ledgers: {total_user_txns} user txns, "
f"all entropy valid and unique"
)
if total_user_txns == 0:
raise AssertionError("No user transactions were included in any ledger")
log("PASS")

View File

@@ -0,0 +1,28 @@
""":descr: healthy non-standalone testnet without UNLReport mints Tier 1 fallback"""
from __future__ import annotations
from helpers import require_entropy, get_entropy_tx, assert_consensus_fallback
async def scenario(ctx, log):
await require_entropy(ctx, log)
# Non-standalone nodes require a ledger-anchored UNLReport before assigning
# validator_quorum / participant_aligned labels. Without it, the RNG pipeline
# may still collect commits/reveals, but injection must remain Tier 1.
await ctx.wait_for_ledgers(3, node_id=0, timeout=60)
log("Pipeline warmed up without UNLReport")
start_seq = ctx.validated_ledger_index(0)
await ctx.wait_for_ledgers(5, node_id=0, timeout=90)
end_seq = ctx.validated_ledger_index(0)
log(f"Inspecting ledgers {start_seq + 1} -> {end_seq}")
for seq in range(start_seq + 1, end_seq + 1):
ce, _ = get_entropy_tx(ctx, seq)
digest, count = assert_consensus_fallback(ce, seq)
log(f" Ledger {seq}: EntropyCount={count} Digest={digest[:16]}...")
log(f"Verified {end_seq - start_seq} ledgers: all consensus_fallback")
log("PASS")

View File

@@ -0,0 +1,160 @@
""":descr: 5/6 validator_quorum, 4/6 participant_aligned (tier 2), recovery
Requires node_count: 6 (see suite.yml) — the smallest NON-degenerate Tier 2
size. At n=6: tier2 floor = 4, validator quorum = 5, validation quorum = 5. So
6/6, 5/6 present -> validator_quorum (EntropyTier=3)
4/6 present -> participant_aligned (EntropyTier=2, count 4) <-- the band
3/6 present -> consensus_fallback (EntropyTier=1)
n=5 has NO tier-2 band (tier2 == quorum == 4), which is why the existing
degradation smoke at 5 nodes only ever sees tier 3 / fallback.
KEY: the 4/6 window is BELOW the 80% validation quorum (5). The 4 survivors
keep CLOSING ledgers that carry tier-2 entropy, but those ledgers do NOT
validate until the network recovers — exactly the transition window Tier 2
serves. So validated_ledger_index() stalls; we instead inspect a surviving
node's CLOSED ledger (its LCL) directly, and cross-check the injection from the
cohort's logs.
"""
from __future__ import annotations
from helpers import (
require_entropy,
get_entropy_tx,
assert_participant_aligned,
assert_validator_quorum,
)
def _closed_entropy(result):
"""(seq, ConsensusEntropy tx) from a ctx.ledger('closed', transactions=True)
result, or (None, None) if the fetch returned no usable ledger.
Enforces the per-ledger invariant that an entropy-enabled closed ledger
carries EXACTLY ONE ConsensusEntropy pseudo-tx (mirroring get_entropy_tx):
a duplicate or missing injection raises here with a clear error instead of
being silently skipped and resurfacing later as a generic 'no tier-2 ledger'.
"""
if not result or not isinstance(result.get("ledger"), dict):
return None, None
led = result["ledger"]
try:
seq = int(led.get("ledger_index"))
except (TypeError, ValueError):
return None, None
ce = [
t
for t in led.get("transactions", [])
if isinstance(t, dict) and t.get("TransactionType") == "ConsensusEntropy"
]
if len(ce) != 1:
raise AssertionError(
f"Closed ledger {seq}: expected 1 ConsensusEntropy txn, got {len(ce)}"
)
return seq, ce[0]
async def scenario(ctx, log):
await require_entropy(ctx, log)
# Baseline: healthy 6/6 produces validator_quorum entropy.
await ctx.wait_for_ledgers(1, node_id=0, timeout=30)
# --- 5/6: settles back to validator_quorum (5 present >= quorum 5) ---
val_before_drop = ctx.validated_ledger_index(0)
ctx.stop_node(5)
await ctx.wait_for_nodes_down(nodes=[5], timeout=30)
# Settle a few ledgers past the membership change. The ledger right at a
# validator drop can carry a transient consensus_fallback (tier 1, count 0,
# deterministic and by design) before the commit/reveal pipeline re-primes,
# so we do NOT assume any single post-drop ledger is already tier 3.
await ctx.wait_for_ledgers(4, node_id=0, timeout=90)
# 5/6 is at/above the 80% quorum (5), so steady state is validator_quorum.
# Scan the post-drop validated ledgers (all carry the 5-node cohort, so a
# tier-3 here has count == 5) and require at least one clean validator_quorum
# — EntropyTier=3, count >= quorum, non-zero digest — tolerating the
# transition fallback instead of depending on where the tip happened to land.
val_5of6 = ctx.validated_ledger_index(0)
t3_seq = None
for seq in range(val_5of6, val_before_drop, -1):
ce, _ = get_entropy_tx(ctx, seq)
tier = ce.get("EntropyTier")
log(f" 5/6 ledger {seq}: tier={tier} count={ce.get('EntropyCount')}")
if tier == 3:
assert_validator_quorum(ce, seq, min_count=5)
t3_seq = seq
break
if t3_seq is None:
raise AssertionError(
f"5/6: no validator_quorum (tier 3) entropy in post-drop validated "
f"ledgers {val_before_drop + 1}..{val_5of6}"
)
log(f"5/6: validator_quorum at validated seq {t3_seq}")
#@@start test-participant-aligned-window
# --- 4/6: participant_aligned (Tier 2) degraded window ---
ctx.stop_node(4)
await ctx.wait_for_nodes_down(nodes=[4], timeout=30)
# ~12s window: confirm tier-2 INJECTION from the cohort's logs, and that the
# round is NOT the impossible/fallback path (which is what distinguishes the
# tier-2 band from the tier-1 fallback regime).
op = await ctx.sleep(12, name="tier2_window")
selected_t2 = ctx.search_logs(
r"RNG: entropy selected seq=\d+ tier=2 count=4",
within=op.window,
nodes=[0, 1, 2, 3],
)
log(f"4/6: 'entropy selected tier=2 count=4' logs: {selected_t2.count}")
if selected_t2.count == 0:
raise AssertionError(
"4/6 window injected no participant_aligned (tier 2) entropy: no "
"'RNG: entropy selected ... tier=2 count=4' on the surviving cohort"
)
ctx.assert_not_log(
r"reason=impossible-entropy-gate", within=op.window, nodes=[0, 1, 2, 3]
)
# Verify the on-ledger EntropyTier=2 DIRECTLY: validation is stalled (4 < 5),
# so sample the surviving cohort's CLOSED ledger (its LCL — built but not yet
# validated). At least one must be participant_aligned with EntropyCount=4.
tier2_on_ledger = 0
last_seq = None
for _ in range(5):
seq, ce = _closed_entropy(
ctx.ledger("closed", transactions=True, node_id=0)
)
if ce is not None and seq is not None and seq != last_seq:
last_seq = seq
tier = ce.get("EntropyTier")
count = ce.get("EntropyCount", -1)
log(f" closed ledger {seq}: tier={tier} count={count}")
if tier == 2:
assert_participant_aligned(ce, seq, expected_count=4)
tier2_on_ledger += 1
await ctx.sleep(3)
if tier2_on_ledger == 0:
raise AssertionError(
"no closed participant_aligned (tier 2) ledger observed during the "
"4/6 window (tier 2 was injected per logs, but not seen on a closed "
"ledger)"
)
log(f"4/6: {tier2_on_ledger} participant_aligned closed ledger(s) verified")
#@@end test-participant-aligned-window
# --- Recovery: liveness — validation resumes once quorum is restored ---
ctx.start_node(4)
ctx.start_node(5)
await ctx.wait_for_ledgers(1, node_id=0, timeout=120)
val_recovered = ctx.validated_ledger_index(0)
if not val_recovered or val_recovered <= val_5of6:
raise AssertionError(
f"Validated ledger did not advance after recovery "
f"({val_5of6} -> {val_recovered})"
)
log(f"Recovered: validated seq {val_5of6} -> {val_recovered}")
log("PASS")

View File

@@ -0,0 +1,148 @@
""":descr: 4/5 liveness, 3/5 fallback-entropy (consensus_fallback), recovery"""
from __future__ import annotations
from helpers import ZERO_DIGEST, require_entropy, get_entropy_tx, entropy_fields
async def scenario(ctx, log):
await require_entropy(ctx, log)
# Baseline: wait 1 ledger to confirm network is healthy.
await ctx.wait_for_ledgers(1, node_id=0, timeout=30)
# --- 4/5 liveness ---
ctx.stop_node(4)
await ctx.wait_for_nodes_down(nodes=[4], timeout=30)
await ctx.wait_for_ledgers(1, node_id=0, timeout=30)
log("4/5: liveness OK")
# Snapshot validated seq before dropping to 3/5.
val_before = ctx.validated_ledger_index(0)
# --- 3/5 degraded window ---
ctx.stop_node(3)
await ctx.wait_for_nodes_down(nodes=[3], timeout=30)
# 10s ≈ 3 rounds at 3s cadence.
await ctx.sleep(10)
val_after = ctx.validated_ledger_index(0)
log(f"3/5: validated ledger {val_before}{val_after}")
# Accepted/built ledgers may still later appear as validated once the full
# network rejoins. For ConsensusEntropy the key invariant is that every
# ledger created during this sub-quorum window carries FALLBACK entropy
# (consensus_fallback: non-zero consensus-bound digest, count 0) — never
# validator-tier entropy.
degraded_fallback = 0
degraded_end = val_after or val_before
if val_before and degraded_end and degraded_end > val_before:
for seq in range(val_before + 1, degraded_end + 1):
ce, _ = get_entropy_tx(ctx, seq)
digest, entropy_count, is_fallback = entropy_fields(ce)
tier = ce.get("EntropyTier")
# consensus_fallback (EntropyTier=1): explicit tier, count 0,
# deterministic NON-zero digest.
if tier != 1:
raise AssertionError(
f"Ledger {seq}: expected EntropyTier==1 "
f"(consensus_fallback) during 3/5 window, got {tier} "
f"(EntropyCount={entropy_count})"
)
if entropy_count != 0:
raise AssertionError(
f"Ledger {seq}: fallback EntropyCount must be 0, got "
f"{entropy_count}"
)
if not digest or digest == ZERO_DIGEST:
raise AssertionError(
f"Ledger {seq}: fallback digest must be non-zero "
f"(consensus_fallback), got {digest[:16]}..."
)
assert is_fallback # tier==1 implies fallback
degraded_fallback += 1
log(
f" Degraded ledger {seq}: EntropyCount={entropy_count} "
f"FALLBACK"
)
log(f"3/5 entropy summary: {degraded_fallback} fallback")
# Log checks tied to current transition mechanics:
# - commit-set SHAMap publication is the observable output of entering the
# commit sidecar phase
# - ConvergingCommit transition is the gateway out of seq=0-only behavior
# - reason=impossible-entropy-gate is the explicit degraded-window fallback path
ctx.log_level("LedgerConsensus", "trace")
ctx.log_level("ConsensusExtensions", "trace")
op = await ctx.sleep(6, name="stall_window")
ctx.assert_not_log(
r"RNG: transitioned to ConvergingCommit", within=op.window, nodes=[0, 1, 2]
)
ctx.assert_not_log(
r"RNG: built commitSet SHAMap", within=op.window, nodes=[0, 1, 2]
)
gate_blocked = ctx.search_logs(
r"STALLDIAG: establish gate blocked reason=(pause|no-tx-consensus)",
within=op.window,
nodes=[0, 1, 2],
)
log(f"3/5: establish gate-blocked logs in 6s: {gate_blocked.count}")
impossible = ctx.search_logs(
r"RNG: skipping commit wait reason=impossible-entropy-gate",
within=op.window,
nodes=[0, 1, 2],
)
log(f"3/5: RNG impossible-entropy-gate skips in 6s: {impossible.count}")
# --- Recovery: restart nodes, verify ledger advancement ---
ctx.start_node(3)
ctx.start_node(4)
await ctx.wait_for_ledgers(1, node_id=0, timeout=120)
val_recovered = ctx.validated_ledger_index(0)
pre_recovery = max(v for v in [val_before, val_after] if v is not None)
log(f"Recovered: validated seq {pre_recovery}{val_recovered}")
if not val_recovered or val_recovered <= pre_recovery:
raise AssertionError(
f"Validated ledger did not advance after recovery "
f"({pre_recovery}{val_recovered})"
)
# Inspect post-recovery ledgers separately from the degraded window above.
# Once the network is back at quorum, validator-tier entropy is expected
# again (transitional fallback ledgers are fine) and must be quorum-met.
fallback_count = 0
validator_count = 0
for seq in range(pre_recovery + 1, val_recovered + 1):
ce, _ = get_entropy_tx(ctx, seq)
digest, entropy_count, is_fallback = entropy_fields(ce)
if is_fallback:
fallback_count += 1
else:
validator_count += 1
if entropy_count < 4:
raise AssertionError(
f"Ledger {seq}: validator entropy with sub-quorum "
f"EntropyCount={entropy_count} (need >= 4)"
)
log(
f" Ledger {seq}: EntropyCount={entropy_count} "
f"{'FALLBACK' if is_fallback else 'VALIDATOR'}"
)
log(
f"Entropy summary: {fallback_count} fallback, "
f"{validator_count} validator"
)
log("PASS")

View File

@@ -0,0 +1,44 @@
""":descr: drop 2 nodes (3/5 stall), restart both, verify recovery"""
from __future__ import annotations
from helpers import require_entropy
async def scenario(ctx, log):
await require_entropy(ctx, log)
await ctx.wait_for_ledgers(1, node_id=0, timeout=60)
log("Baseline OK")
# Drop 2 nodes → validation stall.
ctx.stop_node(3)
ctx.stop_node(4)
await ctx.wait_for_nodes_down(nodes=[3, 4], timeout=30)
info = ctx.rpc.server_info(node_id=0)
val_before = info.get("info", {}).get("validated_ledger", {}).get("seq", 0)
log(f"Stalled at validated seq {val_before}")
# Let it sit for a few rounds in degraded state.
await ctx.sleep(6)
# Bring both nodes back.
ctx.start_node(3)
ctx.start_node(4)
log("Restarted n3 and n4, waiting for recovery...")
# Recovery: wait for ANY validated ledger advance on n0.
await ctx.wait_for_ledger_close(node_id=0, timeout=60)
info = ctx.rpc.server_info(node_id=0)
val_after = info.get("info", {}).get("validated_ledger", {}).get("seq", 0)
log(f"Recovered: validated seq {val_before}{val_after}")
if val_after <= val_before:
raise AssertionError(
f"Validated ledger did not advance after recovery "
f"({val_before}{val_after})"
)
log("PASS")

View File

@@ -0,0 +1,27 @@
""":descr: all 5 nodes healthy, every ledger has valid unique quorum-met entropy"""
from __future__ import annotations
from helpers import require_entropy, get_entropy_tx, assert_valid_entropy
async def scenario(ctx, log):
await require_entropy(ctx, log)
# Wait for RNG pipeline to warm up past bootstrap skip.
await ctx.wait_for_ledgers(3, node_id=0, timeout=60)
log("Pipeline warmed up")
start_seq = ctx.validated_ledger_index(0)
await ctx.wait_for_ledgers(10, node_id=0, timeout=120)
end_seq = ctx.validated_ledger_index(0)
log(f"Inspecting ledgers {start_seq + 1}{end_seq}")
digests = set()
for seq in range(start_seq + 1, end_seq + 1):
ce, _ = get_entropy_tx(ctx, seq)
digest, count = assert_valid_entropy(ce, seq, seen_digests=digests)
log(f" Ledger {seq}: EntropyCount={count} Digest={digest[:16]}...")
log(f"Verified {end_seq - start_seq} ledgers: all quorum entropy, all unique")
log("PASS")

View File

@@ -0,0 +1,104 @@
defaults:
network:
node_count: 5
launcher: tmux
find_ports: true
slave_delay: 0.2
features:
- ConsensusEntropy
- Export
track_features:
- ConsensusEntropy
- Export
unl_report: true
log_levels:
TxQ: info
Protocol: debug
Peer: debug
LedgerConsensus: debug
ConsensusExtensions: debug
NetworkOPs: info
env:
XAHAU_RESOURCE_PER_PORT: "1"
rc:
- rng_poll_ms=333
tests:
# --- CE + Export (80% quorum, SHAMap convergence) ---
- name: steady_state_export_ce
script: .testnet/scenarios/export/steady_state_export.py
- name: retriable_export_ce
script: .testnet/scenarios/export/retriable_export.py
- name: export_degradation_ce
script: .testnet/scenarios/export/export_degradation.py
network:
rc:
- rng_poll_ms=333
- n3:no_export_sig=true
- n4:no_export_sig=true
- name: export_without_unl_report
script: .testnet/scenarios/export/export_without_unl_report.py
network:
features:
- Export
track_features:
- Export
unl_report: false
- name: export_no_veto_missing_observation
script: .testnet/scenarios/export/export_no_veto_missing_observation.py
network:
rc:
- rng_poll_ms=333
- n4:no_export_sig_hash=true
# CE + Export: 1 node suppressed, 4/5 = 80% quorum, should succeed
- name: export_ce_one_node_down
script: .testnet/scenarios/export/export_quorum.py
params:
expect_success: true
network:
rc:
- rng_poll_ms=333
- n4:no_export_sig=true
# --- Export only, no CE (80% active-view quorum) ---
- name: export_only_all_up
script: .testnet/scenarios/export/export_quorum.py
params:
expect_success: true
network:
features:
- Export
track_features:
- Export
- name: export_only_one_node_down
script: .testnet/scenarios/export/export_quorum.py
params:
expect_success: true
network:
features:
- Export
track_features:
- Export
rc:
- rng_poll_ms=333
- n4:no_export_sig=true
- name: export_only_two_nodes_down
script: .testnet/scenarios/export/export_quorum.py
params:
expect_success: false
network:
features:
- Export
track_features:
- Export
rc:
- rng_poll_ms=333
- n3:no_export_sig=true
- n4:no_export_sig=true

View File

@@ -0,0 +1,123 @@
""":descr: Submit ttEXPORT with 2 nodes suppressing export sigs, verify it
retries via terRETRY_EXPORT until LLS expiry (insufficient signatures).
Nodes 3 and 4 have runtime_config no_export_sig=true, so only 3/5 nodes
provide export signatures. With 80% quorum = ceil(5*0.8) = 4 required,
the export cannot reach quorum and should expire via tecEXPORT_EXPIRED.
Flow:
1. Fund alice and bob
2. alice submits ttEXPORT with tight LLS
3. Export retries (only 3/5 sigs available, need 4)
4. Verify export expires with tecEXPORT_EXPIRED
5. Verify subsequent payment still works (sequence not permanently blocked)
"""
from __future__ import annotations
from export_helpers import require_export, assert_shadow_ticket
async def scenario(ctx, log):
await require_export(ctx, log)
# --- Setup ---
await ctx.fund_accounts({"alice": 10000, "bob": 1000})
log("Accounts funded")
alice = ctx.account("alice")
bob = ctx.account("bob")
current_seq = ctx.validated_ledger_index(0)
log(f"Current ledger: {current_seq}")
log("Nodes 3,4 have runtime_config no_export_sig=true (3/5 sigs, need 4)")
#@@start test-export-below-quorum-expiry
# --- Submit ttEXPORT (should retry then expire -- only 3/5 sigs) ---
export_start = ctx.mark("export-degradation-submit-start")
result = await ctx.submit_and_wait(
{
"TransactionType": "Export",
"LastLedgerSequence": current_seq + 8,
"Fee": "1000000",
"ExportedTxn": {
"TransactionType": "Payment",
"Account": alice.address,
"Destination": bob.address,
"Amount": "1000000",
"Fee": "10",
"Sequence": 0,
"TicketSequence": 1,
"FirstLedgerSequence": current_seq + 1,
"LastLedgerSequence": current_seq + 6,
"Flags": 2147483648,
"SigningPubKey": "",
},
},
alice.wallet,
timeout=60,
)
export_end = ctx.mark("export-degradation-submit-end")
final_seq = ctx.validated_ledger_index(0)
engine_result = result.get("engine_result", "")
log(f"Export completed at ledger {final_seq}, result: {engine_result}")
# With only 3/5 sigs and 80% quorum (4 required), export MUST fail
if engine_result == "tesSUCCESS":
raise AssertionError(
"Export should NOT have succeeded with only 3/5 sigs "
"(need 4 for 80% quorum) -- check runtime_config no_export_sig"
)
# Should be tecEXPORT_EXPIRED (LLS reached without quorum). Be exact here:
# any other non-success means the retry/expiry boundary regressed.
if engine_result != "tecEXPORT_EXPIRED":
raise AssertionError(
f"Expected tecEXPORT_EXPIRED below quorum, got {engine_result}"
)
log(f"Export failed as expected ({engine_result})")
retry_logs = ctx.assert_log(
r"Export: insufficient signatures .*result=terRETRY_EXPORT",
since=export_start,
until=export_end,
)
log(f"Export insufficient-signature retries: {retry_logs.count}")
expired_logs = ctx.assert_log(
r"Export: last ledger expired .*result=tecEXPORT_EXPIRED",
since=export_start,
until=export_end,
)
log(f"Export LLS expiry logs: {expired_logs.count}")
# No shadow ticket should exist (export never reached quorum)
assert_shadow_ticket(ctx, alice.address, log, expect_exists=False)
#@@end test-export-below-quorum-expiry
# --- Verify subsequent payment works regardless ---
log("Submitting payment from alice to bob...")
pay_result = await ctx.submit_and_wait(
{
"TransactionType": "Payment",
"Destination": bob.address,
"Amount": "1000000",
"Fee": "12",
},
alice.wallet,
timeout=30,
)
pay_engine = pay_result.get("engine_result", "")
log(f"Payment result: {pay_engine}")
if pay_engine != "tesSUCCESS":
raise AssertionError(
f"Payment failed after expired export: {pay_engine} "
f"-- sequence may be blocked"
)
log("Payment succeeded -- account not permanently blocked")
log("PASS")

View File

@@ -0,0 +1,181 @@
"""Shared helpers for Export scenario tests."""
from __future__ import annotations
from xahaud_scripts.testnet.config import _unl_report_index, feature_name_to_hash
async def require_export(
ctx, log, *, require_unl_report=True, require_runtime_config=True
):
"""Wait for first ledger and assert Export is enabled.
Network-mode Export success requires a parent-ledger UNLReport-backed
active validator view. Most export scenarios seed that report in genesis;
assert it here so a success-path test cannot accidentally pass setup
without the condition Export::doApply requires. The no-UNLReport retry
scenario opts out deliberately.
The tracked export suite also uses XAHAUD_RUNTIME_TEST_CONFIG for polling
and fault-injection knobs. Default binaries reject the runtime_config RPC,
so check it up front rather than silently running without those knobs.
"""
await ctx.wait_for_ledger_close(timeout=120)
if require_runtime_config:
result = ctx.rpc.runtime_config(0)
if not result or result.get("error"):
raise AssertionError(
"Export suite requires a binary built with "
"xahaud_runtime_test_config=ON; runtime_config RPC returned "
f"{result}"
)
log("RuntimeConfig RPC active")
feature = ctx.feature_check(feature_name_to_hash("Export"), node_id=0)
if not feature or not feature.get("enabled", False):
raise AssertionError(f"Export not enabled: {feature}")
log("Export enabled")
if require_unl_report:
result = ctx.rpc.ledger_entry(0, _unl_report_index())
node = (result or {}).get("node", {})
active = node.get("ActiveValidators", [])
if node.get("LedgerEntryType") != "UNLReport" or not active:
raise AssertionError(
"Export success scenario requires a ledger UNLReport with "
f"ActiveValidators, got: {result}"
)
log(f"UNLReport active validators: {len(active)}")
def find_export_txns(ctx, seq):
"""Find Export transactions in a ledger.
Returns list of Export transaction dicts.
"""
result = ctx.ledger(seq, transactions=True)
if not result:
return []
txns = result.get("ledger", {}).get("transactions", [])
return [tx for tx in txns if tx.get("TransactionType") == "Export"]
def dst_param(address):
"""Encode an address as a HookParameter entry for the DST param."""
from xrpl.core.addresscodec import decode_classic_address
dst_hex = decode_classic_address(address).hex().upper()
return {
"HookParameter": {
"HookParameterName": "445354", # "DST"
"HookParameterValue": dst_hex,
}
}
def assert_hook_accepted(meta, log, *, expected_emits=1):
"""Assert hook executed with ACCEPT and the expected emit count.
Checks sfHookExecutions in transaction metadata.
Returns the hook execution entry for further inspection.
"""
hook_execs = meta.get("HookExecutions", [])
if not hook_execs:
raise AssertionError("No HookExecutions in metadata")
exec_entry = hook_execs[0].get("HookExecution", {})
hook_result = exec_entry.get("HookResult", -1)
emit_count = exec_entry.get("HookEmitCount", -1)
return_code = exec_entry.get("HookReturnCode", "")
log(f" HookResult={hook_result} EmitCount={emit_count} ReturnCode={return_code}")
# HookResult 3 = ExitType::ACCEPT
if hook_result != 3:
raise AssertionError(
f"Hook did not ACCEPT: HookResult={hook_result} "
f"ReturnCode={return_code}"
)
if emit_count != expected_emits:
raise AssertionError(
f"Expected {expected_emits} emits, got {emit_count}"
)
# ReturnCode 0 = success; non-zero = ASSERT line number in hook
if return_code and str(return_code) != "0":
raise AssertionError(
f"Hook returned error code {return_code} "
f"(likely ASSERT failure at that line)"
)
return exec_entry
def assert_export_result(meta, log, *, require_signers=True):
"""Assert ExportResult is present and well-formed in metadata.
Returns the ExportResult dict.
"""
export_result = meta.get("ExportResult", {})
if not export_result:
raise AssertionError("ExportResult not found in metadata")
# Must have LedgerSequence and TransactionHash
if "LedgerSequence" not in export_result:
raise AssertionError("ExportResult missing LedgerSequence")
if "TransactionHash" not in export_result:
raise AssertionError("ExportResult missing TransactionHash")
# Must have the inner ExportedTxn object
inner = export_result.get("ExportedTxn", {})
if not inner:
raise AssertionError("ExportResult missing ExportedTxn (multisigned blob)")
log(f" ExportResult: seq={export_result['LedgerSequence']} "
f"hash={export_result['TransactionHash'][:16]}...")
# Inner tx should have Account, Destination, TransactionType
if "Account" not in inner:
raise AssertionError("ExportedTxn missing Account")
if "TransactionType" not in inner:
raise AssertionError("ExportedTxn missing TransactionType")
# Should have empty SigningPubKey (multisigned)
if inner.get("SigningPubKey", "NOT_EMPTY") != "":
raise AssertionError(
f"ExportedTxn SigningPubKey should be empty, "
f"got '{inner.get('SigningPubKey')}'"
)
if require_signers:
signers = inner.get("Signers", [])
if not signers:
raise AssertionError("ExportedTxn has no Signers (multisig not applied)")
log(f" Signers: {len(signers)} validator(s)")
return export_result
def assert_shadow_ticket(ctx, account_address, log, *, expect_exists=True):
"""Assert shadow ticket exists (or doesn't) for the account."""
obj_result = ctx.rpc.request(
0, "account_objects", {"account": account_address}
)
all_objects = (obj_result or {}).get("account_objects", [])
shadow_tickets = [
obj for obj in all_objects
if obj.get("LedgerEntryType") == "ShadowTicket"
]
log(f" Shadow tickets: {len(shadow_tickets)}")
if expect_exists and not shadow_tickets:
raise AssertionError("Expected shadow ticket but none found")
if not expect_exists and shadow_tickets:
raise AssertionError(
f"Expected no shadow tickets but found {len(shadow_tickets)}"
)
return shadow_tickets

View File

@@ -0,0 +1,87 @@
""":descr: Export succeeds when quorum sidecar material exists but one active
validator withholds exportSigSetHash observation.
Node 4 has runtime_config no_export_sig_hash=true. It still attaches export
signatures, but it does not publish its exportSigSetHash in proposals. The
remaining 4/5 active validators can still align on the same export sidecar
hash, so the round must not retry/expire just because fullObservation is false.
"""
from __future__ import annotations
from export_helpers import (
require_export,
assert_export_result,
assert_shadow_ticket,
)
async def scenario(ctx, log):
await require_export(ctx, log)
await ctx.fund_accounts({"alice": 10000, "bob": 1000})
log("Accounts funded")
alice = ctx.account("alice")
bob = ctx.account("bob")
current_seq = ctx.validated_ledger_index(0)
log(f"Current ledger: {current_seq}")
log("Node 4 withholds exportSigSetHash but still attaches export signatures")
export_start = ctx.mark("export-no-veto-submit-start")
result = await ctx.submit_and_wait(
{
"TransactionType": "Export",
"LastLedgerSequence": current_seq + 10,
"Fee": "1000000",
"ExportedTxn": {
"TransactionType": "Payment",
"Account": alice.address,
"Destination": bob.address,
"Amount": "1000000",
"Fee": "10",
"Sequence": 0,
"TicketSequence": 1,
"FirstLedgerSequence": current_seq + 1,
"LastLedgerSequence": current_seq + 8,
"Flags": 2147483648,
"SigningPubKey": "",
},
},
alice.wallet,
timeout=60,
)
export_end = ctx.mark("export-no-veto-submit-end")
final_seq = ctx.validated_ledger_index(0)
engine_result = result.get("engine_result", "")
meta = result.get("meta", {})
log(f"Export completed at ledger {final_seq}, result: {engine_result}")
if engine_result != "tesSUCCESS":
raise AssertionError(f"Expected tesSUCCESS, got {engine_result}")
export_result = assert_export_result(meta, log, require_signers=True)
signers = export_result.get("ExportedTxn", {}).get("Signers", [])
if len(signers) < 4:
raise AssertionError(f"Expected at least 4 signers, got {len(signers)}")
log(f"Export signer count: {len(signers)}")
no_veto_logs = ctx.assert_log(
r"Export: missing exportSigSetHash observation ignored",
since=export_start,
until=export_end,
)
log(f"Export no-veto missing-observation logs: {no_veto_logs.count}")
withhold_logs = ctx.assert_log(
r"Export: withholding exportSigSetHash",
since=export_start,
until=export_end,
)
log(f"Export sidecar hash withholding logs: {withhold_logs.count}")
assert_shadow_ticket(ctx, alice.address, log, expect_exists=True)
log("PASS")

View File

@@ -0,0 +1,117 @@
""":descr: Test Export quorum behavior. When enough active validators sign,
the export should succeed whether or not CE is enabled. When fewer than the
active-view quorum sign, the export should expire.
Parameterized via `expect_success` kwarg from suite.yml.
Flow:
1. Fund alice and bob
2. alice submits ttEXPORT
3. Verify result matches expectation (tesSUCCESS or tecEXPORT_EXPIRED)
4. Verify ExportResult + shadow ticket on success, absence on failure
5. Verify subsequent payment works regardless
"""
from __future__ import annotations
from export_helpers import (
require_export,
assert_export_result,
assert_shadow_ticket,
)
async def scenario(ctx, log, expect_success=True):
await require_export(ctx, log)
# --- Setup ---
await ctx.fund_accounts({"alice": 10000, "bob": 1000})
log("Accounts funded")
alice = ctx.account("alice")
bob = ctx.account("bob")
current_seq = ctx.validated_ledger_index(0)
log(f"Current ledger: {current_seq}")
outcome = "success" if expect_success else "failure (below quorum)"
log(f"Expecting export {outcome}")
# --- Submit ttEXPORT ---
result = await ctx.submit_and_wait(
{
"TransactionType": "Export",
"LastLedgerSequence": current_seq + 10,
"Fee": "1000000",
"ExportedTxn": {
"TransactionType": "Payment",
"Account": alice.address,
"Destination": bob.address,
"Amount": "1000000",
"Fee": "10",
"Sequence": 0,
"TicketSequence": 1,
"FirstLedgerSequence": current_seq + 1,
"LastLedgerSequence": current_seq + 8,
"Flags": 2147483648,
"SigningPubKey": "",
},
},
alice.wallet,
timeout=60,
)
final_seq = ctx.validated_ledger_index(0)
engine_result = result.get("engine_result", "")
meta = result.get("meta", {})
log(f"Export at ledger {final_seq}, result: {engine_result}")
if expect_success:
if engine_result != "tesSUCCESS":
raise AssertionError(
f"Expected tesSUCCESS, got {engine_result}"
)
# Assert ExportResult is well-formed with signers
assert_export_result(meta, log, require_signers=True)
# Assert shadow ticket was created
assert_shadow_ticket(ctx, alice.address, log, expect_exists=True)
log("Export succeeded as expected (active-view quorum reached)")
else:
if engine_result == "tesSUCCESS":
raise AssertionError(
"Export should NOT have succeeded below active-view quorum"
)
if engine_result != "tecEXPORT_EXPIRED":
raise AssertionError(
"Expected tecEXPORT_EXPIRED below active-view quorum, "
f"got {engine_result}"
)
log(f"Export failed as expected ({engine_result})")
# No shadow ticket should exist
assert_shadow_ticket(ctx, alice.address, log, expect_exists=False)
# --- Verify subsequent payment works ---
log("Submitting payment from alice to bob...")
pay_result = await ctx.submit_and_wait(
{
"TransactionType": "Payment",
"Destination": bob.address,
"Amount": "1000000",
"Fee": "12",
},
alice.wallet,
timeout=30,
)
pay_engine = pay_result.get("engine_result", "")
log(f"Payment result: {pay_engine}")
if pay_engine != "tesSUCCESS":
raise AssertionError(f"Payment failed: {pay_engine}")
log("Payment succeeded -- account not blocked")
log("PASS")

View File

@@ -0,0 +1,92 @@
""":descr: Export retries/expires without a ledger-anchored UNLReport view.
All validators may sign, but network-mode Export must not assemble quorum
material from a node-local trusted-config view. Without UNLReport, the export
should retry until LastLedgerSequence and expire without creating a shadow
ticket.
"""
from __future__ import annotations
from export_helpers import require_export, assert_shadow_ticket
async def scenario(ctx, log):
await require_export(ctx, log, require_unl_report=False)
await ctx.fund_accounts({"alice": 10000, "bob": 1000})
log("Accounts funded")
alice = ctx.account("alice")
bob = ctx.account("bob")
current_seq = ctx.validated_ledger_index(0)
log(f"Current ledger: {current_seq}")
log("UNLReport intentionally absent; export must not use local config view")
export_start = ctx.mark("export-without-unlreport-submit-start")
result = await ctx.submit_and_wait(
{
"TransactionType": "Export",
"LastLedgerSequence": current_seq + 8,
"Fee": "1000000",
"ExportedTxn": {
"TransactionType": "Payment",
"Account": alice.address,
"Destination": bob.address,
"Amount": "1000000",
"Fee": "10",
"Sequence": 0,
"TicketSequence": 1,
"FirstLedgerSequence": current_seq + 1,
"LastLedgerSequence": current_seq + 6,
"Flags": 2147483648,
"SigningPubKey": "",
},
},
alice.wallet,
timeout=60,
)
export_end = ctx.mark("export-without-unlreport-submit-end")
final_seq = ctx.validated_ledger_index(0)
engine_result = result.get("engine_result", "")
log(f"Export completed at ledger {final_seq}, result: {engine_result}")
if engine_result == "tesSUCCESS":
raise AssertionError(
"Export should not succeed without a ledger-anchored UNLReport view"
)
# Be exact: without a UNLReport view the export should retry until LLS and
# expire, not fail by some unrelated terminal code.
if engine_result != "tecEXPORT_EXPIRED":
raise AssertionError(
"Expected tecEXPORT_EXPIRED without UNLReport view, "
f"got {engine_result}"
)
warning_logs = ctx.assert_log(
r"Export: retrying without ledger-anchored validator view",
since=export_start,
until=export_end,
)
log(f"Export no-UNLReport retry warnings: {warning_logs.count}")
retry_logs = ctx.assert_log(
r"Export: insufficient signatures .*result=terRETRY_EXPORT",
since=export_start,
until=export_end,
)
log(f"Export retry logs: {retry_logs.count}")
expired_logs = ctx.assert_log(
r"Export: last ledger expired .*result=tecEXPORT_EXPIRED",
since=export_start,
until=export_end,
)
log(f"Export expiry logs: {expired_logs.count}")
assert_shadow_ticket(ctx, alice.address, log, expect_exists=False)
log("PASS")

View File

@@ -0,0 +1,94 @@
""":descr: Submit ttEXPORT directly (no hook), verify it succeeds with
ExportResult in metadata. Then submit a payment from the same account
to verify sequence handling doesn't block subsequent transactions.
Flow:
1. Fund alice and bob
2. alice submits ttEXPORT with inner payment -> tesSUCCESS (provisional)
3. Validators attach sigs via proposals -> quorum -> ExportResult in metadata
4. alice submits a Payment to bob -> should succeed (sequence not blocked)
"""
from __future__ import annotations
from export_helpers import require_export, assert_export_result, assert_shadow_ticket
async def scenario(ctx, log):
await require_export(ctx, log)
# --- Setup ---
await ctx.fund_accounts({"alice": 10000, "bob": 1000})
log("Accounts funded")
alice = ctx.account("alice")
bob = ctx.account("bob")
current_seq = ctx.validated_ledger_index(0)
log(f"Current ledger: {current_seq}")
# --- 1. Submit ttEXPORT ---
result = await ctx.submit_and_wait(
{
"TransactionType": "Export",
"LastLedgerSequence": current_seq + 15,
"Fee": "1000000",
"ExportedTxn": {
"TransactionType": "Payment",
"Account": alice.address,
"Destination": bob.address,
"Amount": "1000000",
"Fee": "10",
"Sequence": 0,
"TicketSequence": 1,
"FirstLedgerSequence": current_seq + 1,
"LastLedgerSequence": current_seq + 10,
"Flags": 2147483648,
"SigningPubKey": "",
},
},
alice.wallet,
timeout=60,
)
export_seq = ctx.validated_ledger_index(0)
engine_result = result.get("engine_result", "")
log(f"Export completed at ledger {export_seq}, result: {engine_result}")
if engine_result != "tesSUCCESS":
raise AssertionError(
f"Expected tesSUCCESS for export, got {engine_result}"
)
# Assert ExportResult is well-formed with signers
meta = result.get("meta", {})
assert_export_result(meta, log, require_signers=True)
# Assert shadow ticket was created
assert_shadow_ticket(ctx, alice.address, log, expect_exists=True)
# --- 2. Submit Payment from same account ---
log("Submitting payment from alice to bob...")
pay_result = await ctx.submit_and_wait(
{
"TransactionType": "Payment",
"Destination": bob.address,
"Amount": "1000000",
"Fee": "12",
},
alice.wallet,
timeout=30,
)
pay_engine = pay_result.get("engine_result", "")
log(f"Payment result: {pay_engine}")
if pay_engine != "tesSUCCESS":
raise AssertionError(f"Payment failed: {pay_engine}")
log(
f"Both transactions succeeded: "
f"Export at ledger {export_seq}, Payment at ledger {ctx.validated_ledger_index(0)}"
)
log("Sequence handling OK - export didn't block subsequent txns")
log("PASS")

View File

@@ -0,0 +1,211 @@
""":descr: install xport hook, trigger export, verify emitted ttEXPORT lifecycle
1. Fund alice (hook holder), bob (trigger), carol (export destination)
2. Install xport hook on alice
3. bob pays alice with DST=carol → hook calls xport() → emits ttEXPORT
4. Emitted ttEXPORT enters open ledger, validators attach sigs via proposals
5. Verify Export transaction appears in a subsequent ledger
"""
from __future__ import annotations
from export_helpers import (
require_export,
find_export_txns,
dst_param,
assert_hook_accepted,
assert_export_result,
assert_shadow_ticket,
)
# C source for the xport hook — verbatim from src/test/app/Export_test_hooks.h
# On Payment to the hook account, exports a 1 XAH payment to the DST param.
XPORT_HOOK_C = r"""
#include <stdint.h>
extern int32_t _g(uint32_t id, uint32_t maxiter);
extern int64_t accept(uint32_t read_ptr, uint32_t read_len, int64_t error_code);
extern int64_t rollback(uint32_t read_ptr, uint32_t read_len, int64_t error_code);
extern int64_t xport(uint32_t write_ptr, uint32_t write_len, uint32_t read_ptr, uint32_t read_len);
extern int64_t xport_reserve(uint32_t count);
extern int64_t hook_account(uint32_t write_ptr, uint32_t write_len);
extern int64_t otxn_param(uint32_t write_ptr, uint32_t write_len, uint32_t name_ptr, uint32_t name_len);
extern int64_t otxn_type(void);
extern int64_t ledger_seq(void);
#define SBUF(x) (uint32_t)(x), sizeof(x)
#define ASSERT(x) if (!(x)) rollback((uint32_t)#x, sizeof(#x), __LINE__)
#define ttPAYMENT 0
#define tfCANONICAL 0x80000000UL
#define amAMOUNT 1
#define amFEE 8
#define atACCOUNT 1
#define atDESTINATION 3
#define ENCODE_TT(buf_out, tt) \
buf_out[0] = 0x12U; buf_out[1] = (tt >> 8) & 0xFFU; buf_out[2] = tt & 0xFFU; buf_out += 3;
#define ENCODE_FLAGS(buf_out, flags) \
buf_out[0] = 0x22U; buf_out[1] = (flags >> 24) & 0xFFU; buf_out[2] = (flags >> 16) & 0xFFU; \
buf_out[3] = (flags >> 8) & 0xFFU; buf_out[4] = flags & 0xFFU; buf_out += 5;
#define ENCODE_SEQUENCE(buf_out, seq) \
buf_out[0] = 0x24U; buf_out[1] = (seq >> 24) & 0xFFU; buf_out[2] = (seq >> 16) & 0xFFU; \
buf_out[3] = (seq >> 8) & 0xFFU; buf_out[4] = seq & 0xFFU; buf_out += 5;
#define ENCODE_FLS(buf_out, fls) \
buf_out[0] = 0x20U; buf_out[1] = 0x1AU; buf_out[2] = (fls >> 24) & 0xFFU; \
buf_out[3] = (fls >> 16) & 0xFFU; buf_out[4] = (fls >> 8) & 0xFFU; \
buf_out[5] = fls & 0xFFU; buf_out += 6;
#define ENCODE_LLS(buf_out, lls) \
buf_out[0] = 0x20U; buf_out[1] = 0x1BU; buf_out[2] = (lls >> 24) & 0xFFU; \
buf_out[3] = (lls >> 16) & 0xFFU; buf_out[4] = (lls >> 8) & 0xFFU; \
buf_out[5] = lls & 0xFFU; buf_out += 6;
#define ENCODE_DROPS(buf_out, drops, amt_type) \
buf_out[0] = 0x60U + amt_type; buf_out[1] = 0x40U + ((drops >> 56) & 0x3FU); \
buf_out[2] = (drops >> 48) & 0xFFU; buf_out[3] = (drops >> 40) & 0xFFU; \
buf_out[4] = (drops >> 32) & 0xFFU; buf_out[5] = (drops >> 24) & 0xFFU; \
buf_out[6] = (drops >> 16) & 0xFFU; buf_out[7] = (drops >> 8) & 0xFFU; \
buf_out[8] = drops & 0xFFU; buf_out += 9;
#define ENCODE_SIGNING_PUBKEY_EMPTY(buf_out) \
buf_out[0] = 0x73U; buf_out[1] = 0x00U; buf_out += 2;
#define ENCODE_ACCOUNT(buf_out, acc, acc_type) \
buf_out[0] = 0x80U + acc_type; buf_out[1] = 0x14U; \
for (int i = 0; i < 20; ++i) buf_out[2+i] = acc[i]; buf_out += 22;
#define PREPARE_PAYMENT_SIMPLE_SIZE 270U
int64_t hook(uint32_t reserved) {
_g(1, 1);
if (otxn_type() != ttPAYMENT)
return accept(0, 0, 0);
ASSERT(xport_reserve(1) == 1);
uint8_t dst[20];
int64_t dst_len = otxn_param(SBUF(dst), "DST", 3);
ASSERT(dst_len == 20);
uint8_t acc[20];
ASSERT(hook_account(SBUF(acc)) == 20);
uint32_t cls = (uint32_t)ledger_seq();
uint8_t tx[PREPARE_PAYMENT_SIMPLE_SIZE];
uint8_t* buf = tx;
ENCODE_TT(buf, ttPAYMENT);
ENCODE_FLAGS(buf, tfCANONICAL);
ENCODE_SEQUENCE(buf, 0);
ENCODE_FLS(buf, cls + 1);
ENCODE_LLS(buf, cls + 5);
// sfTicketSequence = UINT32 field 41 = 0x20 0x29
buf[0] = 0x20U; buf[1] = 0x29U;
buf[2] = 0; buf[3] = 0; buf[4] = 0; buf[5] = 1;
buf += 6;
uint64_t drops = 1000000;
ENCODE_DROPS(buf, drops, amAMOUNT);
ENCODE_DROPS(buf, 10, amFEE);
ENCODE_SIGNING_PUBKEY_EMPTY(buf);
ENCODE_ACCOUNT(buf, acc, atACCOUNT);
ENCODE_ACCOUNT(buf, dst, atDESTINATION);
uint8_t hash[32];
int64_t xport_result = xport(SBUF(hash), (uint32_t)tx, buf - tx);
ASSERT(xport_result == 32);
return accept(0, 0, 0);
}
"""
async def scenario(ctx, log):
# Wait for network to start and amendments to activate
await require_export(ctx, log)
# --- Setup ---
await ctx.fund_accounts({"alice": 10000, "bob": 10000, "carol": 1000})
log("Accounts funded")
alice = ctx.account("alice")
carol = ctx.account("carol")
# Compile and install xport hook on alice
wasm = ctx.compile_hook(XPORT_HOOK_C, label="xport")
await ctx.submit_and_wait(
{
"TransactionType": "SetHook",
"Hooks": [
{
"Hook": {
"CreateCode": wasm.hex().upper(),
"HookOn": "0" * 64,
"HookNamespace": "0" * 64,
"HookApiVersion": 0,
"Flags": 1, # hsfOVERRIDE
}
}
],
"Fee": "100000000",
},
alice.wallet,
)
log(
f"Hook installed on alice ({alice.address[:12]}...) "
f"ledger {ctx.validated_ledger_index(0)}"
)
# --- Trigger ---
# bob pays alice → hook calls xport() → emits ttEXPORT
trigger_result = await ctx.submit_and_wait(
{
"TransactionType": "Payment",
"Destination": alice.address,
"Amount": "100000000",
"Fee": "1000000",
"HookParameters": [dst_param(carol.address)],
},
ctx.account("bob").wallet,
)
trigger_seq = ctx.validated_ledger_index(0)
log(f"Export triggered at ledger {trigger_seq}")
# Assert hook fired with ACCEPT and emitted 1 tx
trigger_meta = trigger_result.get("meta", {})
assert_hook_accepted(trigger_meta, log, expected_emits=1)
# --- Verify: check each ledger close for the Export transaction ---
max_ledgers = 10
for i in range(max_ledgers):
await ctx.wait_for_ledgers(1, node_id=0, timeout=30)
seq = ctx.validated_ledger_index(0)
exports = find_export_txns(ctx, seq)
if exports:
export_tx = exports[0]
meta = export_tx.get("meta", export_tx.get("metaData", {}))
result = meta.get("TransactionResult", "")
log(f"Ledger {seq}: Export txn found, result={result}")
if result != "tesSUCCESS":
raise AssertionError(f"Export did not succeed: {result}")
# Assert ExportResult is well-formed with signers and inner tx
assert_export_result(meta, log, require_signers=True)
# Assert shadow ticket was created
assert_shadow_ticket(ctx, alice.address, log, expect_exists=True)
log("PASS")
return
log(f"Ledger {seq}: no Export txn yet")
raise AssertionError(
f"No Export transaction found after {max_ledgers} ledger closes"
)

View File

@@ -0,0 +1,180 @@
"""Shared helpers for ConsensusEntropy scenario tests."""
from __future__ import annotations
from xahaud_scripts.testnet.config import feature_name_to_hash
ZERO_DIGEST = "0" * 64
CONSENSUS_ENTROPY_FEATURE = feature_name_to_hash("ConsensusEntropy")
def feature_hash(name: str) -> str:
"""Return the amendment hash accepted by feature RPC."""
return feature_name_to_hash(name)
def feature_status(ctx, name: str, node_id=0):
"""Query a feature by amendment hash; feature RPC names are ambiguous."""
return ctx.feature_check(feature_hash(name), node_id=node_id)
def consensus_entropy_feature(ctx, node_id=0):
"""Query ConsensusEntropy by amendment hash."""
return feature_status(ctx, "ConsensusEntropy", node_id=node_id)
async def require_entropy(ctx, log):
"""Wait for first ledger and assert ConsensusEntropy is enabled."""
await ctx.wait_for_ledger_close(timeout=120)
feature = consensus_entropy_feature(ctx, node_id=0)
if not feature or not feature.get("enabled", False):
raise AssertionError(f"ConsensusEntropy not enabled: {feature}")
log("ConsensusEntropy enabled")
def get_entropy_tx(ctx, seq):
"""Fetch ledger and return (ce_tx, user_txns) or raise."""
result = ctx.ledger(seq, transactions=True)
if not result:
raise AssertionError(f"Ledger {seq}: fetch failed")
ledger = result.get("ledger")
if not isinstance(ledger, dict):
raise AssertionError(f"Ledger {seq}: fetch returned no ledger: {result}")
txns = ledger.get("transactions", [])
ce = [tx for tx in txns if tx.get("TransactionType") == "ConsensusEntropy"]
user = [tx for tx in txns if tx.get("TransactionType") != "ConsensusEntropy"]
if len(ce) != 1:
raise AssertionError(
f"Ledger {seq}: expected 1 ConsensusEntropy txn, got {len(ce)}"
)
return ce[0], user
def entropy_fields(ce_tx):
"""Return (digest, entropy_count, is_fallback) from a ConsensusEntropy tx.
consensus_fallback rounds carry a deterministic non-zero consensus-bound
digest with EntropyCount=0 and EntropyTier=1 (consensus_fallback).
Validator entropy has EntropyTier=3 (validator_quorum).
WARNING: is_fallback is ``tier != 3``, so it lumps participant_aligned
(Tier 2) in with fallback. It is only safe where no Tier 2 band exists
(e.g. 5-node networks, where tier2 == quorum). For band-aware scenarios use
the explicit assert_consensus_fallback / assert_participant_aligned /
assert_validator_quorum helpers, which check EntropyTier directly.
"""
digest = ce_tx.get("Digest", "")
entropy_count = ce_tx.get("EntropyCount", -1)
tier = ce_tx.get("EntropyTier", None)
if tier is not None:
is_fallback = tier != 3
else:
is_fallback = entropy_count == 0
return digest, entropy_count, is_fallback
def assert_participant_aligned(ce_tx, seq, expected_count=None):
"""Assert participant_aligned (Tier 2) entropy on a ConsensusEntropy tx.
Tier 2 is the sub-quorum band: the agreed reveal cohort is >= the
participant floor but < the 80% validator quorum, so it carries
EntropyTier=2 with a deterministic non-zero digest. NOTE entropy_fields()'s
is_fallback lumps tier 2 in with fallback (is_fallback = tier != 3), so the
tier must be checked EXPLICITLY here.
"""
digest = ce_tx.get("Digest", "")
count = ce_tx.get("EntropyCount", -1)
tier = ce_tx.get("EntropyTier", None)
if tier != 2:
raise AssertionError(
f"Ledger {seq}: expected EntropyTier==2 (participant_aligned), "
f"got {tier} (EntropyCount={count})"
)
if not digest or digest == ZERO_DIGEST:
raise AssertionError(
f"Ledger {seq}: participant_aligned digest must be non-zero, got "
f"{digest[:16]}..."
)
if expected_count is not None and count != expected_count:
raise AssertionError(
f"Ledger {seq}: participant_aligned EntropyCount must be "
f"{expected_count} (the surviving cohort), got {count}"
)
return digest, count
def assert_validator_quorum(ce_tx, seq, min_count=None):
"""Assert validator_quorum (Tier 3) entropy on a ConsensusEntropy tx:
EntropyTier=3, a deterministic non-zero digest, and (optionally)
EntropyCount >= min_count (the active quorum). The count can EXCEED the
quorum (e.g. a still-full 6/6 ledger caught at a 6->5 transition), so check
>=, not ==.
"""
digest = ce_tx.get("Digest", "")
count = ce_tx.get("EntropyCount", -1)
tier = ce_tx.get("EntropyTier", None)
if tier != 3:
raise AssertionError(
f"Ledger {seq}: expected EntropyTier==3 (validator_quorum), got "
f"{tier} (EntropyCount={count})"
)
if not digest or digest == ZERO_DIGEST:
raise AssertionError(
f"Ledger {seq}: validator_quorum digest must be non-zero, got "
f"{digest[:16]}..."
)
if min_count is not None and count < min_count:
raise AssertionError(
f"Ledger {seq}: validator_quorum EntropyCount={count} < quorum "
f"{min_count}"
)
return digest, count
def assert_consensus_fallback(ce_tx, seq):
"""Assert consensus_fallback (Tier 1) entropy on a ConsensusEntropy tx:
EntropyTier=1, EntropyCount=0, and a deterministic NON-zero digest.
"""
digest = ce_tx.get("Digest", "")
count = ce_tx.get("EntropyCount", -1)
tier = ce_tx.get("EntropyTier", None)
if tier != 1:
raise AssertionError(
f"Ledger {seq}: expected EntropyTier==1 (consensus_fallback), got "
f"{tier} (EntropyCount={count})"
)
if count != 0:
raise AssertionError(
f"Ledger {seq}: consensus_fallback EntropyCount must be 0, got "
f"{count}"
)
if not digest or digest == ZERO_DIGEST:
raise AssertionError(
f"Ledger {seq}: consensus_fallback digest must be non-zero, got "
f"{digest[:16]}..."
)
return digest, count
def assert_valid_entropy(ce_tx, seq, seen_digests=None):
"""Assert quorum-met validator entropy. Optionally check uniqueness."""
digest, entropy_count, is_fallback = entropy_fields(ce_tx)
if is_fallback or not digest or digest == ZERO_DIGEST:
raise AssertionError(f"Ledger {seq}: fallback/empty Digest")
if entropy_count < 4:
raise AssertionError(
f"Ledger {seq}: EntropyCount={entropy_count} < 4 (sub-quorum)"
)
if seen_digests is not None:
if digest in seen_digests:
raise AssertionError(f"Ledger {seq}: duplicate Digest {digest[:16]}...")
seen_digests.add(digest)
return digest, entropy_count

View File

@@ -0,0 +1,92 @@
defaults:
network:
node_count: 5
launcher: tmux
find_ports: true
slave_delay: 0.2
features:
- ConsensusEntropy
- Export
track_features:
- ConsensusEntropy
- Export
unl_report: true
log_levels:
TxQ: info
Protocol: debug
Peer: debug
LedgerConsensus: debug
ConsensusExtensions: debug
NetworkOPs: info
env:
XAHAU_RESOURCE_PER_PORT: "1"
rc:
- rng_poll_ms=250
tests:
- name: latency_baseline_ce
script: .testnet/scenarios/perf/ce_export_latency_probe.py
params:
warmup_ledgers: 3
ledgers: 8
submit_export: false
- name: latency_baseline_export
script: .testnet/scenarios/perf/ce_export_latency_probe.py
params:
warmup_ledgers: 3
ledgers: 8
submit_export: true
- name: latency_proposal_delay_export
script: .testnet/scenarios/perf/ce_export_latency_probe.py
params:
warmup_ledgers: 3
ledgers: 8
submit_export: true
network:
rc:
- rng_poll_ms=250
- delay=100,jitter=25,msg=proposal
- name: latency_directed_pair_delay_export
script: .testnet/scenarios/perf/ce_export_latency_probe.py
params:
warmup_ledgers: 3
ledgers: 8
submit_export: true
network:
rc:
- rng_poll_ms=250
- n0->n2:delay=750,jitter=100,msg=proposal
- n2->n0:delay=750,jitter=100,msg=proposal
- name: latency_slow_minority_export
script: .testnet/scenarios/perf/ce_export_latency_probe.py
params:
warmup_ledgers: 3
ledgers: 8
submit_export: true
export_timeout: 120
network:
rc:
- rng_poll_ms=250
- n3->n0:delay=500,jitter=100,msg=proposal
- n3->n1:delay=500,jitter=100,msg=proposal
- n3->n2:delay=500,jitter=100,msg=proposal
- n4->n0:delay=500,jitter=100,msg=proposal
- n4->n1:delay=500,jitter=100,msg=proposal
- n4->n2:delay=500,jitter=100,msg=proposal
- n0->n3:delay=500,jitter=100,msg=proposal
- n1->n3:delay=500,jitter=100,msg=proposal
- n2->n3:delay=500,jitter=100,msg=proposal
- n0->n4:delay=500,jitter=100,msg=proposal
- n1->n4:delay=500,jitter=100,msg=proposal
- n2->n4:delay=500,jitter=100,msg=proposal
- name: latency_export_no_veto_with_delay
script: .testnet/scenarios/export/export_no_veto_missing_observation.py
network:
rc:
- rng_poll_ms=250
- delay=300,jitter=100,msg=proposal
- n4:no_export_sig_hash=true

View File

@@ -0,0 +1,196 @@
""":descr: measure CE/export behavior while RuntimeConfig injects latency/drop.
The suite supplies runtime fault injection through network.rc. This scenario
does not mutate RuntimeConfig itself; it observes what the launched network does
under that condition and logs enough counters to compare variants.
"""
from __future__ import annotations
from collections import Counter
import json
from export.export_helpers import assert_export_result, require_export
from helpers import consensus_entropy_feature, get_entropy_tx
async def _require_runtime_config(ctx, log):
result = ctx.rpc.runtime_config(0)
if not result or result.get("error"):
raise AssertionError(
"Latency probe requires a binary built with "
"xahaud_runtime_test_config=ON; runtime_config RPC returned "
f"{result}"
)
log("RuntimeConfig RPC active")
async def _require_consensus_entropy(ctx, log):
feature = consensus_entropy_feature(ctx, node_id=0)
if not feature or not feature.get("enabled", False):
raise AssertionError(f"ConsensusEntropy not enabled: {feature}")
log("ConsensusEntropy enabled")
def _log_runtime_config(ctx, log):
for node_id in range(ctx.node_count):
cfg = ctx.rpc.runtime_config(node_id)
if cfg is None:
raise AssertionError(f"runtime_config RPC failed on node {node_id}")
log(
f"runtime_config n{node_id}: "
f"{json.dumps(cfg, sort_keys=True, separators=(',', ':'))}"
)
async def _submit_direct_export(ctx, log, *, timeout):
await ctx.fund_accounts({"alice": 10000, "bob": 1000})
alice = ctx.account("alice")
bob = ctx.account("bob")
current_seq = ctx.validated_ledger_index(0)
if current_seq is None:
raise AssertionError("validated ledger is not available before Export")
log(f"Submitting direct Export at validated ledger {current_seq}")
started = ctx.mark("latency-export-submit-start")
result = await ctx.submit_and_wait(
{
"TransactionType": "Export",
"LastLedgerSequence": current_seq + 12,
"Fee": "1000000",
"ExportedTxn": {
"TransactionType": "Payment",
"Account": alice.address,
"Destination": bob.address,
"Amount": "1000000",
"Fee": "10",
"Sequence": 0,
"TicketSequence": 1,
"FirstLedgerSequence": current_seq + 1,
"LastLedgerSequence": current_seq + 10,
"Flags": 2147483648,
"SigningPubKey": "",
},
},
alice.wallet,
timeout=timeout,
)
ended = ctx.mark("latency-export-submit-end")
elapsed = (ended.monotonic_ns - started.monotonic_ns) / 1_000_000_000
engine_result = result.get("engine_result", "")
log(f"Export result={engine_result} elapsed={elapsed:.3f}s")
if engine_result != "tesSUCCESS":
raise AssertionError(f"Expected Export tesSUCCESS, got {engine_result}")
export_result = assert_export_result(result.get("meta", {}), log)
signers = export_result.get("ExportedTxn", {}).get("Signers", [])
log(f"Export signer count={len(signers)}")
return started, ended
def _summarize_logs(ctx, log, *, label, started, ended):
patterns = {
"rng_selected": r"RNG: entropy selected",
"rng_fallback": r"tier=1",
"rng_participant_aligned": r"tier=2",
"rng_validator_quorum": r"tier=3",
"export_retry": r"terRETRY_EXPORT",
"export_quorum_timeout": r"Export: exportSigSet quorum alignment timeout",
"export_missing_observation_ignored": (
r"Export: missing exportSigSetHash observation ignored"
),
}
for name, pattern in patterns.items():
result = ctx.search_logs(pattern, since=started, until=ended, limit=500)
log(f"log_count {label}.{name}={result.count}")
async def scenario(
ctx,
log,
*,
warmup_ledgers=3,
ledgers=8,
submit_export=False,
export_timeout=90,
):
await ctx.wait_for_ledger_close(timeout=120)
await _require_runtime_config(ctx, log)
_log_runtime_config(ctx, log)
await _require_consensus_entropy(ctx, log)
if submit_export:
# require_export also asserts the UNLReport precondition for successful
# network-mode Export. Keep that explicit in perf runs so a missing
# report does not masquerade as a latency failure.
await require_export(ctx, log, require_runtime_config=False)
await ctx.wait_for_ledgers(warmup_ledgers, node_id=0, timeout=120)
warm_seq = ctx.validated_ledger_index(0)
log(f"Warmup complete at validated ledger {warm_seq}")
export_window = None
if submit_export:
export_window = await _submit_direct_export(
ctx, log, timeout=export_timeout
)
started = ctx.mark("latency-probe-start")
start_seq = ctx.validated_ledger_index(0)
await ctx.wait_for_ledgers(ledgers, node_id=0, timeout=max(120, ledgers * 30))
ended = ctx.mark("latency-probe-end")
end_seq = ctx.validated_ledger_index(0)
if start_seq is None or end_seq is None:
raise AssertionError("validated ledger index unavailable during probe")
elapsed = (ended.monotonic_ns - started.monotonic_ns) / 1_000_000_000
closed = max(0, end_seq - start_seq)
cadence = elapsed / closed if closed else 0.0
log(
f"Observed validated ledgers {start_seq + 1}..{end_seq} "
f"closed={closed} elapsed={elapsed:.3f}s cadence={cadence:.3f}s/ledger"
)
tiers: Counter[int] = Counter()
counts: Counter[int] = Counter()
missing_entropy = 0
for seq in range(start_seq + 1, end_seq + 1):
try:
ce, user_txns = get_entropy_tx(ctx, seq)
except AssertionError as exc:
missing_entropy += 1
log(f" Ledger {seq}: no ConsensusEntropy tx ({exc})")
continue
tier = ce.get("EntropyTier", -1)
count = ce.get("EntropyCount", -1)
tiers[tier] += 1
counts[count] += 1
log(
f" Ledger {seq}: tier={tier} count={count} "
f"user_txns={len(user_txns)} digest={ce.get('Digest', '')[:16]}..."
)
log(
"SUMMARY "
f"closed={closed} elapsed_s={elapsed:.3f} cadence_s={cadence:.3f} "
f"tiers={dict(sorted(tiers.items()))} "
f"counts={dict(sorted(counts.items()))} "
f"missing_entropy={missing_entropy}"
)
_summarize_logs(ctx, log, label="probe", started=started, ended=ended)
if export_window is not None:
_summarize_logs(
ctx,
log,
label="export",
started=export_window[0],
ended=export_window[1],
)
log("PASS")

View File

@@ -0,0 +1,62 @@
defaults:
network:
node_count: 5
launcher: tmux
find_ports: true
slave_delay: 0.2
features:
- ConsensusEntropy
track_features:
- ConsensusEntropy
unl_report: true
log_levels:
TxQ: info
Protocol: debug
Peer: debug
LedgerConsensus: debug
ConsensusExtensions: debug
NetworkOPs: info
env:
XAHAU_RESOURCE_PER_PORT: "1"
rc:
- rng_poll_ms=333
tests:
- name: steady_state_entropy
script: .testnet/scenarios/entropy/steady_state_entropy.py
- name: fallback_without_unl_report
script: .testnet/scenarios/entropy/fallback_without_unl_report.py
network:
unl_report: false
- name: steady_state_entropy_fast_start
script: .testnet/scenarios/entropy/steady_state_entropy.py
network:
env:
XAHAUD_RUNTIME_TEST_CONFIG: '{"set":{"global":{"rng_poll_ms":333,"bootstrap_fast_start":true}}}'
- name: entropy_with_transactions
script: .testnet/scenarios/entropy/entropy_with_transactions.py
- name: quorum_recovery_smoke
script: .testnet/scenarios/entropy/quorum_recovery_smoke.py
- name: quorum_degradation_smoke
script: .testnet/scenarios/entropy/quorum_degradation_smoke.py
network:
log_levels:
LedgerConsensus: trace
ConsensusExtensions: trace
# Tier 2 (participant_aligned) needs 6 nodes: n=5 has no band (tier2 ==
# quorum). At 6, the 4/6 window is the participant_aligned band.
- name: participant_aligned_smoke
script: .testnet/scenarios/entropy/participant_aligned_smoke.py
network:
node_count: 6
log_levels:
LedgerConsensus: trace
ConsensusExtensions: trace
# Export scenarios: see export-suite.yml

235
BUILD.md
View File

@@ -1,7 +1,7 @@
> These instructions assume you have a C++ development environment ready
> with Git, Python, Conan, CMake, and a C++ compiler. For help setting one up
> on Linux, macOS, or Windows, see [our guide](./docs/build/environment.md).
>
| :warning: **WARNING** :warning:
|---|
| These instructions assume you have a C++ development environment ready with Git, Python, Conan, CMake, and a C++ compiler. For help setting one up on Linux, macOS, or Windows, [see this guide](./docs/build/environment.md). |
> These instructions also assume a basic familiarity with Conan and CMake.
> If you are unfamiliar with Conan,
> you can read our [crash course](./docs/build/conan.md)
@@ -29,9 +29,12 @@ branch.
git checkout develop
```
## Minimum Requirements
See [System Requirements](https://xrpl.org/system-requirements.html).
Building rippled generally requires git, Python, Conan, CMake, and a C++ compiler. Some guidance on setting up such a [C++ development environment can be found here](./docs/build/environment.md).
- [Python 3.7](https://www.python.org/downloads/)
- [Conan 2.x](https://conan.io/downloads)
- [CMake 3.16](https://cmake.org/download/)
@@ -41,11 +44,26 @@ The [minimum compiler versions][2] required are:
| Compiler | Version |
|-------------|---------|
| GCC | 10 |
| GCC | 11 |
| Clang | 13 |
| Apple Clang | 13.1.6 |
| MSVC | 19.23 |
### Linux
The Ubuntu operating system has received the highest level of
quality assurance, testing, and support.
Here are [sample instructions for setting up a C++ development environment on Linux](./docs/build/environment.md#linux).
### Mac
Many xahaud engineers use macOS for development.
Here are [sample instructions for setting up a C++ development environment on macOS](./docs/build/environment.md#macos).
### Windows
We don't recommend Windows for `xahaud` production at this time. As of
November 2025, Ubuntu has the highest level of quality assurance, testing,
and support.
@@ -54,21 +72,23 @@ Windows developers should use Visual Studio 2019. `xahaud` isn't
compatible with [Boost](https://www.boost.org/) 1.78 or 1.79, and Conan
can't build earlier Boost versions.
**Note:** 32-bit Windows development isn't supported.
## Steps
### Set Up Conan
1. (Optional) If you've never used Conan, use autodetect to set up a default profile.
After you have a [C++ development environment](./docs/build/environment.md) ready with Git, Python, Conan, CMake, and a C++ compiler, you may need to set up your Conan profile.
These instructions assume a basic familiarity with Conan and CMake.
If you are unfamiliar with Conan, then please read [this crash course](./docs/build/conan.md) or the official [Getting Started][3] walkthrough.
You'll need at least one Conan profile:
```
conan profile detect --force
```
2. Update the compiler settings.
Update the compiler settings:
For Conan 2, you can edit the profile directly at `~/.conan2/profiles/default`,
or use the Conan CLI. Ensure C++20 is set:
@@ -85,10 +105,16 @@ can't build earlier Boost versions.
compiler.cppstd=20
```
Linux developers will commonly have a default Conan [profile][] that compiles
with GCC and links with libstdc++.
If you are linking with libstdc++ (see profile setting `compiler.libcxx`),
then you will need to choose the `libstdc++11` ABI.
Configure Conan (1.x only) to use recipe revisions:
```
conan config set general.revisions_enabled=1
```
**Linux** developers will commonly have a default Conan [profile][] that compiles
with GCC and links with libstdc++.
If you are linking with libstdc++ (see profile setting `compiler.libcxx`),
then you will need to choose the `libstdc++11` ABI:
```
# In ~/.conan2/profiles/default, ensure:
@@ -96,9 +122,23 @@ can't build earlier Boost versions.
compiler.libcxx=libstdc++11
```
On Windows, you should use the x64 native build tools.
An easy way to do that is to run the shortcut "x64 Native Tools Command
Prompt" for the version of Visual Studio that you have installed.
Ensure inter-operability between `boost::string_view` and `std::string_view` types:
```
conan profile update 'conf.tools.build:cxxflags+=["-DBOOST_BEAST_USE_STD_STRING_VIEW"]' default
conan profile update 'env.CXXFLAGS="-DBOOST_BEAST_USE_STD_STRING_VIEW"' default
```
If you have other flags in the `conf.tools.build` or `env.CXXFLAGS` sections, make sure to retain the existing flags and append the new ones. You can check them with:
```
conan profile show default
```
**Windows** developers may need to use the x64 native build tools.
An easy way to do that is to run the shortcut "x64 Native Tools Command
Prompt" for the version of Visual Studio that you have installed.
Windows developers must also build `xahaud` and its dependencies for the x64
architecture.
@@ -109,10 +149,7 @@ can't build earlier Boost versions.
arch=x86_64
```
3. (Optional) If you have multiple compilers installed on your platform,
make sure that Conan and CMake select the one you want to use.
This setting will set the correct variables (`CMAKE_<LANG>_COMPILER`)
in the generated CMake toolchain file.
### Multiple compilers
```
# In ~/.conan2/profiles/default, add under [conf] section:
@@ -129,16 +166,34 @@ can't build earlier Boost versions.
CXX=<path>
```
4. Export our [Conan recipe for Snappy](./external/snappy).
It doesn't explicitly link the C++ standard library,
which allows you to statically link it with GCC, if you want.
It should choose the compiler for dependencies as well,
but not all of them have a Conan recipe that respects this setting (yet).
For the rest, you can set these environment variables.
Replace `<path>` with paths to the desired compilers:
- `conan profile update env.CC=<path> default`
- `conan profile update env.CXX=<path> default`
Export our [Conan recipe for Snappy](./external/snappy).
It does not explicitly link the C++ standard library,
which allows you to statically link it with GCC, if you want.
```
conan export external/snappy --version 1.1.10 --user xahaud --channel stable
```
5. Export our [Conan recipe for SOCI](./external/soci).
It patches their CMake to correctly import its dependencies.
Export our [Conan recipe for RocksDB](./external/rocksdb).
It does not override paths to dependencies when building with Visual Studio.
```
# Conan 1.x
conan export external/rocksdb rocksdb/6.29.5@
# Conan 2.x
conan export --version 6.29.5 external/rocksdb
```
Export our [Conan recipe for SOCI](./external/soci).
It patches their CMake to correctly import its dependencies.
```
conan export external/soci --version 4.0.3 --user xahaud --channel stable
@@ -150,6 +205,17 @@ can't build earlier Boost versions.
conan export external/wasmedge --version 0.11.2 --user xahaud --channel stable
```
Export our [Conan recipe for NuDB](./external/nudb).
It fixes some source files to add missing `#include`s.
```
# Conan 1.x
conan export external/nudb nudb/2.0.8@
# Conan 2.x
conan export --version 2.0.8 external/nudb
```
### Build and Test
1. Create a build directory and move into it.
@@ -168,13 +234,15 @@ can't build earlier Boost versions.
the `install-folder` or `-if` option to every `conan install` command
in the next step.
2. Generate CMake files for every configuration you want to build.
2. Use conan to generate CMake files for every configuration you want to build:
```
conan install .. --output-folder . --build missing --settings build_type=Release
conan install .. --output-folder . --build missing --settings build_type=Debug
```
To build Debug, in the next step, be sure to set `-DCMAKE_BUILD_TYPE=Debug`
For a single-configuration generator, e.g. `Unix Makefiles` or `Ninja`,
you only need to run this command once.
For a multi-configuration generator, e.g. `Visual Studio`, you may want to
@@ -185,13 +253,13 @@ can't build earlier Boost versions.
generated by the first. You can pass the build type on the command line with
`--settings build_type=$BUILD_TYPE` or in the profile itself,
under the section `[settings]` with the key `build_type`.
If you are using a Microsoft Visual C++ compiler,
then you will need to ensure consistency between the `build_type` setting
and the `compiler.runtime` setting.
When `build_type` is `Release`, `compiler.runtime` should be `MT`.
When `build_type` is `Debug`, `compiler.runtime` should be `MTd`.
```
@@ -203,19 +271,22 @@ can't build earlier Boost versions.
`$OUTPUT_FOLDER/build/generators/conan_toolchain.cmake`.
Single-config generators:
```
cmake -DCMAKE_TOOLCHAIN_FILE:FILEPATH=build/generators/conan_toolchain.cmake -DCMAKE_BUILD_TYPE=Release ..
```
Pass the CMake variable [`CMAKE_BUILD_TYPE`][build_type]
and make sure it matches the `build_type` setting you chose in the previous
step.
and make sure it matches the one of the `build_type` settings
you chose in the previous step.
Multi-config gnerators:
For example, to build Debug, in the next command, replace "Release" with "Debug"
```
cmake -DCMAKE_TOOLCHAIN_FILE:FILEPATH=build/generators/conan_toolchain.cmake ..
cmake -DCMAKE_TOOLCHAIN_FILE:FILEPATH=build/generators/conan_toolchain.cmake -DCMAKE_BUILD_TYPE=Release -Dxrpld=ON -Dtests=ON ..
```
Multi-config generators:
```
cmake -DCMAKE_TOOLCHAIN_FILE:FILEPATH=build/generators/conan_toolchain.cmake -Dxrpld=ON -Dtests=ON ..
```
**Note:** You can pass build options for `xahaud` in this step.
@@ -234,7 +305,7 @@ can't build earlier Boost versions.
```
Multi-config generators:
```
cmake --build . --config Release
cmake --build . --config Debug
@@ -259,15 +330,75 @@ can't build earlier Boost versions.
generator. Pass `--help` to see the rest of the command line options.
## Coverage report
The coverage report is intended for developers using compilers GCC
or Clang (including Apple Clang). It is generated by the build target `coverage`,
which is only enabled when the `coverage` option is set, e.g. with
`--options coverage=True` in `conan` or `-Dcoverage=ON` variable in `cmake`
Prerequisites for the coverage report:
- [gcovr tool][gcovr] (can be installed e.g. with [pip][python-pip])
- `gcov` for GCC (installed with the compiler by default) or
- `llvm-cov` for Clang (installed with the compiler by default)
- `Debug` build type
A coverage report is created when the following steps are completed, in order:
1. `rippled` binary built with instrumentation data, enabled by the `coverage`
option mentioned above
2. completed run of unit tests, which populates coverage capture data
3. completed run of the `gcovr` tool (which internally invokes either `gcov` or `llvm-cov`)
to assemble both instrumentation data and the coverage capture data into a coverage report
The above steps are automated into a single target `coverage`. The instrumented
`rippled` binary can also be used for regular development or testing work, at
the cost of extra disk space utilization and a small performance hit
(to store coverage capture). In case of a spurious failure of unit tests, it is
possible to re-run the `coverage` target without rebuilding the `rippled` binary
(since it is simply a dependency of the coverage report target). It is also possible
to select only specific tests for the purpose of the coverage report, by setting
the `coverage_test` variable in `cmake`
The default coverage report format is `html-details`, but the user
can override it to any of the formats listed in `cmake/CodeCoverage.cmake`
by setting the `coverage_format` variable in `cmake`. It is also possible
to generate more than one format at a time by setting the `coverage_extra_args`
variable in `cmake`. The specific command line used to run the `gcovr` tool will be
displayed if the `CODE_COVERAGE_VERBOSE` variable is set.
By default, the code coverage tool runs parallel unit tests with `--unittest-jobs`
set to the number of available CPU cores. This may cause spurious test
errors on Apple. Developers can override the number of unit test jobs with
the `coverage_test_parallelism` variable in `cmake`.
Example use with some cmake variables set:
```
cd .build
conan install .. --output-folder . --build missing --settings build_type=Debug
cmake -DCMAKE_BUILD_TYPE=Debug -Dcoverage=ON -Dxrpld=ON -Dtests=ON -Dcoverage_test_parallelism=2 -Dcoverage_format=html-details -Dcoverage_extra_args="--json coverage.json" -DCMAKE_TOOLCHAIN_FILE:FILEPATH=build/generators/conan_toolchain.cmake ..
cmake --build . --target coverage
```
After the `coverage` target is completed, the generated coverage report will be
stored inside the build directory, as either of:
- file named `coverage.`_extension_ , with a suitable extension for the report format, or
- directory named `coverage`, with the `index.html` and other files inside, for the `html-details` or `html-nested` report formats.
## Options
| Option | Default Value | Description |
| --- | ---| ---|
| `assert` | OFF | Enable assertions.
| `reporting` | OFF | Build the reporting mode feature. |
| `tests` | ON | Build tests. |
| `unity` | ON | Configure a unity build. |
| `coverage` | OFF | Prepare the coverage report. |
| `san` | N/A | Enable a sanitizer with Clang. Choices are `thread` and `address`. |
| `tests` | OFF | Build tests. |
| `unity` | ON | Configure a unity build. |
| `xrpld` | OFF | Build the xrpld (`rippled`) application, and not just the libxrpl library. |
[Unity builds][5] may be faster for the first build
(at the cost of much more memory) since they concatenate sources into fewer
@@ -306,6 +437,18 @@ tools.build:cxxflags=["-Wno-missing-template-arg-list-after-template-kw"]
```
### call to 'async_teardown' is ambiguous
If you are compiling with an early version of Clang 16, then you might hit
a [regression][6] when compiling C++20 that manifests as an [error in a Boost
header][7]. You can workaround it by adding this preprocessor definition:
```
conan profile update 'env.CXXFLAGS="-DBOOST_ASIO_DISABLE_CONCEPTS"' default
conan profile update 'conf.tools.build:cxxflags+=["-DBOOST_ASIO_DISABLE_CONCEPTS"]' default
```
### recompile with -fPIC
If you get a linker error suggesting that you recompile Boost with
@@ -457,6 +600,10 @@ but it is more convenient to put them in a [profile][profile].
[1]: https://github.com/conan-io/conan-center-index/issues/13168
[5]: https://en.wikipedia.org/wiki/Unity_build
[6]: https://github.com/boostorg/beast/issues/2648
[7]: https://github.com/boostorg/beast/issues/2661
[gcovr]: https://gcovr.com/en/stable/getting-started.html
[python-pip]: https://packaging.python.org/en/latest/guides/installing-using-pip-and-virtual-environments/
[build_type]: https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html
[runtime]: https://cmake.org/cmake/help/latest/variable/CMAKE_MSVC_RUNTIME_LIBRARY.html
[toolchain]: https://cmake.org/cmake/help/latest/manual/cmake-toolchains.7.html

View File

@@ -50,7 +50,7 @@ that `test` code should *never* be included in `ripple` code.)
## Validation
The [levelization.sh](levelization.sh) script takes no parameters,
The [levelization.py](levelization.py) script takes no parameters,
reads no environment variables, and can be run from any directory,
as long as it is in the expected location in the rippled repo.
It can be run at any time from within a checked out repo, and will
@@ -84,7 +84,7 @@ It generates many files of [results](results):
Github Actions workflow to test that levelization loops haven't
changed. Unfortunately, if changes are detected, it can't tell if
they are improvements or not, so if you have resolved any issues or
done anything else to improve levelization, run `levelization.sh`,
done anything else to improve levelization, run `levelization.py`,
and commit the updated results.
The `loops.txt` and `ordering.txt` files relate the modules
@@ -108,7 +108,7 @@ The committed files hide the detailed values intentionally, to
prevent false alarms and merging issues, and because it's easy to
get those details locally.
1. Run `levelization.sh`
1. Run `levelization.py`
2. Grep the modules in `paths.txt`.
* For example, if a cycle is found `A ~= B`, simply `grep -w
A Builds/levelization/results/paths.txt | grep -w B`

View File

@@ -0,0 +1,283 @@
#!/usr/bin/env python3
"""
Usage: levelization.py
This script takes no parameters, and can be called from any directory in the file system.
"""
import os
import re
import sys
from collections import defaultdict
from pathlib import Path
# Compile regex patterns once at module level
INCLUDE_PATTERN = re.compile(r"^\s*#include.*/.*\.h")
INCLUDE_PATH_PATTERN = re.compile(r'[<"]([^>"]+)[>"]')
def dictionary_sort_key(s):
"""
Create a sort key that mimics 'sort -d' (dictionary order).
Dictionary order only considers blanks and alphanumeric characters.
"""
return "".join(c for c in s if c.isalnum() or c.isspace())
def get_level(file_path):
"""
Extract the level from a file path (second and third directory components).
Equivalent to bash: cut -d/ -f 2,3
Examples:
src/ripple/app/main.cpp -> ripple.app
src/test/app/Import_test.cpp -> test.app
"""
parts = file_path.split("/")
if len(parts) >= 3:
level = f"{parts[1]}/{parts[2]}"
elif len(parts) >= 2:
level = f"{parts[1]}/toplevel"
else:
level = file_path
# If the "level" indicates a file, cut off the filename
if "." in level.split("/")[-1]:
# Use the "toplevel" label as a workaround for `sort`
# inconsistencies between different utility versions
level = level.rsplit("/", 1)[0] + "/toplevel"
return level.replace("/", ".")
def extract_include_level(include_line):
"""
Extract the include path from an #include directive.
Gets the first two directory components from the include path.
Equivalent to bash: cut -d/ -f 1,2
Examples:
#include <ripple/basics/base_uint.h> -> ripple.basics
#include "ripple/app/main/Application.h" -> ripple.app
"""
match = INCLUDE_PATH_PATTERN.search(include_line)
if not match:
return None
include_path = match.group(1)
parts = include_path.split("/")
if len(parts) >= 2:
include_level = f"{parts[0]}/{parts[1]}"
else:
include_level = include_path
# If the "includelevel" indicates a file, cut off the filename
if "." in include_level.split("/")[-1]:
include_level = include_level.rsplit("/", 1)[0] + "/toplevel"
return include_level.replace("/", ".")
def find_repository_directories(start_path, depth_limit=10):
"""
Find the repository root by looking for src or include folders.
Walks up the directory tree from the start path.
"""
current = start_path.resolve()
for _ in range(depth_limit):
src_path = current / "src"
include_path = current / "include"
has_src = src_path.exists()
has_include = include_path.exists()
if has_src or has_include:
dirs = []
if has_src:
dirs.append(src_path)
if has_include:
dirs.append(include_path)
return current, dirs
parent = current.parent
if parent == current:
break
current = parent
raise RuntimeError(
"Could not find repository root. "
"Expected to find a directory containing 'src' and/or 'include' folders."
)
def main():
script_dir = Path(__file__).parent.resolve()
os.chdir(script_dir)
# Clean up and create results directory.
results_dir = script_dir / "results"
if results_dir.exists():
import shutil
shutil.rmtree(results_dir)
results_dir.mkdir()
# Find the repository root.
try:
repo_root, scan_dirs = find_repository_directories(script_dir)
print(f"Found repository root: {repo_root}")
for scan_dir in scan_dirs:
print(f" Scanning: {scan_dir.relative_to(repo_root)}")
except RuntimeError as e:
print(f"Error: {e}", file=sys.stderr)
sys.exit(1)
# Find all #include directives.
print("\nScanning for raw includes...")
raw_includes = []
rawincludes_file = results_dir / "rawincludes.txt"
with open(rawincludes_file, "w", buffering=8192) as raw_f:
for dir_path in scan_dirs:
for file_path in dir_path.rglob("*"):
if not file_path.is_file():
continue
try:
rel_path_str = str(file_path.relative_to(repo_root))
with open(
file_path, "r", encoding="utf-8", errors="ignore", buffering=8192
) as f:
for line in f:
if "#include" not in line or "boost" in line:
continue
if INCLUDE_PATTERN.match(line):
line_stripped = line.strip()
entry = f"{rel_path_str}:{line_stripped}\n"
print(entry, end="")
raw_f.write(entry)
raw_includes.append((rel_path_str, line_stripped))
except Exception as e:
print(f"Error reading {file_path}: {e}", file=sys.stderr)
# Build levelization paths and count directly.
print("Build levelization paths")
path_counts = defaultdict(int)
for file_path, include_line in raw_includes:
include_level = extract_include_level(include_line)
if not include_level:
continue
level = get_level(file_path)
if level != include_level:
path_counts[(level, include_level)] += 1
# Sort and deduplicate paths.
print("Sort and deduplicate paths")
sorted_items = sorted(
path_counts.items(),
key=lambda x: (dictionary_sort_key(x[0][0]), dictionary_sort_key(x[0][1])),
)
paths_file = results_dir / "paths.txt"
with open(paths_file, "w") as f:
for (level, include_level), count in sorted_items:
line = f"{count:7} {level} {include_level}\n"
print(line.rstrip())
f.write(line)
# Split into flat-file database.
print("Split into flat-file database")
includes_dir = results_dir / "includes"
includedby_dir = results_dir / "includedby"
includes_dir.mkdir()
includedby_dir.mkdir()
includes_data = defaultdict(list)
includedby_data = defaultdict(list)
for (level, include_level), count in sorted_items:
includes_data[level].append((include_level, count))
includedby_data[include_level].append((level, count))
for level in sorted(includes_data.keys(), key=dictionary_sort_key):
with open(includes_dir / level, "w") as f:
for include_level, count in includes_data[level]:
line = f"{include_level} {count}\n"
print(line.rstrip())
f.write(line)
for include_level in sorted(includedby_data.keys(), key=dictionary_sort_key):
with open(includedby_dir / include_level, "w") as f:
for level, count in includedby_data[include_level]:
line = f"{level} {count}\n"
print(line.rstrip())
f.write(line)
# Search for loops.
print("Search for loops")
loops_file = results_dir / "loops.txt"
ordering_file = results_dir / "ordering.txt"
# Pre-load all include files into memory for fast lookup.
includes_cache = {}
includes_lookup = {}
for include_file in sorted(includes_dir.iterdir(), key=lambda p: p.name):
if not include_file.is_file():
continue
includes_cache[include_file.name] = []
includes_lookup[include_file.name] = {}
with open(include_file, "r") as f:
for line in f:
parts = line.strip().split()
if len(parts) >= 2:
name, count = parts[0], int(parts[1])
includes_cache[include_file.name].append((name, count))
includes_lookup[include_file.name][name] = count
loops_found = set()
with open(loops_file, "w", buffering=8192) as loops_f, open(
ordering_file, "w", buffering=8192
) as ordering_f:
for source in sorted(includes_cache.keys()):
for include, include_freq in includes_cache[source]:
if include not in includes_lookup:
continue
source_freq = includes_lookup[include].get(source)
if source_freq is not None:
loop_key = tuple(sorted([source, include]))
if loop_key in loops_found:
continue
loops_found.add(loop_key)
loops_f.write(f"Loop: {source} {include}\n")
diff = include_freq - source_freq
if diff > 3:
loops_f.write(f" {source} > {include}\n\n")
elif diff < -3:
loops_f.write(f" {include} > {source}\n\n")
elif source_freq == include_freq:
loops_f.write(f" {include} == {source}\n\n")
else:
loops_f.write(f" {include} ~= {source}\n\n")
else:
ordering_f.write(f"{source} > {include}\n")
# Print results.
print("\nOrdering:")
with open(ordering_file, "r") as f:
print(f.read(), end="")
print("\nLoops:")
with open(loops_file, "r") as f:
print(f.read(), end="")
if __name__ == "__main__":
main()

View File

@@ -1,130 +0,0 @@
#!/bin/bash
# Usage: levelization.sh
# This script takes no parameters, reads no environment variables,
# and can be run from any directory, as long as it is in the expected
# location in the repo.
pushd $( dirname $0 )
if [ -v PS1 ]
then
# if the shell is interactive, clean up any flotsam before analyzing
git clean -ix
fi
# Ensure all sorting is ASCII-order consistently across platforms.
export LANG=C
rm -rfv results
mkdir results
includes="$( pwd )/results/rawincludes.txt"
pushd ../..
echo Raw includes:
grep -r '^[ ]*#include.*/.*\.h' include src | \
grep -v boost | tee ${includes}
popd
pushd results
oldifs=${IFS}
IFS=:
mkdir includes
mkdir includedby
echo Build levelization paths
exec 3< ${includes} # open rawincludes.txt for input
while read -r -u 3 file include
do
level=$( echo ${file} | cut -d/ -f 2,3 )
# If the "level" indicates a file, cut off the filename
if [[ "${level##*.}" != "${level}" ]]
then
# Use the "toplevel" label as a workaround for `sort`
# inconsistencies between different utility versions
level="$( dirname ${level} )/toplevel"
fi
level=$( echo ${level} | tr '/' '.' )
includelevel=$( echo ${include} | sed 's/.*["<]//; s/[">].*//' | \
cut -d/ -f 1,2 )
if [[ "${includelevel##*.}" != "${includelevel}" ]]
then
# Use the "toplevel" label as a workaround for `sort`
# inconsistencies between different utility versions
includelevel="$( dirname ${includelevel} )/toplevel"
fi
includelevel=$( echo ${includelevel} | tr '/' '.' )
if [[ "$level" != "$includelevel" ]]
then
echo $level $includelevel | tee -a paths.txt
fi
done
echo Sort and dedup paths
sort -ds paths.txt | uniq -c | tee sortedpaths.txt
mv sortedpaths.txt paths.txt
exec 3>&- #close fd 3
IFS=${oldifs}
unset oldifs
echo Split into flat-file database
exec 4<paths.txt # open paths.txt for input
while read -r -u 4 count level include
do
echo ${include} ${count} | tee -a includes/${level}
echo ${level} ${count} | tee -a includedby/${include}
done
exec 4>&- #close fd 4
loops="$( pwd )/loops.txt"
ordering="$( pwd )/ordering.txt"
pushd includes
echo Search for loops
# Redirect stdout to a file
exec 4>&1
exec 1>"${loops}"
for source in *
do
if [[ -f "$source" ]]
then
exec 5<"${source}" # open for input
while read -r -u 5 include includefreq
do
if [[ -f $include ]]
then
if grep -q -w $source $include
then
if grep -q -w "Loop: $include $source" "${loops}"
then
continue
fi
sourcefreq=$( grep -w $source $include | cut -d\ -f2 )
echo "Loop: $source $include"
# If the counts are close, indicate that the two modules are
# on the same level, though they shouldn't be
if [[ $(( $includefreq - $sourcefreq )) -gt 3 ]]
then
echo -e " $source > $include\n"
elif [[ $(( $sourcefreq - $includefreq )) -gt 3 ]]
then
echo -e " $include > $source\n"
elif [[ $sourcefreq -eq $includefreq ]]
then
echo -e " $include == $source\n"
else
echo -e " $include ~= $source\n"
fi
else
echo "$source > $include" >> "${ordering}"
fi
fi
done
exec 5>&- #close fd 5
fi
done
exec 1>&4 #close fd 1
exec 4>&- #close fd 4
cat "${ordering}"
cat "${loops}"
popd
popd
popd

View File

@@ -10,9 +10,6 @@ Loop: test.jtx test.toplevel
Loop: test.jtx test.unit_test
test.unit_test == test.jtx
Loop: xrpl.hook xrpld.app
xrpld.app > xrpl.hook
Loop: xrpl.protocol xrpld.app
xrpld.app > xrpl.protocol
@@ -29,7 +26,7 @@ Loop: xrpld.app xrpld.nodestore
xrpld.app > xrpld.nodestore
Loop: xrpld.app xrpld.overlay
xrpld.overlay ~= xrpld.app
xrpld.overlay == xrpld.app
Loop: xrpld.app xrpld.peerfinder
xrpld.app > xrpld.peerfinder

View File

@@ -12,6 +12,7 @@ libxrpl.server > xrpl.basics
libxrpl.server > xrpl.json
libxrpl.server > xrpl.protocol
libxrpl.server > xrpl.server
test.app > test.shamap
test.app > test.toplevel
test.app > test.unit_test
test.app > xrpl.basics
@@ -21,6 +22,7 @@ test.app > xrpld.ledger
test.app > xrpld.nodestore
test.app > xrpld.overlay
test.app > xrpld.rpc
test.app > xrpld.shamap
test.app > xrpl.hook
test.app > xrpl.json
test.app > xrpl.protocol
@@ -43,6 +45,7 @@ test.consensus > xrpld.app
test.consensus > xrpld.consensus
test.consensus > xrpld.core
test.consensus > xrpld.ledger
test.consensus > xrpl.json
test.consensus > xrpl.protocol
test.core > test.jtx
test.core > test.toplevel
@@ -56,6 +59,8 @@ test.csf > xrpl.basics
test.csf > xrpld.consensus
test.csf > xrpl.json
test.csf > xrpl.protocol
test.formal_verification > xrpld.app
test.formal_verification > xrpld.consensus
test.json > test.jtx
test.json > xrpl.json
test.jtx > xrpl.basics
@@ -84,6 +89,7 @@ test.nodestore > xrpl.basics
test.nodestore > xrpld.core
test.nodestore > xrpld.nodestore
test.nodestore > xrpld.unity
test.nodestore > xrpl.protocol
test.overlay > test.jtx
test.overlay > test.toplevel
test.overlay > test.unit_test
@@ -118,6 +124,7 @@ test.rpc > xrpld.core
test.rpc > xrpld.net
test.rpc > xrpld.overlay
test.rpc > xrpld.rpc
test.rpc > xrpld.shamap
test.rpc > xrpl.hook
test.rpc > xrpl.json
test.rpc > xrpl.protocol
@@ -155,6 +162,7 @@ xrpld.app > xrpl.basics
xrpld.app > xrpld.conditions
xrpld.app > xrpld.consensus
xrpld.app > xrpld.perflog
xrpld.app > xrpl.hook
xrpld.app > xrpl.json
xrpld.app > xrpl.resource
xrpld.conditions > xrpl.basics

View File

@@ -109,7 +109,7 @@ find_package(lz4 REQUIRED)
find_package(LibArchive REQUIRED)
find_package(SOCI REQUIRED)
find_package(SQLite3 REQUIRED)
include(deps/WasmEdge)
option(rocksdb "Enable RocksDB" ON)
if(rocksdb)
find_package(RocksDB REQUIRED)
@@ -118,9 +118,13 @@ if(rocksdb)
)
target_link_libraries(ripple_libs INTERFACE RocksDB::rocksdb)
endif()
find_package(nudb REQUIRED)
find_package(date REQUIRED)
find_package(xxHash REQUIRED)
find_package(magic_enum REQUIRED)
include(deps/WasmEdge)
if(TARGET nudb::core)
set(nudb nudb::core)
elseif(TARGET NuDB::nudb)
@@ -132,11 +136,10 @@ target_link_libraries(ripple_libs INTERFACE ${nudb})
target_link_libraries(ripple_libs INTERFACE
ed25519::ed25519
LibArchive::LibArchive
lz4::lz4
OpenSSL::Crypto
OpenSSL::SSL
# Ripple::grpc_pbufs
# Ripple::pbufs
secp256k1::secp256k1
soci::soci
SQLite::SQLite3

View File

@@ -57,12 +57,12 @@ Ensure that your code compiles according to the build instructions in the
[`documentation`](https://docs.xahau.network/infrastructure/building-xahau).
If you create new source files, they must go under `src/ripple`.
You will need to add them to one of the
[source lists](./cmake/RippledCore.cmake) in CMake.
[source lists](./Builds/CMake/RippledCore.cmake) in CMake.
Please write tests for your code.
If you create new test source files, they must go under `src/test`.
You will need to add them to one of the
[source lists](./cmake/RippledCore.cmake) in CMake.
[source lists](./Builds/CMake/RippledCore.cmake) in CMake.
If your test can be run offline, in under 60 seconds, then it can be an
automatic test run by `rippled --unittest`.
Otherwise, it must be a manual test.

View File

@@ -2,7 +2,7 @@
**Note:** Throughout this README, references to "we" or "our" pertain to the community and contributors involved in the Xahau network. It does not imply a legal entity or a specific collection of individuals.
[Xahau](https://xahau.network/) is a decentralized cryptographic ledger that builds upon the robust foundation of the XRP Ledger. It inherits the XRP Ledger's Byzantine Fault Tolerant consensus algorithm and enhances it with additional features and functionalities. Developers and users familiar with the XRP Ledger will find that most documentation and tutorials available on [xrpl.org](https://xrpl.org) are relevant and applicable to Xahau, including those related to running validators and managing validator keys. For Xahau specific documentation you can visit our [documentation](https://xahau.network/)
[Xahau](https://xahau.network/) is a decentralized cryptographic ledger that builds upon the robust foundation of the XRP Ledger. It inherits the XRP Ledger's Byzantine Fault Tolerant consensus algorithm under the normal XRPL assumptions about configured validator-list overlap, timing, and fault bounds, and enhances it with additional features and functionalities. Developers and users familiar with the XRP Ledger will find that most documentation and tutorials available on [xrpl.org](https://xrpl.org) are relevant and applicable to Xahau, including those related to running validators and managing validator keys. For Xahau specific documentation you can visit our [documentation](https://xahau.network/)
## XAH
XAH is the public, counterparty-free asset native to Xahau and functions primarily as network gas. Transactions submitted to the Xahau network must supply an appropriate amount of XAH, to be burnt by the network as a fee, in order to be successfully included in a validated ledger. In addition, XAH also acts as a bridge currency within the Xahau DEX. XAH is traded on the open-market and is available for anyone to access. Xahau was created in 2023 with a supply of 600 million units of XAH.
@@ -12,7 +12,7 @@ The server software that powers Xahau is called `xahaud` and is available in thi
### Build from Source
* [Read the build instructions in our documentation](https://xahau.network/infrastructure/building-xahau)
* [Read the build instructions in our documentation](https://xahau.network/docs/infrastructure/build-xahaud/)
* If you encounter any issues, please [open an issue](https://github.com/xahau/xahaud/issues)
## Highlights of Xahau

View File

@@ -2,7 +2,7 @@
set -o errexit
marker_base=34be0ce4fef20c978df2923c29321ad6cc17facc
marker_base=f62f74da10c5936c64bd16cd509a8b68f1464e41
marker_commit=${1:-${marker_base}}
if [ $(git merge-base ${marker_commit} ${marker_base}) != ${marker_base} ]; then

View File

@@ -51,10 +51,9 @@ export CMAKE_STATIC_LINKER_FLAGS="-static-libstdc++"
git config --global --add safe.directory /io &&
git checkout src/libxrpl/protocol/BuildInfo.cpp &&
sed -i s/\"0.0.0\"/\"$(date +%Y).$(date +%-m).$(date +%-d)-$(git rev-parse --abbrev-ref HEAD)$(if [ -n "$4" ]; then echo "+$4"; fi)\"/g src/libxrpl/protocol/BuildInfo.cpp &&
sed -i s/\"0.0.0\"/\"$(date +%Y).$(date +%-m).$(date +%-d)-$(git rev-parse --abbrev-ref HEAD)$(if [ -n "$4" ]; then echo "+$4"; fi)\"/g src/libxrpl/protocol/BuildInfo.cpp &&
conan export external/snappy --version 1.1.10 --user xahaud --channel stable &&
conan export external/soci --version 4.0.3 --user xahaud --channel stable &&
conan export external/wasmedge --version 0.11.2 --user xahaud --channel stable &&
cd release-build &&
# Install dependencies - tool_requires in conanfile.py handles glibc 2.28 compatibility
# for build tools (protoc, grpc plugins, b2) in HBB environment
@@ -72,6 +71,7 @@ cmake .. -G Ninja \
-Dxrpld=TRUE \
-Dtests=TRUE &&
ccache -z &&
ccache -p &&
ninja -j $3 && echo "=== Re-running final link with verbose output ===" && rm -f rippled && ninja -v rippled &&
ccache -s &&
strip -s rippled &&
@@ -95,8 +95,16 @@ if [[ "$4" == "" ]]; then
echo "Non GH, local building, no Action runner magic"
else
# GH Action, runner
cp /io/release-build/xahaud /data/builds/$(date +%Y).$(date +%-m).$(date +%-d)-$(git rev-parse --abbrev-ref HEAD)+$4
cp /io/release-build/release.info /data/builds/$(date +%Y).$(date +%-m).$(date +%-d)-$(git rev-parse --abbrev-ref HEAD)+$4.releaseinfo
if [[ "$(git rev-parse --abbrev-ref HEAD)" == "release" ]]; then
echo "building on the release branch... placing it in builds/candidate"
mkdir /data/builds/candidate
cp /io/release-build/xahaud /data/builds/candidate/$(date +%Y).$(date +%-m).$(date +%-d)-$(git rev-parse --abbrev-ref HEAD)+$4
cp /io/release-build/release.info /data/builds/candidate/$(date +%Y).$(date +%-m).$(date +%-d)-$(git rev-parse --abbrev-ref HEAD)+$4.releaseinfo
else
echo "building non-release branch, placing it in builds root"
cp /io/release-build/xahaud /data/builds/$(date +%Y).$(date +%-m).$(date +%-d)-$(git rev-parse --abbrev-ref HEAD)+$4
cp /io/release-build/release.info /data/builds/$(date +%Y).$(date +%-m).$(date +%-d)-$(git rev-parse --abbrev-ref HEAD)+$4.releaseinfo
fi
echo "Published build to: http://build.xahau.tech/"
echo $(date +%Y).$(date +%-m).$(date +%-d)-$(git rev-parse --abbrev-ref HEAD)+$4
fi

View File

@@ -12,17 +12,16 @@ echo "-- GITHUB_REPOSITORY: $1"
echo "-- GITHUB_SHA: $2"
echo "-- GITHUB_RUN_NUMBER: $4"
umask 0000;
umask 0000
####
cd /io;
mkdir -p src/certs;
curl --silent -k https://raw.githubusercontent.com/RichardAH/rippled-release-builder/main/ca-bundle/certbundle.h -o src/certs/certbundle.h;
if [ "`grep certbundle.h src/xrpld/net/detail/RegisterSSLCerts.cpp | wc -l`" -eq "0" ]
then
cp src/xrpld/net/detail/RegisterSSLCerts.cpp src/xrpld/net/detail/RegisterSSLCerts.cpp.old
perl -i -pe "s/^{/{
cd /io
mkdir -p src/certs
curl --silent -k https://raw.githubusercontent.com/RichardAH/rippled-release-builder/main/ca-bundle/certbundle.h -o src/certs/certbundle.h
if [ "$(grep certbundle.h src/xrpld/net/detail/RegisterSSLCerts.cpp | wc -l)" -eq "0" ]; then
cp src/xrpld/net/detail/RegisterSSLCerts.cpp src/xrpld/net/detail/RegisterSSLCerts.cpp.old
perl -i -pe "s/^{/{
#ifdef EMBEDDED_CA_BUNDLE
BIO *cbio = BIO_new_mem_buf(ca_bundle.data(), ca_bundle.size());
X509_STORE *cts = SSL_CTX_get_cert_store(ctx.native_handle());
@@ -68,15 +67,14 @@ fi
source /opt/rh/gcc-toolset-11/enable
export PATH=/usr/local/bin:$PATH
export CC='/usr/lib64/ccache/gcc' &&
export CXX='/usr/lib64/ccache/g++' &&
echo "-- Build Rippled --" &&
pwd &&
export CXX='/usr/lib64/ccache/g++' &&
echo "-- Build Rippled --" &&
pwd &&
echo "MOVING TO [ build-core.sh ]"
echo "MOVING TO [ build-core.sh ]";
printenv > .env.temp;
cat .env.temp | grep '=' | sed s/\\\(^[^=]\\+=\\\)/\\1\\\"/g|sed s/\$/\\\"/g > .env;
rm .env.temp;
printenv >.env.temp
cat .env.temp | grep '=' | sed s/\\\(^[^=]\\+=\\\)/\\1\\\"/g | sed s/\$/\\\"/g >.env
rm .env.temp
echo "Persisting ENV:"
cat .env

View File

@@ -13,7 +13,7 @@
#
# 4. HTTPS Client
#
# 5. Reporting Mode
# 5. <vacated>
#
# 6. Database
#
@@ -282,12 +282,14 @@
# ssl_cert
#
# Specifies the path to the SSL certificate file in PEM format.
# This is not needed if the chain includes it.
# This is not needed if the chain includes it. Use ssl_chain if
# your certificate includes one or more intermediates.
#
# ssl_chain
#
# If you need a certificate chain, specify the path to the
# certificate chain here. The chain may include the end certificate.
# This must be used if the certificate includes intermediates.
#
# ssl_ciphers = <cipherlist>
#
@@ -385,15 +387,32 @@
#
#
#
# [compression]
#
# true or false
#
# true - enables compression
# false - disables compression [default].
#
# The rippled server can save bandwidth by compressing its peer-to-peer communications,
# at a cost of greater CPU usage. If you enable link compression,
# the server automatically compresses communications with peer servers
# that also have link compression enabled.
# https://xrpl.org/enable-link-compression.html
#
#
#
# [ips]
#
# List of hostnames or ips where the XRPL protocol is served. A default
# starter list is included in the code and used if no other hostnames are
# available.
#
# One address or domain name per line is allowed. A port may must be
# specified after adding a space to the address. The ordering of entries
# does not generally matter.
# One address or domain name per line is allowed. A port may be specified
# after adding a space to the address. If a port is not specified, the default
# port of 2459 will be used. Many servers still use the legacy port of 51235.
# To connect to such servers, you must specify the port number. The ordering
# of entries does not generally matter.
#
# The default list of entries is:
# - hubs.xahau.as16089.net 21337
@@ -875,119 +894,6 @@
#
#-------------------------------------------------------------------------------
#
# 5. Reporting Mode
#
#------------
#
# xahaud has an optional operating mode called Reporting Mode. In Reporting
# Mode, xahaud does not connect to the peer to peer network. Instead, xahaud
# will continuously extract data from one or more xahaud servers that are
# connected to the peer to peer network (referred to as an ETL source).
# Reporting mode servers will forward RPC requests that require access to the
# peer to peer network (submit, fee, etc) to an ETL source.
#
# [reporting] Settings for Reporting Mode. If and only if this section is
# present, xahaud will start in reporting mode. This section
# contains a list of ETL source names, and key-value pairs. The
# ETL source names each correspond to a configuration file
# section; the names must match exactly. The key-value pairs are
# optional.
#
#
# [<name>]
#
# A series of key/value pairs that specify an ETL source.
#
# source_ip = <IP-address>
#
# Required. IP address of the ETL source. Can also be a DNS record.
#
# source_ws_port = <number>
#
# Required. Port on which ETL source is accepting unencrypted websocket
# connections.
#
# source_grpc_port = <number>
#
# Required for ETL. Port on which ETL source is accepting gRPC requests.
# If this option is ommitted, this ETL source cannot actually be used for
# ETL; the Reporting Mode server can still forward RPCs to this ETL
# source, but cannot extract data from this ETL source.
#
#
# Key-value pairs (all optional):
#
# read_only Valid values: 0, 1. Default is 0. If set to 1, the server
# will start in strict read-only mode, and will not perform
# ETL. The server will still handle RPC requests, and will
# still forward RPC requests that require access to the p2p
# network.
#
# start_sequence
# Sequence of first ledger to extract if the database is empty.
# ETL extracts ledgers in order. If this setting is absent and
# the database is empty, ETL will start with the next ledger
# validated by the network. If this setting is present and the
# database is not empty, an exception is thrown.
#
# num_markers Degree of parallelism used during the initial ledger
# download. Only used if the database is empty. Valid values
# are 1-256. A higher degree of parallelism results in a
# faster download, but puts more load on the ETL source.
# Default is 2.
#
# Example:
#
# [reporting]
# etl_source1
# etl_source2
# read_only=0
# start_sequence=32570
# num_markers=8
#
# [etl_source1]
# source_ip=1.2.3.4
# source_ws_port=6005
# source_grpc_port=50051
#
# [etl_source2]
# source_ip=5.6.7.8
# source_ws_port=6005
# source_grpc_port=50051
#
# Minimal Example:
#
# [reporting]
# etl_source1
#
# [etl_source1]
# source_ip=1.2.3.4
# source_ws_port=6005
# source_grpc_port=50051
#
#
# Notes:
#
# Reporting Mode requires Postgres (instead of SQLite). The Postgres
# connection info is specified under the [ledger_tx_tables] config section;
# see the Database section for further documentation.
#
# Each ETL source specified must have gRPC enabled (by adding a [port_grpc]
# section to the config). It is recommended to add a secure_gateway entry to
# the gRPC section, in order to bypass the server's rate limiting.
# This section needs to be added to the config of the ETL source, not
# the config of the reporting node. In the example below, the
# reporting server is running at 127.0.0.1. Multiple IPs can be
# specified in secure_gateway via a comma separated list.
#
# [port_grpc]
# ip = 0.0.0.0
# port = 50051
# secure_gateway = 127.0.0.1
#
#
#-------------------------------------------------------------------------------
#
# 6. Database
#
#------------
@@ -995,13 +901,7 @@
# xahaud creates 4 SQLite database to hold bookkeeping information
# about transactions, local credentials, and various other things.
# It also creates the NodeDB, which holds all the objects that
# make up the current and historical ledgers. In Reporting Mode, xahauad
# uses a Postgres database instead of SQLite.
#
# The simplest way to work with Postgres is to install it locally.
# When it is running, execute the initdb.sh script in the current
# directory as: sudo -u postgres ./initdb.sh
# This will create the xahaud user and an empty database of the same name.
# make up the current and historical ledgers.
#
# The size of the NodeDB grows in proportion to the amount of new data and the
# amount of historical data (a configurable setting) so the performance of the
@@ -1043,14 +943,6 @@
# keeping full history is not advised, and using online delete is
# recommended.
#
# type = Cassandra
#
# Apache Cassandra is an open-source, distributed key-value store - see
# https://cassandra.apache.org/ for more details.
#
# Cassandra is an alternative backend to be used only with Reporting Mode.
# See the Reporting Mode section for more details about Reporting Mode.
#
# type = RWDB
#
# RWDB is a high-performance memory store written by XRPL-Labs and optimized
@@ -1068,21 +960,6 @@
# grow unbounded without online_delete. See the
# online_delete section below.
#
# Required keys for Cassandra:
#
# contact_points IP of a node in the Cassandra cluster
#
# port CQL Native Transport Port
#
# secure_connect_bundle
# Absolute path to a secure connect bundle. When using
# a secure connect bundle, contact_points and port are
# not required.
#
# keyspace Name of Cassandra keyspace to use
#
# table_name Name of table in above keyspace to use
#
# Optional keys
#
# cache_size Size of cache for database records. Default is 16384.
@@ -1099,7 +976,7 @@
# default value for the unspecified parameter.
#
# Note: the cache will not be created if online_delete
# is specified, or if shards are used.
# is specified.
#
# fast_load Boolean. If set, load the last persisted ledger
# from disk upon process start before syncing to
@@ -1122,10 +999,6 @@
# earliest_seq The default is 32570 to match the XRP Ledger's
# network's earliest allowed sequence. Alternate
# networks may set this value. Minimum value of 1.
# If a [shard_db] section is defined, and this
# value is present either [node_db] or [shard_db],
# it must be defined with the same value in both
# sections.
#
# Optional keys for NuDB only:
#
@@ -1201,25 +1074,6 @@
# checking until healthy.
# Default is 5.
#
# Optional keys for Cassandra:
#
# username Username to use if Cassandra cluster requires
# authentication
#
# password Password to use if Cassandra cluster requires
# authentication
#
# max_requests_outstanding
# Limits the maximum number of concurrent database
# writes. Default is 10 million. For slower clusters,
# large numbers of concurrent writes can overload the
# cluster. Setting this option can help eliminate
# write timeouts and other write errors due to the
# cluster being overloaded.
# io_threads
# Set the number of IO threads used by the
# Cassandra driver. Defaults to 4.
#
# Notes:
# The 'node_db' entry configures the primary, persistent storage.
#
@@ -1236,32 +1090,6 @@
# your xahaud.cfg file.
# Partial pathnames are relative to the location of the xahaud executable.
#
# [shard_db] Settings for the Shard Database (optional)
#
# Format (without spaces):
# One or more lines of case-insensitive key / value pairs:
# <key> '=' <value>
# ...
#
# Example:
# path=db/shards/nudb
#
# Required keys:
# path Location to store the database
#
# Optional keys:
# max_historical_shards
# The maximum number of historical shards
# to store.
#
# [historical_shard_paths] Additional storage paths for the Shard Database (optional)
#
# Format (without spaces):
# One or more lines, each expressing a full path for storing historical shards:
# /mnt/disk1
# /mnt/disk2
# ...
#
# [sqlite] Tuning settings for the SQLite databases (optional)
#
# Format (without spaces):
@@ -1341,40 +1169,18 @@
# This setting may not be combined with the
# "safety_level" setting.
#
# [ledger_tx_tables] (optional)
# page_size Valid values: integer (MUST be power of 2 between 512 and 65536)
# The default is 4096 bytes. This setting determines
# the size of a page in the transaction.db file.
# See https://www.sqlite.org/pragma.html#pragma_page_size
# for more details about the available options.
#
# conninfo Info for connecting to Postgres. Format is
# postgres://[username]:[password]@[ip]/[database].
# The database and user must already exist. If this
# section is missing and xahaud is running in
# Reporting Mode, xahaud will connect as the
# user running xahaud to a database with the
# same name. On Linux and Mac OS X, the connection
# will take place using the server's UNIX domain
# socket. On Windows, through the localhost IP
# address. Default is empty.
#
# use_tx_tables Valid values: 1, 0
# The default is 1 (true). Determines whether to use
# the SQLite transaction database. If set to 0,
# xahaud will not write to the transaction database,
# and will reject tx, account_tx and tx_history RPCs.
# In Reporting Mode, this setting is ignored.
#
# max_connections Valid values: any positive integer up to 64 bit
# storage length. This configures the maximum
# number of concurrent connections to postgres.
# Default is the maximum possible value to
# fit in a 64 bit integer.
#
# timeout Number of seconds after which idle postgres
# connections are discconnected. If set to 0,
# connections never timeout. Default is 600.
#
#
# remember_ip Value values: 1, 0
# Default is 1 (true). Whether to cache host and
# port connection settings.
# journal_size_limit Valid values: integer
# The default is 1582080. This setting limits
# the size of the journal for transaction.db file. When the limit is
# reached, older entries will be deleted.
# See https://www.sqlite.org/pragma.html#pragma_journal_size_limit
# for more details about the available options.
#
#
#-------------------------------------------------------------------------------
@@ -1640,6 +1446,12 @@
# Admin level API commands over Secure Websockets, when originating
# from the same machine (via the loopback adapter at 127.0.0.1).
#
# "grpc"
#
# ETL commands for Clio. We recommend setting secure_gateway
# in this section to a comma-separated list of the addresses
# of your Clio servers, in order to bypass rippled's rate limiting.
#
# This port is commented out but can be enabled by removing
# the '#' from each corresponding line including the entry under [server]
#
@@ -1684,6 +1496,7 @@ port = 6009
ip = 127.0.0.1
admin = 127.0.0.1
protocol = ws
send_queue_limit = 500
[port_grpc]
port = 50051
@@ -1694,6 +1507,7 @@ secure_gateway = 127.0.0.1
#port = 6008
#ip = 127.0.0.1
#protocol = wss
#send_queue_limit = 500
#-------------------------------------------------------------------------------
@@ -1723,15 +1537,6 @@ advisory_delete=0
/opt/xahaud/db
# To use Postgres, uncomment this section and fill in the appropriate connection
# info. Postgres can only be used in Reporting Mode.
# To disable writing to the transaction database, uncomment this section, and
# set use_tx_tables=0
# [ledger_tx_tables]
# conninfo = postgres://[username]:[password]@[ip]/[database]
# use_tx_tables=1
# This needs to be an absolute directory reference, not a relative one.
# Modify this value as required.
[debug_logfile]
@@ -1781,15 +1586,3 @@ validators-xahau.txt
[network_id]
21337
# 21338
# To run in Reporting Mode, uncomment this section and fill in the appropriate
# connection info for one or more ETL sources.
# [reporting]
# etl_source
#
#
# [etl_source]
# source_grpc_port=50051
# source_ws_port=6005
# source_ip=127.0.0.1

View File

@@ -95,6 +95,9 @@
# - replace both functions setup_target_for_coverage_gcovr_* with a single setup_target_for_coverage_gcovr
# - add support for all gcovr output formats
#
# 2024-04-03, Bronek Kozicki
# - add support for output formats: jacoco, clover, lcov
#
# USAGE:
#
# 1. Copy this file into your cmake modules path.
@@ -256,10 +259,10 @@ endif()
# BASE_DIRECTORY "../" # Base directory for report
# # (defaults to PROJECT_SOURCE_DIR)
# FORMAT "cobertura" # Output format, one of:
# # xml cobertura sonarqube json-summary
# # json-details coveralls csv txt
# # html-single html-nested html-details
# # (xml is an alias to cobertura;
# # xml cobertura sonarqube jacoco clover
# # json-summary json-details coveralls csv
# # txt html-single html-nested html-details
# # lcov (xml is an alias to cobertura;
# # if no format is set, defaults to xml)
# EXCLUDE "src/dir1/*" "src/dir2/*" # Patterns to exclude (can be relative
# # to BASE_DIRECTORY, with CMake 3.4+)
@@ -308,6 +311,8 @@ function(setup_target_for_coverage_gcovr)
set(GCOVR_OUTPUT_FILE ${Coverage_NAME}.txt)
elseif(Coverage_FORMAT STREQUAL "csv")
set(GCOVR_OUTPUT_FILE ${Coverage_NAME}.csv)
elseif(Coverage_FORMAT STREQUAL "lcov")
set(GCOVR_OUTPUT_FILE ${Coverage_NAME}.lcov)
else()
set(GCOVR_OUTPUT_FILE ${Coverage_NAME}.xml)
endif()
@@ -320,6 +325,14 @@ function(setup_target_for_coverage_gcovr)
set(Coverage_FORMAT cobertura) # overwrite xml
elseif(Coverage_FORMAT STREQUAL "sonarqube")
list(APPEND GCOVR_ADDITIONAL_ARGS --sonarqube "${GCOVR_OUTPUT_FILE}" )
elseif(Coverage_FORMAT STREQUAL "jacoco")
list(APPEND GCOVR_ADDITIONAL_ARGS --jacoco "${GCOVR_OUTPUT_FILE}" )
list(APPEND GCOVR_ADDITIONAL_ARGS --jacoco-pretty )
elseif(Coverage_FORMAT STREQUAL "clover")
list(APPEND GCOVR_ADDITIONAL_ARGS --clover "${GCOVR_OUTPUT_FILE}" )
list(APPEND GCOVR_ADDITIONAL_ARGS --clover-pretty )
elseif(Coverage_FORMAT STREQUAL "lcov")
list(APPEND GCOVR_ADDITIONAL_ARGS --lcov "${GCOVR_OUTPUT_FILE}" )
elseif(Coverage_FORMAT STREQUAL "json-summary")
list(APPEND GCOVR_ADDITIONAL_ARGS --json-summary "${GCOVR_OUTPUT_FILE}" )
list(APPEND GCOVR_ADDITIONAL_ARGS --json-summary-pretty)
@@ -380,6 +393,7 @@ function(setup_target_for_coverage_gcovr)
${GCOVR_PATH}
--gcov-executable ${GCOV_TOOL}
--gcov-ignore-parse-errors=negative_hits.warn_once_per_file
--gcov-ignore-parse-errors=suspicious_hits.warn_once_per_file
-r ${BASEDIR}
${GCOVR_ADDITIONAL_ARGS}
${GCOVR_EXCLUDE_ARGS}

View File

@@ -54,6 +54,7 @@ add_library(xrpl.imports.main INTERFACE)
target_link_libraries(xrpl.imports.main
INTERFACE
LibArchive::LibArchive
magic_enum::magic_enum
OpenSSL::Crypto
Ripple::boost
wasmedge::wasmedge
@@ -68,6 +69,17 @@ target_link_libraries(xrpl.imports.main
$<$<BOOL:${voidstar}>:antithesis-sdk-cpp>
)
# date-tz for enhanced logging (always linked, code is #ifdef guarded)
if(TARGET date::date-tz)
target_link_libraries(xrpl.imports.main INTERFACE date::date-tz)
endif()
# BEAST_ENHANCED_LOGGING: enable for Debug builds OR when explicitly requested
# Uses generator expression so it works with multi-config generators (Xcode, VS, Ninja Multi-Config)
target_compile_definitions(xrpl.imports.main INTERFACE
$<$<OR:$<CONFIG:Debug>,$<BOOL:${BEAST_ENHANCED_LOGGING}>>:BEAST_ENHANCED_LOGGING=1>
)
include(add_module)
include(target_link_modules)
@@ -78,12 +90,6 @@ target_link_libraries(xrpl.libxrpl.beast PUBLIC
xrpl.libpb
)
# Conditionally add enhanced logging source when BEAST_ENHANCED_LOGGING is enabled
if(DEFINED BEAST_ENHANCED_LOGGING AND BEAST_ENHANCED_LOGGING)
target_sources(xrpl.libxrpl.beast PRIVATE
src/libxrpl/beast/utility/src/beast_EnhancedLogging.cpp)
endif()
# Level 02
add_module(xrpl basics)
target_link_libraries(xrpl.libxrpl.basics PUBLIC xrpl.libxrpl.beast)
@@ -154,11 +160,18 @@ target_link_modules(xrpl PUBLIC
# $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
# $<INSTALL_INTERFACE:include>)
if(formal_verification AND NOT xrpld)
message(FATAL_ERROR "formal_verification requires xrpld=ON")
endif()
if(xrpld)
add_executable(rippled)
if(tests)
target_compile_definitions(rippled PUBLIC ENABLE_TESTS)
endif()
if(xahaud_runtime_test_config)
target_compile_definitions(rippled PUBLIC XAHAUD_ENABLE_RUNTIME_TEST_CONFIG=1)
endif()
target_include_directories(rippled
PRIVATE
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src>
@@ -174,6 +187,21 @@ if(xrpld)
"${CMAKE_CURRENT_SOURCE_DIR}/src/test/*.cpp"
)
target_sources(rippled PRIVATE ${sources})
set(HOOKS_TEST_DIR "" CACHE PATH "External hook Env-test directory")
if(NOT HOOKS_TEST_DIR AND DEFINED ENV{HOOKS_TEST_DIR})
set(HOOKS_TEST_DIR "$ENV{HOOKS_TEST_DIR}")
endif()
if(HOOKS_TEST_DIR)
file(GLOB_RECURSE hook_test_sources CONFIGURE_DEPENDS
"${HOOKS_TEST_DIR}/*_test.cpp"
)
if(hook_test_sources)
message(STATUS "Including external hook Env tests from ${HOOKS_TEST_DIR}")
target_sources(rippled PRIVATE ${hook_test_sources})
target_include_directories(rippled PRIVATE "${HOOKS_TEST_DIR}")
endif()
endif()
endif()
target_link_libraries(rippled
@@ -187,6 +215,7 @@ if(xrpld)
# This is likely not strictly necessary, but listed explicitly as a good practice.
m
)
include(XahaudFormalVerification)
exclude_if_included(rippled)
# define a macro for tests that might need to
# be exluded or run differently in CI environment

View File

@@ -22,6 +22,9 @@ target_compile_definitions (opts
$<$<BOOL:${beast_no_unit_test_inline}>:BEAST_NO_UNIT_TEST_INLINE=1>
$<$<BOOL:${beast_disable_autolink}>:BEAST_DONT_AUTOLINK_TO_WIN32_LIBRARIES=1>
$<$<BOOL:${single_io_service_thread}>:RIPPLE_SINGLE_IO_SERVICE_THREAD=1>
# Enhanced logging is enabled for Debug builds, or explicitly via
# -DBEAST_ENHANCED_LOGGING=ON for other build types.
$<$<OR:$<CONFIG:Debug>,$<BOOL:${BEAST_ENHANCED_LOGGING}>>:BEAST_ENHANCED_LOGGING=1>
$<$<BOOL:${voidstar}>:ENABLE_VOIDSTAR>)
target_compile_options (opts
INTERFACE

View File

@@ -12,6 +12,21 @@ option(xrpld "Build xrpld" ON)
option(tests "Build tests" ON)
option(xahaud_runtime_test_config
"Enable XAHAUD_RUNTIME_TEST_CONFIG env and runtime_config RPC fault-injection controls"
OFF)
# Conan 2 local opt-in:
# [conf]
# tools.cmake.cmaketoolchain:extra_variables={"xahaud_runtime_test_config":"ON"}
option(formal_verification
"Enable Lean-backed formal-verification cross-check tests"
OFF)
# Default off: this pulls the Lean runtime and the vendored formal model into
# the test binary. Conan/local opt-in mirrors the runtime-test-config pattern:
# [conf]
# tools.cmake.cmaketoolchain:extra_variables={"formal_verification":"ON"}
option(unity "Creates a build using UNITY support in cmake. This is the default" ON)
if(unity)
if(NOT is_ci)

View File

@@ -0,0 +1,65 @@
if(NOT formal_verification)
return()
endif()
if(NOT xrpld)
message(FATAL_ERROR "formal_verification requires xrpld=ON")
endif()
if(NOT tests)
message(FATAL_ERROR "formal_verification requires tests=ON")
endif()
if(CMAKE_CROSSCOMPILING)
message(FATAL_ERROR "formal_verification currently supports native builds only")
endif()
if(WIN32)
message(FATAL_ERROR "formal_verification currently supports Unix-like native builds only")
endif()
set(XAHAU_FORMAL_VERIFICATION_DIR
"${CMAKE_CURRENT_SOURCE_DIR}/formal_verification"
CACHE PATH
"Lean formal-verification project used by formal_verification=ON")
include(XahaudLean)
xahaud_require_lean_toolchain("${XAHAU_FORMAL_VERIFICATION_DIR}")
set(XAHAU_FORMAL_ARCHIVE
"${XAHAU_FORMAL_VERIFICATION_DIR}/.lake/build/lib/libxahau__consensus_XahauConsensus.a")
file(GLOB_RECURSE XAHAU_FORMAL_SOURCES CONFIGURE_DEPENDS
"${XAHAU_FORMAL_VERIFICATION_DIR}/*.lean")
# Lake currently writes package artifacts under the Lean workspace's .lake/
# directory. Keep this option native/test-only until the build is moved to a
# copied CMake-binary-dir workspace or Lake grows a stable external build-dir
# interface we can rely on here.
#
# This target deliberately invokes Lake whenever the formal-enabled `rippled`
# target is built. Lake still performs its own incremental rebuild, but CMake
# must not trust a source-tree `.lake` archive purely by timestamp.
add_custom_target(xahaud_formal_verification_lean
COMMAND "${LAKE_EXECUTABLE}" build XahauConsensus:static
WORKING_DIRECTORY "${XAHAU_FORMAL_VERIFICATION_DIR}"
DEPENDS
"${XAHAU_FORMAL_VERIFICATION_DIR}/lakefile.toml"
"${XAHAU_FORMAL_VERIFICATION_DIR}/lean-toolchain"
"${XAHAU_FORMAL_VERIFICATION_DIR}/lake-manifest.json"
${XAHAU_FORMAL_SOURCES}
BYPRODUCTS "${XAHAU_FORMAL_ARCHIVE}"
COMMENT "Building Lean formal-verification archive"
VERBATIM)
add_dependencies(rippled xahaud_formal_verification_lean)
target_compile_definitions(rippled PRIVATE XAHAUD_ENABLE_FORMAL_VERIFICATION=1)
target_include_directories(rippled PRIVATE "${LEAN_INCLUDE_DIR}")
target_link_libraries(rippled "${XAHAU_FORMAL_ARCHIVE}" "${LEAN_SHARED_LIBRARY}")
if(UNIX)
set_property(TARGET rippled APPEND PROPERTY BUILD_RPATH "${LEAN_SYSROOT}/lib/lean")
endif()
message(STATUS "Formal verification enabled: ${XAHAU_FORMAL_VERIFICATION_DIR}")
message(STATUS "Lean ${LEAN_EXPECTED_VERSION} sysroot: ${LEAN_SYSROOT}")

113
cmake/XahaudLean.cmake Normal file
View File

@@ -0,0 +1,113 @@
include_guard(GLOBAL)
function(xahaud_require_lean_toolchain project_dir)
if(NOT EXISTS "${project_dir}/lean-toolchain")
message(FATAL_ERROR "Lean project is missing lean-toolchain: ${project_dir}")
endif()
file(READ "${project_dir}/lean-toolchain" lean_toolchain)
string(STRIP "${lean_toolchain}" lean_toolchain)
if(NOT lean_toolchain MATCHES "^leanprover/lean4:v([0-9]+\\.[0-9]+\\.[0-9]+([-+._A-Za-z0-9]+)?)$")
message(FATAL_ERROR
"Unsupported lean-toolchain format `${lean_toolchain}` in ${project_dir}")
endif()
set(expected_lean_version "${CMAKE_MATCH_1}")
find_program(LAKE_EXECUTABLE
NAMES lake
HINTS "$ENV{HOME}/.elan/bin")
if(NOT LAKE_EXECUTABLE)
message(FATAL_ERROR
"formal_verification=ON requires Lake on PATH or in ~/.elan/bin. "
"Install elan, then run `lake build` once in ${project_dir}.")
endif()
execute_process(
COMMAND "${LAKE_EXECUTABLE}" env lean --version
WORKING_DIRECTORY "${project_dir}"
OUTPUT_VARIABLE lean_version_output
ERROR_VARIABLE lean_version_error
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_STRIP_TRAILING_WHITESPACE
RESULT_VARIABLE lean_version_result)
if(NOT lean_version_result EQUAL 0)
message(FATAL_ERROR
"Could not run `${LAKE_EXECUTABLE} env lean --version`: "
"${lean_version_error}")
endif()
if(NOT lean_version_output MATCHES "^Lean \\(version ([^,)]+)[,)]")
message(FATAL_ERROR
"Could not parse Lean version from `${lean_version_output}`")
endif()
set(actual_lean_version "${CMAKE_MATCH_1}")
if(NOT actual_lean_version STREQUAL expected_lean_version)
message(FATAL_ERROR
"Lean version mismatch for formal_verification=ON. "
"Expected ${expected_lean_version} from ${project_dir}/lean-toolchain, "
"but `${LAKE_EXECUTABLE} env lean --version` returned "
"`${lean_version_output}`")
endif()
execute_process(
COMMAND "${LAKE_EXECUTABLE}" --version
WORKING_DIRECTORY "${project_dir}"
OUTPUT_VARIABLE lake_version_output
ERROR_VARIABLE lake_version_error
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_STRIP_TRAILING_WHITESPACE
RESULT_VARIABLE lake_version_result)
if(NOT lake_version_result EQUAL 0)
message(FATAL_ERROR
"Could not run `${LAKE_EXECUTABLE} --version`: ${lake_version_error}")
endif()
if(NOT lake_version_output MATCHES "Lean version ([^)]+)\\)")
message(FATAL_ERROR
"Could not parse Lake's Lean version from `${lake_version_output}`")
endif()
set(lake_lean_version "${CMAKE_MATCH_1}")
if(NOT lake_lean_version STREQUAL expected_lean_version)
message(FATAL_ERROR
"Lake version mismatch for formal_verification=ON. "
"Expected Lean ${expected_lean_version} from ${project_dir}/lean-toolchain, "
"but `${LAKE_EXECUTABLE} --version` returned `${lake_version_output}`")
endif()
if(NOT EXISTS "${project_dir}/lakefile.toml")
message(FATAL_ERROR
"formal_verification=ON requires ${project_dir}/lakefile.toml")
endif()
execute_process(
COMMAND "${LAKE_EXECUTABLE}" env printenv LEAN_SYSROOT
WORKING_DIRECTORY "${project_dir}"
OUTPUT_VARIABLE lean_sysroot
ERROR_VARIABLE lean_sysroot_error
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_STRIP_TRAILING_WHITESPACE
RESULT_VARIABLE lean_sysroot_result)
if(NOT lean_sysroot_result EQUAL 0 OR NOT lean_sysroot)
message(FATAL_ERROR
"Could not determine Lean sysroot via "
"`${LAKE_EXECUTABLE} env printenv LEAN_SYSROOT`: ${lean_sysroot_error}")
endif()
set(lean_include_dir "${lean_sysroot}/include")
if(NOT EXISTS "${lean_include_dir}/lean/lean.h")
message(FATAL_ERROR "Lean header not found: ${lean_include_dir}/lean/lean.h")
endif()
find_library(lean_shared_library
NAMES leanshared libleanshared
PATHS "${lean_sysroot}/lib/lean"
NO_DEFAULT_PATH)
if(NOT lean_shared_library)
message(FATAL_ERROR
"Lean shared runtime not found under ${lean_sysroot}/lib/lean")
endif()
set(LAKE_EXECUTABLE "${LAKE_EXECUTABLE}" PARENT_SCOPE)
set(LEAN_SYSROOT "${lean_sysroot}" PARENT_SCOPE)
set(LEAN_INCLUDE_DIR "${lean_include_dir}" PARENT_SCOPE)
set(LEAN_SHARED_LIBRARY "${lean_shared_library}" PARENT_SCOPE)
set(LEAN_EXPECTED_VERSION "${expected_lean_version}" PARENT_SCOPE)
endfunction()

View File

@@ -1,4 +1,4 @@
find_package(Boost 1.83 REQUIRED
find_package(Boost 1.86 REQUIRED
COMPONENTS
chrono
container
@@ -7,6 +7,7 @@ find_package(Boost 1.83 REQUIRED
date_time
filesystem
json
json
program_options
regex
system
@@ -31,7 +32,6 @@ target_link_libraries(ripple_boost
Boost::date_time
Boost::filesystem
Boost::json
Boost::iostreams
Boost::program_options
Boost::regex
Boost::system

View File

@@ -8,7 +8,7 @@
# those warnings.
if (RIPPLED_SOURCE)
execute_process( COMMAND ${CMAKE_COMMAND} -E copy_if_different
${RIPPLED_SOURCE}/cmake/SociConfig.cmake.patched
${RIPPLED_SOURCE}/Builds/CMake/SociConfig.cmake.patched
cmake/SociConfig.cmake )
endif ()

View File

@@ -1,4 +1,5 @@
from conan import ConanFile
from conan.errors import ConanInvalidConfiguration
from conan.tools.cmake import CMake, CMakeToolchain, cmake_layout
import re
@@ -14,6 +15,7 @@ class Xrpl(ConanFile):
'assertions': [True, False],
'coverage': [True, False],
'fPIC': [True, False],
'formal_verification': [True, False],
'jemalloc': [True, False],
'rocksdb': [True, False],
'shared': [True, False],
@@ -29,6 +31,7 @@ class Xrpl(ConanFile):
'date/3.0.3',
'grpc/1.50.1',
'libarchive/3.7.6',
'magic_enum/0.9.5',
'nudb/2.0.8',
'openssl/3.6.0',
'soci/4.0.3@xahaud/stable',
@@ -44,15 +47,16 @@ class Xrpl(ConanFile):
'assertions': False,
'coverage': False,
'fPIC': True,
'formal_verification': False,
'jemalloc': False,
'rocksdb': True,
'shared': False,
'static': True,
'tests': False,
'unity': False,
'xrpld': False,
'with_wasmedge': True,
'tool_requires_b2': False,
'xrpld': False,
'date/*:header_only': False,
'grpc/*:shared': False,
@@ -99,6 +103,7 @@ class Xrpl(ConanFile):
self.version = match.group(1)
def build_requirements(self):
# These provide build tools (protoc, grpc plugins) that run during build
self.tool_requires('grpc/1.50.1')
# Explicitly require b2 (e.g. for building from source for glibc compatibility)
if self.options.tool_requires_b2:
@@ -108,15 +113,22 @@ class Xrpl(ConanFile):
if self.settings.compiler == 'apple-clang':
self.options['boost/*'].visibility = 'global'
def validate(self):
if self.options.formal_verification and (
not self.options.tests or not self.options.xrpld
):
raise ConanInvalidConfiguration(
'formal_verification=True requires tests=True and xrpld=True'
)
def requirements(self):
# Force boost version for all dependencies to avoid conflicts
self.requires('boost/1.86.0', override=True)
self.requires('lz4/1.10.0', force=True)
self.requires('protobuf/3.21.9', force=True)
# Force sqlite3 version to avoid conflicts with soci
self.requires('sqlite3/3.47.0', override=True)
# Force our custom snappy build for all dependencies
self.requires('snappy/1.1.10@xahaud/stable', override=True)
# Force boost version for all dependencies to avoid conflicts
self.requires('boost/1.86.0', override=True)
self.requires('lz4/1.10.0', force=True)
if self.options.with_wasmedge:
self.requires('wasmedge/0.11.2@xahaud/stable')
@@ -131,6 +143,18 @@ class Xrpl(ConanFile):
'cfg/*',
'cmake/*',
'external/*',
'formal_verification/*.json',
'formal_verification/*.lean',
'formal_verification/*.md',
'formal_verification/*.toml',
'formal_verification/lean-toolchain',
'formal_verification/XahauConsensus/*.lean',
'!formal_verification/.lake',
'!formal_verification/.lake/*',
'!formal_verification/.lake/**',
'!formal_verification/**/.lake',
'!formal_verification/**/.lake/*',
'!formal_verification/**/.lake/**',
'include/*',
'src/*',
)
@@ -147,6 +171,7 @@ class Xrpl(ConanFile):
tc.variables['tests'] = self.options.tests
tc.variables['assert'] = self.options.assertions
tc.variables['coverage'] = self.options.coverage
tc.variables['formal_verification'] = self.options.formal_verification
tc.variables['jemalloc'] = self.options.jemalloc
tc.variables['rocksdb'] = self.options.rocksdb
tc.variables['BUILD_SHARED_LIBS'] = self.options.shared
@@ -162,6 +187,11 @@ class Xrpl(ConanFile):
cmake.build()
def package(self):
if self.options.formal_verification:
raise ConanInvalidConfiguration(
'formal_verification=True is a local/CI test build option and '
'is not supported for Conan packages'
)
cmake = CMake(self)
cmake.verbose = True
cmake.install()

3213
docs/formal-proofs.md Normal file

File diff suppressed because it is too large Load Diff

1
formal_verification/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/.lake

View File

@@ -0,0 +1,166 @@
# xahau_consensus
Lean proofs for small Xahau consensus invariants.
This package is intentionally narrow. It does **not** try to verify the C++
implementation directly. It mirrors small formulas and decision ladders from
the consensus-extension code so the safety arguments can be checked as theorems
instead of repeatedly re-derived in review notes.
Current modules:
- `XahauConsensus.Threshold`
- mirrors `calculateParticipantThreshold`
- proves the Tier-2 intersection inequality:
`count + floor(count / 5) < 2 * participantThreshold count`
- proves the threshold is minimal for that strict inequality
- proves the original-view threshold remains safe when nUNL shrinks the
effective view
- includes the `original=10`, `effective=8` regression example showing why
using the effective view for the Tier-2 floor is forkable
- proves `participantThreshold count <= quorumThreshold count` for
non-empty views
- distinguishes raw formula helpers from the live safety-wrapped gate
thresholds used by `ConsensusExtensions`
- `XahauConsensus.ThresholdFacts`
- records small-network values and band-empty/band-present examples
- proves exact multiple-of-five behavior
- proves threshold monotonicity facts
- `XahauConsensus.SixtyPercent`
- defines a naive `ceil(60%)` threshold
- proves naive 60% is unsafe at exact multiples of five
- proves the live derived floor is one higher there and restores strict
intersection safety
- `XahauConsensus.Intersection`
- proves the abstract cardinality argument behind quorum intersection
- shows two threshold-sized cohorts must overlap above the fault bound
whenever `n + f < 2t`
- specializes that argument to the live participant threshold, including
nUNL-shrunk effective views
- `XahauConsensus.HonestOverlap`
- bridges overlap arithmetic to the consensus claim that two cohorts share at
least one honest validator
- specializes that bridge to the participant threshold and `floor(n/5)` fault
bound
- `XahauConsensus.ViewUniverse`
- proves original-view anchoring remains safe under nUNL shrink
- separates strict safety from threshold reachability
- defines cross-view participant-band presence/absence
- shows effective-view thresholds can be unsafe against the original fault
bound
- shows trusted-superset counting universes erode the intersection margin
- `XahauConsensus.NunlCap`
- models the protocol's ceil-25% nUNL disablement cap
- proves 8/6 and 10/8 band collapse examples
- records that 10 at max cap has effective view 7, below the original
participant floor
- records the important counterexample: original `20`, effective `15` does
**not** make validator quorum meet the original participant floor
- `XahauConsensus.SidecarAlignment`
- models aligned participant counting for sidecar hashes
- proves non-active peers and non-active local publication cannot pad the
alignment count
- proves changing nonmember reports cannot change quorum alignment
- `XahauConsensus.EntropySelector`
- models the tier-label ladder from `ConsensusExtensions::selectEntropy`
- proves non-UNLReport views select fallback
- proves the quorum / participant / fallback bands select the expected tier
- `XahauConsensus.SelectorDeterminism`
- models labeled digest output
- proves digest payload bytes do not affect the label when consensus metadata
is fixed
- records examples where changing view provenance or view sizes changes labels
- `XahauConsensus.ExportGate`
- models export's quorum-aligned success rule
- models export's sidecar-gate outcome as `proceed` or `retryOrExpire`, with
no deterministic fallback signature set
- proves missing minority observation does not block a quorum-aligned export
- proves `fullObservation` alone cannot change the export decision
- `XahauConsensus.ExportQuorum`
- proves two 80% export quorums overlap above the standard Byzantine bound
in nonempty active universes
- proves export quorum overlap remains above the original-view Byzantine
bound when nUNL shrinkage is within the protocol cap
- proves Byzantine validators at the standard bound cannot veto quorum
- records concrete overlap margins for 5/10/20-validator universes
- `XahauConsensus.FinsetIntersection`
- uses Mathlib finite sets to prove the cardinality premise behind the
arithmetic intersection theorems
- specializes that bridge for Tier-2 cohorts, nUNL-shrunk cohorts, and export
80% quorums
- `XahauConsensus.Invariants`
- restates cross-module design contracts in one place
- pins the live safety-wrapped threshold relationship
- proves the cross-view entropy gate is exactly the selector's non-fallback
boundary
- pins non-UNLReport fallback and export full-observation independence
Run:
```sh
~/.elan/bin/lake build
```
## Optional C++ cross-checks
The xahaud CMake build can also compile a Lean-backed unit-test path, but it is
off by default and is not part of normal release builds:
Install Lean through `elan` first. The CMake integration intentionally keeps the
tooling rule simple: when `formal_verification=ON`, it looks for `lake` on
`PATH` or in `~/.elan/bin`, asks that Lake environment to run `lean --version`,
verifies the exact version specified by this package's `lean-toolchain`, then
asks Lake for `LEAN_SYSROOT` and checks that `lean.h` and `libleanshared`
exist.
```sh
conan install . --output-folder=build-formal --build=missing \
-s build_type=Release \
-o '&:tests=True' \
-o '&:xrpld=True' \
-o '&:formal_verification=True'
cmake -S . -B build-formal-cmake \
-DCMAKE_TOOLCHAIN_FILE=$PWD/build-formal/build/generators/conan_toolchain.cmake \
-Dtests=ON \
-Dxrpld=ON \
-Dformal_verification=ON
cmake --build build-formal-cmake --target rippled
./build-formal-cmake/rippled --unittest=LeanConsensus
```
This path currently supports native test builds only. It builds
`XahauConsensus:static`, links the resulting Lean archive and runtime into the
test binary, and runs C++ drift tests over selected scalar formulas and helper
predicates. Some checks compare directly to named production helpers; others are
review-oriented safety predicates computed from those helpers. The exported
surface is intentionally scalar and reviewable:
- Byzantine bound, participant threshold, and validator quorum threshold.
- The safety-wrapped zero-view thresholds used by the live gates.
- The cross-view entropy gate threshold, with effective and original view
denominators kept separate.
- The entropy tier selector policy for `(fromUNLReport, participantCount,
effectiveView, originalView)`.
- Sidecar aligned-participant counting, full-observation, quorum-aligned
predicates, and active-view mask-counting samples.
- Export's quorum-only sidecar-gate proceed predicate, where `fullObservation`
is diagnostic rather than success-gating; a small final-apply snapshot model
makes explicit that gate proceed is not the same as closed-ledger
`Export::doApply` success.
- NegativeUNL cap/effective-view arithmetic.
- View-universe safety predicates and naive-60% regression anchors.
This is still a model-to-code cross-check, not a proof that the C++ implements
the Lean model. Its value is narrower and practical: if a production formula,
decision ladder, or helper predicate changes without the formal model changing
too, the gated unit test fails. The formal CMake target invokes Lake on each
formal-enabled `rippled` build and lets Lake decide whether its own artifacts
are current; CMake does not trust an existing source-tree archive by timestamp.
Lake still writes build artifacts under the Lean workspace's `.lake/`
directory, and the Conan recipe intentionally excludes that directory from
exported sources, so keep this option as a local/CI confidence build rather
than a release packaging input. The Conan recipe rejects
`formal_verification=True` unless `tests=True` and `xrpld=True`, and refuses to
package formal-enabled builds.

View File

@@ -0,0 +1,32 @@
# Xahau Lean Roadmap
This package should stay focused on invariants that are compact enough to be
reviewable and stable enough to mirror from C++.
Good targets:
1. Threshold arithmetic
- Tier-2 participant threshold formula
- quorum threshold relation
- nUNL original-view anchoring
- small-network boundary examples
2. Sidecar alignment
- active-view-only counting
- quorum-aligned predicate
- full-observation as diagnostic vs success precondition where applicable
3. Entropy selector
- non-UNLReport fallback
- tier ladder from agreed participant count
- no local pending-state dependency in the tier decision
4. Export gate
- quorum-aligned success without full observation
- no deterministic fallback value
- retry/expire as liveness behavior, not ledger-content substitution
Poor targets for this package:
- direct verification of C++ implementation details
- wall-clock timing and network scheduling liveness
- full ledger execution semantics
Those belong in C++ tests, CSF/testnet scenarios, or a dedicated temporal model.

View File

@@ -0,0 +1,18 @@
-- This module serves as the root of the `XahauConsensus` library.
-- Import modules here that should be built as part of the library.
import XahauConsensus.Threshold
import XahauConsensus.ThresholdFacts
import XahauConsensus.SixtyPercent
import XahauConsensus.Intersection
import XahauConsensus.HonestOverlap
import XahauConsensus.ViewUniverse
import XahauConsensus.NunlCap
import XahauConsensus.SidecarAlignment
import XahauConsensus.SidecarObservation
import XahauConsensus.EntropySelector
import XahauConsensus.SelectorDeterminism
import XahauConsensus.ExportGate
import XahauConsensus.ExportQuorum
import XahauConsensus.FinsetIntersection
import XahauConsensus.Invariants
import XahauConsensus.FFI

View File

@@ -0,0 +1,74 @@
import XahauConsensus.Threshold
namespace XahauConsensus
inductive EntropyTier where
| consensusFallback
| participantAligned
| validatorQuorum
deriving DecidableEq, Repr
/-- Minimal model of `ConsensusExtensions::selectEntropy`'s network,
non-failed, non-empty tier ladder.
The real C++ also computes a digest. This model deliberately focuses on the
part that can fork by labeling the same agreed set differently: the tier
decision from `(fromUNLReport, participantCount, effectiveView, originalView)`.
It does not model the standalone development shortcut, timeout-driven
`entropyFailed_` downgrade, or empty-map fallback; those paths all bypass or
downgrade this ladder rather than producing a stronger non-fallback label.
-/
def selectEntropyTier
(fromUNLReport : Bool)
(participantCount effectiveView originalView : Nat) : EntropyTier :=
if !fromUNLReport then
EntropyTier.consensusFallback
else if participantCount >= safeQuorumThreshold effectiveView then
EntropyTier.validatorQuorum
else if participantCount >= safeParticipantThreshold originalView then
EntropyTier.participantAligned
else
EntropyTier.consensusFallback
/-- Non-standalone nodes must fail closed to fallback until the validator view
is ledger-anchored by a UNLReport. -/
theorem no_unl_report_selects_fallback
(participantCount effectiveView originalView : Nat) :
selectEntropyTier false participantCount effectiveView originalView =
EntropyTier.consensusFallback := by
rfl
/-- At or above the effective-view quorum threshold, the ladder selects the
strongest entropy tier. -/
theorem quorum_count_selects_validator_quorum
{participantCount effectiveView originalView : Nat}
(hQuorum : safeQuorumThreshold effectiveView <= participantCount) :
selectEntropyTier true participantCount effectiveView originalView =
EntropyTier.validatorQuorum := by
unfold selectEntropyTier
simp [hQuorum]
/-- Below validator quorum but at or above the original-view participant floor,
the ladder selects Tier 2. -/
theorem participant_band_selects_tier2
{participantCount effectiveView originalView : Nat}
(hBelowQuorum : participantCount < safeQuorumThreshold effectiveView)
(hParticipant : safeParticipantThreshold originalView <= participantCount) :
selectEntropyTier true participantCount effectiveView originalView =
EntropyTier.participantAligned := by
unfold selectEntropyTier
simp [Nat.not_le_of_gt hBelowQuorum, hParticipant]
/-- Below both thresholds, the ladder falls back. -/
theorem below_participant_floor_selects_fallback
{participantCount effectiveView originalView : Nat}
(hBelowQuorum : participantCount < safeQuorumThreshold effectiveView)
(hBelowParticipant : participantCount < safeParticipantThreshold originalView) :
selectEntropyTier true participantCount effectiveView originalView =
EntropyTier.consensusFallback := by
unfold selectEntropyTier
simp [
Nat.not_le_of_gt hBelowQuorum,
Nat.not_le_of_gt hBelowParticipant]
end XahauConsensus

View File

@@ -0,0 +1,139 @@
namespace XahauConsensus
/-- Minimal model of the sidecar export gate.
`alignedParticipants` is the number of participants observed on the export
sidecar, `quorumThreshold` is the required aligned count, and
`fullObservation` records whether every participant was observed. The C++ gate
must use quorum alignment for success; full observation is only diagnostic.
-/
structure ExportGate where
alignedParticipants : Nat
quorumThreshold : Nat
fullObservation : Bool
deriving DecidableEq, Repr
/-- Export sidecar-gate outcome. This is not the final `Export::doApply`
result: closed-ledger apply re-validates the frozen agreed signature snapshot
before it can create a shadow ticket. -/
inductive ExportOutcome where
| proceed
| retryOrExpire
deriving DecidableEq, Repr
/-- The success predicate used by export: enough participants are aligned. -/
def ExportGate.quorumAligned (gate : ExportGate) : Bool :=
decide (gate.quorumThreshold <= gate.alignedParticipants)
/-- Export proceeds exactly when quorum alignment is met. -/
def ExportGate.proceed (gate : ExportGate) : Bool :=
gate.quorumAligned
/-- Export's externally visible decision shape. -/
def ExportGate.outcome (gate : ExportGate) : ExportOutcome :=
if gate.proceed then ExportOutcome.proceed else ExportOutcome.retryOrExpire
/-- Minimal model of the additional closed-ledger apply preconditions.
The sidecar gate only proves that one `exportSigSetHash` had quorum alignment.
Network-mode `Export::doApply` then independently requires a ledger-anchored
validator view, no convergence failure for the round, a frozen agreed sidecar
map, a parseable/valid signature set, and enough verified signers in that map.
The model intentionally excludes cryptography and metadata construction; it
exists to prevent reading `ExportGate.proceed` as final apply success.
-/
structure ExportApplySnapshot where
fromUNLReport : Bool
convergenceFailed : Bool
agreedSetPresent : Bool
agreedSetValid : Bool
signerCount : Nat
quorumThreshold : Nat
deriving DecidableEq, Repr
/-- Closed-ledger apply can use only a valid, frozen agreed sidecar snapshot. -/
def ExportApplySnapshot.validAgreedSnapshot
(snapshot : ExportApplySnapshot) : Bool :=
snapshot.fromUNLReport &&
!snapshot.convergenceFailed &&
snapshot.agreedSetPresent &&
snapshot.agreedSetValid &&
decide (snapshot.quorumThreshold <= snapshot.signerCount)
/-- Minimal network-mode apply decision: valid agreed snapshot applies; all
other cases retry or expire. -/
def ExportApplySnapshot.outcome
(snapshot : ExportApplySnapshot) : ExportOutcome :=
if snapshot.validAgreedSnapshot then
ExportOutcome.proceed
else
ExportOutcome.retryOrExpire
theorem apply_success_iff_valid_agreed_snapshot
(snapshot : ExportApplySnapshot) :
snapshot.outcome = ExportOutcome.proceed
snapshot.validAgreedSnapshot = true := by
unfold ExportApplySnapshot.outcome
by_cases h : snapshot.validAgreedSnapshot <;> simp [h]
/-- Gate success alone is not final apply success. For example, the sidecar
gate may have quorum alignment while the final apply path has no frozen agreed
sidecar map available and therefore retries. -/
theorem gate_proceed_does_not_imply_apply_success :
gate : ExportGate, snapshot : ExportApplySnapshot,
ExportGate.proceed gate = true
ExportApplySnapshot.outcome snapshot =
ExportOutcome.retryOrExpire := by
refine
ExportGate.mk 4 4 false,
ExportApplySnapshot.mk true false false true 4 4,
?_,
?_ <;> rfl
/-- A missing minority, represented by `fullObservation = false`, does not
prevent export when the quorum threshold is met. -/
theorem missing_minority_does_not_prevent_proceed
{alignedParticipants quorumThreshold : Nat}
(hQuorum : quorumThreshold <= alignedParticipants) :
(ExportGate.mk alignedParticipants quorumThreshold false).proceed = true := by
unfold ExportGate.proceed ExportGate.quorumAligned
simp [hQuorum]
theorem missing_minority_proceeds
{alignedParticipants quorumThreshold : Nat}
(hQuorum : quorumThreshold <= alignedParticipants) :
(ExportGate.mk alignedParticipants quorumThreshold false).outcome =
ExportOutcome.proceed := by
unfold ExportGate.outcome
simp [missing_minority_does_not_prevent_proceed hQuorum]
/-- Export must not proceed below the aligned-participant quorum threshold. -/
theorem below_quorum_does_not_proceed
{alignedParticipants quorumThreshold : Nat}
(fullObservation : Bool)
(hBelow : alignedParticipants < quorumThreshold) :
(ExportGate.mk alignedParticipants quorumThreshold fullObservation).proceed =
false := by
unfold ExportGate.proceed ExportGate.quorumAligned
simp [Nat.not_le_of_gt hBelow]
/-- Below quorum, export retries or expires. There is no deterministic fallback
signature set analogous to RNG's Tier 1 fallback digest. -/
theorem below_quorum_retries_or_expires
{alignedParticipants quorumThreshold : Nat}
(fullObservation : Bool)
(hBelow : alignedParticipants < quorumThreshold) :
(ExportGate.mk alignedParticipants quorumThreshold fullObservation).outcome =
ExportOutcome.retryOrExpire := by
unfold ExportGate.outcome
simp [below_quorum_does_not_proceed fullObservation hBelow]
/-- Flipping only the diagnostic `fullObservation` field cannot change the
export decision. -/
theorem changing_fullObservation_alone_does_not_change_proceed
(alignedParticipants quorumThreshold : Nat) :
(ExportGate.mk alignedParticipants quorumThreshold true).proceed =
(ExportGate.mk alignedParticipants quorumThreshold false).proceed := by
rfl
end XahauConsensus

View File

@@ -0,0 +1,254 @@
import XahauConsensus.Intersection
import XahauConsensus.NunlCap
import XahauConsensus.ThresholdFacts
namespace XahauConsensus
/-!
Nat-cardinality arithmetic for export sidecar quorum uniqueness.
The model deliberately stays at the level used by `Intersection.lean`:
* `n` is the active validator universe size.
* `a` and `b` are the numbers of validators supporting two export sidecar
hashes in that same universe.
* `overlap` is the size of the intersection between those two support sets.
* `faultyOverlap + honestOverlap = overlap` splits that intersection.
No `Finset` structure is needed here; callers supply the usual
inclusion-exclusion cardinality inequality `a + b <= n + overlap`.
-/
theorem disabled_le_cap_mul_four_le
{originalView disabled : Nat}
(hCap : disabled <= disabledCap originalView) :
disabled * 4 <= originalView + 3 := by
unfold disabledCap ceilDiv at hCap
have hFour : 0 < 4 := by decide
simp at hCap
have hMul :=
(Nat.le_div_iff_mul_le hFour).mp hCap
omega
theorem quorumThreshold_mul_five_ge_four_mul (n : Nat) :
4 * n <= 5 * quorumThreshold n := by
unfold quorumThreshold
have hHundred : 0 < 100 := by decide
have hDiv :
(n * 80 + 99) / 100 <= (n * 80 + 99) / 100 :=
Nat.le_refl _
have hBound :=
(Nat.div_le_iff_le_mul hHundred).mp hDiv
omega
theorem byzantineBound_mul_five_le (n : Nat) :
byzantineBound n * 5 <= n := by
unfold byzantineBound
exact Nat.div_mul_le_self n 5
/-- Two 80% export quorums in one active universe overlap by at least
`2 * quorumThreshold n - n`. -/
theorem two_export_quorums_overlap_lower_bound
{n a b overlap : Nat}
(hCardinality : a + b <= n + overlap)
(hA : quorumThreshold n <= a)
(hB : quorumThreshold n <= b) :
2 * quorumThreshold n - n <= overlap := by
omega
/-- The 80% quorum threshold is intersection-safe against the standard
`floor(n / 5)` fault bound for every nonempty active universe. -/
theorem quorumThreshold_intersection_safe
{n : Nat} (hPositive : 0 < n) :
n + byzantineBound n < 2 * quorumThreshold n := by
unfold quorumThreshold byzantineBound
omega
/-- The unconditional version is false: the empty active universe has raw
quorum threshold zero, so there is no strict intersection margin. -/
theorem quorumThreshold_empty_not_intersection_safe :
¬ 0 + byzantineBound 0 < 2 * quorumThreshold 0 := by
native_decide
/-- Two export sidecar hashes both clearing 80% quorum in the same nonempty
active universe must have overlap larger than the standard fault bound. -/
theorem export_hash_quorums_overlap_gt_byzantine
{n a b overlap : Nat}
(hPositive : 0 < n)
(hCardinality : a + b <= n + overlap)
(hA : quorumThreshold n <= a)
(hB : quorumThreshold n <= b) :
byzantineBound n < overlap := by
exact overlap_gt_fault_of_two_threshold_cohorts
hCardinality
hA
hB
(quorumThreshold_intersection_safe hPositive)
/-- If the overlap between two quorum-clearing export hashes is split into
faulty and honest validators, and at most `floor(n / 5)` validators in that
overlap are faulty, then the overlap contains an honest validator. -/
theorem export_hash_quorums_force_honest_overlap
{n a b overlap faultyOverlap honestOverlap : Nat}
(hPositive : 0 < n)
(hCardinality : a + b <= n + overlap)
(hA : quorumThreshold n <= a)
(hB : quorumThreshold n <= b)
(hSplit : overlap = faultyOverlap + honestOverlap)
(hFaulty : faultyOverlap <= byzantineBound n) :
0 < honestOverlap := by
have hOverlap :
byzantineBound n < overlap :=
export_hash_quorums_overlap_gt_byzantine
hPositive
hCardinality
hA
hB
omega
/-- Export quorum intersection remains above the original-view Byzantine bound
when nUNL shrinkage is within the protocol's ceil-25% cap. -/
theorem export_quorum_intersection_safe_under_nunl_cap
{originalView effectiveView disabled : Nat}
(hEffective : effectiveView = originalView - disabled)
(hCap : disabled <= disabledCap originalView)
(hPositive : 0 < effectiveView) :
effectiveView + byzantineBound originalView <
2 * quorumThreshold effectiveView := by
have hCapBound :
disabled * 4 <= originalView + 3 :=
disabled_le_cap_mul_four_le hCap
have hQuorumBound :
4 * effectiveView <= 5 * quorumThreshold effectiveView :=
quorumThreshold_mul_five_ge_four_mul effectiveView
have hByzBound :
byzantineBound originalView * 5 <= originalView :=
byzantineBound_mul_five_le originalView
omega
/-- Two export sidecar hashes both clearing 80% quorum in an nUNL-shrunk
effective view must still overlap above the original-view Byzantine bound,
provided the shrinkage stays within the protocol cap. -/
theorem export_hash_quorums_overlap_gt_original_byzantine_under_nunl_cap
{originalView effectiveView disabled a b overlap : Nat}
(hEffective : effectiveView = originalView - disabled)
(hCap : disabled <= disabledCap originalView)
(hPositive : 0 < effectiveView)
(hCardinality : a + b <= effectiveView + overlap)
(hA : quorumThreshold effectiveView <= a)
(hB : quorumThreshold effectiveView <= b) :
byzantineBound originalView < overlap := by
exact overlap_gt_fault_of_two_threshold_cohorts
hCardinality
hA
hB
(export_quorum_intersection_safe_under_nunl_cap
hEffective
hCap
hPositive)
/-- A Byzantine minority at the standard bound cannot veto export quorum:
after removing `floor(n / 5)` validators, enough validators remain to meet the
80% quorum threshold. -/
theorem byzantineBound_cannot_veto_quorum (n : Nat) :
byzantineBound n + quorumThreshold n <= n := by
unfold byzantineBound quorumThreshold
omega
/-- Equivalent no-veto form using subtraction. -/
theorem quorumThreshold_le_universe_minus_byzantineBound (n : Nat) :
quorumThreshold n <= n - byzantineBound n := by
have hNoVeto := byzantineBound_cannot_veto_quorum n
omega
/-- Concrete regression anchor: in a 5-validator active universe, two 80%
export quorums overlap in at least three validators. -/
theorem export_quorum_five_overlap_at_least_three
{a b overlap : Nat}
(hCardinality : a + b <= 5 + overlap)
(hA : quorumThreshold 5 <= a)
(hB : quorumThreshold 5 <= b) :
3 <= overlap := by
have hLower :
2 * quorumThreshold 5 - 5 <= overlap :=
two_export_quorums_overlap_lower_bound
hCardinality
hA
hB
have hExact : 2 * quorumThreshold 5 - 5 = 3 := by
native_decide
omega
/-- Concrete regression anchor: in a 10-validator active universe, two 80%
export quorums overlap in at least six validators. -/
theorem export_quorum_ten_overlap_at_least_six
{a b overlap : Nat}
(hCardinality : a + b <= 10 + overlap)
(hA : quorumThreshold 10 <= a)
(hB : quorumThreshold 10 <= b) :
6 <= overlap := by
have hLower :
2 * quorumThreshold 10 - 10 <= overlap :=
two_export_quorums_overlap_lower_bound
hCardinality
hA
hB
have hExact : 2 * quorumThreshold 10 - 10 = 6 := by
native_decide
omega
/-- Concrete regression anchor: in a 20-validator active universe, two 80%
export quorums overlap in at least twelve validators. -/
theorem export_quorum_twenty_overlap_at_least_twelve
{a b overlap : Nat}
(hCardinality : a + b <= 20 + overlap)
(hA : quorumThreshold 20 <= a)
(hB : quorumThreshold 20 <= b) :
12 <= overlap := by
have hLower :
2 * quorumThreshold 20 - 20 <= overlap :=
two_export_quorums_overlap_lower_bound
hCardinality
hA
hB
have hExact : 2 * quorumThreshold 20 - 20 = 12 := by
native_decide
omega
/-- On exact multiples of five, two 80% export quorums overlap in at least
`3 * k` validators. -/
theorem export_quorum_five_mul_overlap_at_least_three_mul
{k a b overlap : Nat}
(hCardinality : a + b <= 5 * k + overlap)
(hA : quorumThreshold (5 * k) <= a)
(hB : quorumThreshold (5 * k) <= b) :
3 * k <= overlap := by
have hLower :
2 * quorumThreshold (5 * k) - 5 * k <= overlap :=
two_export_quorums_overlap_lower_bound
hCardinality
hA
hB
rw [quorumThreshold_five_mul] at hLower
omega
/-- On exact multiples of five, quorum overlap strictly exceeds the standard
fault bound by at least `2 * k`. For `k = 0` this is only a non-strict
difference statement; strict safety is provided by
`export_hash_quorums_overlap_gt_byzantine` for nonempty universes. -/
theorem export_quorum_five_mul_overlap_margin
{k a b overlap : Nat}
(hCardinality : a + b <= 5 * k + overlap)
(hA : quorumThreshold (5 * k) <= a)
(hB : quorumThreshold (5 * k) <= b) :
byzantineBound (5 * k) + 2 * k <= overlap := by
have hOverlap :
3 * k <= overlap :=
export_quorum_five_mul_overlap_at_least_three_mul
hCardinality
hA
hB
rw [byzantineBound_five_mul]
omega
end XahauConsensus

View File

@@ -0,0 +1,188 @@
import XahauConsensus.Threshold
import XahauConsensus.Invariants
import XahauConsensus.NunlCap
import XahauConsensus.SidecarAlignment
import XahauConsensus.ViewUniverse
import XahauConsensus.ExportQuorum
import XahauConsensus.SixtyPercent
namespace XahauConsensus
/-! Scalar C ABI exports used by the optional C++ drift tests.
These functions intentionally expose only plain integer formulas. The broader
Lean project proves properties about these definitions; the C++ tests then
check that selected production formulas and helper predicates still compute the
same values.
-/
-- @@start ffi-scalar-export-surface
@[export xahau_byzantine_bound]
def xahauByzantineBound (count : UInt64) : UInt64 :=
(byzantineBound count.toNat).toUInt64
@[export xahau_participant_threshold]
def xahauParticipantThreshold (count : UInt64) : UInt64 :=
(participantThreshold count.toNat).toUInt64
@[export xahau_quorum_threshold]
def xahauQuorumThreshold (count : UInt64) : UInt64 :=
(quorumThreshold count.toNat).toUInt64
@[export xahau_safe_quorum_threshold]
def xahauSafeQuorumThreshold (count : UInt64) : UInt64 :=
(safeQuorumThreshold count.toNat).toUInt64
@[export xahau_safe_participant_threshold]
def xahauSafeParticipantThreshold (count : UInt64) : UInt64 :=
(safeParticipantThreshold count.toNat).toUInt64
@[export xahau_entropy_gate_threshold_for_view]
def xahauEntropyGateThresholdForView
(effectiveView originalView : UInt64) : UInt64 :=
(entropyGateThresholdForView effectiveView.toNat originalView.toNat).toUInt64
def entropyTierCode : EntropyTier UInt8
| EntropyTier.consensusFallback => 1
| EntropyTier.participantAligned => 2
| EntropyTier.validatorQuorum => 3
@[export xahau_select_entropy_tier]
def xahauSelectEntropyTier
(fromUNLReport participantCount effectiveView originalView : UInt64) : UInt8 :=
entropyTierCode <|
selectEntropyTier
(fromUNLReport != 0)
participantCount.toNat
effectiveView.toNat
originalView.toNat
@[export xahau_aligned_participants]
def xahauAlignedParticipants
(aligned localIsMember localPublished : UInt64) : UInt64 :=
(alignedParticipants
aligned.toNat
(localIsMember != 0)
(localPublished != 0)).toUInt64
@[export xahau_quorum_aligned]
def xahauQuorumAligned
(threshold aligned localIsMember localPublished : UInt64) : UInt8 :=
if quorumAligned
threshold.toNat
aligned.toNat
(localIsMember != 0)
(localPublished != 0) then
1
else
0
@[export xahau_full_observation]
def xahauFullObservation (peersSeen txConverged : UInt64) : UInt8 :=
if fullObservation peersSeen.toNat txConverged.toNat then 1 else 0
@[export xahau_export_gate_proceed]
def xahauExportGateProceed
(alignedParticipants quorumThreshold fullObservation : UInt64) : UInt8 :=
if (ExportGate.mk
alignedParticipants.toNat
quorumThreshold.toNat
(fullObservation != 0)).proceed then
1
else
0
@[export xahau_strict_intersection_safe]
def xahauStrictIntersectionSafe
(activeView byzantineUniverse threshold : UInt64) : UInt8 :=
if activeView.toNat + byzantineBound byzantineUniverse.toNat <
2 * threshold.toNat then
1
else
0
@[export xahau_nonvacuous_strict_intersection_safe]
def xahauNonvacuousStrictIntersectionSafe
(activeView byzantineUniverse threshold : UInt64) : UInt8 :=
if threshold.toNat <= activeView.toNat
activeView.toNat + byzantineBound byzantineUniverse.toNat <
2 * threshold.toNat then
1
else
0
@[export xahau_participant_band_nonempty]
def xahauParticipantBandNonempty
(effectiveView originalView : UInt64) : UInt8 :=
if participantThreshold originalView.toNat < quorumThreshold effectiveView.toNat then
1
else
0
@[export xahau_export_quorum_overlap_lower_bound]
def xahauExportQuorumOverlapLowerBound (activeView : UInt64) : UInt64 :=
(2 * quorumThreshold activeView.toNat - activeView.toNat).toUInt64
@[export xahau_export_quorum_safe_under_nunl_cap]
def xahauExportQuorumSafeUnderNunlCap
(originalView effectiveView disabled : UInt64) : UInt8 :=
if effectiveView.toNat = originalView.toNat - disabled.toNat
disabled.toNat <= disabledCap originalView.toNat
0 < effectiveView.toNat
effectiveView.toNat + byzantineBound originalView.toNat <
2 * quorumThreshold effectiveView.toNat then
1
else
0
private def maskBit (mask : UInt64) (peer : Nat) : Bool :=
((mask.toNat / (2 ^ peer)) % 2) == 1
@[export xahau_active_aligned_count_mask]
def xahauActiveAlignedCountMask
(count activeMask alignedMask : UInt64) : UInt64 :=
(activeAlignedCount
(maskBit activeMask)
(maskBit alignedMask)
count.toNat).toUInt64
@[export xahau_quorum_aligned_mask]
def xahauQuorumAlignedMask
(threshold count activeMask alignedMask localIsMember localPublished : UInt64) : UInt8 :=
let aligned :=
activeAlignedCount
(maskBit activeMask)
(maskBit alignedMask)
count.toNat
if quorumAligned
threshold.toNat
aligned
(localIsMember != 0)
(localPublished != 0) then
1
else
0
@[export xahau_naive_sixty_percent_threshold]
def xahauNaiveSixtyPercentThreshold (count : UInt64) : UInt64 :=
(naiveSixtyPercentThreshold count.toNat).toUInt64
@[export xahau_naive_sixty_percent_is_safe]
def xahauNaiveSixtyPercentIsSafe (count : UInt64) : UInt8 :=
if count.toNat + byzantineBound count.toNat <
2 * naiveSixtyPercentThreshold count.toNat then
1
else
0
@[export xahau_disabled_cap]
def xahauDisabledCap (originalView : UInt64) : UInt64 :=
(disabledCap originalView.toNat).toUInt64
@[export xahau_effective_view]
def xahauEffectiveView (originalView disabled : UInt64) : UInt64 :=
(effectiveView originalView.toNat disabled.toNat).toUInt64
-- @@end ffi-scalar-export-surface
end XahauConsensus

View File

@@ -0,0 +1,88 @@
import Mathlib.Data.Finset.Card
import XahauConsensus.ExportQuorum
import XahauConsensus.Intersection
namespace XahauConsensus
/-!
Finite-set bridge for the quorum-intersection arithmetic.
The arithmetic modules prove useful facts from the premise
`a + b <= n + overlap`. This module discharges that premise for actual finite
cohorts `A` and `B` that are both subsets of a common validator universe `U`.
-/
open Finset
/-- Inclusion-exclusion bridge: two finite cohorts inside one universe satisfy
the cardinality premise used by `Intersection.lean`. -/
theorem finset_cardinality_bound
[DecidableEq α]
{U A B : Finset α}
(hA : A U)
(hB : B U) :
A.card + B.card <= U.card + (A B).card := by
have hUnionSubset : A B U := by
intro x hx
rcases Finset.mem_union.mp hx with hxA | hxB
· exact hA hxA
· exact hB hxB
have hUnionCard : (A B).card <= U.card :=
Finset.card_le_card hUnionSubset
have hInclusion :
(A B).card + (A B).card = A.card + B.card :=
Finset.card_union_add_card_inter A B
omega
/-- Set-level Tier-2 form: two participant-threshold cohorts in the same
validator universe overlap above the Byzantine bound. -/
theorem finset_participant_threshold_cohorts_overlap_gt_byzantine
[DecidableEq α]
{U A B : Finset α}
(hAUniverse : A U)
(hBUniverse : B U)
(hAThreshold : participantThreshold U.card <= A.card)
(hBThreshold : participantThreshold U.card <= B.card) :
byzantineBound U.card < (A B).card := by
exact participant_threshold_cohorts_overlap_gt_byzantine
(finset_cardinality_bound hAUniverse hBUniverse)
hAThreshold
hBThreshold
/-- nUNL/set-level form: two original-view participant-threshold cohorts in a
shrunk effective universe still overlap above the original Byzantine bound. -/
theorem finset_participant_threshold_cohorts_overlap_gt_byzantine_under_shrink
[DecidableEq α]
{Original Effective A B : Finset α}
(hEffectiveSubset : Effective Original)
(hAUniverse : A Effective)
(hBUniverse : B Effective)
(hAThreshold : participantThreshold Original.card <= A.card)
(hBThreshold : participantThreshold Original.card <= B.card) :
byzantineBound Original.card < (A B).card := by
have hShrink : Effective.card <= Original.card :=
Finset.card_le_card hEffectiveSubset
exact participant_threshold_cohorts_overlap_gt_byzantine_under_shrink
hShrink
(finset_cardinality_bound hAUniverse hBUniverse)
hAThreshold
hBThreshold
/-- Set-level export form: two 80% export sidecar quorums in the same nonempty
active universe overlap above the standard Byzantine bound. -/
theorem finset_export_hash_quorums_overlap_gt_byzantine
[DecidableEq α]
{U A B : Finset α}
(hNonempty : 0 < U.card)
(hAUniverse : A U)
(hBUniverse : B U)
(hAThreshold : quorumThreshold U.card <= A.card)
(hBThreshold : quorumThreshold U.card <= B.card) :
byzantineBound U.card < (A B).card := by
exact export_hash_quorums_overlap_gt_byzantine
hNonempty
(finset_cardinality_bound hAUniverse hBUniverse)
hAThreshold
hBThreshold
end XahauConsensus

View File

@@ -0,0 +1,70 @@
import XahauConsensus.Intersection
namespace XahauConsensus
/-!
Bridge from cardinality arithmetic to the consensus-language statement:
if cohort overlap is larger than the maximum faulty overlap, then the overlap
contains at least one honest validator.
-/
/-- If the overlap is larger than the number of faulty validators in it, then
some honest validator remains in the overlap. -/
theorem honest_overlap_exists
{overlap faultyInOverlap : Nat}
(hFaultyLtOverlap : faultyInOverlap < overlap) :
0 < overlap - faultyInOverlap := by
omega
/-- If total faulty validators are bounded by `faultBound`, and the overlap is
larger than `faultBound`, then the overlap contains an honest validator. -/
theorem honest_overlap_exists_of_fault_bound
{overlap faultyInOverlap faultBound : Nat}
(hFaultyBound : faultyInOverlap <= faultBound)
(hOverlapGtFaultBound : faultBound < overlap) :
0 < overlap - faultyInOverlap := by
omega
/-- Direct bridge from the abstract two-cohort intersection theorem: two
threshold-sized cohorts under the strict safety inequality have honest overlap,
provided faulty validators in the overlap are bounded by `f`.
-/
theorem honest_overlap_of_two_threshold_cohorts
{n a b overlap threshold faultBound faultyInOverlap : Nat}
(hCardinality : a + b <= n + overlap)
(hA : threshold <= a)
(hB : threshold <= b)
(hSafety : n + faultBound < 2 * threshold)
(hFaultyBound : faultyInOverlap <= faultBound) :
0 < overlap - faultyInOverlap := by
have hOverlapGtFaultBound :
faultBound < overlap :=
overlap_gt_fault_of_two_threshold_cohorts
hCardinality
hA
hB
hSafety
exact honest_overlap_exists_of_fault_bound
hFaultyBound
hOverlapGtFaultBound
/-- Direct participant-threshold form: two Tier-2-sized cohorts in the same
view have honest overlap under the `floor(n/5)` Byzantine bound. -/
theorem honest_overlap_of_participant_threshold_cohorts
{count a b overlap faultyInOverlap : Nat}
(hCardinality : a + b <= count + overlap)
(hA : participantThreshold count <= a)
(hB : participantThreshold count <= b)
(hFaultyBound : faultyInOverlap <= byzantineBound count) :
0 < overlap - faultyInOverlap := by
have hOverlapGtBound :
byzantineBound count < overlap :=
participant_threshold_cohorts_overlap_gt_byzantine
hCardinality
hA
hB
exact honest_overlap_exists_of_fault_bound
hFaultyBound
hOverlapGtBound
end XahauConsensus

View File

@@ -0,0 +1,96 @@
import XahauConsensus.Threshold
namespace XahauConsensus
/-!
Abstract cardinality arithmetic for quorum intersection arguments.
The variables are plain natural-number cardinalities:
* `n`: universe size
* `a`, `b`: cohort sizes
* `o`: overlap size
* `t`: quorum threshold
* `f`: tolerated faulty overlap
The shape `a + b <= n + o` captures the inclusion-exclusion upper bound
without committing to a concrete `Finset` model.
-/
/-- If two threshold-sized cohorts fit in an `n`-sized universe only by
overlapping by `o`, and `n + f < 2 * t`, then the overlap is larger than the
fault bound `f`. -/
theorem overlap_gt_fault_of_two_threshold_cohorts
{n a b o t f : Nat}
(hCardinality : a + b <= n + o)
(hA : t <= a)
(hB : t <= b)
(hSafety : n + f < 2 * t) :
f < o := by
omega
/-- Reviewer-facing contrapositive form: if the overlap is no larger than the
fault bound, then under the strict safety inequality the two cohorts cannot
both meet threshold. -/
theorem not_both_threshold_cohorts_of_overlap_le_fault
{n a b o t f : Nat}
(hOverlap : o <= f)
(hCardinality : a + b <= n + o)
(hSafety : n + f < 2 * t) :
¬ (t <= a t <= b) := by
intro hBoth
have hStrict :
f < o :=
overlap_gt_fault_of_two_threshold_cohorts
hCardinality hBoth.1 hBoth.2 hSafety
omega
/-- Equivalent disjunctive form of the reviewer fact: with insufficient
overlap, at least one candidate cohort must be below threshold. -/
theorem overlap_le_fault_forces_cohort_below_threshold
{n a b o t f : Nat}
(hOverlap : o <= f)
(hCardinality : a + b <= n + o)
(hSafety : n + f < 2 * t) :
a < t b < t := by
have hNotBoth :
¬ (t <= a t <= b) :=
not_both_threshold_cohorts_of_overlap_le_fault
hOverlap hCardinality hSafety
omega
/-- Direct Tier-2 form: two cohorts at the participant threshold in the same
original-view universe must overlap by more than the tolerated Byzantine bound.
-/
theorem participant_threshold_cohorts_overlap_gt_byzantine
{count a b overlap : Nat}
(hCardinality : a + b <= count + overlap)
(hA : participantThreshold count <= a)
(hB : participantThreshold count <= b) :
byzantineBound count < overlap := by
exact overlap_gt_fault_of_two_threshold_cohorts
hCardinality
hA
hB
(participantThreshold_intersection_safe count)
/-- nUNL form: when the effective universe shrinks, the original-view
participant threshold still forces overlap above the original Byzantine bound.
-/
theorem participant_threshold_cohorts_overlap_gt_byzantine_under_shrink
{originalView effectiveView a b overlap : Nat}
(hShrink : effectiveView <= originalView)
(hCardinality : a + b <= effectiveView + overlap)
(hA : participantThreshold originalView <= a)
(hB : participantThreshold originalView <= b) :
byzantineBound originalView < overlap := by
exact overlap_gt_fault_of_two_threshold_cohorts
hCardinality
hA
hB
(participantThreshold_safe_under_effective_shrink
originalView
effectiveView
hShrink)
end XahauConsensus

View File

@@ -0,0 +1,112 @@
import XahauConsensus.Threshold
import XahauConsensus.EntropySelector
import XahauConsensus.ExportGate
namespace XahauConsensus
/-!
Small cross-module invariants that state the design contract in one place.
These do not verify C++ directly. They pin the consensus arguments that the C++
is intended to implement.
-/
/-- Same-count band fact: with both thresholds computed from one view size,
Tier 2 is never stricter than validator quorum. Production nUNL rounds use
cross-view thresholds instead; see `entropyGateThresholdForView`. -/
theorem same_count_tier2_not_stricter_than_validator_quorum (count : Nat) :
safeParticipantThreshold count <= safeQuorumThreshold count :=
safeParticipantThreshold_le_safeQuorumThreshold count
/-- Same-view shorthand: the live entropy gate is the weaker of Tier 2 and
validator quorum, so it is never above validator quorum. -/
def entropyGateThresholdModel (count : Nat) : Nat :=
min (safeQuorumThreshold count) (safeParticipantThreshold count)
theorem entropy_gate_le_validator_quorum (count : Nat) :
entropyGateThresholdModel count <= safeQuorumThreshold count := by
unfold entropyGateThresholdModel
exact Nat.min_le_left _ _
theorem entropy_gate_le_participant_threshold (count : Nat) :
entropyGateThresholdModel count <= safeParticipantThreshold count := by
unfold entropyGateThresholdModel
exact Nat.min_le_right _ _
/-- Production shape: validator quorum is over the effective post-nUNL view,
while Tier 2 is over the original pre-nUNL view. -/
def entropyGateThresholdForView (effectiveView originalView : Nat) : Nat :=
min (safeQuorumThreshold effectiveView) (safeParticipantThreshold originalView)
theorem entropy_gate_for_view_le_validator_quorum
(effectiveView originalView : Nat) :
entropyGateThresholdForView effectiveView originalView <=
safeQuorumThreshold effectiveView := by
unfold entropyGateThresholdForView
exact Nat.min_le_left _ _
theorem entropy_gate_for_view_le_participant_threshold
(effectiveView originalView : Nat) :
entropyGateThresholdForView effectiveView originalView <=
safeParticipantThreshold originalView := by
unfold entropyGateThresholdForView
exact Nat.min_le_right _ _
/-- The entropy gate is exactly the selector's non-fallback boundary: reaching
the lower of the validator-quorum and participant-aligned thresholds is enough
to select a non-fallback tier, and below it the selector falls back. -/
theorem selectEntropyTier_nonfallback_iff_entropy_gate
(participantCount effectiveView originalView : Nat) :
selectEntropyTier true participantCount effectiveView originalView
EntropyTier.consensusFallback
entropyGateThresholdForView effectiveView originalView <=
participantCount := by
unfold selectEntropyTier entropyGateThresholdForView
by_cases hQuorum : safeQuorumThreshold effectiveView <= participantCount
· constructor
· intro _
exact Nat.le_trans (Nat.min_le_left _ _) hQuorum
· intro _
simp [hQuorum]
· by_cases hParticipant :
safeParticipantThreshold originalView <= participantCount
· constructor
· intro _
exact Nat.le_trans (Nat.min_le_right _ _) hParticipant
· intro _
simp [hQuorum, hParticipant]
· constructor
· intro hNonfallback
simp [hQuorum, hParticipant] at hNonfallback
· intro hGate
have hBelowQuorum :
participantCount < safeQuorumThreshold effectiveView :=
Nat.lt_of_not_ge hQuorum
have hBelowParticipant :
participantCount < safeParticipantThreshold originalView :=
Nat.lt_of_not_ge hParticipant
have hBelowGate :
participantCount <
min (safeQuorumThreshold effectiveView)
(safeParticipantThreshold originalView) :=
(Nat.lt_min).mpr hBelowQuorum, hBelowParticipant
exact False.elim (Nat.not_lt_of_ge hGate hBelowGate)
/-- Until the view is ledger-anchored, entropy tier labeling fails closed. -/
theorem non_unl_report_cannot_mint_nonfallback
(participantCount effectiveView originalView : Nat) :
selectEntropyTier false participantCount effectiveView originalView =
EntropyTier.consensusFallback :=
no_unl_report_selects_fallback participantCount effectiveView originalView
/-- Export success is a quorum-alignment property, not a full-observation
property. -/
theorem export_success_independent_of_full_observation
(alignedParticipants quorumThreshold : Nat) :
(ExportGate.mk alignedParticipants quorumThreshold true).proceed =
(ExportGate.mk alignedParticipants quorumThreshold false).proceed :=
changing_fullObservation_alone_does_not_change_proceed
alignedParticipants
quorumThreshold
end XahauConsensus

View File

@@ -0,0 +1,147 @@
import XahauConsensus.Threshold
namespace XahauConsensus
/-!
Arithmetic facts for nUNL-capped view shrinkage.
The examples here intentionally use the original view for the participant
floor and the effective post-nUNL view for validator quorum. That is the
cross-view comparison that matters when disabled validators collapse the space
between the Tier-2 participant floor and the Tier-3 validator-quorum floor.
-/
/-- Integer ceiling division, defined defensively for `d = 0`. -/
def ceilDiv (n d : Nat) : Nat :=
if d = 0 then 0 else (n + d - 1) / d
/-- The protocol's ceil-25% nUNL disablement cap for an original validator view. -/
def disabledCap (originalView : Nat) : Nat :=
ceilDiv originalView 4
/-- The post-nUNL effective validator view after `disabled` validators drop. -/
def effectiveView (originalView disabled : Nat) : Nat :=
originalView - disabled
theorem ceilDiv_zero_right (n : Nat) : ceilDiv n 0 = 0 := by
simp [ceilDiv]
theorem ceilDiv_four_eight : ceilDiv 8 4 = 2 := by
native_decide
theorem ceilDiv_four_ten : ceilDiv 10 4 = 3 := by
native_decide
theorem ceilDiv_four_twenty : ceilDiv 20 4 = 5 := by
native_decide
theorem disabledCap_eight : disabledCap 8 = 2 := by
native_decide
theorem disabledCap_ten : disabledCap 10 = 3 := by
native_decide
theorem disabledCap_twenty : disabledCap 20 = 5 := by
native_decide
theorem effectiveView_eight_at_disabledCap :
effectiveView 8 (disabledCap 8) = 6 := by
native_decide
theorem effectiveView_ten_at_disabledCap :
effectiveView 10 (disabledCap 10) = 7 := by
native_decide
theorem effectiveView_twenty_at_disabledCap :
effectiveView 20 (disabledCap 20) = 15 := by
native_decide
/-- Original 8 with two disabled validators collapses the participant/quorum band. -/
theorem band_collapse_original8_effective6 :
quorumThreshold 6 = participantThreshold 8 := by
native_decide
theorem quorum_original8_effective6_meets_participant_floor :
participantThreshold 8 <= quorumThreshold 6 := by
native_decide
/-- Original 10 with two disabled validators collapses the participant/quorum band. -/
theorem band_collapse_original10_effective8 :
quorumThreshold 8 = participantThreshold 10 := by
native_decide
theorem quorum_original10_effective8_meets_participant_floor :
participantThreshold 10 <= quorumThreshold 8 := by
native_decide
/-- Original 10 at the full ceil-25% cap leaves effective view 7, below the participant floor. -/
theorem quorum_original10_effective7_below_participant_floor :
quorumThreshold 7 < participantThreshold 10 := by
native_decide
theorem max_cap_original10_below_participant_floor :
quorumThreshold (effectiveView 10 (disabledCap 10)) <
participantThreshold 10 := by
native_decide
/-- At original 20, the full ceil-25% cap leaves effective view 15, which is too small. -/
theorem quorum_original20_effective15_below_participant_floor :
quorumThreshold 15 < participantThreshold 20 := by
native_decide
theorem quorum_original20_effective15_does_not_meet_participant_floor :
¬ participantThreshold 20 <= quorumThreshold 15 := by
native_decide
/-- Original 20 with four disabled validators collapses the participant/quorum band. -/
theorem band_collapse_original20_effective16 :
quorumThreshold 16 = participantThreshold 20 := by
native_decide
theorem quorum_original20_effective16_meets_participant_floor :
participantThreshold 20 <= quorumThreshold 16 := by
native_decide
/-- The ceil-25% cap does not by itself guarantee collapse at size 20. -/
theorem max_cap_original20_below_participant_floor :
quorumThreshold (effectiveView 20 (disabledCap 20)) <
participantThreshold 20 := by
native_decide
/--
General cross-view comparison: an effective-view quorum satisfies the
original-view participant floor whenever that quorum clears the original
intersection boundary.
-/
theorem quorumThreshold_meets_participantThreshold_of_intersection_premise
{originalView effectiveView : Nat}
(h :
originalView + byzantineBound originalView <
2 * quorumThreshold effectiveView) :
participantThreshold originalView <= quorumThreshold effectiveView := by
exact participantThreshold_minimal originalView (quorumThreshold effectiveView) h
/--
Once the effective-view quorum threshold meets the original-view participant
floor, any validator count meeting validator quorum also meets the participant
floor anchored to the original view.
-/
theorem validators_meet_participant_floor_of_meet_quorum
{originalView effectiveView validators : Nat}
(hBand : participantThreshold originalView <= quorumThreshold effectiveView)
(hQuorum : quorumThreshold effectiveView <= validators) :
participantThreshold originalView <= validators :=
Nat.le_trans hBand hQuorum
/-- If cross-view quorum is no higher than the participant floor, the in-between band is empty. -/
theorem cross_view_participant_band_empty
{originalView effectiveView : Nat}
(hCollapse : quorumThreshold effectiveView <= participantThreshold originalView) :
¬ participants,
participantThreshold originalView <= participants
participants < quorumThreshold effectiveView := by
intro hExists
rcases hExists with participants, hParticipant, hBelowQuorum
omega
end XahauConsensus

View File

@@ -0,0 +1,64 @@
import XahauConsensus.EntropySelector
namespace XahauConsensus
/-- A minimal digest model: the payload is opaque to the selector, while the
label is the entropy tier chosen from the consensus metadata. -/
structure LabeledDigest (α : Type) where
payload : α
label : EntropyTier
deriving Repr
def labelDigest
(fromUNLReport : Bool)
(participantCount effectiveView originalView : Nat)
(payload : α) : LabeledDigest α :=
{ payload
label :=
selectEntropyTier
fromUNLReport
participantCount
effectiveView
originalView }
/-- The digest payload itself does not affect the selected tier. The label is
entirely determined by the consensus metadata. -/
theorem payload_does_not_affect_tier
{α : Type}
{payloadA payloadB : α}
(fromUNLReport : Bool)
(participantCount effectiveView originalView : Nat) :
(labelDigest
fromUNLReport
participantCount
effectiveView
originalView
payloadA).label =
(labelDigest
fromUNLReport
participantCount
effectiveView
originalView
payloadB).label := by
rfl
/-- Without a UNLReport anchor the same count and views can receive a different
label. -/
theorem label_can_differ_when_fromUNLReport_differs :
(labelDigest true 8 10 10 0).label
(labelDigest false 8 10 10 0).label := by
native_decide
/-- Changing the effective validator view can change the digest label. -/
theorem label_can_differ_when_effective_view_differs :
(labelDigest true 7 8 10 0).label
(labelDigest true 7 10 10 0).label := by
native_decide
/-- Changing the original validator view can change the digest label. -/
theorem label_can_differ_when_original_view_differs :
(labelDigest true 6 10 8 0).label
(labelDigest true 6 10 10 0).label := by
native_decide
end XahauConsensus

View File

@@ -0,0 +1,241 @@
namespace XahauConsensus
/-- Count a local boolean contribution as the `Nat` value used in threshold
comparisons. -/
def localPublishedCount (localPublished : Bool) : Nat :=
if localPublished then 1 else 0
/-- The proof-level participant count behind sidecar alignment.
`aligned` is the count of aligned remote active-view participants; a local
publication contributes one more participant. -/
def alignedParticipants
(aligned : Nat)
(localIsMember localPublished : Bool) : Nat :=
aligned + localPublishedCount (localIsMember && localPublished)
/-- Sidecar quorum predicate, kept boolean to mirror the implementation check. -/
def quorumAligned
(threshold aligned : Nat)
(localIsMember localPublished : Bool) : Bool :=
decide (threshold <= alignedParticipants aligned localIsMember localPublished)
/-- Full sidecar observation means every converged transaction has been seen. -/
def fullObservation (peersSeen txConverged : Nat) : Bool :=
peersSeen == txConverged
/-- Count aligned peers from a finite peer prefix, filtering through the active
view before any alignment bit contributes. -/
def activeAlignedCount
(inActiveView peerAligned : Nat Bool) : Nat Nat
| 0 => 0
| peer + 1 =>
activeAlignedCount inActiveView peerAligned peer +
localPublishedCount (inActiveView peer && peerAligned peer)
theorem localPublishedCount_true :
localPublishedCount true = 1 := by
rfl
theorem localPublishedCount_false :
localPublishedCount false = 0 := by
rfl
theorem localPublishedCount_le_one (published : Bool) :
localPublishedCount published <= 1 := by
cases published <;> simp [localPublishedCount]
/-- Core participant-count equation: aligned remotes plus the local published
contribution. -/
theorem alignedParticipants_eq_aligned_plus_localPublished
(aligned : Nat) (localIsMember localPublished : Bool) :
alignedParticipants aligned localIsMember localPublished =
aligned + localPublishedCount (localIsMember && localPublished) := by
rfl
/-- A non-active local node cannot pad the participant count. -/
theorem alignedParticipants_local_nonmember
(aligned : Nat) (localPublished : Bool) :
alignedParticipants aligned false localPublished = aligned := by
cases localPublished <;> rfl
/-- An active local node contributes exactly when it published the sidecar hash. -/
theorem alignedParticipants_local_member
(aligned : Nat) (localPublished : Bool) :
alignedParticipants aligned true localPublished =
aligned + localPublishedCount localPublished := by
cases localPublished <;> rfl
/-- The local node can add at most one participant to the remote aligned count. -/
theorem alignedParticipants_le_aligned_succ
(aligned : Nat) (localIsMember localPublished : Bool) :
alignedParticipants aligned localIsMember localPublished <= aligned + 1 := by
cases localIsMember <;> cases localPublished <;>
simp [alignedParticipants, localPublishedCount]
/-- The boolean quorum predicate is exactly the threshold comparison over
`alignedParticipants`. -/
theorem quorumAligned_iff_threshold_le_alignedParticipants
(threshold aligned : Nat) (localIsMember localPublished : Bool) :
quorumAligned threshold aligned localIsMember localPublished = true
threshold <= alignedParticipants aligned localIsMember localPublished := by
unfold quorumAligned
simp
/-- The boolean full-observation predicate is exactly equality of the observed
and converged counts. -/
theorem fullObservation_iff_peersSeen_eq_txConverged
(peersSeen txConverged : Nat) :
fullObservation peersSeen txConverged = true
peersSeen = txConverged := by
unfold fullObservation
simp
/-- A peer outside the active view contributes zero, even if its sidecar
alignment bit is set. -/
theorem activeAlignedCount_succ_nonmember
{inActiveView peerAligned : Nat Bool} {peer : Nat}
(hNonmember : inActiveView peer = false) :
activeAlignedCount inActiveView peerAligned (peer + 1) =
activeAlignedCount inActiveView peerAligned peer := by
simp [activeAlignedCount, hNonmember, localPublishedCount]
/-- A prefix of `n` peer positions can contribute at most `n` aligned active
remote participants. -/
theorem activeAlignedCount_le_prefix
(inActiveView peerAligned : Nat Bool) (n : Nat) :
activeAlignedCount inActiveView peerAligned n <= n := by
induction n with
| zero =>
simp [activeAlignedCount]
| succ n ih =>
cases hAligned : inActiveView n && peerAligned n
· simp [activeAlignedCount, hAligned, localPublishedCount]
exact Nat.le_trans ih (Nat.le_succ n)
· simp [activeAlignedCount, hAligned, localPublishedCount]
exact ih
/-- With the optional local contribution included, the participant count is
bounded by the inspected remote prefix plus one. -/
theorem alignedParticipants_le_prefix_succ
(inActiveView peerAligned : Nat Bool)
(n : Nat)
(localIsMember localPublished : Bool) :
alignedParticipants
(activeAlignedCount inActiveView peerAligned n)
localIsMember
localPublished <= n + 1 := by
have hRemote := activeAlignedCount_le_prefix inActiveView peerAligned n
cases localIsMember <;> cases localPublished <;>
simp [alignedParticipants, localPublishedCount]
· exact Nat.le_trans hRemote (Nat.le_succ n)
· exact Nat.le_trans hRemote (Nat.le_succ n)
· exact Nat.le_trans hRemote (Nat.le_succ n)
· exact hRemote
/-- Adding a nonmember peer to the inspected prefix cannot increase
`alignedParticipants`. -/
theorem alignedParticipants_succ_nonmember
{inActiveView peerAligned : Nat Bool} {peer : Nat}
(localIsMember localPublished : Bool)
(hNonmember : inActiveView peer = false) :
alignedParticipants
(activeAlignedCount inActiveView peerAligned (peer + 1))
localIsMember
localPublished =
alignedParticipants
(activeAlignedCount inActiveView peerAligned peer)
localIsMember
localPublished := by
simp [alignedParticipants, activeAlignedCount_succ_nonmember hNonmember]
/-- Consequently, a nonmember peer cannot change the quorum-aligned result. -/
theorem quorumAligned_succ_nonmember
{inActiveView peerAligned : Nat Bool} {peer threshold : Nat}
(localIsMember localPublished : Bool)
(hNonmember : inActiveView peer = false) :
quorumAligned threshold
(activeAlignedCount inActiveView peerAligned (peer + 1))
localIsMember
localPublished =
quorumAligned threshold
(activeAlignedCount inActiveView peerAligned peer)
localIsMember
localPublished := by
simp [
quorumAligned,
alignedParticipants_succ_nonmember
localIsMember
localPublished
hNonmember]
/-- Active-view filtering: only member peers' alignment bits can affect the
aligned remote count. -/
theorem activeAlignedCount_ext_on_members
{n : Nat} {inActiveView alignedA alignedB : Nat Bool}
(hSameOnMembers :
peer, peer < n inActiveView peer = true
alignedA peer = alignedB peer) :
activeAlignedCount inActiveView alignedA n =
activeAlignedCount inActiveView alignedB n := by
induction n with
| zero =>
rfl
| succ n ih =>
have hPrefix :
peer, peer < n inActiveView peer = true
alignedA peer = alignedB peer := by
intro peer hLt hMember
exact hSameOnMembers peer (Nat.lt_trans hLt (Nat.lt_succ_self n)) hMember
have hAt :
localPublishedCount (inActiveView n && alignedA n) =
localPublishedCount (inActiveView n && alignedB n) := by
cases hMember : inActiveView n
· simp [localPublishedCount]
· have hEq := hSameOnMembers n (Nat.lt_succ_self n) hMember
simp [hEq, localPublishedCount]
simp [activeAlignedCount, ih hPrefix, hAt]
/-- Changing sidecar alignment reports for nonmembers cannot change the final
participant count. -/
theorem alignedParticipants_ext_on_members
{n : Nat} {inActiveView alignedA alignedB : Nat Bool}
{localIsMember : Bool}
{localPublished : Bool}
(hSameOnMembers :
peer, peer < n inActiveView peer = true
alignedA peer = alignedB peer) :
alignedParticipants
(activeAlignedCount inActiveView alignedA n)
localIsMember
localPublished =
alignedParticipants
(activeAlignedCount inActiveView alignedB n)
localIsMember
localPublished := by
simp [
alignedParticipants,
activeAlignedCount_ext_on_members hSameOnMembers]
/-- Changing sidecar alignment reports for nonmembers cannot turn quorum on or
off. -/
theorem quorumAligned_ext_on_members
{n threshold : Nat} {inActiveView alignedA alignedB : Nat Bool}
{localIsMember : Bool}
{localPublished : Bool}
(hSameOnMembers :
peer, peer < n inActiveView peer = true
alignedA peer = alignedB peer) :
quorumAligned threshold
(activeAlignedCount inActiveView alignedA n)
localIsMember
localPublished =
quorumAligned threshold
(activeAlignedCount inActiveView alignedB n)
localIsMember
localPublished := by
simp [
quorumAligned,
alignedParticipants_ext_on_members hSameOnMembers]
end XahauConsensus

View File

@@ -0,0 +1,97 @@
import XahauConsensus.SidecarAlignment
namespace XahauConsensus
/-- Minimal model of the RNG entropy sidecar accept gate.
The C++ gate accepts validator-derived entropy only when the local view has a
quorum-aligned sidecar hash and full local observation of tx-converged peers.
This model deliberately excludes the sidecar-map contents; those are covered by
the accepted-hash discipline in `selectEntropy`.
-/
def entropyObservationGate
(threshold aligned : Nat)
(localIsMember localPublished : Bool)
(peersSeen txConverged : Nat) : Bool :=
quorumAligned threshold aligned localIsMember localPublished &&
fullObservation peersSeen txConverged
/-- A quorum-only variant for comparison. This is not a claim that the C++ must
use this policy; it is a small model used to isolate what the full-observation
term contributes to the decision. -/
def entropyQuorumOnlyGate
(threshold aligned : Nat)
(localIsMember localPublished : Bool) : Bool :=
quorumAligned threshold aligned localIsMember localPublished
theorem entropyObservationGate_iff_quorum_and_full
(threshold aligned : Nat)
(localIsMember localPublished : Bool)
(peersSeen txConverged : Nat) :
entropyObservationGate
threshold
aligned
localIsMember
localPublished
peersSeen
txConverged = true
quorumAligned threshold aligned localIsMember localPublished = true
fullObservation peersSeen txConverged = true := by
unfold entropyObservationGate
simp
/-- `fullObservation` is local equality of two observed counts. If the same node
has already seen `seen` advertised sidecar hashes, then merely learning about
one more tx-converged peer that has not advertised flips full observation from
true to false. -/
theorem fullObservation_flips_when_unadvertised_peer_is_seen (seen : Nat) :
fullObservation seen seen = true
fullObservation seen (seen + 1) = false := by
constructor
· unfold fullObservation
simp
· unfold fullObservation
simp
/-- Concrete counterexample for the RNG gate.
Both local views have the same quorum-aligned sidecar hash: three aligned remote
participants plus the local validator meet threshold four. The only difference
is whether the observer has seen a fourth tx-converged peer that did not
advertise an entropy sidecar hash.
The first view has `peersSeen = txConverged = 3` and accepts. The second has
`peersSeen = 3`, `txConverged = 4` and does not. This captures the subtle point:
`fullObservation` is not global completeness and does not by itself synchronize
accept-vs-fallback decisions across nodes.
-/
theorem same_quorum_alignment_fullObservation_can_change_accept :
entropyObservationGate 4 3 true true 3 3 = true
entropyObservationGate 4 3 true true 3 4 = false := by
native_decide
/-- In the same counterexample, a quorum-only gate would make the same decision
for both observers. This theorem is descriptive only: it isolates the source of
the branch difference; it does not model the liveness/timing costs of changing
the production gate. -/
theorem same_quorum_alignment_quorumOnly_same_decision :
entropyQuorumOnlyGate 4 3 true true = true
entropyQuorumOnlyGate 4 3 true true = true := by
native_decide
/-- Quorum alignment and full observation are independent predicates: a view can
have quorum alignment while still lacking full observation. -/
theorem quorumAligned_does_not_imply_fullObservation :
quorumAligned 4 3 true true = true
fullObservation 3 4 = false := by
native_decide
/-- Full observation also does not imply quorum alignment. A node can have seen
all tx-converged peers in its local view while too few participants align with
its sidecar hash. -/
theorem fullObservation_does_not_imply_quorumAligned :
fullObservation 3 3 = true
quorumAligned 4 2 true true = false := by
native_decide
end XahauConsensus

View File

@@ -0,0 +1,56 @@
import XahauConsensus.Threshold
namespace XahauConsensus
/-!
Review-oriented facts about the tempting `ceil(60%)` participant threshold.
The live `participantThreshold` is one higher than naive 60% at exact
multiples of five. That extra vote is what turns equality at the
Byzantine-overlap boundary into strict intersection safety.
-/
/-- A naive `ceil(0.6 * count)` threshold. -/
def naiveSixtyPercentThreshold (count : Nat) : Nat :=
(count * 60 + 99) / 100
theorem naiveSixtyPercentThreshold_five_mul (k : Nat) :
naiveSixtyPercentThreshold (5 * k) = 3 * k := by
unfold naiveSixtyPercentThreshold
omega
theorem participantThreshold_five_mul_eq_naiveSixtyPercentThreshold_succ
(k : Nat) :
participantThreshold (5 * k) =
naiveSixtyPercentThreshold (5 * k) + 1 := by
unfold participantThreshold byzantineBound naiveSixtyPercentThreshold
omega
/-- At exact multiples of five, naive 60% only reaches the unsafe boundary. -/
theorem naiveSixtyPercentThreshold_five_mul_hits_intersection_boundary
(k : Nat) :
2 * naiveSixtyPercentThreshold (5 * k) =
5 * k + byzantineBound (5 * k) := by
unfold naiveSixtyPercentThreshold byzantineBound
omega
theorem naiveSixtyPercentThreshold_five_mul_not_intersection_safe
(k : Nat) :
¬ 5 * k + byzantineBound (5 * k) <
2 * naiveSixtyPercentThreshold (5 * k) := by
rw [naiveSixtyPercentThreshold_five_mul_hits_intersection_boundary k]
omega
theorem participantThreshold_five_mul_intersection_safe (k : Nat) :
5 * k + byzantineBound (5 * k) <
2 * participantThreshold (5 * k) := by
exact participantThreshold_intersection_safe (5 * k)
/-- At exact multiples of five, the live threshold clears the boundary by two. -/
theorem participantThreshold_five_mul_intersection_margin (k : Nat) :
2 * participantThreshold (5 * k) =
(5 * k + byzantineBound (5 * k)) + 2 := by
unfold participantThreshold byzantineBound
omega
end XahauConsensus

View File

@@ -0,0 +1,124 @@
namespace XahauConsensus
/-- C++: `count / 5`, the conservative Byzantine bound used by
`calculateParticipantThreshold`. -/
def byzantineBound (count : Nat) : Nat :=
count / 5
/-- C++: `calculateParticipantThreshold(count)`.
This is the smallest integer `t` satisfying `2 * t > count + floor(count / 5)`.
-/
def participantThreshold (count : Nat) : Nat :=
(count + byzantineBound count) / 2 + 1
/-- C++: `calculateQuorumThreshold(count)`, i.e. `ceil(0.8 * count)`. -/
def quorumThreshold (count : Nat) : Nat :=
(count * 80 + 99) / 100
/-- C++: `ConsensusExtensions::quorumThreshold()`.
The raw formula gives `0` for an empty view, but the live consensus-extension
gate requires at least one aligned participant for safety.
-/
def safeQuorumThreshold (count : Nat) : Nat :=
if count = 0 then 1 else quorumThreshold count
/-- C++: `ConsensusExtensions::tier2Threshold()`.
`participantThreshold 0` already returns `1`; this wrapper makes the
zero-view safety rule explicit and mirrors the C++ method shape.
-/
def safeParticipantThreshold (count : Nat) : Nat :=
if count = 0 then 1 else participantThreshold count
/-- The Tier-2 threshold strictly exceeds the Byzantine-overlap boundary.
This is the load-bearing equivocation invariant behind participant-aligned
entropy: two cohorts of this size in a `count`-sized universe overlap in more
than `floor(count / 5)` validators.
-/
theorem participantThreshold_intersection_safe (count : Nat) :
count + byzantineBound count < 2 * participantThreshold count := by
unfold participantThreshold byzantineBound
omega
/-- Anchoring the Tier-2 threshold to the original pre-nUNL view remains safe
when the effective post-nUNL view shrinks.
This is the arithmetic reason `originalViewSize` is the right denominator:
smaller effective universes only increase the intersection margin.
-/
theorem participantThreshold_safe_under_effective_shrink
(originalView effectiveView : Nat)
(hShrink : effectiveView <= originalView) :
effectiveView + byzantineBound originalView <
2 * participantThreshold originalView := by
have hSafe := participantThreshold_intersection_safe originalView
omega
/-- Concrete regression example: if `originalView = 10` and `effectiveView = 8`,
using the effective view's participant threshold (`5`) leaves the overlap equal
to the original-view Byzantine bound (`2`), not strictly greater than it.
This is why the C++ must not replace `originalViewSize` with `size()` for the
Tier-2 floor.
-/
theorem effective_threshold_regression_hits_boundary_example :
2 * participantThreshold 8 <= 8 + byzantineBound 10 := by
native_decide
theorem threshold_minimal_for_boundary (boundary threshold : Nat) :
boundary < 2 * threshold boundary / 2 + 1 <= threshold := by
omega
theorem below_threshold_not_safe_for_boundary (boundary threshold : Nat) :
threshold < boundary / 2 + 1 2 * threshold <= boundary := by
omega
/-- `participantThreshold` is the smallest threshold satisfying the strict
intersection-safety inequality. -/
theorem participantThreshold_minimal (count threshold : Nat) :
count + byzantineBound count < 2 * threshold
participantThreshold count <= threshold := by
intro hSafe
unfold participantThreshold
exact threshold_minimal_for_boundary
(count + byzantineBound count)
threshold
hSafe
/-- Anything below `participantThreshold` fails the strict intersection-safety
inequality. -/
theorem below_participantThreshold_not_safe (count threshold : Nat) :
threshold < participantThreshold count
2 * threshold <= count + byzantineBound count := by
intro hBelow
unfold participantThreshold at hBelow
exact below_threshold_not_safe_for_boundary
(count + byzantineBound count)
threshold
hBelow
/-- The participant threshold never exceeds the 80% validator-quorum threshold.
This is useful because Tier 2 should form a band below Tier 3, not a stricter
condition than validator quorum.
-/
theorem participantThreshold_le_quorumThreshold (count : Nat) :
0 < count participantThreshold count <= quorumThreshold count := by
intro hCount
unfold participantThreshold quorumThreshold byzantineBound
omega
/-- With the live safety wrappers, the participant threshold never exceeds the
validator-quorum threshold, including the empty-view edge case. -/
theorem safeParticipantThreshold_le_safeQuorumThreshold (count : Nat) :
safeParticipantThreshold count <= safeQuorumThreshold count := by
unfold safeParticipantThreshold safeQuorumThreshold
by_cases hZero : count = 0
· simp [hZero]
· have hPositive : 0 < count := Nat.pos_of_ne_zero hZero
simp [hZero, participantThreshold_le_quorumThreshold count hPositive]
end XahauConsensus

View File

@@ -0,0 +1,223 @@
import XahauConsensus.Threshold
namespace XahauConsensus
/-!
Additional arithmetic facts about the Xahau consensus thresholds.
These lemmas are deliberately small and review-oriented: they expose concrete
edge cases, exact multiples-of-five behavior, participant/quorum band facts,
and monotonicity of the threshold functions.
-/
theorem byzantineBound_zero : byzantineBound 0 = 0 := by
native_decide
theorem participantThreshold_zero : participantThreshold 0 = 1 := by
native_decide
theorem quorumThreshold_zero : quorumThreshold 0 = 0 := by
native_decide
theorem safeQuorumThreshold_zero : safeQuorumThreshold 0 = 1 := by
native_decide
theorem safeParticipantThreshold_zero : safeParticipantThreshold 0 = 1 := by
native_decide
theorem byzantineBound_one : byzantineBound 1 = 0 := by
native_decide
theorem participantThreshold_one : participantThreshold 1 = 1 := by
native_decide
theorem quorumThreshold_one : quorumThreshold 1 = 1 := by
native_decide
theorem safeQuorumThreshold_one : safeQuorumThreshold 1 = 1 := by
native_decide
theorem safeParticipantThreshold_one : safeParticipantThreshold 1 = 1 := by
native_decide
theorem participantThreshold_two : participantThreshold 2 = 2 := by
native_decide
theorem quorumThreshold_two : quorumThreshold 2 = 2 := by
native_decide
theorem participantThreshold_three : participantThreshold 3 = 2 := by
native_decide
theorem quorumThreshold_three : quorumThreshold 3 = 3 := by
native_decide
theorem participantThreshold_four : participantThreshold 4 = 3 := by
native_decide
theorem quorumThreshold_four : quorumThreshold 4 = 4 := by
native_decide
theorem byzantineBound_five : byzantineBound 5 = 1 := by
native_decide
theorem participantThreshold_five : participantThreshold 5 = 4 := by
native_decide
theorem quorumThreshold_five : quorumThreshold 5 = 4 := by
native_decide
theorem byzantineBound_ten : byzantineBound 10 = 2 := by
native_decide
theorem participantThreshold_ten : participantThreshold 10 = 7 := by
native_decide
theorem quorumThreshold_ten : quorumThreshold 10 = 8 := by
native_decide
theorem byzantineBound_twenty : byzantineBound 20 = 4 := by
native_decide
theorem participantThreshold_twenty : participantThreshold 20 = 13 := by
native_decide
theorem quorumThreshold_twenty : quorumThreshold 20 = 16 := by
native_decide
theorem byzantineBound_five_mul (k : Nat) :
byzantineBound (5 * k) = k := by
unfold byzantineBound
omega
theorem participantThreshold_five_mul (k : Nat) :
participantThreshold (5 * k) = 3 * k + 1 := by
unfold participantThreshold byzantineBound
omega
theorem quorumThreshold_five_mul (k : Nat) :
quorumThreshold (5 * k) = 4 * k := by
unfold quorumThreshold
omega
/-- On exact multiples of five, the strict safety margin is exactly two. -/
theorem participantThreshold_five_mul_margin (k : Nat) :
2 * participantThreshold (5 * k) =
(5 * k + byzantineBound (5 * k)) + 2 := by
rw [participantThreshold_five_mul, byzantineBound_five_mul]
omega
/-- One below the multiple-of-five participant threshold reaches only equality
with the unsafe boundary, so the strict safety inequality fails. -/
theorem below_participantThreshold_five_mul_hits_boundary (k : Nat) :
2 * (participantThreshold (5 * k) - 1) =
5 * k + byzantineBound (5 * k) := by
rw [participantThreshold_five_mul, byzantineBound_five_mul]
omega
theorem participantThreshold_five_mul_lt_quorumThreshold_five_mul
{k : Nat} (h : 1 < k) :
participantThreshold (5 * k) < quorumThreshold (5 * k) := by
rw [participantThreshold_five_mul, quorumThreshold_five_mul]
omega
theorem participantThreshold_five_eq_quorumThreshold_five :
participantThreshold 5 = quorumThreshold 5 := by
native_decide
theorem participantThreshold_ten_lt_quorumThreshold_ten :
participantThreshold 10 < quorumThreshold 10 := by
native_decide
theorem participant_band_nonempty {count : Nat}
(h : participantThreshold count < quorumThreshold count) :
participants,
participantThreshold count <= participants
participants < quorumThreshold count := by
exact participantThreshold count, Nat.le_refl _, h
theorem participant_band_empty {count : Nat}
(h : quorumThreshold count <= participantThreshold count) :
¬ participants,
participantThreshold count <= participants
participants < quorumThreshold count := by
intro hExists
rcases hExists with participants, hParticipant, hBelowQuorum
omega
theorem participant_band_empty_zero :
¬ participants,
participantThreshold 0 <= participants
participants < quorumThreshold 0 := by
apply participant_band_empty
native_decide
theorem participant_band_empty_one :
¬ participants,
participantThreshold 1 <= participants
participants < quorumThreshold 1 := by
apply participant_band_empty
native_decide
theorem participant_band_empty_two :
¬ participants,
participantThreshold 2 <= participants
participants < quorumThreshold 2 := by
apply participant_band_empty
native_decide
theorem participant_band_empty_five :
¬ participants,
participantThreshold 5 <= participants
participants < quorumThreshold 5 := by
apply participant_band_empty
native_decide
theorem participant_band_nonempty_three :
participants,
participantThreshold 3 <= participants
participants < quorumThreshold 3 := by
apply participant_band_nonempty
native_decide
theorem participant_band_nonempty_four :
participants,
participantThreshold 4 <= participants
participants < quorumThreshold 4 := by
apply participant_band_nonempty
native_decide
theorem participant_band_nonempty_ten :
participants,
participantThreshold 10 <= participants
participants < quorumThreshold 10 := by
apply participant_band_nonempty
native_decide
theorem participant_band_nonempty_five_mul {k : Nat} (h : 1 < k) :
participants,
participantThreshold (5 * k) <= participants
participants < quorumThreshold (5 * k) := by
exact participant_band_nonempty
(participantThreshold_five_mul_lt_quorumThreshold_five_mul h)
theorem byzantineBound_mono {a b : Nat} (h : a <= b) :
byzantineBound a <= byzantineBound b := by
unfold byzantineBound
exact Nat.div_le_div_right h
theorem participantThreshold_mono {a b : Nat} (h : a <= b) :
participantThreshold a <= participantThreshold b := by
unfold participantThreshold
apply Nat.succ_le_succ
apply Nat.div_le_div_right
have hByzantine := byzantineBound_mono h
omega
theorem quorumThreshold_mono {a b : Nat} (h : a <= b) :
quorumThreshold a <= quorumThreshold b := by
unfold quorumThreshold
apply Nat.div_le_div_right
omega
end XahauConsensus

View File

@@ -0,0 +1,201 @@
import XahauConsensus.ThresholdFacts
namespace XahauConsensus
/-!
Concrete arithmetic examples for the distinction between the active effective
view, the original pre-nUNL view, and any larger trusted counting universe.
The safety shape is deliberately Nat-only: two cohorts of size `threshold` in
an `activeView` overlap strictly beyond the Byzantine bound charged to
`byzantineUniverse` when
`activeView + byzantineBound byzantineUniverse < 2 * threshold`.
-/
def strictIntersectionSafe
(activeView byzantineUniverse threshold : Nat) : Prop :=
activeView + byzantineBound byzantineUniverse < 2 * threshold
/-- Strict intersection safety plus reachability of the threshold inside the
active view. This separates "safe if it happens" from "possible to happen". -/
def nonvacuousStrictIntersectionSafe
(activeView byzantineUniverse threshold : Nat) : Prop :=
threshold <= activeView strictIntersectionSafe activeView byzantineUniverse threshold
/-- Cross-view Tier-2 band: participant floor is anchored to the original view,
validator quorum to the effective view. -/
def participantBandNonempty
(effectiveView originalView : Nat) : Prop :=
participants,
participantThreshold originalView <= participants
participants < quorumThreshold effectiveView
theorem participantBandNonempty_iff
(effectiveView originalView : Nat) :
participantBandNonempty effectiveView originalView
participantThreshold originalView < quorumThreshold effectiveView := by
constructor
· intro h
rcases h with participants, hParticipant, hBelowQuorum
omega
· intro h
exact participantThreshold originalView, Nat.le_refl _, h
/-- The original-view participant threshold remains safe when nUNL shrinks the
active effective view. -/
theorem original_threshold_safe_under_nunl_shrink
{originalView effectiveView : Nat}
(hShrink : effectiveView <= originalView) :
strictIntersectionSafe
effectiveView
originalView
(participantThreshold originalView) := by
unfold strictIntersectionSafe
exact participantThreshold_safe_under_effective_shrink
originalView
effectiveView
hShrink
theorem original_threshold_nonvacuous_under_nunl_shrink
{originalView effectiveView : Nat}
(hShrink : effectiveView <= originalView)
(hReachable : participantThreshold originalView <= effectiveView) :
nonvacuousStrictIntersectionSafe
effectiveView
originalView
(participantThreshold originalView) := by
constructor
· exact hReachable
· exact original_threshold_safe_under_nunl_shrink hShrink
/-- The original-view threshold is also safe if the Byzantine counting universe
is no larger than the original view. -/
theorem original_threshold_safe_for_no_larger_counting_universe
{originalView effectiveView countingUniverse : Nat}
(hShrink : effectiveView <= originalView)
(hCounting : countingUniverse <= originalView) :
strictIntersectionSafe
effectiveView
countingUniverse
(participantThreshold originalView) := by
unfold strictIntersectionSafe
have hOriginal :=
participantThreshold_safe_under_effective_shrink
originalView
effectiveView
hShrink
have hBound := byzantineBound_mono hCounting
omega
/-- Any threshold at or below the overlap boundary is not strictly safe. -/
theorem not_strictIntersectionSafe_of_threshold_le_boundary
{activeView byzantineUniverse threshold : Nat}
(hBoundary : 2 * threshold <= activeView + byzantineBound byzantineUniverse) :
¬ strictIntersectionSafe activeView byzantineUniverse threshold := by
unfold strictIntersectionSafe
omega
/-- If the effective-view threshold is below what the original Byzantine bound
requires, it cannot prove strict intersection safety against that original
bound. -/
theorem effective_threshold_not_safe_against_original_bound
{originalView effectiveView : Nat}
(hBelow :
participantThreshold effectiveView <
(effectiveView + byzantineBound originalView) / 2 + 1) :
¬ strictIntersectionSafe
effectiveView
originalView
(participantThreshold effectiveView) := by
apply not_strictIntersectionSafe_of_threshold_le_boundary
exact below_threshold_not_safe_for_boundary
(effectiveView + byzantineBound originalView)
(participantThreshold effectiveView)
hBelow
/-- A larger trusted counting universe increases the Byzantine side of the
boundary, eroding the strict-intersection margin. -/
theorem original_boundary_le_trusted_superset_boundary
{originalView effectiveView trustedUniverse : Nat}
(hSuperset : originalView <= trustedUniverse) :
effectiveView + byzantineBound originalView <=
effectiveView + byzantineBound trustedUniverse := by
have hBound := byzantineBound_mono hSuperset
omega
/-- Concrete nUNL example: `originalView = 10`, `effectiveView = 8`, and the
original threshold still clears the original Byzantine bound. -/
theorem original_ten_effective_eight_original_threshold_safe :
strictIntersectionSafe 8 10 (participantThreshold 10) := by
unfold strictIntersectionSafe
native_decide
theorem original_ten_effective_eight_participant_band_empty :
¬ participantBandNonempty 8 10 := by
rw [participantBandNonempty_iff]
native_decide
theorem original_ten_effective_eight_original_threshold_reachable :
nonvacuousStrictIntersectionSafe 8 10 (participantThreshold 10) := by
apply original_threshold_nonvacuous_under_nunl_shrink
· native_decide
· native_decide
/-- Concrete regression: for `originalView = 10` and `effectiveView = 8`, the
effective threshold does not strictly clear the original Byzantine bound. -/
theorem original_ten_effective_eight_effective_threshold_not_safe :
¬ strictIntersectionSafe 8 10 (participantThreshold 8) := by
apply not_strictIntersectionSafe_of_threshold_le_boundary
native_decide
/-- The same failure as a direct boundary comparison, useful when reviewing the
raw arithmetic. -/
theorem original_ten_effective_eight_effective_threshold_hits_boundary :
2 * participantThreshold 8 <= 8 + byzantineBound 10 := by
native_decide
/-- Larger concrete nUNL example with the original threshold anchored at
`20`. -/
theorem original_twenty_effective_sixteen_original_threshold_safe :
strictIntersectionSafe 16 20 (participantThreshold 20) := by
unfold strictIntersectionSafe
native_decide
theorem original_twenty_effective_sixteen_participant_band_empty :
¬ participantBandNonempty 16 20 := by
rw [participantBandNonempty_iff]
native_decide
theorem original_twenty_effective_fifteen_participant_band_empty :
¬ participantBandNonempty 15 20 := by
rw [participantBandNonempty_iff]
native_decide
theorem original_twenty_effective_fifteen_original_threshold_reachable :
nonvacuousStrictIntersectionSafe 15 20 (participantThreshold 20) := by
apply original_threshold_nonvacuous_under_nunl_shrink
· native_decide
· native_decide
/-- With `originalView = 20` and `effectiveView = 16`, using the effective
threshold again reaches the unsafe boundary. -/
theorem original_twenty_effective_sixteen_effective_threshold_not_safe :
¬ strictIntersectionSafe 16 20 (participantThreshold 16) := by
apply not_strictIntersectionSafe_of_threshold_le_boundary
native_decide
/-- Counting Byzantine stake over a trusted universe of `20` instead of the
original view of `10` erodes the margin all the way to equality. -/
theorem trusted_superset_twenty_erodes_original_ten_margin_to_boundary :
2 * participantThreshold 10 = 10 + byzantineBound 20 := by
native_decide
/-- The equality above means the original threshold for `10` is not strictly
safe if Byzantine weight is counted over the larger trusted universe `20`. -/
theorem trusted_superset_twenty_original_ten_threshold_not_safe :
¬ strictIntersectionSafe 10 20 (participantThreshold 10) := by
apply not_strictIntersectionSafe_of_threshold_le_boundary
native_decide
end XahauConsensus

View File

@@ -0,0 +1,96 @@
{"version": "1.2.0",
"packagesDir": ".lake/packages",
"packages":
[{"url": "https://github.com/leanprover-community/mathlib4.git",
"type": "git",
"subDir": null,
"scope": "",
"rev": "fabf563a7c95a166b8d7b6efca11c8b4dc9d911f",
"name": "mathlib",
"manifestFile": "lake-manifest.json",
"inputRev": "v4.31.0",
"inherited": false,
"configFile": "lakefile.lean"},
{"url": "https://github.com/leanprover-community/plausible",
"type": "git",
"subDir": null,
"scope": "leanprover-community",
"rev": "63045536fe95024e6c18fc7b48e03f506701c5bc",
"name": "plausible",
"manifestFile": "lake-manifest.json",
"inputRev": "main",
"inherited": true,
"configFile": "lakefile.toml"},
{"url": "https://github.com/leanprover-community/LeanSearchClient",
"type": "git",
"subDir": null,
"scope": "leanprover-community",
"rev": "c5d5b8fe6e5158def25cd28eb94e4141ad97c843",
"name": "LeanSearchClient",
"manifestFile": "lake-manifest.json",
"inputRev": "main",
"inherited": true,
"configFile": "lakefile.toml"},
{"url": "https://github.com/leanprover-community/import-graph",
"type": "git",
"subDir": null,
"scope": "leanprover-community",
"rev": "5c7542ed018c78194f1e2b903eaf6a792b74c03d",
"name": "importGraph",
"manifestFile": "lake-manifest.json",
"inputRev": "main",
"inherited": true,
"configFile": "lakefile.toml"},
{"url": "https://github.com/leanprover-community/ProofWidgets4",
"type": "git",
"subDir": null,
"scope": "leanprover-community",
"rev": "24b0d9dc081c5423f8eec7e866c441e5184f29d9",
"name": "proofwidgets",
"manifestFile": "lake-manifest.json",
"inputRev": "main",
"inherited": true,
"configFile": "lakefile.lean"},
{"url": "https://github.com/leanprover-community/aesop",
"type": "git",
"subDir": null,
"scope": "leanprover-community",
"rev": "e3cb2f741431ce31bf73549fb52316a57368b06f",
"name": "aesop",
"manifestFile": "lake-manifest.json",
"inputRev": "master",
"inherited": true,
"configFile": "lakefile.toml"},
{"url": "https://github.com/leanprover-community/quote4",
"type": "git",
"subDir": null,
"scope": "leanprover-community",
"rev": "f46324995fca5f0483b742e4eb4daec7f4ee50d2",
"name": "Qq",
"manifestFile": "lake-manifest.json",
"inputRev": "master",
"inherited": true,
"configFile": "lakefile.toml"},
{"url": "https://github.com/leanprover-community/batteries",
"type": "git",
"subDir": null,
"scope": "leanprover-community",
"rev": "fa08db58b30eb033edcdab331bba000827f9f785",
"name": "batteries",
"manifestFile": "lake-manifest.json",
"inputRev": "main",
"inherited": true,
"configFile": "lakefile.toml"},
{"url": "https://github.com/leanprover/lean4-cli",
"type": "git",
"subDir": null,
"scope": "leanprover",
"rev": "92564e5770e4d09f2d86dfbf8ada1e9c715b384c",
"name": "Cli",
"manifestFile": "lake-manifest.json",
"inputRev": "v4.31.0",
"inherited": true,
"configFile": "lakefile.toml"}],
"name": "xahau_consensus",
"lakeDir": ".lake",
"fixedToolchain": false}

View File

@@ -0,0 +1,11 @@
name = "xahau_consensus"
version = "0.1.0"
defaultTargets = ["XahauConsensus"]
[[require]]
name = "mathlib"
git = "https://github.com/leanprover-community/mathlib4.git"
rev = "v4.31.0"
[[lean_lib]]
name = "XahauConsensus"

View File

@@ -0,0 +1 @@
leanprover/lean4:v4.31.0

View File

@@ -47,5 +47,8 @@
#define MEM_OVERLAP -43
#define TOO_MANY_STATE_MODIFICATIONS -44
#define TOO_MANY_NAMESPACES -45
#define EXPORT_FAILURE -46
#define TOO_MANY_EXPORTED_TXN -47
#define TOO_LITTLE_ENTROPY -48
#define HOOK_ERROR_CODES
#endif //HOOK_ERROR_CODES

View File

@@ -2,6 +2,9 @@
// Generated using generate_extern.sh
#include <stdint.h>
#ifndef HOOK_EXTERN
#ifdef __cplusplus
extern "C" {
#endif
extern int32_t __attribute__((noduplicate))
_g(uint32_t guard_id, uint32_t maxiter);
@@ -336,5 +339,43 @@ prepare(
uint32_t read_ptr,
uint32_t read_len);
extern int64_t
xport_reserve(uint32_t count);
extern int64_t
xport(
uint32_t write_ptr,
uint32_t write_len,
uint32_t read_ptr,
uint32_t read_len);
extern int64_t
xport_cancel(uint32_t ticket_seq);
/*
Consensus entropy APIs.
min_tier is a fail-closed floor:
1 = consensus_fallback, 2 = participant_aligned, 3 = validator_quorum.
min_count is the minimum validator/reveal count the caller accepts.
If the most recent finalized entropy object does not satisfy both floors,
these APIs return TOO_LITTLE_ENTROPY. Open-ledger and simulate execution
are provisional previews over the entropy currently visible to the node;
final ordered ledger execution may see a different entropy object.
*/
extern int64_t
dice(uint32_t sides, uint32_t min_tier, uint32_t min_count);
extern int64_t
random(
uint32_t write_ptr,
uint32_t write_len,
uint32_t min_tier,
uint32_t min_count);
#ifdef __cplusplus
}
#endif
#define HOOK_EXTERN
#endif // HOOK_EXTERN

View File

@@ -9,7 +9,7 @@ ENUM_FILE="$SCRIPT_DIR/../include/xrpl/hook/Enum.h"
echo '// For documentation please see: https://xrpl-hooks.readme.io/reference/'
echo '// Generated using generate_error.sh'
echo '#ifndef HOOK_ERROR_CODES'
sed -n '/enum hook_return_code/,/};/p' "$ENUM_FILE" |
sed -n '/enum class hook_return_code/,/};/p' "$ENUM_FILE" |
awk '
function ltrim(s) { sub(/^[[:space:]]+/, "", s); return s }
function rtrim(s) { sub(/[[:space:]]+$/, "", s); return s }
@@ -31,7 +31,7 @@ sed -n '/enum hook_return_code/,/};/p' "$ENUM_FILE" |
{
line = $0
if (line ~ /enum[[:space:]]+hook_return_code/)
if (line ~ /enum[[:space:]]+class[[:space:]]+hook_return_code/)
next
if (line ~ /^[[:space:]]*\{/)
next

View File

@@ -11,6 +11,9 @@ APPLY_HOOK="$SCRIPT_DIR/../include/xrpl/hook/hook_api.macro"
echo '// Generated using generate_extern.sh'
echo '#include <stdint.h>'
echo '#ifndef HOOK_EXTERN'
echo '#ifdef __cplusplus'
echo 'extern "C" {'
echo '#endif'
echo
awk '
function trim(s) {
@@ -38,6 +41,21 @@ APPLY_HOOK="$SCRIPT_DIR/../include/xrpl/hook/hook_api.macro"
# Insert __attribute__((noduplicate)) before _g
sub(/[[:space:]]+_g/, " __attribute__((noduplicate)) _g", line);
}
if (line ~ /[[:space:]]+dice[[:space:]]*\(/) {
print "/*";
print " Consensus entropy APIs.";
print "";
print " min_tier is a fail-closed floor:";
print " 1 = consensus_fallback, 2 = participant_aligned, 3 = validator_quorum.";
print " min_count is the minimum validator/reveal count the caller accepts.";
print "";
print " If the most recent finalized entropy object does not satisfy both floors,";
print " these APIs return TOO_LITTLE_ENTROPY. Open-ledger and simulate execution";
print " are provisional previews over the entropy currently visible to the node;";
print " final ordered ledger execution may see a different entropy object.";
print "*/";
}
# printf("\n");
@@ -46,6 +64,9 @@ APPLY_HOOK="$SCRIPT_DIR/../include/xrpl/hook/hook_api.macro"
}
' "$APPLY_HOOK"
echo '#ifdef __cplusplus'
echo '}'
echo '#endif'
echo '#define HOOK_EXTERN'
echo '#endif // HOOK_EXTERN'
} | (

82
hook/generate_lsflags.sh Executable file
View File

@@ -0,0 +1,82 @@
#!/bin/bash
set -eu
SCRIPT_DIR=$(dirname "$0")
SCRIPT_DIR=$(cd "$SCRIPT_DIR" && pwd)
RIPPLED_ROOT="$SCRIPT_DIR/../include/xrpl"
LEDGER_FORMATS="$RIPPLED_ROOT/protocol/LedgerFormats.h"
echo '// Generated using generate_lsflags.sh'
echo ''
echo '#ifndef HOOKLSFLAGS_INCLUDED'
echo '#define HOOKLSFLAGS_INCLUDED 1'
echo ''
awk '
function ltrim(s) { sub(/^[[:space:]]+/, "", s); return s }
function rtrim(s) { sub(/[[:space:]]+$/, "", s); return s }
function trim(s) { return rtrim(ltrim(s)) }
function flush_group() {
if (entry_count > 0 && group != "") {
printf "enum %s {\n", group
for (i = 1; i <= entry_count; i++) {
printf " %s,\n", entries[i]
}
printf "};\n"
}
delete entries
entry_count = 0
}
/enum LedgerSpecificFlags \{/ { inside = 1; next }
inside && /^\};/ { inside = 0; flush_group(); next }
!inside { next }
# Group header comments: // ltFOO or // remarks
/^[[:space:]]*\/\/[[:space:]]*(lt[A-Z_]+|remarks)[[:space:]]*$/ {
flush_group()
line = $0
sub(/.*\/\/[[:space:]]*/, "", line)
group = trim(line)
next
}
# Skip pure comment lines (not group headers)
/^[[:space:]]*\/\// { next }
# Skip blank lines
/^[[:space:]]*$/ { next }
# Accumulate flag lines (handle multi-line values)
{
line = $0
# Strip inline comments
sub(/\/\/.*/, "", line)
line = trim(line)
if (line == "") next
if (pending != "") {
pending = pending " " line
} else {
pending = line
}
# If line ends with comma, the entry is complete
if (pending ~ /,$/) {
# Remove trailing comma
sub(/,$/, "", pending)
entries[++entry_count] = pending
pending = ""
}
}
BEGIN {
inside = 0
group = ""
pending = ""
entry_count = 0
}
' "$LEDGER_FORMATS"
echo ''
echo '#endif // HOOKLSFLAGS_INCLUDED'

25
hook/generate_txflags.sh Executable file
View File

@@ -0,0 +1,25 @@
#!/bin/bash
set -eu
SCRIPT_DIR=$(dirname "$0")
SCRIPT_DIR=$(cd "$SCRIPT_DIR" && pwd)
RIPPLED_ROOT="$SCRIPT_DIR/../include/xrpl"
TX_FLAGS="$RIPPLED_ROOT/protocol/TxFlags.h"
echo '// Generated using generate_txflags.sh'
echo '#include "ls_flags.h"'
echo '#include <stdint.h>'
echo ''
cat "$TX_FLAGS" |
awk '
/^[[:space:]]*enum / {
if (count > 0) print ""
inside = 1
count++
}
inside {
print
if (/};/) inside = 0
}
'

203
hook/genesis/build_xahau_h.sh Executable file
View File

@@ -0,0 +1,203 @@
#!/bin/bash
# build_xahau_h.sh
# Builds genesis hook WASMs and updates xahau.h with hex arrays
set -euo pipefail
# Color codes for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Script directory and path constants
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
XAHAU_H="${SCRIPT_DIR}/../../include/xrpl/hook/xahau.h"
TEMP_DIR="${SCRIPT_DIR}/.temp"
# Hook file mappings (space-separated: name:file)
HOOK_FILES=(
"GovernanceHook:govern.wasm"
"RewardHook:reward.wasm"
# "MintHook:mint.wasm"
)
# Cleanup function
cleanup() {
local exit_code=$?
if [ ${exit_code} -eq 0 ] && [ -d "${TEMP_DIR}" ]; then
rm -rf "${TEMP_DIR}"
elif [ ${exit_code} -ne 0 ]; then
echo -e "${RED}Error: Script failed with exit code ${exit_code}${NC}" >&2
if [ -d "${TEMP_DIR}" ]; then
echo -e "${YELLOW}Temp files preserved at: ${TEMP_DIR}${NC}" >&2
fi
fi
exit ${exit_code}
}
trap cleanup EXIT INT TERM
# Tool verification
echo -e "${BLUE}==> Checking required tools...${NC}"
REQUIRED_TOOLS=("make" "xxd" "sed" "clang-format" "wasm-opt")
for tool in "${REQUIRED_TOOLS[@]}"; do
if ! command -v "${tool}" &> /dev/null; then
echo -e "${RED}Error: Required tool '${tool}' not found${NC}" >&2
exit 1
fi
echo -e "${GREEN}${tool}${NC}"
done
# Verify wasm-opt version is exactly 100
WASM_OPT_VERSION=$(wasm-opt --version | grep -oE '[0-9]+' | head -1)
if [ "${WASM_OPT_VERSION}" != "100" ]; then
echo -e "${RED}Error: wasm-opt version must be 100, but found ${WASM_OPT_VERSION}${NC}" >&2
exit 1
fi
echo -e "${GREEN} ✓ wasm-opt version 100${NC}"
# Verify xahau.h exists
if [ ! -f "${XAHAU_H}" ]; then
echo -e "${RED}Error: xahau.h not found at ${XAHAU_H}${NC}" >&2
exit 1
fi
# Create temp directory
mkdir -p "${TEMP_DIR}"
# Build all WASM files
echo -e "${BLUE}==> Building WASM files with 'make all'...${NC}"
cd "${SCRIPT_DIR}"
make all
echo -e "${GREEN} Build completed successfully${NC}"
# Function to convert WASM to hex array
wasm_to_hex_array() {
local wasm_file="$1"
local indent=" "
if [ ! -f "${wasm_file}" ]; then
echo -e "${RED}Error: WASM file not found: ${wasm_file}${NC}" >&2
return 1
fi
# Convert to hex with xxd, format with sed
xxd -p -u -c 10 "${wasm_file}" | \
sed 's/../0x&U,/g' | \
sed "s/^/${indent}/g" | \
sed '$ s/,$//'
}
# Function to update hook array in xahau.h
update_hook_array() {
local hook_name="$1"
local hex_array="$2"
local temp_file="${TEMP_DIR}/xahau.h.tmp"
echo -e "${BLUE}==> Updating ${hook_name}...${NC}"
# Check if hook already exists
if grep -q "static const std::vector<uint8_t> ${hook_name} = {" "${XAHAU_H}"; then
echo -e "${YELLOW} Replacing existing ${hook_name}${NC}"
# Use awk to replace the array content
awk -v hook="${hook_name}" -v hex="${hex_array}" '
BEGIN { in_array=0 }
{
if ($0 ~ "static const std::vector<uint8_t> " hook " = {") {
print $0
print hex
in_array=1
next
}
if (in_array && $0 ~ /};/) {
print "};"
in_array=0
next
}
if (!in_array) {
print $0
}
}
' "${XAHAU_H}" > "${temp_file}"
mv "${temp_file}" "${XAHAU_H}"
else
echo -e "${YELLOW} Adding new ${hook_name}${NC}"
# Find the position before #endif and add the new hook
awk -v hook="${hook_name}" -v hex="${hex_array}" '
{
if ($0 ~ /#endif.*XAHAU_GENESIS_HOOKS/) {
print ""
print "static const std::vector<uint8_t> " hook " = {"
print hex
print "};"
print ""
print $0
} else {
print $0
}
}
' "${XAHAU_H}" > "${temp_file}"
mv "${temp_file}" "${XAHAU_H}"
fi
echo -e "${GREEN}${hook_name} updated${NC}"
}
# Process each hook
for hook_entry in "${HOOK_FILES[@]}"; do
hook_name="${hook_entry%%:*}"
wasm_file="${SCRIPT_DIR}/${hook_entry##*:}"
echo -e "${BLUE}==> Converting ${wasm_file} to hex array...${NC}"
hex_array=$(wasm_to_hex_array "${wasm_file}")
if [ $? -ne 0 ]; then
echo -e "${RED}Error: Failed to convert ${wasm_file}${NC}" >&2
exit 1
fi
echo -e "${GREEN} Conversion successful ($(echo "${hex_array}" | wc -l) lines)${NC}"
update_hook_array "${hook_name}" "${hex_array}"
done
# Format with clang-format
echo -e "${BLUE}==> Formatting with clang-format...${NC}"
cp "${XAHAU_H}" "${TEMP_DIR}/xahau.h.before_format"
clang-format -i "${XAHAU_H}"
echo -e "${GREEN} Formatting completed${NC}"
# Verification
echo -e "${BLUE}==> Verifying changes...${NC}"
for hook_entry in "${HOOK_FILES[@]}"; do
hook_name="${hook_entry%%:*}"
if grep -q "static const std::vector<uint8_t> ${hook_name} = {" "${XAHAU_H}"; then
echo -e "${GREEN}${hook_name} found in xahau.h${NC}"
else
echo -e "${RED}${hook_name} NOT found in xahau.h${NC}" >&2
exit 1
fi
done
# Show summary
echo ""
echo -e "${GREEN}========================================${NC}"
echo -e "${GREEN}Successfully updated xahau.h${NC}"
echo -e "${GREEN}========================================${NC}"
echo -e "Updated hooks:"
for hook_entry in "${HOOK_FILES[@]}"; do
hook_name="${hook_entry%%:*}"
wasm_file="${SCRIPT_DIR}/${hook_entry##*:}"
size=$(wc -c < "${wasm_file}" | tr -d ' ')
echo -e " - ${hook_name}: ${size} bytes"
done
echo ""
echo -e "File location: ${XAHAU_H}"
echo ""

View File

@@ -0,0 +1,46 @@
// For documentation please see: https://xrpl-hooks.readme.io/reference/
// Generated using generate_error.sh
#ifndef HOOK_ERROR_CODES
#define SUCCESS 0
#define OUT_OF_BOUNDS -1
#define INTERNAL_ERROR -2
#define TOO_BIG -3
#define TOO_SMALL -4
#define DOESNT_EXIST -5
#define NO_FREE_SLOTS -6
#define INVALID_ARGUMENT -7
#define ALREADY_SET -8
#define PREREQUISITE_NOT_MET -9
#define FEE_TOO_LARGE -10
#define EMISSION_FAILURE -11
#define TOO_MANY_NONCES -12
#define TOO_MANY_EMITTED_TXN -13
#define NOT_IMPLEMENTED -14
#define INVALID_ACCOUNT -15
#define GUARD_VIOLATION -16
#define INVALID_FIELD -17
#define PARSE_ERROR -18
#define RC_ROLLBACK -19
#define RC_ACCEPT -20
#define NO_SUCH_KEYLET -21
#define NOT_AN_ARRAY -22
#define NOT_AN_OBJECT -23
#define INVALID_FLOAT -10024
#define DIVISION_BY_ZERO -25
#define MANTISSA_OVERSIZED -26
#define MANTISSA_UNDERSIZED -27
#define EXPONENT_OVERSIZED -28
#define EXPONENT_UNDERSIZED -29
#define OVERFLOW -30
#define NOT_IOU_AMOUNT -31
#define NOT_AN_AMOUNT -32
#define CANT_RETURN_NEGATIVE -33
#define NOT_AUTHORIZED -34
#define PREVIOUS_FAILURE_PREVENTS_RETRY -35
#define TOO_MANY_PARAMS -36
#define INVALID_TXN -37
#define RESERVE_INSUFFICIENT -38
#define COMPLEX_NOT_SUPPORTED -39
#define DOES_NOT_MATCH -40
#define HOOK_ERROR_CODES
#endif //HOOK_ERROR_CODES

View File

@@ -0,0 +1,352 @@
// For documentation please see: https://xrpl-hooks.readme.io/reference/
// Generated using generate_extern.sh
#include <stdint.h>
#ifndef HOOK_EXTERN
extern int32_t __attribute__((noduplicate))
_g(uint32_t guard_id, uint32_t maxiter);
extern int64_t
accept(uint32_t read_ptr, uint32_t read_len, int64_t error_code);
extern int64_t
emit(
uint32_t write_ptr,
uint32_t write_len,
uint32_t read_ptr,
uint32_t read_len);
extern int64_t
etxn_burden(void);
extern int64_t
etxn_details(uint32_t write_ptr, uint32_t write_len);
extern int64_t
etxn_fee_base(uint32_t read_ptr, uint32_t read_len);
extern int64_t
etxn_generation(void);
extern int64_t
etxn_nonce(uint32_t write_ptr, uint32_t write_len);
extern int64_t
etxn_reserve(uint32_t count);
extern int64_t
fee_base(void);
extern int64_t
float_compare(int64_t float1, int64_t float2, uint32_t mode);
extern int64_t
float_divide(int64_t float1, int64_t float2);
extern int64_t
float_exponent(int64_t float1);
extern int64_t
float_exponent_set(int64_t float1, int32_t exponent);
extern int64_t
float_int(int64_t float1, uint32_t decimal_places, uint32_t abs);
extern int64_t
float_invert(int64_t float1);
extern int64_t
float_log(int64_t float1);
extern int64_t
float_mantissa(int64_t float1);
extern int64_t
float_mantissa_set(int64_t float1, int64_t mantissa);
extern int64_t
float_mulratio(
int64_t float1,
uint32_t round_up,
uint32_t numerator,
uint32_t denominator);
extern int64_t
float_multiply(int64_t float1, int64_t float2);
extern int64_t
float_negate(int64_t float1);
extern int64_t
float_one(void);
extern int64_t
float_root(int64_t float1, uint32_t n);
extern int64_t
float_set(int32_t exponent, int64_t mantissa);
extern int64_t
float_sign(int64_t float1);
extern int64_t
float_sign_set(int64_t float1, uint32_t negative);
extern int64_t
float_sto(
uint32_t write_ptr,
uint32_t write_len,
uint32_t cread_ptr,
uint32_t cread_len,
uint32_t iread_ptr,
uint32_t iread_len,
int64_t float1,
uint32_t field_code);
extern int64_t
float_sto_set(uint32_t read_ptr, uint32_t read_len);
extern int64_t
float_sum(int64_t float1, int64_t float2);
extern int64_t
hook_account(uint32_t write_ptr, uint32_t write_len);
extern int64_t
hook_again(void);
extern int64_t
hook_hash(uint32_t write_ptr, uint32_t write_len, int32_t hook_no);
extern int64_t
hook_param(
uint32_t write_ptr,
uint32_t write_len,
uint32_t read_ptr,
uint32_t read_len);
extern int64_t
otxn_param(
uint32_t write_ptr,
uint32_t write_len,
uint32_t read_ptr,
uint32_t read_len);
extern int64_t
hook_param_set(
uint32_t read_ptr,
uint32_t read_len,
uint32_t kread_ptr,
uint32_t kread_len,
uint32_t hread_ptr,
uint32_t hread_len);
extern int64_t
hook_pos(void);
extern int64_t
hook_skip(uint32_t read_ptr, uint32_t read_len, uint32_t flags);
extern int64_t
ledger_keylet(
uint32_t write_ptr,
uint32_t write_len,
uint32_t lread_ptr,
uint32_t lread_len,
uint32_t hread_ptr,
uint32_t hread_len);
extern int64_t
ledger_last_hash(uint32_t write_ptr, uint32_t write_len);
extern int64_t
ledger_last_time(void);
extern int64_t
ledger_nonce(uint32_t write_ptr, uint32_t write_len);
extern int64_t
ledger_seq(void);
extern int64_t
meta_slot(uint32_t slot_no);
extern int64_t
otxn_burden(void);
extern int64_t
otxn_field(uint32_t write_ptr, uint32_t write_len, uint32_t field_id);
extern int64_t
otxn_field_txt(uint32_t write_ptr, uint32_t write_len, uint32_t field_id);
extern int64_t
otxn_generation(void);
extern int64_t
otxn_id(uint32_t write_ptr, uint32_t write_len, uint32_t flags);
extern int64_t
otxn_slot(uint32_t slot_no);
extern int64_t
otxn_type(void);
extern int64_t
rollback(uint32_t read_ptr, uint32_t read_len, int64_t error_code);
extern int64_t
slot(uint32_t write_ptr, uint32_t write_len, uint32_t slot);
extern int64_t
slot_clear(uint32_t slot);
extern int64_t
slot_count(uint32_t slot);
extern int64_t
slot_float(uint32_t slot_no);
extern int64_t
slot_id(uint32_t write_ptr, uint32_t write_len, uint32_t slot);
extern int64_t
slot_set(uint32_t read_ptr, uint32_t read_len, uint32_t slot);
extern int64_t
slot_size(uint32_t slot);
extern int64_t
slot_subarray(uint32_t parent_slot, uint32_t array_id, uint32_t new_slot);
extern int64_t
slot_subfield(uint32_t parent_slot, uint32_t field_id, uint32_t new_slot);
extern int64_t
slot_type(uint32_t slot_no, uint32_t flags);
extern int64_t
state(
uint32_t write_ptr,
uint32_t write_len,
uint32_t kread_ptr,
uint32_t kread_len);
extern int64_t
state_foreign(
uint32_t write_ptr,
uint32_t write_len,
uint32_t kread_ptr,
uint32_t kread_len,
uint32_t nread_ptr,
uint32_t nread_len,
uint32_t aread_ptr,
uint32_t aread_len);
extern int64_t
state_foreign_set(
uint32_t read_ptr,
uint32_t read_len,
uint32_t kread_ptr,
uint32_t kread_len,
uint32_t nread_ptr,
uint32_t nread_len,
uint32_t aread_ptr,
uint32_t aread_len);
extern int64_t
state_set(
uint32_t read_ptr,
uint32_t read_len,
uint32_t kread_ptr,
uint32_t kread_len);
extern int64_t
sto_emplace(
uint32_t write_ptr,
uint32_t write_len,
uint32_t sread_ptr,
uint32_t sread_len,
uint32_t fread_ptr,
uint32_t fread_len,
uint32_t field_id);
extern int64_t
sto_erase(
uint32_t write_ptr,
uint32_t write_len,
uint32_t read_ptr,
uint32_t read_len,
uint32_t field_id);
extern int64_t
sto_subarray(uint32_t read_ptr, uint32_t read_len, uint32_t array_id);
extern int64_t
sto_subfield(uint32_t read_ptr, uint32_t read_len, uint32_t field_id);
extern int64_t
sto_validate(uint32_t tread_ptr, uint32_t tread_len);
extern int64_t
trace(
uint32_t mread_ptr,
uint32_t mread_len,
uint32_t dread_ptr,
uint32_t dread_len,
uint32_t as_hex);
extern int64_t
trace_float(uint32_t read_ptr, uint32_t read_len, int64_t float1);
extern int64_t
trace_num(uint32_t read_ptr, uint32_t read_len, int64_t number);
extern int64_t
trace_slot(uint32_t read_ptr, uint32_t read_len, uint32_t slot);
extern int64_t
util_accid(
uint32_t write_ptr,
uint32_t write_len,
uint32_t read_ptr,
uint32_t read_len);
extern int64_t
util_keylet(
uint32_t write_ptr,
uint32_t write_len,
uint32_t keylet_type,
uint32_t a,
uint32_t b,
uint32_t c,
uint32_t d,
uint32_t e,
uint32_t f);
extern int64_t
util_raddr(
uint32_t write_ptr,
uint32_t write_len,
uint32_t read_ptr,
uint32_t read_len);
extern int64_t
util_sha512h(
uint32_t write_ptr,
uint32_t write_len,
uint32_t read_ptr,
uint32_t read_len);
extern int64_t
util_verify(
uint32_t dread_ptr,
uint32_t dread_len,
uint32_t sread_ptr,
uint32_t sread_len,
uint32_t kread_ptr,
uint32_t kread_len);
extern int64_t xpop_slot(uint32_t, uint32_t);
#define HOOK_EXTERN
#endif // HOOK_EXTERN

View File

@@ -0,0 +1,50 @@
/**
* Hook API include file
*
* Note to the reader:
* This include defines two types of things: external functions and macros
* Functions are used sparingly because a non-inlining compiler may produce
* undesirable output.
*
* Find documentation here: https://xrpl-hooks.readme.io/reference/
*/
#ifndef HOOKAPI_INCLUDED
#define HOOKAPI_INCLUDED 1
#define KEYLET_HOOK 1
#define KEYLET_HOOK_STATE 2
#define KEYLET_ACCOUNT 3
#define KEYLET_AMENDMENTS 4
#define KEYLET_CHILD 5
#define KEYLET_SKIP 6
#define KEYLET_FEES 7
#define KEYLET_NEGATIVE_UNL 8
#define KEYLET_LINE 9
#define KEYLET_OFFER 10
#define KEYLET_QUALITY 11
#define KEYLET_EMITTED_DIR 12
#define KEYLET_TICKET 13
#define KEYLET_SIGNERS 14
#define KEYLET_CHECK 15
#define KEYLET_DEPOSIT_PREAUTH 16
#define KEYLET_UNCHECKED 17
#define KEYLET_OWNER_DIR 18
#define KEYLET_PAGE 19
#define KEYLET_ESCROW 20
#define KEYLET_PAYCHAN 21
#define KEYLET_EMITTED 22
#define KEYLET_NFT_OFFER 23
#define KEYLET_HOOK_DEFINITION 24
#define COMPARE_EQUAL 1U
#define COMPARE_LESS 2U
#define COMPARE_GREATER 4U
#include "error.h"
#include "extern.h"
#include "sfcodes.h"
#include "macro.h"
#include "types.h"
#endif

View File

@@ -0,0 +1,679 @@
/**
* These are helper macros for writing hooks, all of them are optional as is including hookmacro.h at all
*/
#include <stdint.h>
#include "hookapi.h"
#include "sfcodes.h"
#ifndef HOOKMACROS_INCLUDED
#define HOOKMACROS_INCLUDED 1
#ifdef NDEBUG
#define DEBUG 0
#else
#define DEBUG 1
#endif
#define TRACEVAR(v) if (DEBUG) trace_num((uint32_t)(#v), (uint32_t)(sizeof(#v) - 1), (int64_t)v);
#define TRACEHEX(v) if (DEBUG) trace((uint32_t)(#v), (uint32_t)(sizeof(#v) - 1), (uint32_t)(v), (uint32_t)(sizeof(v)), 1);
#define TRACEXFL(v) if (DEBUG) trace_float((uint32_t)(#v), (uint32_t)(sizeof(#v) - 1), (int64_t)v);
#define TRACESTR(v) if (DEBUG) trace((uint32_t)(#v), (uint32_t)(sizeof(#v) - 1), (uint32_t)(v), sizeof(v), 0);
// hook developers should use this guard macro, simply GUARD(<maximum iterations>)
#define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1)
#define GUARDM(maxiter, n) _g(( (1ULL << 31U) + (__LINE__ << 16) + n), (maxiter)+1)
#define SBUF(str) (uint32_t)(str), sizeof(str)
#define REQUIRE(cond, str)\
{\
if (!(cond))\
rollback(SBUF(str), __LINE__);\
}
// make a report buffer as a c-string
// provide a name for a buffer to declare (buf)
// provide a static string
// provide an integer to print after the string
#define RBUF(buf, out_len, str, num)\
unsigned char buf[sizeof(str) + 21];\
int out_len = 0;\
{\
int i = 0;\
for (; GUARDM(sizeof(str),1),i < sizeof(str); ++i)\
(buf)[i] = str[i];\
if ((buf)[sizeof(str)-1] == 0) i--;\
if ((num) < 0) (buf)[i++] = '-';\
uint64_t unsigned_num = (uint64_t)( (num) < 0 ? (num) * -1 : (num) );\
uint64_t j = 10000000000000000000ULL;\
int start = 1;\
for (; GUARDM(20,2), unsigned_num > 0 && j > 0; j /= 10)\
{\
unsigned char digit = ( unsigned_num / j ) % 10;\
if (digit == 0 && start)\
continue;\
start = 0;\
(buf)[i++] = '0' + digit;\
}\
(buf)[i] = '\0';\
out_len = i;\
}
#define RBUF2(buff, out_len, str, num, str2, num2)\
unsigned char buff[sizeof(str) + sizeof(str2) + 42];\
int out_len = 0;\
{\
unsigned char* buf = buff;\
int i = 0;\
for (; GUARDM(sizeof(str),1),i < sizeof(str); ++i)\
(buf)[i] = str[i];\
if ((buf)[sizeof(str)-1] == 0) i--;\
if ((num) < 0) (buf)[i++] = '-';\
uint64_t unsigned_num = (uint64_t)( (num) < 0 ? (num) * -1 : (num) );\
uint64_t j = 10000000000000000000ULL;\
int start = 1;\
for (; GUARDM(20,2), unsigned_num > 0 && j > 0; j /= 10)\
{\
unsigned char digit = ( unsigned_num / j ) % 10;\
if (digit == 0 && start)\
continue;\
start = 0;\
(buf)[i++] = '0' + digit;\
}\
buf += i;\
out_len += i;\
i = 0;\
for (; GUARDM(sizeof(str2),3),i < sizeof(str2); ++i)\
(buf)[i] = str2[i];\
if ((buf)[sizeof(str2)-1] == 0) i--;\
if ((num2) < 0) (buf)[i++] = '-';\
unsigned_num = (uint64_t)( (num2) < 0 ? (num2) * -1 : (num2) );\
j = 10000000000000000000ULL;\
start = 1;\
for (; GUARDM(20,4), unsigned_num > 0 && j > 0; j /= 10)\
{\
unsigned char digit = ( unsigned_num / j ) % 10;\
if (digit == 0 && start)\
continue;\
start = 0;\
(buf)[i++] = '0' + digit;\
}\
(buf)[i] = '\0';\
out_len += i;\
}
#define CLEARBUF(b)\
{\
for (int x = 0; GUARD(sizeof(b)), x < sizeof(b); ++x)\
b[x] = 0;\
}
// returns an in64_t, negative if error, non-negative if valid drops
#define AMOUNT_TO_DROPS(amount_buffer)\
(((amount_buffer)[0] >> 7) ? -2 : (\
((((uint64_t)((amount_buffer)[0])) & 0xb00111111) << 56) +\
(((uint64_t)((amount_buffer)[1])) << 48) +\
(((uint64_t)((amount_buffer)[2])) << 40) +\
(((uint64_t)((amount_buffer)[3])) << 32) +\
(((uint64_t)((amount_buffer)[4])) << 24) +\
(((uint64_t)((amount_buffer)[5])) << 16) +\
(((uint64_t)((amount_buffer)[6])) << 8) +\
(((uint64_t)((amount_buffer)[7])))))
#define SUB_OFFSET(x) ((int32_t)(x >> 32))
#define SUB_LENGTH(x) ((int32_t)(x & 0xFFFFFFFFULL))
#define BUFFER_EQUAL_20(buf1, buf2)\
(\
*(((uint64_t*)(buf1)) + 0) == *(((uint64_t*)(buf2)) + 0) &&\
*(((uint64_t*)(buf1)) + 1) == *(((uint64_t*)(buf2)) + 1) &&\
*(((uint32_t*)(buf1)) + 4) == *(((uint32_t*)(buf2)) + 4))
#define BUFFER_EQUAL_32(buf1, buf2)\
(\
*(((uint64_t*)(buf1)) + 0) == *(((uint64_t*)(buf2)) + 0) &&\
*(((uint64_t*)(buf1)) + 1) == *(((uint64_t*)(buf2)) + 1) &&\
*(((uint64_t*)(buf1)) + 2) == *(((uint64_t*)(buf2)) + 2) &&\
*(((uint64_t*)(buf1)) + 3) == *(((uint64_t*)(buf2)) + 3))
// when using this macro buf1len may be dynamic but buf2len must be static
// provide n >= 1 to indicate how many times the macro will be hit on the line of code
// e.g. if it is in a loop that loops 10 times n = 10
#define BUFFER_EQUAL_GUARD(output, buf1, buf1len, buf2, buf2len, n)\
{\
output = ((buf1len) == (buf2len) ? 1 : 0);\
for (int x = 0; GUARDM( (buf2len) * (n), 1 ), output && x < (buf2len);\
++x)\
output = *(((uint8_t*)(buf1)) + x) == *(((uint8_t*)(buf2)) + x);\
}
#define BUFFER_SWAP(x,y)\
{\
uint8_t* z = x;\
x = y;\
y = z;\
}
#define ACCOUNT_COMPARE(compare_result, buf1, buf2)\
{\
compare_result = 0;\
for (int i = 0; GUARD(20), i < 20; ++i)\
{\
if (buf1[i] > buf2[i])\
{\
compare_result = 1;\
break;\
}\
else if (buf1[i] < buf2[i])\
{\
compare_result = -1;\
break;\
}\
}\
}
#define BUFFER_EQUAL_STR_GUARD(output, buf1, buf1len, str, n)\
BUFFER_EQUAL_GUARD(output, buf1, buf1len, str, (sizeof(str)-1), n)
#define BUFFER_EQUAL_STR(output, buf1, buf1len, str)\
BUFFER_EQUAL_GUARD(output, buf1, buf1len, str, (sizeof(str)-1), 1)
#define BUFFER_EQUAL(output, buf1, buf2, compare_len)\
BUFFER_EQUAL_GUARD(output, buf1, compare_len, buf2, compare_len, 1)
#define UINT16_TO_BUF(buf_raw, i)\
{\
unsigned char* buf = (unsigned char*)buf_raw;\
buf[0] = (((uint64_t)i) >> 8) & 0xFFUL;\
buf[1] = (((uint64_t)i) >> 0) & 0xFFUL;\
}
#define UINT16_FROM_BUF(buf)\
(((uint64_t)((buf)[0]) << 8) +\
((uint64_t)((buf)[1]) << 0))
#define UINT32_TO_BUF(buf_raw, i)\
{\
unsigned char* buf = (unsigned char*)buf_raw;\
buf[0] = (((uint64_t)i) >> 24) & 0xFFUL;\
buf[1] = (((uint64_t)i) >> 16) & 0xFFUL;\
buf[2] = (((uint64_t)i) >> 8) & 0xFFUL;\
buf[3] = (((uint64_t)i) >> 0) & 0xFFUL;\
}
#define UINT32_FROM_BUF(buf)\
(((uint64_t)((buf)[0]) << 24) +\
((uint64_t)((buf)[1]) << 16) +\
((uint64_t)((buf)[2]) << 8) +\
((uint64_t)((buf)[3]) << 0))
#define UINT64_TO_BUF(buf_raw, i)\
{\
unsigned char* buf = (unsigned char*)buf_raw;\
buf[0] = (((uint64_t)i) >> 56) & 0xFFUL;\
buf[1] = (((uint64_t)i) >> 48) & 0xFFUL;\
buf[2] = (((uint64_t)i) >> 40) & 0xFFUL;\
buf[3] = (((uint64_t)i) >> 32) & 0xFFUL;\
buf[4] = (((uint64_t)i) >> 24) & 0xFFUL;\
buf[5] = (((uint64_t)i) >> 16) & 0xFFUL;\
buf[6] = (((uint64_t)i) >> 8) & 0xFFUL;\
buf[7] = (((uint64_t)i) >> 0) & 0xFFUL;\
}
#define UINT64_FROM_BUF(buf)\
(((uint64_t)((buf)[0]) << 56) +\
((uint64_t)((buf)[1]) << 48) +\
((uint64_t)((buf)[2]) << 40) +\
((uint64_t)((buf)[3]) << 32) +\
((uint64_t)((buf)[4]) << 24) +\
((uint64_t)((buf)[5]) << 16) +\
((uint64_t)((buf)[6]) << 8) +\
((uint64_t)((buf)[7]) << 0))
#define INT64_FROM_BUF(buf)\
((((uint64_t)((buf)[0] & 0x7FU) << 56) +\
((uint64_t)((buf)[1]) << 48) +\
((uint64_t)((buf)[2]) << 40) +\
((uint64_t)((buf)[3]) << 32) +\
((uint64_t)((buf)[4]) << 24) +\
((uint64_t)((buf)[5]) << 16) +\
((uint64_t)((buf)[6]) << 8) +\
((uint64_t)((buf)[7]) << 0)) * (buf[0] & 0x80U ? -1 : 1))
#define INT64_TO_BUF(buf_raw, i)\
{\
unsigned char* buf = (unsigned char*)buf_raw;\
buf[0] = (((uint64_t)i) >> 56) & 0x7FUL;\
buf[1] = (((uint64_t)i) >> 48) & 0xFFUL;\
buf[2] = (((uint64_t)i) >> 40) & 0xFFUL;\
buf[3] = (((uint64_t)i) >> 32) & 0xFFUL;\
buf[4] = (((uint64_t)i) >> 24) & 0xFFUL;\
buf[5] = (((uint64_t)i) >> 16) & 0xFFUL;\
buf[6] = (((uint64_t)i) >> 8) & 0xFFUL;\
buf[7] = (((uint64_t)i) >> 0) & 0xFFUL;\
if (i < 0) buf[0] |= 0x80U;\
}
#define ttPAYMENT 0
#define ttESCROW_CREATE 1
#define ttESCROW_FINISH 2
#define ttACCOUNT_SET 3
#define ttESCROW_CANCEL 4
#define ttREGULAR_KEY_SET 5
#define ttOFFER_CREATE 7
#define ttOFFER_CANCEL 8
#define ttTICKET_CREATE 10
#define ttSIGNER_LIST_SET 12
#define ttPAYCHAN_CREATE 13
#define ttPAYCHAN_FUND 14
#define ttPAYCHAN_CLAIM 15
#define ttCHECK_CREATE 16
#define ttCHECK_CASH 17
#define ttCHECK_CANCEL 18
#define ttDEPOSIT_PREAUTH 19
#define ttTRUST_SET 20
#define ttACCOUNT_DELETE 21
#define ttHOOK_SET 22
#define ttNFTOKEN_MINT 25
#define ttNFTOKEN_BURN 26
#define ttNFTOKEN_CREATE_OFFER 27
#define ttNFTOKEN_CANCEL_OFFER 28
#define ttNFTOKEN_ACCEPT_OFFER 29
#define ttURITOKEN_MINT 45
#define ttURITOKEN_BURN 46
#define ttURITOKEN_BUY 47
#define ttURITOKEN_CREATE_SELL_OFFER 48
#define ttURITOKEN_CANCEL_SELL_OFFER 49
#define ttCLAIM_REWARD 98
#define ttINVOKE 99
#define ttAMENDMENT 100
#define ttFEE 101
#define ttUNL_MODIFY 102
#define ttEMIT_FAILURE 103
#define tfCANONICAL 0x80000000UL
#define atACCOUNT 1U
#define atOWNER 2U
#define atDESTINATION 3U
#define atISSUER 4U
#define atAUTHORIZE 5U
#define atUNAUTHORIZE 6U
#define atTARGET 7U
#define atREGULARKEY 8U
#define atPSEUDOCALLBACK 9U
#define amAMOUNT 1U
#define amBALANCE 2U
#define amLIMITAMOUNT 3U
#define amTAKERPAYS 4U
#define amTAKERGETS 5U
#define amLOWLIMIT 6U
#define amHIGHLIMIT 7U
#define amFEE 8U
#define amSENDMAX 9U
#define amDELIVERMIN 10U
#define amMINIMUMOFFER 16U
#define amRIPPLEESCROW 17U
#define amDELIVEREDAMOUNT 18U
/**
* RH NOTE -- PAY ATTENTION
*
* ALL 'ENCODE' MACROS INCREMENT BUF_OUT
* THIS IS TO MAKE CHAINING EASY
* BUF_OUT IS A SACRIFICIAL POINTER
*
* 'ENCODE' MACROS WITH CONSTANTS HAVE
* ALIASING TO ASSIST YOU WITH ORDER
* _TYPECODE_FIELDCODE_ENCODE_MACRO
* TO PRODUCE A SERIALIZED OBJECT
* IN CANONICAL FORMAT YOU MUST ORDER
* FIRST BY TYPE CODE THEN BY FIELD CODE
*
* ALL 'PREPARE' MACROS PRESERVE POINTERS
*
**/
#define ENCODE_TL_SIZE 49
#define ENCODE_TL(buf_out, tlamt, amount_type)\
{\
uint8_t uat = amount_type; \
buf_out[0] = 0x60U +(uat & 0x0FU ); \
for (int i = 1; GUARDM(48, 1), i < 49; ++i)\
buf_out[i] = tlamt[i-1];\
buf_out += ENCODE_TL_SIZE;\
}
#define _06_XX_ENCODE_TL(buf_out, drops, amount_type )\
ENCODE_TL(buf_out, drops, amount_type );
#define ENCODE_TL_AMOUNT(buf_out, drops )\
ENCODE_TL(buf_out, drops, amAMOUNT );
#define _06_01_ENCODE_TL_AMOUNT(buf_out, drops )\
ENCODE_TL_AMOUNT(buf_out, drops );
// Encode drops to serialization format
// consumes 9 bytes
#define ENCODE_DROPS_SIZE 9
#define ENCODE_DROPS(buf_out, drops, amount_type ) \
{\
uint8_t uat = amount_type; \
uint64_t udrops = drops; \
buf_out[0] = 0x60U +(uat & 0x0FU ); \
buf_out[1] = 0b01000000 + (( udrops >> 56 ) & 0b00111111 ); \
buf_out[2] = (udrops >> 48) & 0xFFU; \
buf_out[3] = (udrops >> 40) & 0xFFU; \
buf_out[4] = (udrops >> 32) & 0xFFU; \
buf_out[5] = (udrops >> 24) & 0xFFU; \
buf_out[6] = (udrops >> 16) & 0xFFU; \
buf_out[7] = (udrops >> 8) & 0xFFU; \
buf_out[8] = (udrops >> 0) & 0xFFU; \
buf_out += ENCODE_DROPS_SIZE; \
}
#define _06_XX_ENCODE_DROPS(buf_out, drops, amount_type )\
ENCODE_DROPS(buf_out, drops, amount_type );
#define ENCODE_DROPS_AMOUNT(buf_out, drops )\
ENCODE_DROPS(buf_out, drops, amAMOUNT );
#define _06_01_ENCODE_DROPS_AMOUNT(buf_out, drops )\
ENCODE_DROPS_AMOUNT(buf_out, drops );
#define ENCODE_DROPS_FEE(buf_out, drops )\
ENCODE_DROPS(buf_out, drops, amFEE );
#define _06_08_ENCODE_DROPS_FEE(buf_out, drops )\
ENCODE_DROPS_FEE(buf_out, drops );
#define ENCODE_TT_SIZE 3
#define ENCODE_TT(buf_out, tt )\
{\
uint8_t utt = tt;\
buf_out[0] = 0x12U;\
buf_out[1] =(utt >> 8 ) & 0xFFU;\
buf_out[2] =(utt >> 0 ) & 0xFFU;\
buf_out += ENCODE_TT_SIZE; \
}
#define _01_02_ENCODE_TT(buf_out, tt)\
ENCODE_TT(buf_out, tt);
#define ENCODE_ACCOUNT_SIZE 22
#define ENCODE_ACCOUNT(buf_out, account_id, account_type)\
{\
uint8_t uat = account_type;\
buf_out[0] = 0x80U + uat;\
buf_out[1] = 0x14U;\
*(uint64_t*)(buf_out + 2) = *(uint64_t*)(account_id + 0);\
*(uint64_t*)(buf_out + 10) = *(uint64_t*)(account_id + 8);\
*(uint32_t*)(buf_out + 18) = *(uint32_t*)(account_id + 16);\
buf_out += ENCODE_ACCOUNT_SIZE;\
}
#define _08_XX_ENCODE_ACCOUNT(buf_out, account_id, account_type)\
ENCODE_ACCOUNT(buf_out, account_id, account_type);
#define ENCODE_ACCOUNT_SRC_SIZE 22
#define ENCODE_ACCOUNT_SRC(buf_out, account_id)\
ENCODE_ACCOUNT(buf_out, account_id, atACCOUNT);
#define _08_01_ENCODE_ACCOUNT_SRC(buf_out, account_id)\
ENCODE_ACCOUNT_SRC(buf_out, account_id);
#define ENCODE_ACCOUNT_DST_SIZE 22
#define ENCODE_ACCOUNT_DST(buf_out, account_id)\
ENCODE_ACCOUNT(buf_out, account_id, atDESTINATION);
#define _08_03_ENCODE_ACCOUNT_DST(buf_out, account_id)\
ENCODE_ACCOUNT_DST(buf_out, account_id);
#define ENCODE_ACCOUNT_OWNER_SIZE 22
#define ENCODE_ACCOUNT_OWNER(buf_out, account_id) \
ENCODE_ACCOUNT(buf_out, account_id, atOWNER);
#define _08_02_ENCODE_ACCOUNT_OWNER(buf_out, account_id) \
ENCODE_ACCOUNT_OWNER(buf_out, account_id);
#define ENCODE_UINT32_COMMON_SIZE 5U
#define ENCODE_UINT32_COMMON(buf_out, i, field)\
{\
uint32_t ui = i; \
uint8_t uf = field; \
buf_out[0] = 0x20U +(uf & 0x0FU); \
buf_out[1] =(ui >> 24 ) & 0xFFU; \
buf_out[2] =(ui >> 16 ) & 0xFFU; \
buf_out[3] =(ui >> 8 ) & 0xFFU; \
buf_out[4] =(ui >> 0 ) & 0xFFU; \
buf_out += ENCODE_UINT32_COMMON_SIZE; \
}
#define _02_XX_ENCODE_UINT32_COMMON(buf_out, i, field)\
ENCODE_UINT32_COMMON(buf_out, i, field)\
#define ENCODE_UINT32_UNCOMMON_SIZE 6U
#define ENCODE_UINT32_UNCOMMON(buf_out, i, field)\
{\
uint32_t ui = i; \
uint8_t uf = field; \
buf_out[0] = 0x20U; \
buf_out[1] = uf; \
buf_out[2] =(ui >> 24 ) & 0xFFU; \
buf_out[3] =(ui >> 16 ) & 0xFFU; \
buf_out[4] =(ui >> 8 ) & 0xFFU; \
buf_out[5] =(ui >> 0 ) & 0xFFU; \
buf_out += ENCODE_UINT32_UNCOMMON_SIZE; \
}
#define _02_XX_ENCODE_UINT32_UNCOMMON(buf_out, i, field)\
ENCODE_UINT32_UNCOMMON(buf_out, i, field)\
#define ENCODE_LLS_SIZE 6U
#define ENCODE_LLS(buf_out, lls )\
ENCODE_UINT32_UNCOMMON(buf_out, lls, 0x1B );
#define _02_27_ENCODE_LLS(buf_out, lls )\
ENCODE_LLS(buf_out, lls );
#define ENCODE_FLS_SIZE 6U
#define ENCODE_FLS(buf_out, fls )\
ENCODE_UINT32_UNCOMMON(buf_out, fls, 0x1A );
#define _02_26_ENCODE_FLS(buf_out, fls )\
ENCODE_FLS(buf_out, fls );
#define ENCODE_TAG_SRC_SIZE 5
#define ENCODE_TAG_SRC(buf_out, tag )\
ENCODE_UINT32_COMMON(buf_out, tag, 0x3U );
#define _02_03_ENCODE_TAG_SRC(buf_out, tag )\
ENCODE_TAG_SRC(buf_out, tag );
#define ENCODE_TAG_DST_SIZE 5
#define ENCODE_TAG_DST(buf_out, tag )\
ENCODE_UINT32_COMMON(buf_out, tag, 0xEU );
#define _02_14_ENCODE_TAG_DST(buf_out, tag )\
ENCODE_TAG_DST(buf_out, tag );
#define ENCODE_SEQUENCE_SIZE 5
#define ENCODE_SEQUENCE(buf_out, sequence )\
ENCODE_UINT32_COMMON(buf_out, sequence, 0x4U );
#define _02_04_ENCODE_SEQUENCE(buf_out, sequence )\
ENCODE_SEQUENCE(buf_out, sequence );
#define ENCODE_FLAGS_SIZE 5
#define ENCODE_FLAGS(buf_out, tag )\
ENCODE_UINT32_COMMON(buf_out, tag, 0x2U );
#define _02_02_ENCODE_FLAGS(buf_out, tag )\
ENCODE_FLAGS(buf_out, tag );
#define ENCODE_SIGNING_PUBKEY_SIZE 35
#define ENCODE_SIGNING_PUBKEY(buf_out, pkey )\
{\
buf_out[0] = 0x73U;\
buf_out[1] = 0x21U;\
*(uint64_t*)(buf_out + 2) = *(uint64_t*)(pkey + 0);\
*(uint64_t*)(buf_out + 10) = *(uint64_t*)(pkey + 8);\
*(uint64_t*)(buf_out + 18) = *(uint64_t*)(pkey + 16);\
*(uint64_t*)(buf_out + 26) = *(uint64_t*)(pkey + 24);\
buf[34] = pkey[32];\
buf_out += ENCODE_SIGNING_PUBKEY_SIZE;\
}
#define _07_03_ENCODE_SIGNING_PUBKEY(buf_out, pkey )\
ENCODE_SIGNING_PUBKEY(buf_out, pkey );
#define ENCODE_SIGNING_PUBKEY_NULL_SIZE 2
#define ENCODE_SIGNING_PUBKEY_NULL(buf_out )\
{\
*buf_out++ = 0x73U;\
*buf_out++ = 0x00U;\
}
#define _07_03_ENCODE_SIGNING_PUBKEY_NULL(buf_out )\
ENCODE_SIGNING_PUBKEY_NULL(buf_out );
#define _0E_0E_ENCODE_HOOKOBJ(buf_out, hhash)\
{\
uint8_t* hook0 = (hhash);\
*buf_out++ = 0xEEU; /* hook obj start */ \
if (hook0 == 0) /* noop */\
{\
/* do nothing */ \
}\
else\
{\
*buf_out++ = 0x22U; /* flags = override */\
*buf_out++ = 0x00U;\
*buf_out++ = 0x00U;\
*buf_out++ = 0x00U;\
*buf_out++ = 0x01U;\
if (hook0 == 0xFFFFFFFFUL) /* delete operation */ \
{\
*buf_out++ = 0x7BU; /* empty createcode */ \
*buf_out++ = 0x00U;\
}\
else\
{\
*buf_out++ = 0x50U; /* HookHash */\
*buf_out++ = 0x1FU;\
uint64_t* d = (uint64_t*)buf_out;\
uint64_t* s = (uint64_t*)hook0;\
*d++ = *s++;\
*d++ = *s++;\
*d++ = *s++;\
*d++ = *s++;\
buf_out+=32;\
}\
}\
*buf_out++ = 0xE1U;\
}
#define PREPARE_HOOKSET(buf_out_master, maxlen, h, sizeout)\
{\
uint8_t* buf_out = (buf_out_master); \
uint8_t acc[20]; \
uint32_t cls = (uint32_t)ledger_seq(); \
hook_account(SBUF(acc)); \
_01_02_ENCODE_TT (buf_out, ttHOOK_SET ); \
_02_02_ENCODE_FLAGS (buf_out, tfCANONICAL ); \
_02_04_ENCODE_SEQUENCE (buf_out, 0 ); \
_02_26_ENCODE_FLS (buf_out, cls + 1 ); \
_02_27_ENCODE_LLS (buf_out, cls + 5 ); \
uint8_t* fee_ptr = buf_out; \
_06_08_ENCODE_DROPS_FEE (buf_out, 0 ); \
_07_03_ENCODE_SIGNING_PUBKEY_NULL (buf_out ); \
_08_01_ENCODE_ACCOUNT_SRC (buf_out, acc ); \
uint32_t remaining_size = (maxlen) - (buf_out - (buf_out_master)); \
int64_t edlen = etxn_details((uint32_t)buf_out, remaining_size); \
buf_out += edlen; \
*buf_out++ = 0xFBU; /* hook array start */ \
_0E_0E_ENCODE_HOOKOBJ (buf_out, h[0]); \
_0E_0E_ENCODE_HOOKOBJ (buf_out, h[1]); \
_0E_0E_ENCODE_HOOKOBJ (buf_out, h[2]); \
_0E_0E_ENCODE_HOOKOBJ (buf_out, h[3]); \
_0E_0E_ENCODE_HOOKOBJ (buf_out, h[4]); \
_0E_0E_ENCODE_HOOKOBJ (buf_out, h[5]); \
_0E_0E_ENCODE_HOOKOBJ (buf_out, h[6]); \
_0E_0E_ENCODE_HOOKOBJ (buf_out, h[7]); \
_0E_0E_ENCODE_HOOKOBJ (buf_out, h[8]); \
_0E_0E_ENCODE_HOOKOBJ (buf_out, h[9]); \
*buf_out++ = 0xF1U; /* hook array end */ \
sizeout = (buf_out - (buf_out_master)); \
int64_t fee = etxn_fee_base(buf_out_master, sizeout); \
_06_08_ENCODE_DROPS_FEE (fee_ptr, fee ); \
}
#ifdef HAS_CALLBACK
#define PREPARE_PAYMENT_SIMPLE_SIZE 270U
#else
#define PREPARE_PAYMENT_SIMPLE_SIZE 248U
#endif
#define PREPARE_PAYMENT_SIMPLE( \
buf_out_master, drops_amount_raw, to_address, dest_tag_raw, src_tag_raw) \
{ \
uint8_t* buf_out = buf_out_master; \
uint8_t acc[20]; \
uint64_t drops_amount = (drops_amount_raw); \
uint32_t dest_tag = (dest_tag_raw); \
uint32_t src_tag = (src_tag_raw); \
uint32_t cls = (uint32_t)ledger_seq(); \
hook_account(SBUF(acc)); \
_01_02_ENCODE_TT(buf_out, ttPAYMENT); /* uint16 | size 3 */ \
_02_02_ENCODE_FLAGS(buf_out, tfCANONICAL); /* uint32 | size 5 */ \
_02_03_ENCODE_TAG_SRC(buf_out, src_tag); /* uint32 | size 5 */ \
_02_04_ENCODE_SEQUENCE(buf_out, 0); /* uint32 | size 5 */ \
_02_14_ENCODE_TAG_DST(buf_out, dest_tag); /* uint32 | size 5 */ \
_02_26_ENCODE_FLS(buf_out, cls + 1); /* uint32 | size 6 */ \
_02_27_ENCODE_LLS(buf_out, cls + 5); /* uint32 | size 6 */ \
_06_01_ENCODE_DROPS_AMOUNT( \
buf_out, drops_amount); /* amount | size 9 */ \
uint8_t* fee_ptr = buf_out; \
_06_08_ENCODE_DROPS_FEE(buf_out, 0); /* amount | size 9 */ \
_07_03_ENCODE_SIGNING_PUBKEY_NULL(buf_out); /* pk | size 35 */ \
_08_01_ENCODE_ACCOUNT_SRC(buf_out, acc); /* account | size 22 */ \
_08_03_ENCODE_ACCOUNT_DST( \
buf_out, to_address); /* account | size 22 */ \
int64_t edlen = etxn_details( \
(uint32_t)buf_out, \
PREPARE_PAYMENT_SIMPLE_SIZE); /* emitdet | size 1?? */ \
int64_t fee = \
etxn_fee_base(buf_out_master, PREPARE_PAYMENT_SIMPLE_SIZE); \
_06_08_ENCODE_DROPS_FEE(fee_ptr, fee); \
}
#ifdef HAS_CALLBACK
#define PREPARE_PAYMENT_SIMPLE_TRUSTLINE_SIZE 309
#else
#define PREPARE_PAYMENT_SIMPLE_TRUSTLINE_SIZE 287
#endif
#define PREPARE_PAYMENT_SIMPLE_TRUSTLINE( \
buf_out_master, tlamt, to_address, dest_tag_raw, src_tag_raw) \
{ \
uint8_t* buf_out = buf_out_master; \
uint8_t acc[20]; \
uint32_t dest_tag = (dest_tag_raw); \
uint32_t src_tag = (src_tag_raw); \
uint32_t cls = (uint32_t)ledger_seq(); \
hook_account(SBUF(acc)); \
_01_02_ENCODE_TT(buf_out, ttPAYMENT); /* uint16 | size 3 */ \
_02_02_ENCODE_FLAGS(buf_out, tfCANONICAL); /* uint32 | size 5 */ \
_02_03_ENCODE_TAG_SRC(buf_out, src_tag); /* uint32 | size 5 */ \
_02_04_ENCODE_SEQUENCE(buf_out, 0); /* uint32 | size 5 */ \
_02_14_ENCODE_TAG_DST(buf_out, dest_tag); /* uint32 | size 5 */ \
_02_26_ENCODE_FLS(buf_out, cls + 1); /* uint32 | size 6 */ \
_02_27_ENCODE_LLS(buf_out, cls + 5); /* uint32 | size 6 */ \
_06_01_ENCODE_TL_AMOUNT(buf_out, tlamt); /* amount | size 48 */ \
uint8_t* fee_ptr = buf_out; \
_06_08_ENCODE_DROPS_FEE(buf_out, 0); /* amount | size 9 */ \
_07_03_ENCODE_SIGNING_PUBKEY_NULL(buf_out); /* pk | size 35 */ \
_08_01_ENCODE_ACCOUNT_SRC(buf_out, acc); /* account | size 22 */ \
_08_03_ENCODE_ACCOUNT_DST( \
buf_out, to_address); /* account | size 22 */ \
etxn_details( \
(uint32_t)buf_out, \
PREPARE_PAYMENT_SIMPLE_TRUSTLINE_SIZE); /* emitdet | size 1?? */ \
int64_t fee = etxn_fee_base( \
buf_out_master, PREPARE_PAYMENT_SIMPLE_TRUSTLINE_SIZE); \
_06_08_ENCODE_DROPS_FEE(fee_ptr, fee); \
}
#endif

View File

@@ -0,0 +1,215 @@
// For documentation please see: https://xrpl-hooks.readme.io/reference/
// Generated using generate_sfcodes.sh
#define sfCloseResolution ((16U << 16U) + 1U)
#define sfMethod ((16U << 16U) + 2U)
#define sfTransactionResult ((16U << 16U) + 3U)
#define sfTickSize ((16U << 16U) + 16U)
#define sfUNLModifyDisabling ((16U << 16U) + 17U)
#define sfHookResult ((16U << 16U) + 18U)
#define sfLedgerEntryType ((1U << 16U) + 1U)
#define sfTransactionType ((1U << 16U) + 2U)
#define sfSignerWeight ((1U << 16U) + 3U)
#define sfTransferFee ((1U << 16U) + 4U)
#define sfVersion ((1U << 16U) + 16U)
#define sfHookStateChangeCount ((1U << 16U) + 17U)
#define sfHookEmitCount ((1U << 16U) + 18U)
#define sfHookExecutionIndex ((1U << 16U) + 19U)
#define sfHookApiVersion ((1U << 16U) + 20U)
#define sfNetworkID ((2U << 16U) + 1U)
#define sfFlags ((2U << 16U) + 2U)
#define sfSourceTag ((2U << 16U) + 3U)
#define sfSequence ((2U << 16U) + 4U)
#define sfPreviousTxnLgrSeq ((2U << 16U) + 5U)
#define sfLedgerSequence ((2U << 16U) + 6U)
#define sfCloseTime ((2U << 16U) + 7U)
#define sfParentCloseTime ((2U << 16U) + 8U)
#define sfSigningTime ((2U << 16U) + 9U)
#define sfExpiration ((2U << 16U) + 10U)
#define sfTransferRate ((2U << 16U) + 11U)
#define sfWalletSize ((2U << 16U) + 12U)
#define sfOwnerCount ((2U << 16U) + 13U)
#define sfDestinationTag ((2U << 16U) + 14U)
#define sfHighQualityIn ((2U << 16U) + 16U)
#define sfHighQualityOut ((2U << 16U) + 17U)
#define sfLowQualityIn ((2U << 16U) + 18U)
#define sfLowQualityOut ((2U << 16U) + 19U)
#define sfQualityIn ((2U << 16U) + 20U)
#define sfQualityOut ((2U << 16U) + 21U)
#define sfStampEscrow ((2U << 16U) + 22U)
#define sfBondAmount ((2U << 16U) + 23U)
#define sfLoadFee ((2U << 16U) + 24U)
#define sfOfferSequence ((2U << 16U) + 25U)
#define sfFirstLedgerSequence ((2U << 16U) + 26U)
#define sfLastLedgerSequence ((2U << 16U) + 27U)
#define sfTransactionIndex ((2U << 16U) + 28U)
#define sfOperationLimit ((2U << 16U) + 29U)
#define sfReferenceFeeUnits ((2U << 16U) + 30U)
#define sfReserveBase ((2U << 16U) + 31U)
#define sfReserveIncrement ((2U << 16U) + 32U)
#define sfSetFlag ((2U << 16U) + 33U)
#define sfClearFlag ((2U << 16U) + 34U)
#define sfSignerQuorum ((2U << 16U) + 35U)
#define sfCancelAfter ((2U << 16U) + 36U)
#define sfFinishAfter ((2U << 16U) + 37U)
#define sfSignerListID ((2U << 16U) + 38U)
#define sfSettleDelay ((2U << 16U) + 39U)
#define sfTicketCount ((2U << 16U) + 40U)
#define sfTicketSequence ((2U << 16U) + 41U)
#define sfNFTokenTaxon ((2U << 16U) + 42U)
#define sfMintedNFTokens ((2U << 16U) + 43U)
#define sfBurnedNFTokens ((2U << 16U) + 44U)
#define sfHookStateCount ((2U << 16U) + 45U)
#define sfEmitGeneration ((2U << 16U) + 46U)
#define sfLockCount ((2U << 16U) + 47U)
#define sfRewardTime ((2U << 16U) + 98U)
#define sfRewardLgrFirst ((2U << 16U) + 99U)
#define sfRewardLgrLast ((2U << 16U) + 100U)
#define sfIndexNext ((3U << 16U) + 1U)
#define sfIndexPrevious ((3U << 16U) + 2U)
#define sfBookNode ((3U << 16U) + 3U)
#define sfOwnerNode ((3U << 16U) + 4U)
#define sfBaseFee ((3U << 16U) + 5U)
#define sfExchangeRate ((3U << 16U) + 6U)
#define sfLowNode ((3U << 16U) + 7U)
#define sfHighNode ((3U << 16U) + 8U)
#define sfDestinationNode ((3U << 16U) + 9U)
#define sfCookie ((3U << 16U) + 10U)
#define sfServerVersion ((3U << 16U) + 11U)
#define sfNFTokenOfferNode ((3U << 16U) + 12U)
#define sfEmitBurden ((3U << 16U) + 13U)
#define sfHookInstructionCount ((3U << 16U) + 17U)
#define sfHookReturnCode ((3U << 16U) + 18U)
#define sfReferenceCount ((3U << 16U) + 19U)
#define sfRewardAccumulator ((3U << 16U) + 100U)
#define sfEmailHash ((4U << 16U) + 1U)
#define sfTakerPaysCurrency ((10U << 16U) + 1U)
#define sfTakerPaysIssuer ((10U << 16U) + 2U)
#define sfTakerGetsCurrency ((10U << 16U) + 3U)
#define sfTakerGetsIssuer ((10U << 16U) + 4U)
#define sfLedgerHash ((5U << 16U) + 1U)
#define sfParentHash ((5U << 16U) + 2U)
#define sfTransactionHash ((5U << 16U) + 3U)
#define sfAccountHash ((5U << 16U) + 4U)
#define sfPreviousTxnID ((5U << 16U) + 5U)
#define sfLedgerIndex ((5U << 16U) + 6U)
#define sfWalletLocator ((5U << 16U) + 7U)
#define sfRootIndex ((5U << 16U) + 8U)
#define sfAccountTxnID ((5U << 16U) + 9U)
#define sfNFTokenID ((5U << 16U) + 10U)
#define sfEmitParentTxnID ((5U << 16U) + 11U)
#define sfEmitNonce ((5U << 16U) + 12U)
#define sfEmitHookHash ((5U << 16U) + 13U)
#define sfBookDirectory ((5U << 16U) + 16U)
#define sfInvoiceID ((5U << 16U) + 17U)
#define sfNickname ((5U << 16U) + 18U)
#define sfAmendment ((5U << 16U) + 19U)
#define sfHookOn ((5U << 16U) + 20U)
#define sfDigest ((5U << 16U) + 21U)
#define sfChannel ((5U << 16U) + 22U)
#define sfConsensusHash ((5U << 16U) + 23U)
#define sfCheckID ((5U << 16U) + 24U)
#define sfValidatedHash ((5U << 16U) + 25U)
#define sfPreviousPageMin ((5U << 16U) + 26U)
#define sfNextPageMin ((5U << 16U) + 27U)
#define sfNFTokenBuyOffer ((5U << 16U) + 28U)
#define sfNFTokenSellOffer ((5U << 16U) + 29U)
#define sfHookStateKey ((5U << 16U) + 30U)
#define sfHookHash ((5U << 16U) + 31U)
#define sfHookNamespace ((5U << 16U) + 32U)
#define sfHookSetTxnID ((5U << 16U) + 33U)
#define sfOfferID ((5U << 16U) + 34U)
#define sfEscrowID ((5U << 16U) + 35U)
#define sfURITokenID ((5U << 16U) + 36U)
#define sfAmount ((6U << 16U) + 1U)
#define sfBalance ((6U << 16U) + 2U)
#define sfLimitAmount ((6U << 16U) + 3U)
#define sfTakerPays ((6U << 16U) + 4U)
#define sfTakerGets ((6U << 16U) + 5U)
#define sfLowLimit ((6U << 16U) + 6U)
#define sfHighLimit ((6U << 16U) + 7U)
#define sfFee ((6U << 16U) + 8U)
#define sfSendMax ((6U << 16U) + 9U)
#define sfDeliverMin ((6U << 16U) + 10U)
#define sfMinimumOffer ((6U << 16U) + 16U)
#define sfRippleEscrow ((6U << 16U) + 17U)
#define sfDeliveredAmount ((6U << 16U) + 18U)
#define sfNFTokenBrokerFee ((6U << 16U) + 19U)
#define sfHookCallbackFee ((6U << 16U) + 20U)
#define sfLockedBalance ((6U << 16U) + 21U)
#define sfPublicKey ((7U << 16U) + 1U)
#define sfMessageKey ((7U << 16U) + 2U)
#define sfSigningPubKey ((7U << 16U) + 3U)
#define sfTxnSignature ((7U << 16U) + 4U)
#define sfURI ((7U << 16U) + 5U)
#define sfSignature ((7U << 16U) + 6U)
#define sfDomain ((7U << 16U) + 7U)
#define sfFundCode ((7U << 16U) + 8U)
#define sfRemoveCode ((7U << 16U) + 9U)
#define sfExpireCode ((7U << 16U) + 10U)
#define sfCreateCode ((7U << 16U) + 11U)
#define sfMemoType ((7U << 16U) + 12U)
#define sfMemoData ((7U << 16U) + 13U)
#define sfMemoFormat ((7U << 16U) + 14U)
#define sfFulfillment ((7U << 16U) + 16U)
#define sfCondition ((7U << 16U) + 17U)
#define sfMasterSignature ((7U << 16U) + 18U)
#define sfUNLModifyValidator ((7U << 16U) + 19U)
#define sfValidatorToDisable ((7U << 16U) + 20U)
#define sfValidatorToReEnable ((7U << 16U) + 21U)
#define sfHookStateData ((7U << 16U) + 22U)
#define sfHookReturnString ((7U << 16U) + 23U)
#define sfHookParameterName ((7U << 16U) + 24U)
#define sfHookParameterValue ((7U << 16U) + 25U)
#define sfBlob ((7U << 16U) + 26U)
#define sfAccount ((8U << 16U) + 1U)
#define sfOwner ((8U << 16U) + 2U)
#define sfDestination ((8U << 16U) + 3U)
#define sfIssuer ((8U << 16U) + 4U)
#define sfAuthorize ((8U << 16U) + 5U)
#define sfUnauthorize ((8U << 16U) + 6U)
#define sfRegularKey ((8U << 16U) + 8U)
#define sfNFTokenMinter ((8U << 16U) + 9U)
#define sfEmitCallback ((8U << 16U) + 10U)
#define sfHookAccount ((8U << 16U) + 16U)
#define sfIndexes ((19U << 16U) + 1U)
#define sfHashes ((19U << 16U) + 2U)
#define sfAmendments ((19U << 16U) + 3U)
#define sfNFTokenOffers ((19U << 16U) + 4U)
#define sfHookNamespaces ((19U << 16U) + 5U)
#define sfPaths ((18U << 16U) + 1U)
#define sfTransactionMetaData ((14U << 16U) + 2U)
#define sfCreatedNode ((14U << 16U) + 3U)
#define sfDeletedNode ((14U << 16U) + 4U)
#define sfModifiedNode ((14U << 16U) + 5U)
#define sfPreviousFields ((14U << 16U) + 6U)
#define sfFinalFields ((14U << 16U) + 7U)
#define sfNewFields ((14U << 16U) + 8U)
#define sfTemplateEntry ((14U << 16U) + 9U)
#define sfMemo ((14U << 16U) + 10U)
#define sfSignerEntry ((14U << 16U) + 11U)
#define sfNFToken ((14U << 16U) + 12U)
#define sfEmitDetails ((14U << 16U) + 13U)
#define sfHook ((14U << 16U) + 14U)
#define sfSigner ((14U << 16U) + 16U)
#define sfMajority ((14U << 16U) + 18U)
#define sfDisabledValidator ((14U << 16U) + 19U)
#define sfEmittedTxn ((14U << 16U) + 20U)
#define sfHookExecution ((14U << 16U) + 21U)
#define sfHookDefinition ((14U << 16U) + 22U)
#define sfHookParameter ((14U << 16U) + 23U)
#define sfHookGrant ((14U << 16U) + 24U)
#define sfSigners ((15U << 16U) + 3U)
#define sfSignerEntries ((15U << 16U) + 4U)
#define sfTemplate ((15U << 16U) + 5U)
#define sfNecessary ((15U << 16U) + 6U)
#define sfSufficient ((15U << 16U) + 7U)
#define sfAffectedNodes ((15U << 16U) + 8U)
#define sfMemos ((15U << 16U) + 9U)
#define sfNFTokens ((15U << 16U) + 10U)
#define sfHooks ((15U << 16U) + 11U)
#define sfMajorities ((15U << 16U) + 16U)
#define sfDisabledValidators ((15U << 16U) + 17U)
#define sfHookExecutions ((15U << 16U) + 18U)
#define sfHookParameters ((15U << 16U) + 19U)
#define sfHookGrants ((15U << 16U) + 20U)
#define sfActiveValidators ((15U << 16U) + 95U)

View File

@@ -0,0 +1,239 @@
#include <stdint.h>
// 8 byte-int = 1 bytes
#define SFL_CLOSERESOLUTION 1
#define SFL_METHOD 1
#define SFL_TRANSACTIONRESULT 1
#define SFL_TICKSIZE 1
#define SFL_UNLMODIFYDISABLING 1
#define SFL_HOOKRESULT 1
// 16 byte-int = 2 bytes
#define SFL_LEDGERENTRYTYPE 2
#define SFL_TRANSACTIONTYPE 2
#define SFL_SIGNERWEIGHT 2
#define SFL_TRANSFERFEE 2
#define SFL_VERSION 2
#define SFL_HOOKSTATECHANGECOUNT 2
#define SFL_HOOKEMITCOUNT 2
#define SFL_HOOKEXECUTIONINDEX 2
#define SFL_HOOKAPIVERSION 2
// 32 byte-int = 4 bytes
#define SFL_NETWORKID 4
#define SFL_FLAGS 4
#define SFL_SOURCETAG 4
#define SFL_SEQUENCE 4
#define SFL_PREVIOUSTXNLGRSEQ 4
#define SFL_LEDGERSEQUENCE 4
#define SFL_CLOSETIME 4
#define SFL_PARENTCLOSETIME 4
#define SFL_SIGNINGTIME 4
#define SFL_EXPIRATION 4
#define SFL_TRANSFERRATE 4
#define SFL_WALLETSIZE 4
#define SFL_OWNERCOUNT 4
#define SFL_DESTINATIONTAG 4
#define SFL_HIGHQUALITYIN 4
#define SFL_HIGHQUALITYOUT 4
#define SFL_LOWQUALITYIN 4
#define SFL_LOWQUALITYOUT 4
#define SFL_QUALITYIN 4
#define SFL_QUALITYOUT 4
#define SFL_STAMPESCROW 4
#define SFL_BONDAMOUNT 4
#define SFL_LOADFEE 4
#define SFL_OFFERSEQUENCE 4
#define SFL_FIRSTLEDGERSEQUENCE 4
#define SFL_LASTLEDGERSEQUENCE 4
#define SFL_TRANSACTIONINDEX 4
#define SFL_OPERATIONLIMIT 4
#define SFL_REFERENCEFEEUNITS 4
#define SFL_RESERVEBASE 4
#define SFL_RESERVEINCREMENT 4
#define SFL_SETFLAG 4
#define SFL_CLEARFLAG 4
#define SFL_SIGNERQUORUM 4
#define SFL_CANCELAFTER 4
#define SFL_FINISHAFTER 4
#define SFL_SIGNERLISTID 4
#define SFL_SETTLEDELAY 4
#define SFL_TICKETCOUNT 4
#define SFL_TICKETSEQUENCE 4
#define SFL_NFTOKENTAXON 4
#define SFL_MINTEDNFTOKENS 4
#define SFL_BURNEDNFTOKENS 4
#define SFL_HOOKSTATECOUNT 4
#define SFL_EMITGENERATION 4
#define SFL_LOCKCOUNT 4
#define SFL_REWARDTIME 4
#define SFL_REWARDLGRFIRST 4
#define SFL_REWARDLGRLAST 4
#define SFL_FIRSTNFTOKENSEQUENCE 4
// 64 byte-int = 8 bytes
#define SFL_INDEX_NEXT 8
#define SFL_INDEX_PREVIOUS 8
#define SFL_BOOK_NODE 8
#define SFL_OWNER_NODE 8
#define SFL_BASE_FEE 8
#define SFL_EXCHANGE_RATE 8
#define SFL_LOW_NODE 8
#define SFL_HIGH_NODE 8
#define SFL_DESTINATION_NODE 8
#define SFL_COOKIE 8
#define SFL_SERVER_VERSION 8
#define SFL_EMIT_BURDEN 8
#define SFL_NFTOKEN_OFFER_NODE 8
#define SFL_HOOK_INSTRUCTION_COUNT 8
#define SFL_HOOK_RETURN_CODE 8
#define SFL_REFERENCE_COUNT 8
#define SFL_REWARD_ACCUMULATOR 8
// 128 byte-int = 4 bytes
#define SFL_EMAIL_HASH 128
// 160 byte-int = 4 bytes
#define SFL_TAKER_PAYS_CURRENCY 160
#define SFL_TAKER_PAYS_ISSUER 160
#define SFL_TAKER_GETS_CURRENCY 160
#define SFL_TAKER_GETS_ISSUER 160
// 256 byte-int = ??? bytes
#define SFL_LEDGER_HASH 256
#define SFL_PARENT_HASH 256
#define SFL_TRANSACTION_HASH 256
#define SFL_ACCOUNT_HASH 256
#define SFL_HOOK_ON 256
#define SFL_PREVIOUS_TXN_ID 256
#define SFL_LEDGER_INDEX 256
#define SFL_WALLET_LOCATOR 256
#define SFL_ROOT_INDEX 256
#define SFL_ACCOUNT_TXN_ID 256
#define SFL_NFTOKEN_ID 256
#define SFL_EMIT_PARENT_TXN_ID 256
#define SFL_EMIT_NONCE 256
#define SFL_EMIT_HOOK_HASH 256
// 256 byte-int = ??? bytes
#define SFL_BOOK_DIRECTORY 256
#define SFL_INVOICE_ID 256
#define SFL_NICKNAME 256
#define SFL_AMENDMENT 256
#define SFL_DIGEST 256
#define SFL_CHANNEL 256
#define SFL_CONSENSUS_HASH 256
#define SFL_CHECK_ID 256
#define SFL_VALIDATED_HASH 256
#define SFL_PREVIOUS_PAGE_MIN 256
#define SFL_NEXT_PAGE_MIN 256
#define SFL_NFTOKEN_BUY_OFFER 256
#define SFL_NFTOKEN_SELL_OFFER 256
#define SFL_HOOK_STATE_KEY 256
#define SFL_HOOK_HASH 256
#define SFL_HOOK_NAMESPACE 256
#define SFL_HOOK_SET_TXN_ID 256
#define SFL_OFFER_ID 256
#define SFL_ESCROW_ID 256
#define SFL_URITOKEN_ID 256
// 20 bytes
#define SFL_AMOUNT 20
#define SFL_BALANCE 20
#define SFL_LIMIT_AMOUNT 20
#define SFL_TAKER_PAYS 20
#define SFL_TAKER_GETS 20
#define SFL_LOW_LIMIT 20
#define SFL_HIGH_LIMIT 20
#define SFL_FEE 20
#define SFL_SEND_MAX 20
#define SFL_DELIVER_MIN 20
#define SFL_LOCKED_BALANCE 20
// Unimplemented
#define SFL_AMOUNT_MINIMUM_OFFER 8
#define SFL_AMOUNT_RIPPLE_ESCROW 8
#define SFL_AMOUNT_DELIVERED_AMOUNT 8
#define SFL_AMOUNT_NFTOKEN_BROKER_FEE 8
#define SFL_AMOUNT_HOOK_CALLBACK_FEE 8
#define SFL_AMOUNT_BASE_FEE_DROPS 8
#define SFL_AMOUNT_RESERVE_BASE_DROPS 8
#define SFL_AMOUNT_RESERVE_INCREMENT_DROPS 8
// Unimplemented
#define SFL_VL_PUBLIC_KEY 64
#define SFL_VL_MESSAGE_KEY 64
#define SFL_VL_SIGNING_PUB_KEY 64
// Unimplemented
#define SFL_VL_TXN_SIGNATURE 96
// Unimplemented
#define SFL_VL_URI 256
// Unimplemented
#define SFL_VL_SIGNATURE 96
// Unimplemented
#define SFL_VL_DOMAIN 256
#define SFL_VL_FUND_CODE 256
#define SFL_VL_REMOVE_CODE 256
#define SFL_VL_EXPIRE_CODE 256
#define SFL_VL_CREATE_CODE 256
#define SFL_VL_MEMO_TYPE 256
#define SFL_VL_MEMO_DATA 256
#define SFL_VL_MEMO_FORMAT 256
#define SFL_VL_FULFILLMENT 256
#define SFL_VL_CONDITION 256
// Unimplemented
#define SFL_VL_MASTER_SIGNATURE 96
// Unimplemented
#define SFL_VL_UNL_MODIFY_VALIDATOR 256
#define SFL_VL_VALIDATOR_TO_DISABLE 256
#define SFL_VL_VALIDATOR_TO_RE_ENABLE 256
#define SFL_VL_HOOK_STATE_DATA 256
#define SFL_VL_HOOK_RETURN_STRING 256
#define SFL_VL_HOOK_PARAMETER_NAME 256
#define SFL_VL_HOOK_PARAMETER_VALUE 256
#define SFL_VL_BLOB 256
// 20 bytes
#define SFL_ACCOUNT 20
#define SFL_OWNER 20
#define SFL_DESTINATION 20
#define SFL_ISSUER 20
#define SFL_AUTHORIZE 20
#define SFL_UNAUTHORIZE 20
#define SFL_REGULAR_KEY 20
#define SFL_NFTOKEN_MINTER 20
#define SFL_EMIT_CALLBACK 20
#define SFL_HOOK_ACCOUNT 20
#define SFL_NFTOKEN_MINTER 20
// Unimplemented
#define SFL_PATHS 1
// Unimplemented
#define SFL_VECTOR256_INDEXES 32
#define SFL_VECTOR256_HASHES 32
#define SFL_VECTOR256_AMENDMENTS 32
#define SFL_VECTOR256_NFTOKEN_OFFERS 32
#define SFL_VECTOR256_HOOK_NAMESPACES 32
// Unimplemented
#define SFL_TRANSACTION_META_DATA 1
#define SFL_CREATED_NODE 1
#define SFL_DELETED_NODE 1
#define SFL_MODIFIED_NODE 1
#define SFL_PREVIOUS_FIELDS 1
#define SFL_FINAL_FIELDS 1
#define SFL_NEW_FIELDS 1
#define SFL_TEMPLATE_ENTRY 1
#define SFL_MEMO 1
#define SFL_SIGNER_ENTRY 1
#define SFL_NFTOKEN 1
#define SFL_EMIT_DETAILS 1
#define SFL_HOOK 1
#define SFL_SIGNER 1
#define SFL_MAJORITY 1
#define SFL_DISABLED_VALIDATOR 1
#define SFL_EMITTED_TXN 1
#define SFL_HOOK_EXECUTION 1
#define SFL_HOOK_DEFINITION 1
#define SFL_HOOK_PARAMETER 1
#define SFL_HOOK_GRANT 1
#define SFL_SIGNERS 1
#define SFL_SIGNER_ENTRIES 1
#define SFL_TEMPLATE 1
#define SFL_NECESSARY 1
#define SFL_SUFFICIENT 1
#define SFL_AFFECTED_NODES 1
#define SFL_MEMOS 1
#define SFL_NFTOKENS 1
#define SFL_HOOKS 1
#define SFL_MAJORITIES 1
#define SFL_DISABLED_VALIDATORS 1
#define SFL_HOOK_EXECUTIONS 1
#define SFL_HOOK_EXECUTION 1

View File

@@ -1,9 +1,9 @@
all: reward govern mint
accept:
wasmcc accept.c -o accept.wasm -Oz -Wl,--allow-undefined -I../
wasmcc accept.c -o accept.wasm -Oz -Wl,--allow-undefined -I./headers
hook-cleaner accept.wasm
reward:
wasmcc reward.c -o reward.wasm -Oz -Wl,--allow-undefined -I../
wasmcc reward.c -o reward.wasm -Oz -Wl,--allow-undefined -I./headers
wasm-opt reward.wasm -o reward.wasm \
--shrink-level=100000000 \
--coalesce-locals-learning \
@@ -58,7 +58,7 @@ reward:
hook-cleaner reward.wasm
guard_checker reward.wasm
govern:
wasmcc govern.c -o govern.wasm -Oz -Wl,--allow-undefined -I../
wasmcc govern.c -o govern.wasm -Oz -Wl,--allow-undefined -I./headers
wasm-opt govern.wasm -o govern.wasm \
--shrink-level=100000000 \
--coalesce-locals-learning \
@@ -113,7 +113,7 @@ govern:
hook-cleaner govern.wasm
guard_checker govern.wasm
mint:
wasmcc mint.c -o mint.wasm -Oz -Wl,--allow-undefined -I../
wasmcc mint.c -o mint.wasm -Oz -Wl,--allow-undefined -I./headers
wasm-opt mint.wasm -o mint.wasm \
--shrink-level=100000000 \
--coalesce-locals-learning \
@@ -142,5 +142,5 @@ mint:
hook-cleaner mint.wasm
guard_checker mint.wasm
nftoken:
wasmcc nftoken.c -o nftoken.wasm -Oz -Wl,--allow-undefined -I../
wasmcc nftoken.c -o nftoken.wasm -Oz -Wl,--allow-undefined -I./headers
hook-cleaner nftoken.wasm

View File

@@ -49,4 +49,7 @@
#include "macro.h"
#include "tts.h"
#include "ls_flags.h"
#include "tx_flags.h"
#endif

75
hook/ls_flags.h Normal file
View File

@@ -0,0 +1,75 @@
// Generated using generate_lsflags.sh
#ifndef HOOKLSFLAGS_INCLUDED
#define HOOKLSFLAGS_INCLUDED 1
enum ltACCOUNT_ROOT {
lsfPasswordSpent = 0x00010000,
lsfRequireDestTag = 0x00020000,
lsfRequireAuth = 0x00040000,
lsfDisallowXRP = 0x00080000,
lsfDisableMaster = 0x00100000,
lsfNoFreeze = 0x00200000,
lsfGlobalFreeze = 0x00400000,
lsfDefaultRipple = 0x00800000,
lsfDepositAuth = 0x01000000,
lsfTshCollect = 0x02000000,
lsfDisallowIncomingNFTokenOffer = 0x04000000,
lsfDisallowIncomingCheck = 0x08000000,
lsfDisallowIncomingPayChan = 0x10000000,
lsfDisallowIncomingTrustline = 0x20000000,
lsfURITokenIssuer = 0x40000000,
lsfDisallowIncomingRemit = 0x80000000,
lsfAllowTrustLineClawback = 0x00001000,
};
enum ltOFFER {
lsfPassive = 0x00010000,
lsfSell = 0x00020000,
};
enum ltRIPPLE_STATE {
lsfLowReserve = 0x00010000,
lsfHighReserve = 0x00020000,
lsfLowAuth = 0x00040000,
lsfHighAuth = 0x00080000,
lsfLowNoRipple = 0x00100000,
lsfHighNoRipple = 0x00200000,
lsfLowFreeze = 0x00400000,
lsfHighFreeze = 0x00800000,
lsfLowDeepFreeze = 0x02000000,
lsfHighDeepFreeze = 0x04000000,
lsfAMMNode = 0x01000000,
};
enum ltSIGNER_LIST {
lsfOneOwnerCount = 0x00010000,
};
enum ltDIR_NODE {
lsfNFTokenBuyOffers = 0x00000001,
lsfNFTokenSellOffers = 0x00000002,
lsfEmittedDir = 0x00000004,
};
enum ltNFTOKEN_OFFER {
lsfSellNFToken = 0x00000001,
};
enum ltURI_TOKEN {
lsfBurnable = 0x00000001,
};
enum remarks {
lsfImmutable = 1,
};
enum ltMPTOKEN_ISSUANCE {
lsfMPTLocked = 0x00000001,
lsfMPTCanLock = 0x00000002,
lsfMPTRequireAuth = 0x00000004,
lsfMPTCanEscrow = 0x00000008,
lsfMPTCanTrade = 0x00000010,
lsfMPTCanTransfer = 0x00000020,
lsfMPTCanClawback = 0x00000040,
};
enum ltMPTOKEN {
lsfMPTAuthorized = 0x00000002,
};
enum ltCREDENTIAL {
lsfAccepted = 0x00010000,
};
#endif // HOOKLSFLAGS_INCLUDED

View File

@@ -9,6 +9,8 @@
#define sfUNLModifyDisabling ((16U << 16U) + 17U)
#define sfHookResult ((16U << 16U) + 18U)
#define sfWasLockingChainSend ((16U << 16U) + 19U)
#define sfSidecarType ((16U << 16U) + 20U)
#define sfEntropyTier ((16U << 16U) + 21U)
#define sfLedgerEntryType ((1U << 16U) + 1U)
#define sfTransactionType ((1U << 16U) + 2U)
#define sfSignerWeight ((1U << 16U) + 3U)
@@ -22,6 +24,8 @@
#define sfHookApiVersion ((1U << 16U) + 20U)
#define sfHookStateScale ((1U << 16U) + 21U)
#define sfLedgerFixType ((1U << 16U) + 22U)
#define sfHookExportCount ((1U << 16U) + 98U)
#define sfEntropyCount ((1U << 16U) + 99U)
#define sfNetworkID ((2U << 16U) + 1U)
#define sfFlags ((2U << 16U) + 2U)
#define sfSourceTag ((2U << 16U) + 3U)
@@ -80,6 +84,7 @@
#define sfRewardTime ((2U << 16U) + 98U)
#define sfRewardLgrFirst ((2U << 16U) + 99U)
#define sfRewardLgrLast ((2U << 16U) + 100U)
#define sfCancelTicketSequence ((2U << 16U) + 101U)
#define sfIndexNext ((3U << 16U) + 1U)
#define sfIndexPrevious ((3U << 16U) + 2U)
#define sfBookNode ((3U << 16U) + 3U)
@@ -152,13 +157,14 @@
#define sfEscrowID ((5U << 16U) + 35U)
#define sfURITokenID ((5U << 16U) + 36U)
#define sfDomainID ((5U << 16U) + 37U)
#define sfGovernanceFlags ((5U << 16U) + 99U)
#define sfGovernanceMarks ((5U << 16U) + 98U)
#define sfEmittedTxnID ((5U << 16U) + 97U)
#define sfHookCanEmit ((5U << 16U) + 96U)
#define sfCron ((5U << 16U) + 95U)
#define sfHookOnIncoming ((5U << 16U) + 94U)
#define sfHookOnOutgoing ((5U << 16U) + 93U)
#define sfHookOnIncoming ((5U << 16U) + 94U)
#define sfCron ((5U << 16U) + 95U)
#define sfHookCanEmit ((5U << 16U) + 96U)
#define sfEmittedTxnID ((5U << 16U) + 97U)
#define sfGovernanceMarks ((5U << 16U) + 98U)
#define sfGovernanceFlags ((5U << 16U) + 99U)
#define sfEntropyDigest ((5U << 16U) + 100U)
#define sfNumber ((9U << 16U) + 1U)
#define sfAmount ((6U << 16U) + 1U)
#define sfBalance ((6U << 16U) + 2U)
@@ -189,6 +195,7 @@
#define sfSignatureReward ((6U << 16U) + 29U)
#define sfMinAccountCreateAmount ((6U << 16U) + 30U)
#define sfLPTokenBalance ((6U << 16U) + 31U)
#define sfTrustLineRewardAccumulator ((6U << 16U) + 99U)
#define sfPublicKey ((7U << 16U) + 1U)
#define sfMessageKey ((7U << 16U) + 2U)
#define sfSigningPubKey ((7U << 16U) + 3U)
@@ -220,6 +227,7 @@
#define sfProvider ((7U << 16U) + 30U)
#define sfMPTokenMetadata ((7U << 16U) + 31U)
#define sfCredentialType ((7U << 16U) + 32U)
#define sfHookName ((7U << 16U) + 97U)
#define sfRemarkValue ((7U << 16U) + 98U)
#define sfRemarkName ((7U << 16U) + 99U)
#define sfAccount ((8U << 16U) + 1U)
@@ -255,6 +263,7 @@
#define sfIssuingChainIssue ((24U << 16U) + 2U)
#define sfAsset ((24U << 16U) + 3U)
#define sfAsset2 ((24U << 16U) + 4U)
#define sfClaimCurrency ((24U << 16U) + 5U)
#define sfXChainBridge ((25U << 16U) + 1U)
#define sfTransactionMetaData ((14U << 16U) + 2U)
#define sfCreatedNode ((14U << 16U) + 3U)
@@ -274,7 +283,6 @@
#define sfDisabledValidator ((14U << 16U) + 19U)
#define sfEmittedTxn ((14U << 16U) + 20U)
#define sfHookExecution ((14U << 16U) + 21U)
#define sfHookDefinition ((14U << 16U) + 22U)
#define sfHookParameter ((14U << 16U) + 23U)
#define sfHookGrant ((14U << 16U) + 24U)
#define sfVoteEntry ((14U << 16U) + 25U)
@@ -286,6 +294,7 @@
#define sfXChainCreateAccountAttestationCollectionElement ((14U << 16U) + 31U)
#define sfPriceData ((14U << 16U) + 32U)
#define sfCredential ((14U << 16U) + 33U)
#define sfExportedTxn ((14U << 16U) + 90U)
#define sfAmountEntry ((14U << 16U) + 91U)
#define sfMintURIToken ((14U << 16U) + 92U)
#define sfHookEmission ((14U << 16U) + 93U)
@@ -293,6 +302,9 @@
#define sfActiveValidator ((14U << 16U) + 95U)
#define sfGenesisMint ((14U << 16U) + 96U)
#define sfRemark ((14U << 16U) + 97U)
#define sfHighReward ((14U << 16U) + 98U)
#define sfLowReward ((14U << 16U) + 99U)
#define sfExportResult ((14U << 16U) + 100U)
#define sfSigners ((15U << 16U) + 3U)
#define sfSignerEntries ((15U << 16U) + 4U)
#define sfTemplate ((15U << 16U) + 5U)
@@ -315,9 +327,9 @@
#define sfAuthorizeCredentials ((15U << 16U) + 26U)
#define sfUnauthorizeCredentials ((15U << 16U) + 27U)
#define sfAcceptedCredentials ((15U << 16U) + 28U)
#define sfRemarks ((15U << 16U) + 97U)
#define sfGenesisMints ((15U << 16U) + 96U)
#define sfActiveValidators ((15U << 16U) + 95U)
#define sfImportVLKeys ((15U << 16U) + 94U)
#define sfHookEmissions ((15U << 16U) + 93U)
#define sfAmounts ((15U << 16U) + 92U)
#define sfHookEmissions ((15U << 16U) + 93U)
#define sfImportVLKeys ((15U << 16U) + 94U)
#define sfActiveValidators ((15U << 16U) + 95U)
#define sfGenesisMints ((15U << 16U) + 96U)
#define sfRemarks ((15U << 16U) + 97U)

View File

@@ -61,6 +61,7 @@
#define ttNFTOKEN_MODIFY 70
#define ttPERMISSIONED_DOMAIN_SET 71
#define ttPERMISSIONED_DOMAIN_DELETE 72
#define ttEXPORT 91
#define ttCRON 92
#define ttCRON_SET 93
#define ttREMARKS_SET 94
@@ -74,3 +75,4 @@
#define ttUNL_MODIFY 102
#define ttEMIT_FAILURE 103
#define ttUNL_REPORT 104
#define ttCONSENSUS_ENTROPY 105

122
hook/tx_flags.h Normal file
View File

@@ -0,0 +1,122 @@
// Generated using generate_txflags.sh
#include "ls_flags.h"
#include <stdint.h>
enum UniversalFlags : uint32_t {
tfFullyCanonicalSig = 0x80000000,
};
enum AccountSetFlags : uint32_t {
tfRequireDestTag = 0x00010000,
tfOptionalDestTag = 0x00020000,
tfRequireAuth = 0x00040000,
tfOptionalAuth = 0x00080000,
tfDisallowXRP = 0x00100000,
tfAllowXRP = 0x00200000,
};
enum AccountFlags : uint32_t {
asfRequireDest = 1,
asfRequireAuth = 2,
asfDisallowXRP = 3,
asfDisableMaster = 4,
asfAccountTxnID = 5,
asfNoFreeze = 6,
asfGlobalFreeze = 7,
asfDefaultRipple = 8,
asfDepositAuth = 9,
asfAuthorizedNFTokenMinter = 10,
asfTshCollect = 11,
asfDisallowIncomingNFTokenOffer = 12,
asfDisallowIncomingCheck = 13,
asfDisallowIncomingPayChan = 14,
asfDisallowIncomingTrustline = 15,
asfDisallowIncomingRemit = 16,
asfAllowTrustLineClawback = 17,
};
enum OfferCreateFlags : uint32_t {
tfPassive = 0x00010000,
tfImmediateOrCancel = 0x00020000,
tfFillOrKill = 0x00040000,
tfSell = 0x00080000,
};
enum PaymentFlags : uint32_t {
tfNoRippleDirect = 0x00010000,
tfPartialPayment = 0x00020000,
tfLimitQuality = 0x00040000,
};
enum TrustSetFlags : uint32_t {
tfSetfAuth = 0x00010000,
tfSetNoRipple = 0x00020000,
tfClearNoRipple = 0x00040000,
tfSetFreeze = 0x00100000,
tfClearFreeze = 0x00200000,
tfSetDeepFreeze = 0x00400000,
tfClearDeepFreeze = 0x00800000
};
enum EnableAmendmentFlags : uint32_t {
tfGotMajority = 0x00010000,
tfLostMajority = 0x00020000,
tfTestSuite = 0x80000000,
};
enum PaymentChannelClaimFlags : uint32_t {
tfRenew = 0x00010000,
tfClose = 0x00020000,
};
enum NFTokenMintFlags : uint32_t {
tfBurnable = 0x00000001,
tfOnlyXRP = 0x00000002,
tfTrustLine = 0x00000004,
tfTransferable = 0x00000008,
tfMutable = 0x00000010,
tfStrongTSH = 0x00008000,
};
enum MPTokenIssuanceCreateFlags : uint32_t {
tfMPTCanLock = lsfMPTCanLock,
tfMPTRequireAuth = lsfMPTRequireAuth,
tfMPTCanEscrow = lsfMPTCanEscrow,
tfMPTCanTrade = lsfMPTCanTrade,
tfMPTCanTransfer = lsfMPTCanTransfer,
tfMPTCanClawback = lsfMPTCanClawback,
};
enum MPTokenAuthorizeFlags : uint32_t {
tfMPTUnauthorize = 0x00000001,
};
enum MPTokenIssuanceSetFlags : uint32_t {
tfMPTLock = 0x00000001,
tfMPTUnlock = 0x00000002,
};
enum NFTokenCreateOfferFlags : uint32_t {
tfSellNFToken = 0x00000001,
};
enum ClaimRewardFlags : uint32_t {
tfOptOut = 0x00000001,
};
enum CronSetFlags : uint32_t {
tfCronUnset = 0x00000001,
};
enum AMMClawbackFlags : uint32_t {
tfClawTwoAssets = 0x00000001,
};
enum BridgeModifyFlags : uint32_t {
tfClearAccountCreateAmount = 0x00010000,
};
enum ConsensusEntropyFlags : uint32_t {
tfEntropyCommit = 0x00000001, // entry is a commitment in commitSet
tfEntropyReveal = 0x00000002, // entry is a reveal in entropySet
};

View File

@@ -138,14 +138,14 @@ public:
template <typename U>
requires std::convertible_to<U, T>
constexpr Expected(U&& r)
: Base(boost::outcome_v2::success(T(std::forward<U>(r))))
: Base(boost::outcome_v2::in_place_type_t<T>{}, std::forward<U>(r))
{
}
template <typename U>
requires std::convertible_to<U, E> && (!std::is_reference_v<U>)
constexpr Expected(Unexpected<U> e)
: Base(boost::outcome_v2::failure(E(std::move(e.value()))))
: Base(boost::outcome_v2::in_place_type_t<E>{}, std::move(e.value()))
{
}

View File

@@ -15,7 +15,10 @@
#define uint256 std::string
#define featureHooksUpdate1 "1"
#define featureHooksUpdate2 "1"
#define featureExport "1"
#define featureConsensusEntropy "1"
#define fix20250131 "1"
#define fixGuardDepth32 "1"
namespace hook_api {
struct Rules
{
@@ -319,7 +322,7 @@ namespace compare_mode {
enum compare_mode : uint32_t { EQUAL = 1, LESS = 2, GREATER = 4 };
}
enum hook_return_code : int64_t {
enum class hook_return_code : int64_t {
SUCCESS =
0, // return codes > 0 are reserved for hook apis to return "success"
OUT_OF_BOUNDS =
@@ -383,10 +386,13 @@ enum hook_return_code : int64_t {
MEM_OVERLAP = -43, // one or more specified buffers are the same memory
TOO_MANY_STATE_MODIFICATIONS = -44, // more than 5000 modified state
// entires in the combined hook chains
TOO_MANY_NAMESPACES = -45
TOO_MANY_NAMESPACES = -45,
EXPORT_FAILURE = -46,
TOO_MANY_EXPORTED_TXN = -47,
TOO_LITTLE_ENTROPY = -48,
};
enum ExitType : uint8_t {
enum class ExitType : uint8_t {
UNSET = 0,
WASM_ERROR = 1,
ROLLBACK = 2,
@@ -397,6 +403,7 @@ const uint16_t max_state_modifications = 256;
const uint8_t max_slots = 255;
const uint8_t max_nonce = 255;
const uint8_t max_emit = 255;
const uint8_t max_export = 2;
const uint8_t max_params = 16;
const double fee_base_multiplier = 1.1f;
@@ -437,12 +444,9 @@ getImportWhitelist(Rules const& rules)
return whitelist;
}
#undef HOOK_API_DEFINITION
#undef I32
#undef I64
enum GuardRulesVersion : uint64_t {
GuardRuleFix20250131 = 0x00000001,
GuardRuleDepth32 = 0x00000002,
};
inline uint64_t
@@ -451,6 +455,8 @@ getGuardRulesVersion(Rules const& rules)
uint64_t version = 0;
if (rules.enabled(fix20250131))
version |= GuardRuleFix20250131;
if (rules.enabled(fixGuardDepth32))
version |= GuardRuleDepth32;
return version;
}

View File

@@ -204,9 +204,13 @@ struct WasmBlkInf
}
// compute worst case execution time
inline uint64_t
compute_wce(const WasmBlkInf* blk, int level, bool* recursion_limit_reached)
compute_wce(
const WasmBlkInf* blk,
int level,
int max_level,
bool* recursion_limit_reached)
{
if (level > 16)
if (level > max_level)
{
*recursion_limit_reached = true;
return 0;
@@ -233,8 +237,8 @@ compute_wce(const WasmBlkInf* blk, int level, bool* recursion_limit_reached)
if (blk->children.size() > 0)
for (auto const& child : blk->children)
worst_case_execution +=
compute_wce(child, level + 1, recursion_limit_reached);
worst_case_execution += compute_wce(
child, level + 1, max_level, recursion_limit_reached);
if (parent == 0 ||
parent->iteration_bound ==
@@ -788,12 +792,17 @@ check_guard(
}
bool recursion_limit_reached = false;
uint64_t wce = compute_wce(&(*root), 0, &recursion_limit_reached);
int max_level = 16;
if (rulesVersion & hook_api::GuardRuleDepth32)
max_level = 32;
uint64_t wce =
compute_wce(&(*root), 0, max_level, &recursion_limit_reached);
if (recursion_limit_reached)
{
GUARDLOG(hook::log::NESTING_LIMIT)
<< "GuardCheck "
<< "Maximum allowable depth of blocks reached (16 levels). Flatten "
<< "Maximum allowable depth of blocks reached (" << max_level
<< " levels). Flatten "
"your loops and conditions!.\n";
return {};
}

Some files were not shown because too many files have changed in this diff Show More