Phase-10 brought in the upstream nodestore/peerfinder/consensus reorganisation along with its own write-path telemetry, which collided with the sync-diagnostic signals on this branch. Twelve files conflicted; every resolution keeps both intents rather than picking a side. The nodestore write timing was implemented twice, independently. Both sides added getStoreDurationUs()/getFetchDurationUs() to Database and both timed the backend call in each concrete store(). Keeping both would have added twice to storeDurationUs_ per store while storeStats() still counted one, so the mean write latency would have read double on every dashboard -- silently, since no test on either side asserts an exact microsecond figure. Resolved to one accumulator API: recordStoreDuration(), which takes a duration, clamps a sub-microsecond sample to zero and uses a relaxed atomic add. Phase-10's storeDurationStats() is gone and its two call sites now use the survivor, so all three store paths -- both store() overrides and importInternal() -- add exactly once. SlotCensus and its pure virtual moved from src/xrpld/peerfinder/ to include/xrpl/peerfinder/PeerfinderManager.h, following the Manager interface upstream relocated. The xrpld header is now phase-10's makeConfig shim, and Overlay.h, MetricMacros.cpp and the getSlotCensus() override chain point at the new location. ConsensusSpanNames.h and peerfinder Slot.h/Config.h include paths followed their headers into libxrpl the same way. InboundLedger gained phase-10's AcquireStats counters next to this branch's span activations in both the destructor abort path and done(); neither displaces the other. nodestore_state keeps the constant-based name this branch requires of it and phase-10's fuller description. Upstream #7292 deleted src/test/nodestore/Database_test.cpp, which held this branch's testDurationAccessors. Phase-10 restored the per-store half of that coverage in DatabaseConfig_test, but nothing covered importInternal -- it writes through storeBatch() and never through store(), so it is a third store path that has to time itself. That half is ported to a GTest in src/tests/libxrpl/nodestore/Database.cpp, keeping the exact zero-before and accumulate-after assertions and the per-instance negative check. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Levelization
Levelization is the term used to describe efforts to prevent xrpld from having or creating cyclic dependencies.
xrpld code is organized into directories under src/xrpld, src/libxrpl (and
src/test) representing modules. The modules are intended to be
organized into "tiers" or "levels" such that a module from one level can
only include code from lower levels. Additionally, a module
in one level should never include code in an impl or detail folder of any level
other than its own.
The codebase is split into two main areas:
- libxrpl (
src/libxrpl,include/xrpl): Reusable library modules with public interfaces - xrpld (
src/xrpld): Application-specific implementation code
Unfortunately, over time, enforcement of levelization has been inconsistent, so the current state of the code doesn't necessarily reflect these rules. Whenever possible, developers should refactor any levelization violations they find (by moving files or individual classes). At the very least, don't make things worse.
The table below summarizes the desired division of modules, based on the current state of the xrpld code. The levels are numbered from the bottom up with the lower level, lower numbered, more independent modules listed first, and the higher level, higher numbered modules with more dependencies listed later.
tl;dr: The modules listed first are more independent than the modules listed later.
libxrpl Modules (Reusable Libraries)
| Level / Tier | Module(s) |
|---|---|
| 01 | xrpl/beast |
| 02 | xrpl/basics |
| 03 | xrpl/json xrpl/crypto |
| 04 | xrpl/protocol |
| 05 | xrpl/core xrpl/resource xrpl/server |
| 06 | xrpl/ledger xrpl/nodestore xrpl/net |
| 07 | xrpl/shamap xrpl/consensus |
xrpld Modules (Application Implementation)
| Level / Tier | Module(s) |
|---|---|
| 05 | xrpld/conditions |
| 06 | xrpld/core xrpld/peerfinder |
| 07 | xrpld/shamap xrpld/overlay |
| 08 | xrpld/app |
| 09 | xrpld/rpc |
| 10 | xrpld/perflog |
Test Modules
| Level / Tier | Module(s) |
|---|---|
| 11 | test/jtx test/beast test/csf |
| 12 | test/unit_test |
| 13 | test/crypto test/conditions test/json test/resource test/shamap test/peerfinder test/basics test/overlay |
| 14 | test |
| 15 | test/net test/protocol test/ledger test/consensus test/core test/server test/nodestore |
| 16 | test/rpc test/app |
(Note that test levelization is much less important and much less
strictly enforced than xrpl/xrpld levelization, other than the requirement
that test code should never be included in xrpl or xrpld code.)
Validation
The levelization 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 xrpld repo.
It can be run at any time from within a checked out repo, and will
do an analysis of all the #includes in
the xrpld source. The only caveat is that it runs much slower
under Windows than in Linux. It hasn't yet been tested under MacOS.
It generates many files of results:
rawincludes.txt: The raw dump of the#includespaths.txt: A second dump grouping the source module to the destination module, de-duped, and with frequency counts.includes/: A directory where each file represents a module and contains a list of modules and counts that the module includes.included_by/: Similar toincludes/, but the other way around. Each file represents a module and contains a list of modules and counts that include the module.loops.txt: A list of direct loops detected between modules as they actually exist, as opposed to how they are desired as described above. In a perfect repo, this file will be empty. This file is committed to the repo, and is used by the levelization Github workflow to validate that nothing changed.ordering.txt: A list showing relationships between modules where there are no loops as they actually exist, as opposed to how they are desired as described above. This file is committed to the repo, and is used by the levelization Github workflow to validate that nothing changed.levelization.ymlGithub 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, rungenerate.py, and commit the updated results.
The loops.txt and ordering.txt files relate the modules
using comparison signs, which indicate the number of times each
module is included in the other.
A > Bmeans that A should probably be at a higher level than B, because B is included in A significantly more than A is included in B. These results can be included in bothloops.txtandordering.txt. Becauseordering.txtonly includes relationships where B is not included in A at all, it will only include these types of results.A ~= Bmeans that A and B are included in each other a different number of times, but the values are so close that the script can't definitively say that one should be above the other. These results will only be included inloops.txt.A == Bmeans that A and B include each other the same number of times, so the script has no clue which should be higher. These results will only be included inloops.txt.
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.
- Run
generate.py - Grep the modules in
paths.txt.- For example, if a cycle is found
A ~= B, simplygrep -w A .github/scripts/levelization/results/paths.txt | grep -w B
- For example, if a cycle is found