mirror of
https://github.com/XRPLF/rippled.git
synced 2026-06-06 18:26:51 +00:00
Compare commits
73 Commits
dangell7/d
...
bthomee/jq
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ecd0136844 | ||
|
|
02f20331d5 | ||
|
|
949887feb9 | ||
|
|
fc57dab78b | ||
|
|
63ffdc39dc | ||
|
|
6571f75d39 | ||
|
|
2111bb4b95 | ||
|
|
8abe82eefa | ||
|
|
5b8e6cd1dd | ||
|
|
12e81abef3 | ||
|
|
6c543426c3 | ||
|
|
e5cf1a0985 | ||
|
|
023bdaeeed | ||
|
|
96b2c0964f | ||
|
|
1441d4690d | ||
|
|
225ed204ad | ||
|
|
ad111bcc22 | ||
|
|
d4cb68d5a1 | ||
|
|
e209ee5371 | ||
|
|
109b649106 | ||
|
|
0fffe23abc | ||
|
|
7e15621e7b | ||
|
|
99431d7833 | ||
|
|
47365f4220 | ||
|
|
1599c1a672 | ||
|
|
763dd503be | ||
|
|
2f3558c610 | ||
|
|
f9551ac5ca | ||
|
|
1acc42313c | ||
|
|
396d772a15 | ||
|
|
1438bf1c67 | ||
|
|
7da643d864 | ||
|
|
1162371def | ||
|
|
2a0feca46b | ||
|
|
108a4c8217 | ||
|
|
4584b01bde | ||
|
|
7c59786565 | ||
|
|
9623e67b76 | ||
|
|
85af406a0f | ||
|
|
ac33fb32a7 | ||
|
|
23d0812827 | ||
|
|
49567e7283 | ||
|
|
633ef4706f | ||
|
|
49cb3f45a4 | ||
|
|
22a21b175e | ||
|
|
e9d885bd9b | ||
|
|
a911f9089e | ||
|
|
e34c2667d7 | ||
|
|
30de556224 | ||
|
|
dcd2ff0b5f | ||
|
|
dfb9b8ed9a | ||
|
|
179e73594a | ||
|
|
15dd653e4b | ||
|
|
a37afe13ff | ||
|
|
3547a9335f | ||
|
|
1a98182e23 | ||
|
|
79308705c5 | ||
|
|
e24de65f42 | ||
|
|
7fdaa0a5ef | ||
|
|
795dc5e364 | ||
|
|
f6fd5ddb0a | ||
|
|
afcf6fbcdc | ||
|
|
28cc20c816 | ||
|
|
a830ab10ef | ||
|
|
8c0080020f | ||
|
|
9cb0740673 | ||
|
|
242ce3e9e4 | ||
|
|
a5d238e7d4 | ||
|
|
9cb049276d | ||
|
|
93ac1aa7aa | ||
|
|
d9a3af8207 | ||
|
|
8d1083e5ea | ||
|
|
1e45d363c5 |
@@ -154,7 +154,7 @@ Checks: "-*,
|
||||
"
|
||||
# ---
|
||||
# readability-inconsistent-declaration-parameter-name, # in this codebase this check will break a lot of arg names
|
||||
# readability-static-accessed-through-instance, # this check is probably unnecessary. it makes the code less readable
|
||||
# readability-static-accessed-through-instance, # this check is probably unnecessary. It makes the code less readable
|
||||
# ---
|
||||
|
||||
CheckOptions:
|
||||
@@ -191,11 +191,14 @@ CheckOptions:
|
||||
readability-identifier-naming.ParameterCase: camelBack
|
||||
readability-identifier-naming.FunctionCase: camelBack
|
||||
readability-identifier-naming.MemberCase: camelBack
|
||||
readability-identifier-naming.PrivateMemberCase: camelBack
|
||||
readability-identifier-naming.PrivateMemberSuffix: _
|
||||
readability-identifier-naming.ProtectedMemberCase: camelBack
|
||||
readability-identifier-naming.ProtectedMemberSuffix: _
|
||||
readability-identifier-naming.PublicMemberCase: camelBack
|
||||
readability-identifier-naming.PublicMemberSuffix: ""
|
||||
readability-identifier-naming.GlobalFunctionIgnoredRegexp: "^(to_string|hash_append|tuple_hash)$"
|
||||
|
||||
HeaderFilterRegex: '^.*/(test|xrpl|xrpld)/.*\.(h|hpp|ipp)$'
|
||||
HeaderFilterRegex: '^.*/(tests?|xrpl|xrpld)/.*\.(h|hpp|ipp)$'
|
||||
ExcludeHeaderFilterRegex: '^.*/protocol_autogen/.*\.(h|hpp)$'
|
||||
WarningsAsErrors: "*"
|
||||
|
||||
@@ -11,9 +11,6 @@ endfunction()
|
||||
function(create_symbolic_link target link)
|
||||
endfunction()
|
||||
|
||||
function(xrpl_add_test name)
|
||||
endfunction()
|
||||
|
||||
macro(exclude_from_default target_)
|
||||
endmacro()
|
||||
|
||||
|
||||
19
.github/actions/build-deps/action.yml
vendored
19
.github/actions/build-deps/action.yml
vendored
@@ -35,14 +35,13 @@ runs:
|
||||
LOG_VERBOSITY: ${{ inputs.log_verbosity }}
|
||||
SANITIZERS: ${{ inputs.sanitizers }}
|
||||
run: |
|
||||
echo 'Installing dependencies.'
|
||||
conan install \
|
||||
--profile ci \
|
||||
--build="${BUILD_OPTION}" \
|
||||
--options:host='&:tests=True' \
|
||||
--options:host='&:xrpld=True' \
|
||||
--settings:all build_type="${BUILD_TYPE}" \
|
||||
--conf:all tools.build:jobs=${BUILD_NPROC} \
|
||||
--conf:all tools.build:verbosity="${LOG_VERBOSITY}" \
|
||||
--conf:all tools.compilation:verbosity="${LOG_VERBOSITY}" \
|
||||
.
|
||||
--profile:all ci \
|
||||
--build="${BUILD_OPTION}" \
|
||||
--options:host='&:tests=True' \
|
||||
--options:host='&:xrpld=True' \
|
||||
--settings:all build_type="${BUILD_TYPE}" \
|
||||
--conf:all tools.build:jobs=${BUILD_NPROC} \
|
||||
--conf:all tools.build:verbosity="${LOG_VERBOSITY}" \
|
||||
--conf:all tools.compilation:verbosity="${LOG_VERBOSITY}" \
|
||||
.
|
||||
|
||||
10
.github/actions/generate-version/action.yml
vendored
10
.github/actions/generate-version/action.yml
vendored
@@ -15,7 +15,7 @@ runs:
|
||||
shell: bash
|
||||
env:
|
||||
VERSION: ${{ github.ref_name }}
|
||||
run: echo "VERSION=${VERSION}" >> "${GITHUB_ENV}"
|
||||
run: echo "VERSION=${VERSION}" >>"${GITHUB_ENV}"
|
||||
|
||||
# When a tag is not pushed, then the version (e.g. 1.2.3-b0) is extracted
|
||||
# from the BuildInfo.cpp file and the shortened commit hash appended to it.
|
||||
@@ -28,17 +28,17 @@ runs:
|
||||
echo 'Extracting version from BuildInfo.cpp.'
|
||||
VERSION="$(cat src/libxrpl/protocol/BuildInfo.cpp | grep "versionString =" | awk -F '"' '{print $2}')"
|
||||
if [[ -z "${VERSION}" ]]; then
|
||||
echo 'Unable to extract version from BuildInfo.cpp.'
|
||||
exit 1
|
||||
echo 'Unable to extract version from BuildInfo.cpp.'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo 'Appending shortened commit hash to version.'
|
||||
SHA='${{ github.sha }}'
|
||||
VERSION="${VERSION}+${SHA:0:7}"
|
||||
|
||||
echo "VERSION=${VERSION}" >> "${GITHUB_ENV}"
|
||||
echo "VERSION=${VERSION}" >>"${GITHUB_ENV}"
|
||||
|
||||
- name: Output version
|
||||
id: version
|
||||
shell: bash
|
||||
run: echo "version=${VERSION}" >> "${GITHUB_OUTPUT}"
|
||||
run: echo "version=${VERSION}" >>"${GITHUB_OUTPUT}"
|
||||
|
||||
34
.github/actions/set-compiler-env/action.yml
vendored
Normal file
34
.github/actions/set-compiler-env/action.yml
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
name: Set compiler environment
|
||||
description: "Set CC and CXX environment variables for the given compiler."
|
||||
|
||||
inputs:
|
||||
compiler:
|
||||
description: 'The compiler to use ("gcc" or "clang").'
|
||||
required: true
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
|
||||
steps:
|
||||
- name: Set CC and CXX for gcc
|
||||
if: ${{ inputs.compiler == 'gcc' }}
|
||||
shell: bash
|
||||
run: |
|
||||
echo "CC=gcc" >>"${GITHUB_ENV}"
|
||||
echo "CXX=g++" >>"${GITHUB_ENV}"
|
||||
|
||||
- name: Set CC and CXX for clang
|
||||
if: ${{ inputs.compiler == 'clang' }}
|
||||
shell: bash
|
||||
run: |
|
||||
echo "CC=clang" >>"${GITHUB_ENV}"
|
||||
echo "CXX=clang++" >>"${GITHUB_ENV}"
|
||||
|
||||
- name: Fail on unknown compiler
|
||||
if: ${{ inputs.compiler != 'gcc' && inputs.compiler != 'clang' }}
|
||||
shell: bash
|
||||
env:
|
||||
COMPILER: ${{ inputs.compiler }}
|
||||
run: |
|
||||
echo "Unknown compiler: $COMPILER" >&2
|
||||
exit 1
|
||||
25
.github/actions/setup-conan/action.yml
vendored
25
.github/actions/setup-conan/action.yml
vendored
@@ -15,32 +15,35 @@ runs:
|
||||
using: composite
|
||||
|
||||
steps:
|
||||
- name: Set up Conan configuration
|
||||
- name: Apply custom configuration to global.conf
|
||||
shell: bash
|
||||
run: |
|
||||
echo 'Installing configuration.'
|
||||
cat conan/global.conf ${{ runner.os == 'Linux' && '>>' || '>' }} $(conan config home)/global.conf
|
||||
|
||||
echo 'Conan configuration:'
|
||||
conan config show '*'
|
||||
|
||||
- name: Set up Conan profile
|
||||
- name: Show global configuration
|
||||
shell: bash
|
||||
run: |
|
||||
conan config show '*'
|
||||
|
||||
- name: Install profiles
|
||||
shell: bash
|
||||
run: |
|
||||
echo 'Installing profile.'
|
||||
conan config install conan/profiles/ -tf $(conan config home)/profiles/
|
||||
|
||||
echo 'Conan profile:'
|
||||
- name: Show CI profile
|
||||
shell: bash
|
||||
run: |
|
||||
conan profile show --profile ci
|
||||
|
||||
- name: Set up Conan remote
|
||||
- name: Add a remote
|
||||
shell: bash
|
||||
env:
|
||||
REMOTE_NAME: ${{ inputs.remote_name }}
|
||||
REMOTE_URL: ${{ inputs.remote_url }}
|
||||
run: |
|
||||
echo "Adding Conan remote '${REMOTE_NAME}' at '${REMOTE_URL}'."
|
||||
conan remote add --index 0 --force "${REMOTE_NAME}" "${REMOTE_URL}"
|
||||
|
||||
echo 'Listing Conan remotes.'
|
||||
- name: List remotes
|
||||
shell: bash
|
||||
run: |
|
||||
conan remote list
|
||||
|
||||
40
.github/dependabot.yml
vendored
40
.github/dependabot.yml
vendored
@@ -1,40 +1,12 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: github-actions
|
||||
directory: /
|
||||
schedule:
|
||||
interval: weekly
|
||||
day: monday
|
||||
time: "04:00"
|
||||
timezone: Etc/GMT
|
||||
commit-message:
|
||||
prefix: "ci: [DEPENDABOT] "
|
||||
target-branch: develop
|
||||
|
||||
- package-ecosystem: github-actions
|
||||
directory: .github/actions/build-deps/
|
||||
schedule:
|
||||
interval: weekly
|
||||
day: monday
|
||||
time: "04:00"
|
||||
timezone: Etc/GMT
|
||||
commit-message:
|
||||
prefix: "ci: [DEPENDABOT] "
|
||||
target-branch: develop
|
||||
|
||||
- package-ecosystem: github-actions
|
||||
directory: .github/actions/generate-version/
|
||||
schedule:
|
||||
interval: weekly
|
||||
day: monday
|
||||
time: "04:00"
|
||||
timezone: Etc/GMT
|
||||
commit-message:
|
||||
prefix: "ci: [DEPENDABOT] "
|
||||
target-branch: develop
|
||||
|
||||
- package-ecosystem: github-actions
|
||||
directory: .github/actions/setup-conan/
|
||||
directories:
|
||||
- /
|
||||
- .github/actions/build-deps/
|
||||
- .github/actions/generate-version/
|
||||
- .github/actions/set-compiler-env/
|
||||
- .github/actions/setup-conan/
|
||||
schedule:
|
||||
interval: weekly
|
||||
day: monday
|
||||
|
||||
403
.github/scripts/format-inline-bash.py
vendored
Executable file
403
.github/scripts/format-inline-bash.py
vendored
Executable file
@@ -0,0 +1,403 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
"""
|
||||
Format embedded shell snippets using the shfmt hook configured in
|
||||
.pre-commit-config.yaml.
|
||||
|
||||
Two shapes are recognised:
|
||||
|
||||
* YAML workflow/action files: literal block-scalar runs (`run: |`) and
|
||||
single-line runs (`run: some command`). A single-line run is upgraded to
|
||||
a `run: |` block scalar if shfmt's output spans multiple lines.
|
||||
|
||||
* Markdown files: ``` ```bash ``` fenced code blocks.
|
||||
|
||||
Any block that shfmt cannot parse is skipped with a warning on stderr, so
|
||||
the file is left untouched and surrounding blocks still get formatted.
|
||||
|
||||
For each occurrence the body is dedented, written to a temp .sh file,
|
||||
formatted via `pre-commit run shfmt --files <temp>` (falling back to
|
||||
`prek`), then re-indented and written back in place.
|
||||
|
||||
When invoked without arguments, every .yml/.yaml under .github/ plus every
|
||||
.md file in the repo is scanned. When invoked with file arguments (the
|
||||
pre-commit case), only those files are processed.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Union
|
||||
|
||||
REPO = Path(__file__).resolve().parents[2]
|
||||
|
||||
_HOOK_RUNNER = next((cmd for cmd in ("pre-commit", "prek") if shutil.which(cmd)), None)
|
||||
if _HOOK_RUNNER is None:
|
||||
sys.exit("error: neither `pre-commit` nor `prek` found on PATH")
|
||||
|
||||
RUN_BLOCK_RE = re.compile(r"^(?P<prefix>[ \t]*(?:- )?)run:[ \t]*\|[+-]?[ \t]*$")
|
||||
RUN_INLINE_RE = re.compile(
|
||||
r"^(?P<prefix>[ \t]*(?:- )?)run:[ \t]+" r"(?P<value>(?!\|[+-]?[ \t]*$)\S.*?)[ \t]*$"
|
||||
)
|
||||
MD_BASH_OPEN_RE = re.compile(r"^(?P<indent>[ ]{0,3})`{3}bash[ \t]*$")
|
||||
MD_FENCE_CLOSE_RE = re.compile(r"^[ ]{0,3}`{3,}[ \t]*$")
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class BlockRun:
|
||||
"""A `run: |` block scalar; `body_start:body_end` slices into `lines`."""
|
||||
|
||||
body_start: int
|
||||
body_end: int
|
||||
body_indent: int
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class InlineRun:
|
||||
"""A single-line `run: value` at `line_idx`."""
|
||||
|
||||
line_idx: int
|
||||
prefix: str
|
||||
value: str
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class MdBashBlock:
|
||||
"""A markdown ``` ```bash ``` fenced code block.
|
||||
|
||||
`body_start:body_end` slices into the file's lines; `open_line_idx`
|
||||
points at the opening fence line.
|
||||
"""
|
||||
|
||||
open_line_idx: int
|
||||
body_start: int
|
||||
body_end: int
|
||||
body_indent: int
|
||||
|
||||
|
||||
RunItem = Union[BlockRun, InlineRun]
|
||||
|
||||
|
||||
def _scan_block_body(
|
||||
lines: list[str], body_start: int, run_col: int
|
||||
) -> tuple[int | None, int]:
|
||||
"""Locate the body of a `run: |` block scalar starting at `body_start`.
|
||||
|
||||
Returns `(body_indent, scan_end)`. `scan_end` is the line index where the
|
||||
outer scanner should resume. `body_indent` is `None` when no body is
|
||||
present (the scalar is empty, or the next non-blank line has indent
|
||||
`<= run_col`).
|
||||
"""
|
||||
body_indent: int | None = None
|
||||
scan_end = len(lines)
|
||||
for idx in range(body_start, len(lines)):
|
||||
line = lines[idx]
|
||||
if line.strip() == "":
|
||||
continue
|
||||
indent = len(line) - len(line.lstrip(" "))
|
||||
if body_indent is None:
|
||||
if indent > run_col:
|
||||
body_indent = indent
|
||||
else:
|
||||
scan_end = idx
|
||||
break
|
||||
elif indent < body_indent:
|
||||
scan_end = idx
|
||||
break
|
||||
if body_indent is not None:
|
||||
while scan_end > body_start and lines[scan_end - 1].strip() == "":
|
||||
scan_end -= 1
|
||||
if scan_end <= body_start:
|
||||
body_indent = None
|
||||
return body_indent, scan_end
|
||||
|
||||
|
||||
def find_run_blocks(lines: list[str]) -> list[RunItem]:
|
||||
"""Return run items in document order."""
|
||||
items: list[RunItem] = []
|
||||
line_idx = 0
|
||||
while line_idx < len(lines):
|
||||
line = lines[line_idx]
|
||||
if block_match := RUN_BLOCK_RE.match(line):
|
||||
run_col = len(block_match.group("prefix"))
|
||||
body_start = line_idx + 1
|
||||
body_indent, scan_end = _scan_block_body(lines, body_start, run_col)
|
||||
if body_indent is not None:
|
||||
items.append(
|
||||
BlockRun(
|
||||
body_start=body_start,
|
||||
body_end=scan_end,
|
||||
body_indent=body_indent,
|
||||
)
|
||||
)
|
||||
line_idx = scan_end
|
||||
continue
|
||||
if inline_match := RUN_INLINE_RE.match(line):
|
||||
items.append(
|
||||
InlineRun(
|
||||
line_idx=line_idx,
|
||||
prefix=inline_match.group("prefix"),
|
||||
value=inline_match.group("value"),
|
||||
)
|
||||
)
|
||||
line_idx += 1
|
||||
return items
|
||||
|
||||
|
||||
def find_md_bash_blocks(lines: list[str]) -> list[MdBashBlock]:
|
||||
"""Return ``` ```bash ``` fenced code blocks in document order."""
|
||||
blocks: list[MdBashBlock] = []
|
||||
line_idx = 0
|
||||
while line_idx < len(lines):
|
||||
open_match = MD_BASH_OPEN_RE.match(lines[line_idx])
|
||||
if not open_match:
|
||||
line_idx += 1
|
||||
continue
|
||||
body_start = line_idx + 1
|
||||
close_idx = next(
|
||||
(
|
||||
j
|
||||
for j in range(body_start, len(lines))
|
||||
if MD_FENCE_CLOSE_RE.match(lines[j])
|
||||
),
|
||||
None,
|
||||
)
|
||||
if close_idx is None:
|
||||
line_idx = body_start
|
||||
continue
|
||||
body = lines[body_start:close_idx]
|
||||
non_blank = [b for b in body if b.strip()]
|
||||
body_indent = (
|
||||
min(len(b) - len(b.lstrip(" ")) for b in non_blank)
|
||||
if non_blank
|
||||
else len(open_match.group("indent"))
|
||||
)
|
||||
blocks.append(
|
||||
MdBashBlock(
|
||||
open_line_idx=line_idx,
|
||||
body_start=body_start,
|
||||
body_end=close_idx,
|
||||
body_indent=body_indent,
|
||||
)
|
||||
)
|
||||
line_idx = close_idx + 1
|
||||
return blocks
|
||||
|
||||
|
||||
def dedent(lines: list[str], n: int) -> list[str]:
|
||||
pad = " " * n
|
||||
return [
|
||||
(
|
||||
""
|
||||
if line.strip() == ""
|
||||
else (line[n:] if line.startswith(pad) else line.lstrip(" "))
|
||||
)
|
||||
for line in lines
|
||||
]
|
||||
|
||||
|
||||
def reindent(lines: list[str], n: int) -> list[str]:
|
||||
pad = " " * n
|
||||
return [pad + line if line else "" for line in lines]
|
||||
|
||||
|
||||
_SHFMT_ERR_RE = re.compile(r"\.sh:\d+:\d+:\s")
|
||||
_GHA_EXPR_RE = re.compile(r"\$\{\{.*?\}\}", re.DOTALL)
|
||||
_GHA_PLACEHOLDER_RE = re.compile(r"__GHA_EXPR_(\d+)__")
|
||||
|
||||
|
||||
def _encode_gha_exprs(text: str) -> tuple[str, list[str]]:
|
||||
"""Replace `${{ ... }}` expressions with bash-safe placeholder identifiers."""
|
||||
exprs: list[str] = []
|
||||
|
||||
def repl(match: re.Match[str]) -> str:
|
||||
exprs.append(match.group(0))
|
||||
return f"__GHA_EXPR_{len(exprs) - 1}__"
|
||||
|
||||
return _GHA_EXPR_RE.sub(repl, text), exprs
|
||||
|
||||
|
||||
def _decode_gha_exprs(text: str, exprs: list[str]) -> str:
|
||||
"""Restore `${{ ... }}` expressions from placeholder identifiers."""
|
||||
return _GHA_PLACEHOLDER_RE.sub(lambda m: exprs[int(m.group(1))], text)
|
||||
|
||||
|
||||
def shfmt_via_hook(tmp_path: Path) -> tuple[bool, str]:
|
||||
# `${{ ... }}` is not valid shell, so swap it for a placeholder identifier
|
||||
# that shfmt can parse, then restore it after formatting.
|
||||
encoded, exprs = _encode_gha_exprs(tmp_path.read_text())
|
||||
if exprs:
|
||||
tmp_path.write_text(encoded)
|
||||
res = subprocess.run(
|
||||
[_HOOK_RUNNER, "run", "shfmt", "--files", str(tmp_path)],
|
||||
cwd=REPO,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
output = res.stdout + res.stderr
|
||||
# shfmt emits parse errors as "<path>:<line>:<col>: <message>".
|
||||
parse_err = bool(_SHFMT_ERR_RE.search(output))
|
||||
# A non-zero exit that is neither a parse error nor pre-commit's "I had
|
||||
# to modify files" signal means the hook itself failed to run (missing
|
||||
# binary, install failure, bad config, ...). Surface that loudly rather
|
||||
# than silently treating it as a no-op.
|
||||
if (
|
||||
res.returncode != 0
|
||||
and not parse_err
|
||||
and "files were modified by this hook" not in output
|
||||
):
|
||||
sys.exit(
|
||||
f"error: `{_HOOK_RUNNER} run shfmt` failed with exit {res.returncode}:\n{output}"
|
||||
)
|
||||
if exprs and not parse_err:
|
||||
tmp_path.write_text(_decode_gha_exprs(tmp_path.read_text(), exprs))
|
||||
return not parse_err, output
|
||||
|
||||
|
||||
def _skip(path: Path, where: int, kind: str, output: str) -> None:
|
||||
print(
|
||||
f" shfmt could not parse {kind} at {path}:{where + 1} — skipped",
|
||||
file=sys.stderr,
|
||||
)
|
||||
print(f" {output.strip()}", file=sys.stderr)
|
||||
|
||||
|
||||
def process_yaml_file(path: Path, tmp_path: Path) -> int:
|
||||
text = path.read_text()
|
||||
had_nl = text.endswith("\n")
|
||||
lines = text.split("\n")
|
||||
if had_nl:
|
||||
lines = lines[:-1]
|
||||
items = find_run_blocks(lines)
|
||||
if not items:
|
||||
return 0
|
||||
changed = 0
|
||||
# Process in reverse so earlier indices remain valid as we splice.
|
||||
for item in reversed(items):
|
||||
if isinstance(item, BlockRun):
|
||||
body = lines[item.body_start : item.body_end]
|
||||
tmp_path.write_text("\n".join(dedent(body, item.body_indent)) + "\n")
|
||||
ok, output = shfmt_via_hook(tmp_path)
|
||||
if not ok:
|
||||
_skip(path, item.body_start, "block", output)
|
||||
continue
|
||||
formatted = tmp_path.read_text().rstrip("\n")
|
||||
new_body = reindent(formatted.split("\n"), item.body_indent)
|
||||
if new_body != body:
|
||||
lines[item.body_start : item.body_end] = new_body
|
||||
changed += 1
|
||||
else:
|
||||
tmp_path.write_text(item.value + "\n")
|
||||
ok, output = shfmt_via_hook(tmp_path)
|
||||
if not ok:
|
||||
_skip(path, item.line_idx, "inline run", output)
|
||||
continue
|
||||
formatted = tmp_path.read_text().rstrip("\n")
|
||||
if formatted == item.value:
|
||||
continue
|
||||
formatted_lines = formatted.split("\n")
|
||||
if len(formatted_lines) == 1:
|
||||
lines[item.line_idx] = f"{item.prefix}run: {formatted}"
|
||||
else:
|
||||
body_indent = len(item.prefix) + 2
|
||||
lines[item.line_idx : item.line_idx + 1] = [
|
||||
f"{item.prefix}run: |",
|
||||
*reindent(formatted_lines, body_indent),
|
||||
]
|
||||
changed += 1
|
||||
new_text = "\n".join(lines) + ("\n" if had_nl else "")
|
||||
if new_text != text:
|
||||
path.write_text(new_text)
|
||||
return changed
|
||||
|
||||
|
||||
def process_md_file(path: Path, tmp_path: Path) -> int:
|
||||
text = path.read_text()
|
||||
had_nl = text.endswith("\n")
|
||||
lines = text.split("\n")
|
||||
if had_nl:
|
||||
lines = lines[:-1]
|
||||
blocks = find_md_bash_blocks(lines)
|
||||
if not blocks:
|
||||
return 0
|
||||
changed = 0
|
||||
for block in reversed(blocks):
|
||||
body = lines[block.body_start : block.body_end]
|
||||
tmp_path.write_text("\n".join(dedent(body, block.body_indent)) + "\n")
|
||||
ok, output = shfmt_via_hook(tmp_path)
|
||||
if not ok:
|
||||
_skip(path, block.open_line_idx, "```bash block", output)
|
||||
continue
|
||||
formatted = tmp_path.read_text().rstrip("\n")
|
||||
formatted_lines = formatted.split("\n") if formatted else []
|
||||
new_body = reindent(formatted_lines, block.body_indent)
|
||||
if new_body != body:
|
||||
lines[block.body_start : block.body_end] = new_body
|
||||
changed += 1
|
||||
new_text = "\n".join(lines) + ("\n" if had_nl else "")
|
||||
if new_text != text:
|
||||
path.write_text(new_text)
|
||||
return changed
|
||||
|
||||
|
||||
def process_file(path: Path, tmp_path: Path) -> int:
|
||||
if path.suffix in (".yml", ".yaml"):
|
||||
return process_yaml_file(path, tmp_path)
|
||||
if path.suffix == ".md":
|
||||
return process_md_file(path, tmp_path)
|
||||
return 0
|
||||
|
||||
|
||||
def gather_files(argv: list[str]) -> list[Path]:
|
||||
"""Return YAML workflow/action files and markdown files that we should
|
||||
process — either the paths in `argv` or, when `argv` is empty, every
|
||||
such file in the repo (skipping `external/`)."""
|
||||
if argv:
|
||||
candidates: list[Path] = [
|
||||
(REPO / a).resolve() if not Path(a).is_absolute() else Path(a) for a in argv
|
||||
]
|
||||
else:
|
||||
gh = REPO / ".github"
|
||||
candidates = [
|
||||
*gh.rglob("*.yml"),
|
||||
*gh.rglob("*.yaml"),
|
||||
*(
|
||||
p
|
||||
for p in REPO.rglob("*.md")
|
||||
if "external" not in p.relative_to(REPO).parts
|
||||
),
|
||||
]
|
||||
return sorted(
|
||||
p
|
||||
for p in candidates
|
||||
if p.exists()
|
||||
and (
|
||||
(p.suffix in (".yml", ".yaml") and ".github" in p.parts)
|
||||
or p.suffix == ".md"
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def main(argv: list[str]) -> int:
|
||||
files = gather_files(argv)
|
||||
if not files:
|
||||
return 0
|
||||
with tempfile.TemporaryDirectory(prefix="format-inline-bash-") as tmpdir:
|
||||
tmp_path = Path(tmpdir) / "shfmt.sh"
|
||||
total = 0
|
||||
for f in files:
|
||||
n = process_file(f, tmp_path)
|
||||
if n:
|
||||
print(f"{f.relative_to(REPO)}: reformatted {n} block(s)")
|
||||
total += n
|
||||
return 1 if total else 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main(sys.argv[1:]))
|
||||
0
.github/scripts/levelization/generate.py
vendored
Normal file → Executable file
0
.github/scripts/levelization/generate.py
vendored
Normal file → Executable file
2
.github/scripts/rename/binary.sh
vendored
2
.github/scripts/rename/binary.sh
vendored
@@ -6,7 +6,7 @@ set -e
|
||||
# On MacOS, ensure that GNU sed is installed and available as `gsed`.
|
||||
SED_COMMAND=sed
|
||||
if [[ "${OSTYPE}" == 'darwin'* ]]; then
|
||||
if ! command -v gsed &> /dev/null; then
|
||||
if ! command -v gsed &>/dev/null; then
|
||||
echo "Error: gsed is not installed. Please install it using 'brew install gnu-sed'."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
16
.github/scripts/rename/cmake.sh
vendored
16
.github/scripts/rename/cmake.sh
vendored
@@ -8,12 +8,12 @@ set -e
|
||||
SED_COMMAND=sed
|
||||
HEAD_COMMAND=head
|
||||
if [[ "${OSTYPE}" == 'darwin'* ]]; then
|
||||
if ! command -v gsed &> /dev/null; then
|
||||
if ! command -v gsed &>/dev/null; then
|
||||
echo "Error: gsed is not installed. Please install it using 'brew install gnu-sed'."
|
||||
exit 1
|
||||
fi
|
||||
SED_COMMAND=gsed
|
||||
if ! command -v ghead &> /dev/null; then
|
||||
if ! command -v ghead &>/dev/null; then
|
||||
echo "Error: ghead is not installed. Please install it using 'brew install coreutils'."
|
||||
exit 1
|
||||
fi
|
||||
@@ -43,9 +43,6 @@ pushd "${DIRECTORY}"
|
||||
# Rename the files.
|
||||
find cmake -type f -name 'Rippled*.cmake' -exec bash -c 'mv "${1}" "${1/Rippled/Xrpl}"' - {} \;
|
||||
find cmake -type f -name 'Ripple*.cmake' -exec bash -c 'mv "${1}" "${1/Ripple/Xrpl}"' - {} \;
|
||||
if [ -e cmake/xrpl_add_test.cmake ]; then
|
||||
mv cmake/xrpl_add_test.cmake cmake/XrplAddTest.cmake
|
||||
fi
|
||||
if [ -e include/xrpl/proto/ripple.proto ]; then
|
||||
mv include/xrpl/proto/ripple.proto include/xrpl/proto/xrpl.proto
|
||||
fi
|
||||
@@ -60,7 +57,6 @@ find cmake -type f -name '*.cmake' | while read -r FILE; do
|
||||
done
|
||||
${SED_COMMAND} -i -E 's/Rippled?/Xrpl/g' CMakeLists.txt
|
||||
${SED_COMMAND} -i 's/ripple/xrpl/g' CMakeLists.txt
|
||||
${SED_COMMAND} -i 's/include(xrpl_add_test)/include(XrplAddTest)/' src/tests/libxrpl/CMakeLists.txt
|
||||
${SED_COMMAND} -i 's/ripple.pb.h/xrpl.pb.h/' include/xrpl/protocol/messages.h
|
||||
${SED_COMMAND} -i 's/ripple.pb.h/xrpl.pb.h/' BUILD.md
|
||||
${SED_COMMAND} -i 's/ripple.pb.h/xrpl.pb.h/' BUILD.md
|
||||
@@ -74,10 +70,10 @@ if grep -q '"xrpld"' cmake/XrplCore.cmake; then
|
||||
# The script has been rerun, so just restore the name of the binary.
|
||||
${SED_COMMAND} -i 's/"xrpld"/"rippled"/' cmake/XrplCore.cmake
|
||||
elif ! grep -q '"rippled"' cmake/XrplCore.cmake; then
|
||||
${HEAD_COMMAND} -n -1 cmake/XrplCore.cmake > cmake.tmp
|
||||
echo ' # For the time being, we will keep the name of the binary as it was.' >> cmake.tmp
|
||||
echo ' set_target_properties(xrpld PROPERTIES OUTPUT_NAME "rippled")' >> cmake.tmp
|
||||
tail -1 cmake/XrplCore.cmake >> cmake.tmp
|
||||
${HEAD_COMMAND} -n -1 cmake/XrplCore.cmake >cmake.tmp
|
||||
echo ' # For the time being, we will keep the name of the binary as it was.' >>cmake.tmp
|
||||
echo ' set_target_properties(xrpld PROPERTIES OUTPUT_NAME "rippled")' >>cmake.tmp
|
||||
tail -1 cmake/XrplCore.cmake >>cmake.tmp
|
||||
mv cmake.tmp cmake/XrplCore.cmake
|
||||
fi
|
||||
|
||||
|
||||
2
.github/scripts/rename/config.sh
vendored
2
.github/scripts/rename/config.sh
vendored
@@ -6,7 +6,7 @@ set -e
|
||||
# On MacOS, ensure that GNU sed is installed and available as `gsed`.
|
||||
SED_COMMAND=sed
|
||||
if [[ "${OSTYPE}" == 'darwin'* ]]; then
|
||||
if ! command -v gsed &> /dev/null; then
|
||||
if ! command -v gsed &>/dev/null; then
|
||||
echo "Error: gsed is not installed. Please install it using 'brew install gnu-sed'."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
24
.github/scripts/rename/copyright.sh
vendored
24
.github/scripts/rename/copyright.sh
vendored
@@ -6,7 +6,7 @@ set -e
|
||||
# On MacOS, ensure that GNU sed is installed and available as `gsed`.
|
||||
SED_COMMAND=sed
|
||||
if [[ "${OSTYPE}" == 'darwin'* ]]; then
|
||||
if ! command -v gsed &> /dev/null; then
|
||||
if ! command -v gsed &>/dev/null; then
|
||||
echo "Error: gsed is not installed. Please install it using 'brew install gnu-sed'."
|
||||
exit 1
|
||||
fi
|
||||
@@ -62,37 +62,37 @@ done
|
||||
# restoring the verbiage that is already present in LICENSE.md. Ensure that if
|
||||
# the script is run multiple times, duplicate notices are not added.
|
||||
if ! grep -q 'Raw Material Software' include/xrpl/beast/core/CurrentThreadName.h; then
|
||||
echo -e "// Portions of this file are from JUCE (http://www.juce.com).\n// Copyright (c) 2013 - Raw Material Software Ltd.\n// Please visit http://www.juce.com\n\n$(cat include/xrpl/beast/core/CurrentThreadName.h)" > include/xrpl/beast/core/CurrentThreadName.h
|
||||
echo -e "// Portions of this file are from JUCE (http://www.juce.com).\n// Copyright (c) 2013 - Raw Material Software Ltd.\n// Please visit http://www.juce.com\n\n$(cat include/xrpl/beast/core/CurrentThreadName.h)" >include/xrpl/beast/core/CurrentThreadName.h
|
||||
fi
|
||||
if ! grep -q 'Dev Null' src/test/app/NetworkID_test.cpp; then
|
||||
echo -e "// Copyright (c) 2020 Dev Null Productions\n\n$(cat src/test/app/NetworkID_test.cpp)" > src/test/app/NetworkID_test.cpp
|
||||
echo -e "// Copyright (c) 2020 Dev Null Productions\n\n$(cat src/test/app/NetworkID_test.cpp)" >src/test/app/NetworkID_test.cpp
|
||||
fi
|
||||
if ! grep -q 'Dev Null' src/test/app/tx/apply_test.cpp; then
|
||||
echo -e "// Copyright (c) 2020 Dev Null Productions\n\n$(cat src/test/app/tx/apply_test.cpp)" > src/test/app/tx/apply_test.cpp
|
||||
echo -e "// Copyright (c) 2020 Dev Null Productions\n\n$(cat src/test/app/tx/apply_test.cpp)" >src/test/app/tx/apply_test.cpp
|
||||
fi
|
||||
if ! grep -q 'Dev Null' src/test/rpc/ManifestRPC_test.cpp; then
|
||||
echo -e "// Copyright (c) 2020 Dev Null Productions\n\n$(cat src/test/rpc/ManifestRPC_test.cpp)" > src/test/rpc/ManifestRPC_test.cpp
|
||||
echo -e "// Copyright (c) 2020 Dev Null Productions\n\n$(cat src/test/rpc/ManifestRPC_test.cpp)" >src/test/rpc/ManifestRPC_test.cpp
|
||||
fi
|
||||
if ! grep -q 'Dev Null' src/test/rpc/ValidatorInfo_test.cpp; then
|
||||
echo -e "// Copyright (c) 2020 Dev Null Productions\n\n$(cat src/test/rpc/ValidatorInfo_test.cpp)" > src/test/rpc/ValidatorInfo_test.cpp
|
||||
echo -e "// Copyright (c) 2020 Dev Null Productions\n\n$(cat src/test/rpc/ValidatorInfo_test.cpp)" >src/test/rpc/ValidatorInfo_test.cpp
|
||||
fi
|
||||
if ! grep -q 'Dev Null' src/xrpld/rpc/handlers/server_info/Manifest.cpp; then
|
||||
echo -e "// Copyright (c) 2019 Dev Null Productions\n\n$(cat src/xrpld/rpc/handlers/server_info/Manifest.cpp)" > src/xrpld/rpc/handlers/server_info/Manifest.cpp
|
||||
echo -e "// Copyright (c) 2019 Dev Null Productions\n\n$(cat src/xrpld/rpc/handlers/server_info/Manifest.cpp)" >src/xrpld/rpc/handlers/server_info/Manifest.cpp
|
||||
fi
|
||||
if ! grep -q 'Dev Null' src/xrpld/rpc/handlers/admin/status/ValidatorInfo.cpp; then
|
||||
echo -e "// Copyright (c) 2019 Dev Null Productions\n\n$(cat src/xrpld/rpc/handlers/admin/status/ValidatorInfo.cpp)" > src/xrpld/rpc/handlers/admin/status/ValidatorInfo.cpp
|
||||
echo -e "// Copyright (c) 2019 Dev Null Productions\n\n$(cat src/xrpld/rpc/handlers/admin/status/ValidatorInfo.cpp)" >src/xrpld/rpc/handlers/admin/status/ValidatorInfo.cpp
|
||||
fi
|
||||
if ! grep -q 'Bougalis' include/xrpl/basics/SlabAllocator.h; then
|
||||
echo -e "// Copyright (c) 2022, Nikolaos D. Bougalis <nikb@bougalis.net>\n\n$(cat include/xrpl/basics/SlabAllocator.h)" > include/xrpl/basics/SlabAllocator.h # cspell: ignore Nikolaos Bougalis nikb
|
||||
echo -e "// Copyright (c) 2022, Nikolaos D. Bougalis <nikb@bougalis.net>\n\n$(cat include/xrpl/basics/SlabAllocator.h)" >include/xrpl/basics/SlabAllocator.h # cspell: ignore Nikolaos Bougalis nikb
|
||||
fi
|
||||
if ! grep -q 'Bougalis' include/xrpl/basics/spinlock.h; then
|
||||
echo -e "// Copyright (c) 2022, Nikolaos D. Bougalis <nikb@bougalis.net>\n\n$(cat include/xrpl/basics/spinlock.h)" > include/xrpl/basics/spinlock.h # cspell: ignore Nikolaos Bougalis nikb
|
||||
echo -e "// Copyright (c) 2022, Nikolaos D. Bougalis <nikb@bougalis.net>\n\n$(cat include/xrpl/basics/spinlock.h)" >include/xrpl/basics/spinlock.h # cspell: ignore Nikolaos Bougalis nikb
|
||||
fi
|
||||
if ! grep -q 'Bougalis' include/xrpl/basics/tagged_integer.h; then
|
||||
echo -e "// Copyright (c) 2014, Nikolaos D. Bougalis <nikb@bougalis.net>\n\n$(cat include/xrpl/basics/tagged_integer.h)" > include/xrpl/basics/tagged_integer.h # cspell: ignore Nikolaos Bougalis nikb
|
||||
echo -e "// Copyright (c) 2014, Nikolaos D. Bougalis <nikb@bougalis.net>\n\n$(cat include/xrpl/basics/tagged_integer.h)" >include/xrpl/basics/tagged_integer.h # cspell: ignore Nikolaos Bougalis nikb
|
||||
fi
|
||||
if ! grep -q 'Ritchford' include/xrpl/beast/utility/Zero.h; then
|
||||
echo -e "// Copyright (c) 2014, Tom Ritchford <tom@swirly.com>\n\n$(cat include/xrpl/beast/utility/Zero.h)" > include/xrpl/beast/utility/Zero.h # cspell: ignore Ritchford
|
||||
echo -e "// Copyright (c) 2014, Tom Ritchford <tom@swirly.com>\n\n$(cat include/xrpl/beast/utility/Zero.h)" >include/xrpl/beast/utility/Zero.h # cspell: ignore Ritchford
|
||||
fi
|
||||
|
||||
# Restore newlines and tabs in string literals in the affected file.
|
||||
|
||||
2
.github/scripts/rename/definitions.sh
vendored
2
.github/scripts/rename/definitions.sh
vendored
@@ -6,7 +6,7 @@ set -e
|
||||
# On MacOS, ensure that GNU sed is installed and available as `gsed`.
|
||||
SED_COMMAND=sed
|
||||
if [[ "${OSTYPE}" == 'darwin'* ]]; then
|
||||
if ! command -v gsed &> /dev/null; then
|
||||
if ! command -v gsed &>/dev/null; then
|
||||
echo "Error: gsed is not installed. Please install it using 'brew install gnu-sed'."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
2
.github/scripts/rename/docs.sh
vendored
2
.github/scripts/rename/docs.sh
vendored
@@ -6,7 +6,7 @@ set -e
|
||||
# On MacOS, ensure that GNU sed is installed and available as `gsed`.
|
||||
SED_COMMAND=sed
|
||||
if [[ "${OSTYPE}" == 'darwin'* ]]; then
|
||||
if ! command -v gsed &> /dev/null; then
|
||||
if ! command -v gsed &>/dev/null; then
|
||||
echo "Error: gsed is not installed. Please install it using 'brew install gnu-sed'."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
2
.github/scripts/rename/namespace.sh
vendored
2
.github/scripts/rename/namespace.sh
vendored
@@ -6,7 +6,7 @@ set -e
|
||||
# On MacOS, ensure that GNU sed is installed and available as `gsed`.
|
||||
SED_COMMAND=sed
|
||||
if [[ "${OSTYPE}" == 'darwin'* ]]; then
|
||||
if ! command -v gsed &> /dev/null; then
|
||||
if ! command -v gsed &>/dev/null; then
|
||||
echo "Error: gsed is not installed. Please install it using 'brew install gnu-sed'."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
579
.github/scripts/strategy-matrix/generate.py
vendored
579
.github/scripts/strategy-matrix/generate.py
vendored
@@ -1,384 +1,281 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import dataclasses
|
||||
import itertools
|
||||
import json
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
|
||||
THIS_DIR = Path(__file__).parent.resolve()
|
||||
|
||||
_BASE_CMAKE_ARGS = ["-Dtests=ON", "-Dwerr=ON", "-Dxrpld=ON", "-Dwextra=ON"]
|
||||
|
||||
@dataclass
|
||||
class Config:
|
||||
architecture: list[dict]
|
||||
os: list[dict]
|
||||
# Maps sanitizer names (as used in cmake) to short config-name suffixes.
|
||||
_SANITIZER_SUFFIX: dict[str, str] = {
|
||||
"address": "asan",
|
||||
"undefinedbehavior": "ubsan",
|
||||
"thread": "tsan",
|
||||
}
|
||||
|
||||
|
||||
def get_cmake_args(build_type: str, extra_args: str) -> str:
|
||||
"""Get the full list of CMake arguments for a config."""
|
||||
args = _BASE_CMAKE_ARGS.copy()
|
||||
if build_type == "Release":
|
||||
args.append("-Dassert=ON")
|
||||
if extra_args:
|
||||
args.extend(extra_args.split())
|
||||
return " ".join(args)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Input types — shapes of the JSON config files
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class LinuxConfig:
|
||||
"""One entry in linux.json's 'configs' or 'package_configs' arrays."""
|
||||
|
||||
compiler: list[str]
|
||||
build_type: list[str]
|
||||
cmake_args: list[str]
|
||||
arch: list[str]
|
||||
sanitizers: list[str] = dataclasses.field(default_factory=list)
|
||||
suffix: str = ""
|
||||
extra_cmake_args: str = ""
|
||||
image: str = "" # only used by package_configs entries
|
||||
|
||||
|
||||
"""
|
||||
Generate a strategy matrix for GitHub Actions CI.
|
||||
@dataclasses.dataclass
|
||||
class LinuxFile:
|
||||
"""Shape of linux.json."""
|
||||
|
||||
On each PR commit we will build a selection of Debian, RHEL, Ubuntu, MacOS, and
|
||||
Windows configurations, while upon merge into the develop or release branches,
|
||||
we will build all configurations, and test most of them.
|
||||
image_tag: str
|
||||
configs: dict[str, list[LinuxConfig]] # distro → configs
|
||||
package_configs: dict[str, list[LinuxConfig]] # distro → packaging configs
|
||||
|
||||
We will further set additional CMake arguments as follows:
|
||||
- All builds will have the `tests`, `werr`, and `xrpld` options.
|
||||
- All builds will have the `wextra` option except for GCC 12 and Clang 16.
|
||||
- All release builds will have the `assert` option.
|
||||
- Certain Debian Bookworm configurations will change the reference fee, enable
|
||||
codecov, and enable voidstar in PRs.
|
||||
"""
|
||||
@classmethod
|
||||
def load(cls, path: Path) -> "LinuxFile":
|
||||
data = json.loads(path.read_text())
|
||||
|
||||
def parse(section: dict) -> dict[str, list[LinuxConfig]]:
|
||||
return {
|
||||
distro: [LinuxConfig(**c) for c in cfgs]
|
||||
for distro, cfgs in section.items()
|
||||
}
|
||||
|
||||
return cls(
|
||||
image_tag=data["image_tag"],
|
||||
configs=parse(data["configs"]),
|
||||
package_configs=parse(data.get("package_configs", {})),
|
||||
)
|
||||
|
||||
|
||||
def build_config_name(os_entry: dict[str, str], platform: str, build_type: str) -> str:
|
||||
parts = [os_entry["distro_name"]]
|
||||
for key in ("distro_version", "compiler_name", "compiler_version"):
|
||||
if value := os_entry[key]:
|
||||
parts.append(value)
|
||||
parts.append("arm64" if "arm64" in platform else "amd64")
|
||||
parts.append(build_type.lower())
|
||||
return "-".join(parts)
|
||||
@dataclasses.dataclass
|
||||
class PlatformConfig:
|
||||
"""One entry in macos.json's or windows.json's 'configs' array."""
|
||||
|
||||
build_type: list[str]
|
||||
build_only: bool = False # if true, skip tests (e.g. macos/Windows Debug)
|
||||
extra_cmake_args: str = ""
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
if isinstance(self.build_type, str):
|
||||
self.build_type = [self.build_type]
|
||||
|
||||
|
||||
def generate_packaging_matrix(config: Config) -> list[dict]:
|
||||
"""Emit one entry per os entry with `package: true`. Architecture is
|
||||
hardcoded to linux/amd64 here (and the runner is hardcoded at the
|
||||
workflow level) until arm64 packaging is ready.
|
||||
@dataclasses.dataclass
|
||||
class PlatformFile:
|
||||
"""Shape of macos.json and windows.json."""
|
||||
|
||||
platform: str # e.g. "macos/arm64" or "windows/amd64"
|
||||
runner: list[str] # GitHub Actions runner labels
|
||||
configs: list[PlatformConfig]
|
||||
|
||||
@classmethod
|
||||
def load(cls, path: Path) -> "PlatformFile":
|
||||
data = json.loads(path.read_text())
|
||||
return cls(
|
||||
platform=data["platform"],
|
||||
runner=data["runner"],
|
||||
configs=[PlatformConfig(**c) for c in data["configs"]],
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Output types — shapes of the generated GitHub Actions matrix entries
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class Architecture:
|
||||
platform: str
|
||||
runner: list[str]
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class MatrixEntry:
|
||||
"""One entry in the generated build/test strategy matrix."""
|
||||
|
||||
config_name: str
|
||||
cmake_args: str
|
||||
cmake_target: str
|
||||
build_only: bool
|
||||
build_type: str
|
||||
architecture: Architecture
|
||||
sanitizers: str
|
||||
image: str = "" # container image; empty for macOS/Windows (runs natively)
|
||||
compiler: str = "" # compiler name ("gcc" or "clang"); empty for macOS/Windows
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class PackagingEntry:
|
||||
"""One entry in the generated packaging strategy matrix."""
|
||||
|
||||
artifact_name: str
|
||||
image: str
|
||||
distro: str # e.g. "debian" or "rhel"; drives package-format-specific steps
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Matrix expansion
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
_ARCHS: dict[str, Architecture] = {
|
||||
"amd64": Architecture(
|
||||
platform="linux/amd64", runner=["self-hosted", "Linux", "X64", "heavy"]
|
||||
),
|
||||
"arm64": Architecture(
|
||||
platform="linux/arm64",
|
||||
runner=["self-hosted", "Linux", "ARM64", "heavy-arm64"],
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
def expand_linux_matrix(linux: LinuxFile) -> list[MatrixEntry]:
|
||||
"""Expand a LinuxFile into a flat list of matrix entries.
|
||||
|
||||
Each config entry is expanded over the cross-product of its
|
||||
compiler, build_type, sanitizers, and architecture lists.
|
||||
"""
|
||||
return [
|
||||
{
|
||||
"artifact_name": f"xrpld-{build_config_name(os, 'linux/amd64', 'Release')}",
|
||||
"os": os,
|
||||
}
|
||||
for os in config.os
|
||||
if os.get("package", False)
|
||||
]
|
||||
entries: list[MatrixEntry] = []
|
||||
|
||||
for distro, configs in linux.configs.items():
|
||||
for cfg in configs:
|
||||
# An empty sanitizers list means "one entry with no sanitizer".
|
||||
effective_sanitizers = cfg.sanitizers or [""]
|
||||
effective_archs = {arch: _ARCHS[arch] for arch in cfg.arch}
|
||||
|
||||
def generate_strategy_matrix(all: bool, config: Config) -> list[dict]:
|
||||
configurations = []
|
||||
for architecture, os, build_type, cmake_args in itertools.product(
|
||||
config.architecture, config.os, config.build_type, config.cmake_args
|
||||
):
|
||||
# The default CMake target is 'all' for Linux and MacOS and 'install'
|
||||
# for Windows, but it can get overridden for certain configurations.
|
||||
cmake_target = "install" if os["distro_name"] == "windows" else "all"
|
||||
|
||||
# We build and test all configurations by default, except for Windows in
|
||||
# Debug, because it is too slow, as well as when code coverage is
|
||||
# enabled as that mode already runs the tests.
|
||||
build_only = False
|
||||
if os["distro_name"] == "windows" and build_type == "Debug":
|
||||
build_only = True
|
||||
|
||||
# Only generate a subset of configurations in PRs.
|
||||
if not all:
|
||||
# Debian:
|
||||
# - Bookworm using GCC 13: Debug on linux/amd64, set the reference
|
||||
# fee to 500 and enable code coverage (which will be done below).
|
||||
# - Bookworm using GCC 15: Debug on linux/amd64, enable Address and
|
||||
# UB sanitizers (which will be done below).
|
||||
# - Bookworm using Clang 16: Debug on linux/amd64, enable voidstar.
|
||||
# - Bookworm using Clang 17: Release on linux/amd64, set the
|
||||
# reference fee to 1000.
|
||||
# - Bookworm using Clang 20: Debug on linux/amd64, enable Address
|
||||
# and UB sanitizers (which will be done below).
|
||||
if os["distro_name"] == "debian":
|
||||
skip = True
|
||||
if os["distro_version"] == "bookworm":
|
||||
if (
|
||||
f"{os['compiler_name']}-{os['compiler_version']}" == "gcc-13"
|
||||
and build_type == "Debug"
|
||||
and architecture["platform"] == "linux/amd64"
|
||||
):
|
||||
cmake_args = f"-DUNIT_TEST_REFERENCE_FEE=500 {cmake_args}"
|
||||
skip = False
|
||||
if (
|
||||
f"{os['compiler_name']}-{os['compiler_version']}" == "gcc-15"
|
||||
and build_type == "Release"
|
||||
and architecture["platform"] == "linux/amd64"
|
||||
):
|
||||
skip = False
|
||||
if (
|
||||
f"{os['compiler_name']}-{os['compiler_version']}" == "clang-16"
|
||||
and build_type == "Debug"
|
||||
and architecture["platform"] == "linux/amd64"
|
||||
):
|
||||
cmake_args = f"-Dvoidstar=ON {cmake_args}"
|
||||
skip = False
|
||||
if (
|
||||
f"{os['compiler_name']}-{os['compiler_version']}" == "clang-17"
|
||||
and build_type == "Release"
|
||||
and architecture["platform"] == "linux/amd64"
|
||||
):
|
||||
cmake_args = f"-DUNIT_TEST_REFERENCE_FEE=1000 {cmake_args}"
|
||||
skip = False
|
||||
elif os["distro_version"] == "trixie":
|
||||
if (
|
||||
f"{os['compiler_name']}-{os['compiler_version']}" == "clang-22"
|
||||
and build_type == "Debug"
|
||||
and architecture["platform"] == "linux/amd64"
|
||||
):
|
||||
skip = False
|
||||
if skip:
|
||||
continue
|
||||
|
||||
# RHEL:
|
||||
# - 9 using GCC 12: Debug and Release on linux/amd64
|
||||
# (Release is required for RPM packaging).
|
||||
# - 10 using Clang: Release on linux/amd64.
|
||||
if os["distro_name"] == "rhel":
|
||||
skip = True
|
||||
if os["distro_version"] == "9":
|
||||
if (
|
||||
f"{os['compiler_name']}-{os['compiler_version']}" == "gcc-12"
|
||||
and build_type in ["Debug", "Release"]
|
||||
and architecture["platform"] == "linux/amd64"
|
||||
):
|
||||
skip = False
|
||||
elif os["distro_version"] == "10":
|
||||
if (
|
||||
f"{os['compiler_name']}-{os['compiler_version']}" == "clang-any"
|
||||
and build_type == "Release"
|
||||
and architecture["platform"] == "linux/amd64"
|
||||
):
|
||||
skip = False
|
||||
if skip:
|
||||
continue
|
||||
|
||||
# Ubuntu:
|
||||
# - Jammy using GCC 12: Debug on linux/arm64, Release on
|
||||
# linux/amd64 (Release is required for DEB packaging).
|
||||
# - Noble using GCC 14: Release on linux/amd64.
|
||||
# - Noble using Clang 18: Debug on linux/amd64.
|
||||
# - Noble using Clang 19: Release on linux/arm64.
|
||||
if os["distro_name"] == "ubuntu":
|
||||
skip = True
|
||||
if os["distro_version"] == "jammy":
|
||||
if (
|
||||
f"{os['compiler_name']}-{os['compiler_version']}" == "gcc-12"
|
||||
and build_type == "Debug"
|
||||
and architecture["platform"] == "linux/arm64"
|
||||
):
|
||||
skip = False
|
||||
if (
|
||||
f"{os['compiler_name']}-{os['compiler_version']}" == "gcc-12"
|
||||
and build_type == "Release"
|
||||
and architecture["platform"] == "linux/amd64"
|
||||
):
|
||||
skip = False
|
||||
elif os["distro_version"] == "noble":
|
||||
if (
|
||||
f"{os['compiler_name']}-{os['compiler_version']}" == "gcc-14"
|
||||
and build_type == "Release"
|
||||
and architecture["platform"] == "linux/amd64"
|
||||
):
|
||||
skip = False
|
||||
if (
|
||||
f"{os['compiler_name']}-{os['compiler_version']}" == "clang-18"
|
||||
and build_type == "Debug"
|
||||
and architecture["platform"] == "linux/amd64"
|
||||
):
|
||||
skip = False
|
||||
if (
|
||||
f"{os['compiler_name']}-{os['compiler_version']}" == "clang-19"
|
||||
and build_type == "Release"
|
||||
and architecture["platform"] == "linux/arm64"
|
||||
):
|
||||
skip = False
|
||||
if skip:
|
||||
continue
|
||||
|
||||
# MacOS:
|
||||
# - Debug on macos/arm64.
|
||||
if os["distro_name"] == "macos" and not (
|
||||
build_type == "Debug" and architecture["platform"] == "macos/arm64"
|
||||
for compiler, build_type, sanitizer, (arch, arch_info) in itertools.product(
|
||||
cfg.compiler,
|
||||
cfg.build_type,
|
||||
effective_sanitizers,
|
||||
effective_archs.items(),
|
||||
):
|
||||
continue
|
||||
name = f"{distro}-{compiler}-{build_type.lower()}-{arch}"
|
||||
suffix_parts = [
|
||||
s for s in [cfg.suffix, _SANITIZER_SUFFIX.get(sanitizer, "")] if s
|
||||
]
|
||||
if suffix_parts:
|
||||
name += "-" + "-".join(suffix_parts)
|
||||
|
||||
# Windows:
|
||||
# - Release on windows/amd64.
|
||||
if os["distro_name"] == "windows" and not (
|
||||
build_type == "Release" and architecture["platform"] == "windows/amd64"
|
||||
):
|
||||
continue
|
||||
|
||||
# Additional CMake arguments.
|
||||
cmake_args = f"{cmake_args} -Dtests=ON -Dwerr=ON -Dxrpld=ON"
|
||||
if not f"{os['compiler_name']}-{os['compiler_version']}" in [
|
||||
"gcc-12",
|
||||
"clang-16",
|
||||
]:
|
||||
cmake_args = f"{cmake_args} -Dwextra=ON"
|
||||
if build_type == "Release":
|
||||
cmake_args = f"{cmake_args} -Dassert=ON"
|
||||
|
||||
# We skip all RHEL on arm64 due to a build failure that needs further
|
||||
# investigation.
|
||||
if os["distro_name"] == "rhel" and architecture["platform"] == "linux/arm64":
|
||||
continue
|
||||
|
||||
# We skip all clang 20+ on arm64 due to Boost build error.
|
||||
if (
|
||||
os["compiler_name"] == "clang"
|
||||
and os["compiler_version"].isdigit()
|
||||
and int(os["compiler_version"]) >= 20
|
||||
and architecture["platform"] == "linux/arm64"
|
||||
):
|
||||
continue
|
||||
|
||||
# Enable code coverage for Debian Bookworm using GCC 13 in Debug on
|
||||
# linux/amd64.
|
||||
if (
|
||||
f"{os['distro_name']}-{os['distro_version']}" == "debian-bookworm"
|
||||
and f"{os['compiler_name']}-{os['compiler_version']}" == "gcc-13"
|
||||
and build_type == "Debug"
|
||||
and architecture["platform"] == "linux/amd64"
|
||||
):
|
||||
cmake_args = f"{cmake_args} -Dcoverage=ON -Dcoverage_format=xml -DCODE_COVERAGE_VERBOSE=ON -DCMAKE_C_FLAGS=-O0 -DCMAKE_CXX_FLAGS=-O0"
|
||||
|
||||
# Enable unity build for Ubuntu Jammy using GCC 12 in Debug on
|
||||
# linux/amd64.
|
||||
if (
|
||||
f"{os['distro_name']}-{os['distro_version']}" == "ubuntu-jammy"
|
||||
and f"{os['compiler_name']}-{os['compiler_version']}" == "gcc-12"
|
||||
and build_type == "Debug"
|
||||
and architecture["platform"] == "linux/amd64"
|
||||
):
|
||||
cmake_args = f"{cmake_args} -Dunity=ON"
|
||||
|
||||
# Generate a unique name for the configuration, e.g. macos-arm64-debug
|
||||
# or debian-bookworm-gcc-12-amd64-release.
|
||||
config_name = build_config_name(os, architecture["platform"], build_type)
|
||||
if "-Dcoverage=ON" in cmake_args:
|
||||
config_name += "-coverage"
|
||||
if "-Dunity=ON" in cmake_args:
|
||||
config_name += "-unity"
|
||||
|
||||
# Add the configuration to the list, with the most unique fields first,
|
||||
# so that they are easier to identify in the GitHub Actions UI, as long
|
||||
# names get truncated.
|
||||
# Add Address and UB sanitizers as separate configurations for specific
|
||||
# bookworm distros. Thread sanitizer is currently disabled (see below).
|
||||
# GCC-Asan xrpld-embedded tests are failing because of https://github.com/google/sanitizers/issues/856
|
||||
if (
|
||||
os["distro_version"] == "bookworm"
|
||||
and f"{os['compiler_name']}-{os['compiler_version']}" == "gcc-15"
|
||||
) or (
|
||||
os["distro_version"] == "trixie"
|
||||
and f"{os['compiler_name']}-{os['compiler_version']}" == "clang-22"
|
||||
):
|
||||
# Add ASAN and UBSAN configurations for both gcc-15 and clang-22
|
||||
configurations.append(
|
||||
{
|
||||
"config_name": config_name + "-asan",
|
||||
"cmake_args": cmake_args,
|
||||
"cmake_target": cmake_target,
|
||||
"build_only": build_only,
|
||||
"build_type": build_type,
|
||||
"os": os,
|
||||
"architecture": architecture,
|
||||
"sanitizers": "address",
|
||||
}
|
||||
)
|
||||
configurations.append(
|
||||
{
|
||||
"config_name": config_name + "-ubsan",
|
||||
"cmake_args": cmake_args,
|
||||
"cmake_target": cmake_target,
|
||||
"build_only": build_only,
|
||||
"build_type": build_type,
|
||||
"os": os,
|
||||
"architecture": architecture,
|
||||
"sanitizers": "undefinedbehavior",
|
||||
}
|
||||
)
|
||||
# TSAN is deactivated due to seg faults with latest compilers.
|
||||
activate_tsan = False
|
||||
if activate_tsan:
|
||||
configurations.append(
|
||||
{
|
||||
"config_name": config_name + "-tsan-ubsan",
|
||||
"cmake_args": cmake_args,
|
||||
"cmake_target": cmake_target,
|
||||
"build_only": build_only,
|
||||
"build_type": build_type,
|
||||
"os": os,
|
||||
"architecture": architecture,
|
||||
"sanitizers": "thread,undefinedbehavior",
|
||||
}
|
||||
entries.append(
|
||||
MatrixEntry(
|
||||
config_name=name,
|
||||
image=f"ghcr.io/xrplf/xrpld/nix-{distro}:{linux.image_tag}",
|
||||
cmake_args=get_cmake_args(build_type, cfg.extra_cmake_args),
|
||||
cmake_target="all",
|
||||
build_only=False,
|
||||
build_type=build_type,
|
||||
architecture=arch_info,
|
||||
sanitizers=sanitizer,
|
||||
compiler=compiler,
|
||||
)
|
||||
)
|
||||
|
||||
return entries
|
||||
|
||||
|
||||
def expand_linux_packaging(linux: LinuxFile) -> list[PackagingEntry]:
|
||||
"""Generate the packaging matrix from a LinuxFile's package_configs section.
|
||||
|
||||
Packaging uses vanilla distro images (debian:bookworm, ubi9, …) instead of
|
||||
the nix-based build images, because deb/rpm tooling (debhelper, rpm-build)
|
||||
is taken from the distro's archive rather than from nixpkgs. Each config
|
||||
entry carries its own 'image'.
|
||||
"""
|
||||
entries = []
|
||||
for distro, configs in linux.package_configs.items():
|
||||
for cfg in configs:
|
||||
for compiler, build_type in itertools.product(cfg.compiler, cfg.build_type):
|
||||
entries.append(
|
||||
PackagingEntry(
|
||||
artifact_name=f"xrpld-{distro}-{compiler}-{build_type.lower()}-amd64",
|
||||
image=cfg.image,
|
||||
distro=distro,
|
||||
)
|
||||
)
|
||||
|
||||
return entries
|
||||
|
||||
|
||||
def expand_platform_matrix(pf: PlatformFile) -> list[MatrixEntry]:
|
||||
"""Expand a PlatformFile (macOS or Windows) into matrix entries."""
|
||||
platform_name, arch = pf.platform.split("/")
|
||||
is_windows = platform_name == "windows"
|
||||
|
||||
entries: list[MatrixEntry] = []
|
||||
for cfg in pf.configs:
|
||||
for build_type in cfg.build_type:
|
||||
entries.append(
|
||||
MatrixEntry(
|
||||
config_name=f"{platform_name}-{arch}-{build_type.lower()}",
|
||||
cmake_args=get_cmake_args(build_type, cfg.extra_cmake_args),
|
||||
cmake_target="install" if is_windows else "all",
|
||||
build_only=cfg.build_only,
|
||||
build_type=build_type,
|
||||
architecture=Architecture(platform=pf.platform, runner=pf.runner),
|
||||
sanitizers="",
|
||||
)
|
||||
else:
|
||||
configurations.append(
|
||||
{
|
||||
"config_name": config_name,
|
||||
"cmake_args": cmake_args,
|
||||
"cmake_target": cmake_target,
|
||||
"build_only": build_only,
|
||||
"build_type": build_type,
|
||||
"os": os,
|
||||
"architecture": architecture,
|
||||
"sanitizers": "",
|
||||
}
|
||||
)
|
||||
|
||||
return configurations
|
||||
return entries
|
||||
|
||||
|
||||
def read_config(file: Path) -> Config:
|
||||
config = json.loads(file.read_text())
|
||||
if (
|
||||
config["architecture"] is None
|
||||
or config["os"] is None
|
||||
or config["build_type"] is None
|
||||
or config["cmake_args"] is None
|
||||
):
|
||||
raise Exception("Invalid configuration file.")
|
||||
|
||||
return Config(**config)
|
||||
# ---------------------------------------------------------------------------
|
||||
# Entry point
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument(
|
||||
"-a",
|
||||
"--all",
|
||||
help="Set to generate all configurations (generally used when merging a PR) or leave unset to generate a subset of configurations (generally used when committing to a PR).",
|
||||
action="store_true",
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Generate a CI strategy matrix for all platforms or a specific one."
|
||||
)
|
||||
parser.add_argument(
|
||||
"-c",
|
||||
"--config",
|
||||
help="Path to the JSON file containing the strategy matrix configurations.",
|
||||
required=False,
|
||||
type=Path,
|
||||
help="Platform to generate for ('linux', 'macos', or 'windows'). Defaults to all platforms.",
|
||||
choices=["linux", "macos", "windows"],
|
||||
default=None,
|
||||
)
|
||||
parser.add_argument(
|
||||
"-p",
|
||||
"--packaging",
|
||||
help="Emit the packaging matrix (derived from the 'package' field on os entries) instead of the build/test matrix.",
|
||||
help="Emit the Linux packaging matrix instead of the build/test matrix.",
|
||||
action="store_true",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
matrix = []
|
||||
if args.packaging:
|
||||
config_path = args.config if args.config else THIS_DIR / "linux.json"
|
||||
matrix += generate_packaging_matrix(read_config(config_path))
|
||||
elif args.config is None or args.config == "":
|
||||
matrix += generate_strategy_matrix(
|
||||
args.all, read_config(THIS_DIR / "linux.json")
|
||||
)
|
||||
matrix += generate_strategy_matrix(
|
||||
args.all, read_config(THIS_DIR / "macos.json")
|
||||
)
|
||||
matrix += generate_strategy_matrix(
|
||||
args.all, read_config(THIS_DIR / "windows.json")
|
||||
)
|
||||
else:
|
||||
matrix += generate_strategy_matrix(args.all, read_config(args.config))
|
||||
matrix: list[MatrixEntry] | list[PackagingEntry] = []
|
||||
|
||||
# Generate the strategy matrix.
|
||||
print(f"matrix={json.dumps({'include': matrix})}")
|
||||
if args.packaging:
|
||||
matrix = expand_linux_packaging(LinuxFile.load(THIS_DIR / "linux.json"))
|
||||
else:
|
||||
if args.config in ("linux", None):
|
||||
matrix += expand_linux_matrix(LinuxFile.load(THIS_DIR / "linux.json"))
|
||||
if args.config in ("macos", None):
|
||||
matrix += expand_platform_matrix(PlatformFile.load(THIS_DIR / "macos.json"))
|
||||
if args.config in ("windows", None):
|
||||
matrix += expand_platform_matrix(
|
||||
PlatformFile.load(THIS_DIR / "windows.json")
|
||||
)
|
||||
|
||||
print(f"matrix={json.dumps({'include': [dataclasses.asdict(e) for e in matrix]})}")
|
||||
|
||||
300
.github/scripts/strategy-matrix/linux.json
vendored
300
.github/scripts/strategy-matrix/linux.json
vendored
@@ -1,221 +1,83 @@
|
||||
{
|
||||
"architecture": [
|
||||
{
|
||||
"platform": "linux/amd64",
|
||||
"runner": ["self-hosted", "Linux", "X64", "heavy"]
|
||||
},
|
||||
{
|
||||
"platform": "linux/arm64",
|
||||
"runner": ["self-hosted", "Linux", "ARM64", "heavy-arm64"]
|
||||
}
|
||||
],
|
||||
"os": [
|
||||
{
|
||||
"distro_name": "debian",
|
||||
"distro_version": "bookworm",
|
||||
"compiler_name": "gcc",
|
||||
"compiler_version": "12",
|
||||
"image_sha": "4c086b9"
|
||||
},
|
||||
{
|
||||
"distro_name": "debian",
|
||||
"distro_version": "bookworm",
|
||||
"compiler_name": "gcc",
|
||||
"compiler_version": "13",
|
||||
"image_sha": "4c086b9"
|
||||
},
|
||||
{
|
||||
"distro_name": "debian",
|
||||
"distro_version": "bookworm",
|
||||
"compiler_name": "gcc",
|
||||
"compiler_version": "14",
|
||||
"image_sha": "4c086b9"
|
||||
},
|
||||
{
|
||||
"distro_name": "debian",
|
||||
"distro_version": "bookworm",
|
||||
"compiler_name": "gcc",
|
||||
"compiler_version": "15",
|
||||
"image_sha": "4c086b9"
|
||||
},
|
||||
{
|
||||
"distro_name": "debian",
|
||||
"distro_version": "bookworm",
|
||||
"compiler_name": "clang",
|
||||
"compiler_version": "16",
|
||||
"image_sha": "4c086b9"
|
||||
},
|
||||
{
|
||||
"distro_name": "debian",
|
||||
"distro_version": "bookworm",
|
||||
"compiler_name": "clang",
|
||||
"compiler_version": "17",
|
||||
"image_sha": "4c086b9"
|
||||
},
|
||||
{
|
||||
"distro_name": "debian",
|
||||
"distro_version": "bookworm",
|
||||
"compiler_name": "clang",
|
||||
"compiler_version": "18",
|
||||
"image_sha": "4c086b9"
|
||||
},
|
||||
{
|
||||
"distro_name": "debian",
|
||||
"distro_version": "bookworm",
|
||||
"compiler_name": "clang",
|
||||
"compiler_version": "19",
|
||||
"image_sha": "4c086b9"
|
||||
},
|
||||
{
|
||||
"distro_name": "debian",
|
||||
"distro_version": "bookworm",
|
||||
"compiler_name": "clang",
|
||||
"compiler_version": "20",
|
||||
"image_sha": "4c086b9"
|
||||
},
|
||||
{
|
||||
"distro_name": "debian",
|
||||
"distro_version": "trixie",
|
||||
"compiler_name": "gcc",
|
||||
"compiler_version": "14",
|
||||
"image_sha": "4c086b9"
|
||||
},
|
||||
{
|
||||
"distro_name": "debian",
|
||||
"distro_version": "trixie",
|
||||
"compiler_name": "gcc",
|
||||
"compiler_version": "15",
|
||||
"image_sha": "4c086b9"
|
||||
},
|
||||
{
|
||||
"distro_name": "debian",
|
||||
"distro_version": "trixie",
|
||||
"compiler_name": "clang",
|
||||
"compiler_version": "20",
|
||||
"image_sha": "4c086b9"
|
||||
},
|
||||
{
|
||||
"distro_name": "debian",
|
||||
"distro_version": "trixie",
|
||||
"compiler_name": "clang",
|
||||
"compiler_version": "21",
|
||||
"image_sha": "4c086b9"
|
||||
},
|
||||
{
|
||||
"distro_name": "debian",
|
||||
"distro_version": "trixie",
|
||||
"compiler_name": "clang",
|
||||
"compiler_version": "22",
|
||||
"image_sha": "4c086b9"
|
||||
},
|
||||
{
|
||||
"distro_name": "rhel",
|
||||
"distro_version": "8",
|
||||
"compiler_name": "gcc",
|
||||
"compiler_version": "14",
|
||||
"image_sha": "4c086b9"
|
||||
},
|
||||
{
|
||||
"distro_name": "rhel",
|
||||
"distro_version": "8",
|
||||
"compiler_name": "clang",
|
||||
"compiler_version": "any",
|
||||
"image_sha": "4c086b9"
|
||||
},
|
||||
{
|
||||
"distro_name": "rhel",
|
||||
"distro_version": "9",
|
||||
"compiler_name": "gcc",
|
||||
"compiler_version": "12",
|
||||
"image_sha": "4c086b9",
|
||||
"package": true
|
||||
},
|
||||
{
|
||||
"distro_name": "rhel",
|
||||
"distro_version": "9",
|
||||
"compiler_name": "gcc",
|
||||
"compiler_version": "13",
|
||||
"image_sha": "4c086b9"
|
||||
},
|
||||
{
|
||||
"distro_name": "rhel",
|
||||
"distro_version": "9",
|
||||
"compiler_name": "gcc",
|
||||
"compiler_version": "14",
|
||||
"image_sha": "4c086b9"
|
||||
},
|
||||
{
|
||||
"distro_name": "rhel",
|
||||
"distro_version": "9",
|
||||
"compiler_name": "clang",
|
||||
"compiler_version": "any",
|
||||
"image_sha": "4c086b9"
|
||||
},
|
||||
{
|
||||
"distro_name": "rhel",
|
||||
"distro_version": "10",
|
||||
"compiler_name": "gcc",
|
||||
"compiler_version": "14",
|
||||
"image_sha": "4c086b9"
|
||||
},
|
||||
{
|
||||
"distro_name": "rhel",
|
||||
"distro_version": "10",
|
||||
"compiler_name": "clang",
|
||||
"compiler_version": "any",
|
||||
"image_sha": "4c086b9"
|
||||
},
|
||||
{
|
||||
"distro_name": "ubuntu",
|
||||
"distro_version": "jammy",
|
||||
"compiler_name": "gcc",
|
||||
"compiler_version": "12",
|
||||
"image_sha": "4c086b9",
|
||||
"package": true
|
||||
},
|
||||
{
|
||||
"distro_name": "ubuntu",
|
||||
"distro_version": "noble",
|
||||
"compiler_name": "gcc",
|
||||
"compiler_version": "13",
|
||||
"image_sha": "4c086b9"
|
||||
},
|
||||
{
|
||||
"distro_name": "ubuntu",
|
||||
"distro_version": "noble",
|
||||
"compiler_name": "gcc",
|
||||
"compiler_version": "14",
|
||||
"image_sha": "4c086b9"
|
||||
},
|
||||
{
|
||||
"distro_name": "ubuntu",
|
||||
"distro_version": "noble",
|
||||
"compiler_name": "clang",
|
||||
"compiler_version": "16",
|
||||
"image_sha": "4c086b9"
|
||||
},
|
||||
{
|
||||
"distro_name": "ubuntu",
|
||||
"distro_version": "noble",
|
||||
"compiler_name": "clang",
|
||||
"compiler_version": "17",
|
||||
"image_sha": "4c086b9"
|
||||
},
|
||||
{
|
||||
"distro_name": "ubuntu",
|
||||
"distro_version": "noble",
|
||||
"compiler_name": "clang",
|
||||
"compiler_version": "18",
|
||||
"image_sha": "4c086b9"
|
||||
},
|
||||
{
|
||||
"distro_name": "ubuntu",
|
||||
"distro_version": "noble",
|
||||
"compiler_name": "clang",
|
||||
"compiler_version": "19",
|
||||
"image_sha": "4c086b9"
|
||||
}
|
||||
],
|
||||
"build_type": ["Debug", "Release"],
|
||||
"cmake_args": [""]
|
||||
"image_tag": "sha-8abe82e",
|
||||
"configs": {
|
||||
"ubuntu": [
|
||||
{
|
||||
"compiler": ["gcc", "clang"],
|
||||
"build_type": ["Debug", "Release"],
|
||||
"arch": ["amd64", "arm64"]
|
||||
},
|
||||
|
||||
{
|
||||
"compiler": ["gcc", "clang"],
|
||||
"build_type": ["Debug"],
|
||||
"arch": ["amd64"],
|
||||
"sanitizers": ["address", "undefinedbehavior"]
|
||||
},
|
||||
|
||||
{
|
||||
"compiler": ["gcc"],
|
||||
"build_type": ["Debug"],
|
||||
"arch": ["amd64"],
|
||||
"suffix": "coverage",
|
||||
"extra_cmake_args": "-DUNIT_TEST_REFERENCE_FEE=500 -Dcoverage=ON -Dcoverage_format=xml -DCODE_COVERAGE_VERBOSE=ON -DCMAKE_C_FLAGS=-O0 -DCMAKE_CXX_FLAGS=-O0"
|
||||
},
|
||||
{
|
||||
"compiler": ["clang"],
|
||||
"build_type": ["Debug"],
|
||||
"arch": ["amd64"],
|
||||
"suffix": "voidstar",
|
||||
"extra_cmake_args": "-Dvoidstar=ON"
|
||||
},
|
||||
{
|
||||
"compiler": ["clang"],
|
||||
"build_type": ["Release"],
|
||||
"arch": ["amd64"],
|
||||
"suffix": "reffee",
|
||||
"extra_cmake_args": "-DUNIT_TEST_REFERENCE_FEE=1000"
|
||||
},
|
||||
{
|
||||
"compiler": ["gcc"],
|
||||
"build_type": ["Debug"],
|
||||
"arch": ["amd64"],
|
||||
"suffix": "unity",
|
||||
"extra_cmake_args": "-Dunity=ON"
|
||||
}
|
||||
],
|
||||
|
||||
"debian": [
|
||||
{
|
||||
"compiler": ["gcc"],
|
||||
"build_type": ["Release"],
|
||||
"arch": ["amd64"]
|
||||
}
|
||||
],
|
||||
|
||||
"rhel": [
|
||||
{
|
||||
"compiler": ["gcc"],
|
||||
"build_type": ["Release"],
|
||||
"arch": ["amd64"]
|
||||
}
|
||||
]
|
||||
},
|
||||
"package_configs": {
|
||||
"debian": [
|
||||
{
|
||||
"compiler": ["gcc"],
|
||||
"build_type": ["Release"],
|
||||
"arch": ["amd64"],
|
||||
"image": "debian:bookworm"
|
||||
}
|
||||
],
|
||||
|
||||
"rhel": [
|
||||
{
|
||||
"compiler": ["gcc"],
|
||||
"build_type": ["Release"],
|
||||
"arch": ["amd64"],
|
||||
"image": "registry.access.redhat.com/ubi9/ubi:latest"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
24
.github/scripts/strategy-matrix/macos.json
vendored
24
.github/scripts/strategy-matrix/macos.json
vendored
@@ -1,19 +1,15 @@
|
||||
{
|
||||
"architecture": [
|
||||
"platform": "macos/arm64",
|
||||
"runner": ["self-hosted", "macOS", "ARM64", "mac-runner-m1"],
|
||||
"configs": [
|
||||
{
|
||||
"platform": "macos/arm64",
|
||||
"runner": ["self-hosted", "macOS", "ARM64", "mac-runner-m1"]
|
||||
}
|
||||
],
|
||||
"os": [
|
||||
"build_type": "Release",
|
||||
"extra_cmake_args": "-DCMAKE_POLICY_VERSION_MINIMUM=3.5"
|
||||
},
|
||||
{
|
||||
"distro_name": "macos",
|
||||
"distro_version": "",
|
||||
"compiler_name": "",
|
||||
"compiler_version": "",
|
||||
"image_sha": ""
|
||||
"build_type": "Debug",
|
||||
"extra_cmake_args": "-DCMAKE_POLICY_VERSION_MINIMUM=3.5",
|
||||
"build_only": true
|
||||
}
|
||||
],
|
||||
"build_type": ["Debug", "Release"],
|
||||
"cmake_args": ["-DCMAKE_POLICY_VERSION_MINIMUM=3.5"]
|
||||
]
|
||||
}
|
||||
|
||||
23
.github/scripts/strategy-matrix/windows.json
vendored
23
.github/scripts/strategy-matrix/windows.json
vendored
@@ -1,19 +1,8 @@
|
||||
{
|
||||
"architecture": [
|
||||
{
|
||||
"platform": "windows/amd64",
|
||||
"runner": ["self-hosted", "Windows", "devbox"]
|
||||
}
|
||||
],
|
||||
"os": [
|
||||
{
|
||||
"distro_name": "windows",
|
||||
"distro_version": "",
|
||||
"compiler_name": "",
|
||||
"compiler_version": "",
|
||||
"image_sha": ""
|
||||
}
|
||||
],
|
||||
"build_type": ["Debug", "Release"],
|
||||
"cmake_args": [""]
|
||||
"platform": "windows/amd64",
|
||||
"runner": ["self-hosted", "Windows", "devbox"],
|
||||
"configs": [
|
||||
{ "build_type": "Release" },
|
||||
{ "build_type": "Debug", "build_only": true }
|
||||
]
|
||||
}
|
||||
|
||||
101
.github/workflows/build-nix-image.yml
vendored
101
.github/workflows/build-nix-image.yml
vendored
@@ -1,101 +0,0 @@
|
||||
name: Build Nix Docker image
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- develop
|
||||
paths:
|
||||
- ".github/workflows/build-nix-image.yml"
|
||||
- "docker/nix.Dockerfile"
|
||||
- "flake.nix"
|
||||
- "flake.lock"
|
||||
- "nix/**"
|
||||
pull_request:
|
||||
paths:
|
||||
- ".github/workflows/build-nix-image.yml"
|
||||
- "docker/nix.Dockerfile"
|
||||
- "flake.nix"
|
||||
- "flake.lock"
|
||||
- "nix/**"
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
env:
|
||||
UBUNTU_VERSION: "20.04"
|
||||
RHEL_VERSION: "9"
|
||||
DEBIAN_VERSION: "bookworm"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build and push Nix image (${{ matrix.distro }})
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- distro: nixos
|
||||
- distro: ubuntu
|
||||
- distro: rhel
|
||||
- distro: debian
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Determine base image
|
||||
id: vars
|
||||
run: |
|
||||
case "${{ matrix.distro }}" in
|
||||
nixos)
|
||||
echo "base_image=nixos/nix:latest" >> $GITHUB_OUTPUT
|
||||
;;
|
||||
ubuntu)
|
||||
echo "base_image=ubuntu:${UBUNTU_VERSION}" >> $GITHUB_OUTPUT
|
||||
;;
|
||||
rhel)
|
||||
echo "base_image=registry.access.redhat.com/ubi${RHEL_VERSION}/ubi:latest" >> $GITHUB_OUTPUT
|
||||
;;
|
||||
debian)
|
||||
echo "base_image=debian:${DEBIAN_VERSION}" >> $GITHUB_OUTPUT
|
||||
;;
|
||||
esac
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
if: github.event_name == 'push'
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Docker metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0
|
||||
with:
|
||||
images: ghcr.io/xrplf/ci/nix-${{ matrix.distro }}
|
||||
tags: |
|
||||
type=sha,prefix=sha-,format=short
|
||||
type=raw,value=latest
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
|
||||
with:
|
||||
context: .
|
||||
file: docker/nix.Dockerfile
|
||||
platforms: linux/amd64
|
||||
push: ${{ github.event_name == 'push' }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
build-args: BASE_IMAGE=${{ steps.vars.outputs.base_image }}
|
||||
56
.github/workflows/build-nix-images.yml
vendored
Normal file
56
.github/workflows/build-nix-images.yml
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
name: Build Nix Docker images
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- develop
|
||||
paths:
|
||||
- ".github/workflows/build-nix-images.yml"
|
||||
- ".github/workflows/reusable-build-docker-image.yml"
|
||||
- ".github/workflows/reusable-build-merge-docker-images.yml"
|
||||
- "flake.nix"
|
||||
- "flake.lock"
|
||||
- "nix/**"
|
||||
pull_request:
|
||||
paths:
|
||||
- ".github/workflows/build-nix-images.yml"
|
||||
- ".github/workflows/reusable-build-docker-image.yml"
|
||||
- ".github/workflows/reusable-build-merge-docker-images.yml"
|
||||
- "flake.nix"
|
||||
- "flake.lock"
|
||||
- "nix/**"
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
jobs:
|
||||
build-merge:
|
||||
name: Build and push nix-${{ matrix.distro.name }}
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
# The base images are the oldest supported version of each distro
|
||||
# that we want to build images for.
|
||||
distro:
|
||||
- name: nixos
|
||||
base_image: nixos/nix:latest
|
||||
- name: ubuntu
|
||||
base_image: ubuntu:20.04
|
||||
- name: debian
|
||||
base_image: debian:bookworm
|
||||
- name: rhel
|
||||
base_image: registry.access.redhat.com/ubi9/ubi:latest
|
||||
uses: ./.github/workflows/reusable-build-merge-docker-images.yml
|
||||
with:
|
||||
image_name: ghcr.io/xrplf/xrpld/nix-${{ matrix.distro.name }}
|
||||
dockerfile: nix/docker/Dockerfile
|
||||
base_image: ${{ matrix.distro.base_image }}
|
||||
48
.github/workflows/build-packaging-images.yml
vendored
Normal file
48
.github/workflows/build-packaging-images.yml
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
name: Build packaging Docker images
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- develop
|
||||
paths:
|
||||
- ".github/workflows/build-packaging-images.yml"
|
||||
- ".github/workflows/reusable-build-docker-image.yml"
|
||||
- ".github/workflows/reusable-build-merge-docker-images.yml"
|
||||
- "package/Dockerfile"
|
||||
- "package/install-packaging-tools.sh"
|
||||
pull_request:
|
||||
paths:
|
||||
- ".github/workflows/build-packaging-images.yml"
|
||||
- ".github/workflows/reusable-build-docker-image.yml"
|
||||
- ".github/workflows/reusable-build-merge-docker-images.yml"
|
||||
- "package/Dockerfile"
|
||||
- "package/install-packaging-tools.sh"
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
jobs:
|
||||
build-merge:
|
||||
name: Build and push packaging-${{ matrix.distro.name }}
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
distro:
|
||||
- name: debian
|
||||
base_image: debian:bookworm
|
||||
- name: rhel
|
||||
base_image: registry.access.redhat.com/ubi9/ubi:latest
|
||||
uses: ./.github/workflows/reusable-build-merge-docker-images.yml
|
||||
with:
|
||||
image_name: ghcr.io/xrplf/xrpld/packaging-${{ matrix.distro.name }}
|
||||
dockerfile: package/Dockerfile
|
||||
base_image: ${{ matrix.distro.base_image }}
|
||||
25
.github/workflows/check-pr-description.yml
vendored
25
.github/workflows/check-pr-description.yml
vendored
@@ -5,8 +5,17 @@ on:
|
||||
types:
|
||||
- checks_requested
|
||||
pull_request:
|
||||
types: [opened, edited, reopened, synchronize, ready_for_review]
|
||||
branches: [develop]
|
||||
types:
|
||||
- opened
|
||||
- edited
|
||||
- reopened
|
||||
- synchronize
|
||||
- ready_for_review
|
||||
branches:
|
||||
- develop
|
||||
- "release-*"
|
||||
- "release/*"
|
||||
- "staging/*"
|
||||
|
||||
jobs:
|
||||
check_description:
|
||||
@@ -14,17 +23,17 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
|
||||
- name: Write PR body to file
|
||||
env:
|
||||
PR_BODY: ${{ github.event.pull_request.body }}
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
run: printenv PR_BODY > pr_body.md
|
||||
run: printenv PR_BODY >pr_body.md
|
||||
|
||||
- name: Check PR description differs from template
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
run: >
|
||||
python .github/scripts/check-pr-description.py
|
||||
--template-file .github/pull_request_template.md
|
||||
--pr-body-file pr_body.md
|
||||
run: |
|
||||
python .github/scripts/check-pr-description.py \
|
||||
--template-file .github/pull_request_template.md \
|
||||
--pr-body-file pr_body.md
|
||||
|
||||
15
.github/workflows/check-pr-title.yml
vendored
15
.github/workflows/check-pr-title.yml
vendored
@@ -5,10 +5,19 @@ on:
|
||||
types:
|
||||
- checks_requested
|
||||
pull_request:
|
||||
types: [opened, edited, reopened, synchronize, ready_for_review]
|
||||
branches: [develop]
|
||||
types:
|
||||
- opened
|
||||
- edited
|
||||
- reopened
|
||||
- synchronize
|
||||
- ready_for_review
|
||||
branches:
|
||||
- develop
|
||||
- "release-*"
|
||||
- "release/*"
|
||||
- "staging/*"
|
||||
|
||||
jobs:
|
||||
check_title:
|
||||
if: ${{ github.event.pull_request.draft != true }}
|
||||
uses: XRPLF/actions/.github/workflows/check-pr-title.yml@291206777251b4d493641b5afbdf7c23009d2988
|
||||
uses: XRPLF/actions/.github/workflows/check-pr-title.yml@cba1f0891650baf1a9c88624dc2d72573be2eb81
|
||||
|
||||
2
.github/workflows/conflicting-pr.yml
vendored
2
.github/workflows/conflicting-pr.yml
vendored
@@ -17,7 +17,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check if PRs are dirty
|
||||
uses: eps1lon/actions-label-merge-conflict@1df065ebe6e3310545d4f4c4e862e43bdca146f0 # v3.0.3
|
||||
uses: eps1lon/actions-label-merge-conflict@0273be72a0bbd58fcd71d0d6c02c209b50d1e5e1 # v3.1.0
|
||||
with:
|
||||
dirtyLabel: "PR: has conflicts"
|
||||
repoToken: "${{ secrets.GITHUB_TOKEN }}"
|
||||
|
||||
10
.github/workflows/on-pr.yml
vendored
10
.github/workflows/on-pr.yml
vendored
@@ -33,7 +33,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- name: Determine changed files
|
||||
# This step checks whether any files have changed that should
|
||||
# cause the next jobs to run. We do it this way rather than
|
||||
@@ -98,7 +98,7 @@ jobs:
|
||||
READY: ${{ contains(github.event.pull_request.labels.*.name, 'Ready to merge') }}
|
||||
MERGE: ${{ github.event_name == 'merge_group' }}
|
||||
run: |
|
||||
echo "go=${{ (env.DRAFT != 'true' && env.READY == 'true') || env.FILES == 'true' || env.MERGE == 'true' }}" >> "${GITHUB_OUTPUT}"
|
||||
echo "go=${{ (env.DRAFT != 'true' && env.READY == 'true') || env.FILES == 'true' || env.MERGE == 'true' }}" >>"${GITHUB_OUTPUT}"
|
||||
cat "${GITHUB_OUTPUT}"
|
||||
outputs:
|
||||
go: ${{ steps.go.outputs.go == 'true' }}
|
||||
@@ -168,9 +168,9 @@ jobs:
|
||||
PR_URL: ${{ github.event.pull_request.html_url }}
|
||||
run: |
|
||||
gh api --method POST -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" \
|
||||
/repos/xrplf/clio/dispatches -f "event_type=check_libxrpl" \
|
||||
-F "client_payload[ref]=${{ needs.upload-recipe.outputs.recipe_ref }}" \
|
||||
-F "client_payload[pr_url]=${PR_URL}"
|
||||
/repos/xrplf/clio/dispatches -f "event_type=check_libxrpl" \
|
||||
-F "client_payload[ref]=${{ needs.upload-recipe.outputs.recipe_ref }}" \
|
||||
-F "client_payload[pr_url]=${PR_URL}"
|
||||
|
||||
passed:
|
||||
if: failure() || cancelled()
|
||||
|
||||
1
.github/workflows/on-tag.yml
vendored
1
.github/workflows/on-tag.yml
vendored
@@ -33,7 +33,6 @@ jobs:
|
||||
with:
|
||||
ccache_enabled: false
|
||||
os: ${{ matrix.os }}
|
||||
strategy_matrix: minimal
|
||||
secrets:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
|
||||
1
.github/workflows/on-trigger.yml
vendored
1
.github/workflows/on-trigger.yml
vendored
@@ -88,7 +88,6 @@ jobs:
|
||||
# not identical to a regular compilation.
|
||||
ccache_enabled: ${{ github.repository_owner == 'XRPLF' && !startsWith(github.ref, 'refs/heads/release') }}
|
||||
os: ${{ matrix.os }}
|
||||
strategy_matrix: ${{ github.event_name == 'schedule' && 'all' || 'minimal' }}
|
||||
secrets:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
|
||||
2
.github/workflows/pre-commit.yml
vendored
2
.github/workflows/pre-commit.yml
vendored
@@ -14,7 +14,7 @@ on:
|
||||
jobs:
|
||||
# Call the workflow in the XRPLF/actions repo that runs the pre-commit hooks.
|
||||
run-hooks:
|
||||
uses: XRPLF/actions/.github/workflows/pre-commit.yml@5e942d61bf32f7557a7c159cfac4712a687b3e3a
|
||||
uses: XRPLF/actions/.github/workflows/pre-commit.yml@cba1f0891650baf1a9c88624dc2d72573be2eb81
|
||||
with:
|
||||
runs_on: ubuntu-latest
|
||||
container: '{ "image": "ghcr.io/xrplf/ci/tools-rippled-pre-commit:sha-41ec7c1" }'
|
||||
|
||||
20
.github/workflows/publish-docs.yml
vendored
20
.github/workflows/publish-docs.yml
vendored
@@ -41,10 +41,10 @@ env:
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
container: ghcr.io/xrplf/ci/tools-rippled-documentation:sha-a8c7be1
|
||||
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-8abe82e
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
|
||||
- name: Prepare runner
|
||||
uses: XRPLF/actions/prepare-runner@90f11ee655d1687824fb8793db770477d52afbab
|
||||
@@ -57,19 +57,11 @@ jobs:
|
||||
with:
|
||||
subtract: ${{ env.NPROC_SUBTRACT }}
|
||||
|
||||
- name: Check configuration
|
||||
run: |
|
||||
echo 'Checking path.'
|
||||
echo ${PATH} | tr ':' '\n'
|
||||
- name: Print build environment
|
||||
uses: XRPLF/actions/print-build-env@59dec886e4afb05a1724443af08baccbc045b574
|
||||
|
||||
echo 'Checking environment variables.'
|
||||
env | sort
|
||||
|
||||
echo 'Checking CMake version.'
|
||||
cmake --version
|
||||
|
||||
echo 'Checking Doxygen version.'
|
||||
doxygen --version
|
||||
- name: Check Doxygen version
|
||||
run: doxygen --version
|
||||
|
||||
- name: Build documentation
|
||||
env:
|
||||
|
||||
89
.github/workflows/reusable-build-docker-image.yml
vendored
Normal file
89
.github/workflows/reusable-build-docker-image.yml
vendored
Normal file
@@ -0,0 +1,89 @@
|
||||
# Build a single-platform Docker image. On push, the image is pushed to
|
||||
# GHCR with arch-suffixed tags (e.g. `:latest-amd64`, `:sha-abc-amd64`)
|
||||
# so the calling workflow can stitch per-arch builds into a multi-arch
|
||||
# manifest without needing to pass digests around.
|
||||
name: Reusable build Docker image (single platform)
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
image_name:
|
||||
description: "Full image name without tag (e.g. 'ghcr.io/xrplf/xrpld/nix-ubuntu')"
|
||||
required: true
|
||||
type: string
|
||||
dockerfile:
|
||||
description: "Path to the Dockerfile, relative to the repository root"
|
||||
required: true
|
||||
type: string
|
||||
base_image:
|
||||
description: "Value passed to the Dockerfile as the BASE_IMAGE build arg"
|
||||
required: true
|
||||
type: string
|
||||
platform:
|
||||
description: "Docker platform string, e.g. linux/amd64"
|
||||
required: true
|
||||
type: string
|
||||
runner:
|
||||
description: "GitHub Actions runner label to build on"
|
||||
required: true
|
||||
type: string
|
||||
push:
|
||||
description: "Whether to push the image to GHCR"
|
||||
required: true
|
||||
type: boolean
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build ${{ inputs.platform }}
|
||||
runs-on: ${{ inputs.runner }}
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
|
||||
- name: Determine arch
|
||||
id: vars
|
||||
env:
|
||||
PLATFORM: ${{ inputs.platform }}
|
||||
run: |
|
||||
echo "arch=${PLATFORM##*/}" >>$GITHUB_OUTPUT
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
if: inputs.push
|
||||
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Docker metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6.1.0
|
||||
with:
|
||||
images: ${{ inputs.image_name }}
|
||||
tags: |
|
||||
type=sha,prefix=sha-,format=short
|
||||
type=raw,value=latest
|
||||
flavor: |
|
||||
suffix=-${{ steps.vars.outputs.arch }},onlatest=true
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0
|
||||
with:
|
||||
context: .
|
||||
file: ${{ inputs.dockerfile }}
|
||||
platforms: ${{ inputs.platform }}
|
||||
push: ${{ inputs.push }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
build-args: BASE_IMAGE=${{ inputs.base_image }}
|
||||
89
.github/workflows/reusable-build-merge-docker-images.yml
vendored
Normal file
89
.github/workflows/reusable-build-merge-docker-images.yml
vendored
Normal file
@@ -0,0 +1,89 @@
|
||||
name: Reusable build and merge Docker image (multi-arch)
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
image_name:
|
||||
description: "Full image name without tag (e.g. 'ghcr.io/xrplf/xrpld/nix-ubuntu')"
|
||||
required: true
|
||||
type: string
|
||||
dockerfile:
|
||||
description: "Path to the Dockerfile, relative to the repository root"
|
||||
required: true
|
||||
type: string
|
||||
base_image:
|
||||
description: "Value passed to the Dockerfile as the BASE_IMAGE build arg"
|
||||
required: true
|
||||
type: string
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build ${{ inputs.image_name }}
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
target:
|
||||
- platform: linux/amd64
|
||||
runner: ubuntu-latest
|
||||
- platform: linux/arm64
|
||||
runner: ubuntu-24.04-arm
|
||||
|
||||
uses: ./.github/workflows/reusable-build-docker-image.yml
|
||||
with:
|
||||
image_name: ${{ inputs.image_name }}
|
||||
dockerfile: ${{ inputs.dockerfile }}
|
||||
base_image: ${{ inputs.base_image }}
|
||||
platform: ${{ matrix.target.platform }}
|
||||
runner: ${{ matrix.target.runner }}
|
||||
push: ${{ github.repository == 'XRPLF/rippled' && github.event_name == 'push' }}
|
||||
|
||||
merge:
|
||||
name: Merge ${{ inputs.image_name }}
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
steps:
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0
|
||||
|
||||
- name: Docker metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6.1.0
|
||||
with:
|
||||
images: ${{ inputs.image_name }}
|
||||
tags: |
|
||||
type=sha,prefix=sha-,format=short
|
||||
type=raw,value=latest
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Create multi-arch manifests
|
||||
if: ${{ github.repository == 'XRPLF/rippled' && github.event_name == 'push' }}
|
||||
run: |
|
||||
for tag in $(jq -cr '.tags[]' <<<"$DOCKER_METADATA_OUTPUT_JSON"); do
|
||||
docker buildx imagetools create -t "$tag" "${tag}-amd64" "${tag}-arm64"
|
||||
done
|
||||
|
||||
- name: Inspect image
|
||||
if: ${{ github.repository == 'XRPLF/rippled' && github.event_name == 'push' }}
|
||||
env:
|
||||
IMAGE_NAME: ${{ inputs.image_name }}
|
||||
IMAGE_VERSION: ${{ steps.meta.outputs.version }}
|
||||
run: |
|
||||
docker buildx imagetools inspect "${IMAGE_NAME}:${IMAGE_VERSION}"
|
||||
146
.github/workflows/reusable-build-test-config.yml
vendored
146
.github/workflows/reusable-build-test-config.yml
vendored
@@ -57,6 +57,12 @@ on:
|
||||
type: string
|
||||
default: ""
|
||||
|
||||
compiler:
|
||||
description: 'The compiler to use ("gcc" or "clang"). Leave empty for macOS/Windows (uses system default).'
|
||||
required: false
|
||||
type: string
|
||||
default: ""
|
||||
|
||||
secrets:
|
||||
CODECOV_TOKEN:
|
||||
description: "The Codecov token to use for uploading coverage reports."
|
||||
@@ -104,7 +110,7 @@ jobs:
|
||||
uses: XRPLF/actions/cleanup-workspace@c7d9ce5ebb03c752a354889ecd870cadfc2b1cd4
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
|
||||
- name: Prepare runner
|
||||
uses: XRPLF/actions/prepare-runner@90f11ee655d1687824fb8793db770477d52afbab
|
||||
@@ -113,7 +119,7 @@ jobs:
|
||||
|
||||
- name: Set ccache log file
|
||||
if: ${{ inputs.ccache_enabled && runner.debug == '1' }}
|
||||
run: echo "CCACHE_LOGFILE=${{ runner.temp }}/ccache.log" >> "${GITHUB_ENV}"
|
||||
run: echo "CCACHE_LOGFILE=${{ runner.temp }}/ccache.log" >>"${GITHUB_ENV}"
|
||||
|
||||
- name: Print build environment
|
||||
uses: XRPLF/actions/print-build-env@59dec886e4afb05a1724443af08baccbc045b574
|
||||
@@ -124,6 +130,12 @@ jobs:
|
||||
with:
|
||||
subtract: ${{ inputs.nproc_subtract }}
|
||||
|
||||
- name: Set compiler environment (Linux)
|
||||
if: ${{ runner.os == 'Linux' }}
|
||||
uses: ./.github/actions/set-compiler-env
|
||||
with:
|
||||
compiler: ${{ inputs.compiler }}
|
||||
|
||||
- name: Setup Conan
|
||||
env:
|
||||
SANITIZERS: ${{ inputs.sanitizers }}
|
||||
@@ -146,11 +158,11 @@ jobs:
|
||||
CMAKE_ARGS: ${{ inputs.cmake_args }}
|
||||
run: |
|
||||
cmake \
|
||||
-G '${{ runner.os == 'Windows' && 'Visual Studio 17 2022' || 'Ninja' }}' \
|
||||
-DCMAKE_TOOLCHAIN_FILE:FILEPATH=build/generators/conan_toolchain.cmake \
|
||||
-DCMAKE_BUILD_TYPE="${BUILD_TYPE}" \
|
||||
${CMAKE_ARGS} \
|
||||
..
|
||||
-G '${{ runner.os == 'Windows' && 'Visual Studio 17 2022' || 'Ninja' }}' \
|
||||
-DCMAKE_TOOLCHAIN_FILE:FILEPATH=build/generators/conan_toolchain.cmake \
|
||||
-DCMAKE_BUILD_TYPE="${BUILD_TYPE}" \
|
||||
${CMAKE_ARGS} \
|
||||
..
|
||||
|
||||
- name: Check protocol autogen files are up-to-date
|
||||
working-directory: ${{ env.BUILD_DIR }}
|
||||
@@ -172,32 +184,47 @@ jobs:
|
||||
cmake --build . --target code_gen
|
||||
DIFF=$(git -C .. status --porcelain -- include/xrpl/protocol_autogen src/tests/libxrpl/protocol_autogen)
|
||||
if [ -n "${DIFF}" ]; then
|
||||
echo "::error::Generated protocol files are out of date"
|
||||
git -C .. diff -- include/xrpl/protocol_autogen src/tests/libxrpl/protocol_autogen
|
||||
echo "${MESSAGE}"
|
||||
exit 1
|
||||
echo "::error::Generated protocol files are out of date"
|
||||
git -C .. diff -- include/xrpl/protocol_autogen src/tests/libxrpl/protocol_autogen
|
||||
echo "${MESSAGE}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Build the binary
|
||||
working-directory: ${{ env.BUILD_DIR }}
|
||||
env:
|
||||
BUILD_NPROC: ${{ runner.os == 'Linux' && '16' || steps.nproc.outputs.nproc }}
|
||||
BUILD_NPROC: ${{ steps.nproc.outputs.nproc }}
|
||||
BUILD_TYPE: ${{ inputs.build_type }}
|
||||
CMAKE_TARGET: ${{ inputs.cmake_target }}
|
||||
run: |
|
||||
cmake \
|
||||
--build . \
|
||||
--config "${BUILD_TYPE}" \
|
||||
--parallel "${BUILD_NPROC}" \
|
||||
--target "${CMAKE_TARGET}"
|
||||
--build . \
|
||||
--config "${BUILD_TYPE}" \
|
||||
--parallel "${BUILD_NPROC}" \
|
||||
--target "${CMAKE_TARGET}"
|
||||
|
||||
# This step is needed to allow running in non-Nix environments
|
||||
- name: Patch binary to use default loader and remove rpath (Linux)
|
||||
if: ${{ runner.os == 'Linux' && env.SANITIZERS_ENABLED == 'false' }}
|
||||
run: |
|
||||
loader="$(/tmp/loader-path.sh)"
|
||||
patchelf --set-interpreter "${loader}" --remove-rpath "${{ env.BUILD_DIR }}/xrpld"
|
||||
|
||||
# We're only running aarch64 Linux builds in Ubuntu-based images, so this is kept simple
|
||||
- name: Install libatomic (Linux aarch64)
|
||||
if: ${{ runner.os == 'Linux' && runner.arch == 'ARM64' }}
|
||||
run: |
|
||||
apt update --yes
|
||||
apt install -y --no-install-recommends \
|
||||
libatomic1
|
||||
|
||||
- name: Show ccache statistics
|
||||
if: ${{ inputs.ccache_enabled }}
|
||||
run: |
|
||||
ccache --show-stats -vv
|
||||
if [ '${{ runner.debug }}' = '1' ]; then
|
||||
cat "${CCACHE_LOGFILE}"
|
||||
curl ${CCACHE_REMOTE_STORAGE%|*}/status || true
|
||||
cat "${CCACHE_LOGFILE}"
|
||||
curl ${CCACHE_REMOTE_STORAGE%|*}/status || true
|
||||
fi
|
||||
|
||||
- name: Upload the binary (Linux)
|
||||
@@ -209,15 +236,24 @@ jobs:
|
||||
retention-days: 3
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Upload the test binary (Linux)
|
||||
if: ${{ github.event.repository.visibility == 'public' && runner.os == 'Linux' }}
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: xrpl_tests-${{ inputs.config_name }}
|
||||
path: ${{ env.BUILD_DIR }}/xrpl_tests
|
||||
retention-days: 3
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Export server definitions
|
||||
if: ${{ runner.os != 'Windows' && !inputs.build_only && env.VOIDSTAR_ENABLED != 'true' }}
|
||||
working-directory: ${{ env.BUILD_DIR }}
|
||||
run: |
|
||||
set -o pipefail
|
||||
./xrpld --definitions | python3 -m json.tool > server_definitions.json
|
||||
./xrpld --definitions | python3 -m json.tool >server_definitions.json
|
||||
|
||||
- name: Upload server definitions
|
||||
if: ${{ github.event.repository.visibility == 'public' && inputs.config_name == 'debian-bookworm-gcc-13-amd64-release' }}
|
||||
if: ${{ github.event.repository.visibility == 'public' && inputs.config_name == 'debian-gcc-release-amd64' }}
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: server-definitions
|
||||
@@ -231,10 +267,10 @@ jobs:
|
||||
run: |
|
||||
ldd ./xrpld
|
||||
if [ "$(ldd ./xrpld | grep -E '(libstdc\+\+|libgcc)' | wc -l)" -eq 0 ]; then
|
||||
echo 'The binary is statically linked.'
|
||||
echo 'The binary is statically linked.'
|
||||
else
|
||||
echo 'The binary is dynamically linked.'
|
||||
exit 1
|
||||
echo 'The binary is dynamically linked.'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Verify presence of instrumentation (Linux)
|
||||
@@ -250,25 +286,17 @@ jobs:
|
||||
run: |
|
||||
ASAN_OPTS="include=${GITHUB_WORKSPACE}/sanitizers/suppressions/runtime-asan-options.txt:suppressions=${GITHUB_WORKSPACE}/sanitizers/suppressions/asan.supp"
|
||||
if [[ "${CONFIG_NAME}" == *gcc* ]]; then
|
||||
ASAN_OPTS="${ASAN_OPTS}:alloc_dealloc_mismatch=0"
|
||||
ASAN_OPTS="${ASAN_OPTS}:alloc_dealloc_mismatch=0"
|
||||
fi
|
||||
echo "ASAN_OPTIONS=${ASAN_OPTS}" >> ${GITHUB_ENV}
|
||||
echo "TSAN_OPTIONS=include=${GITHUB_WORKSPACE}/sanitizers/suppressions/runtime-tsan-options.txt:suppressions=${GITHUB_WORKSPACE}/sanitizers/suppressions/tsan.supp" >> ${GITHUB_ENV}
|
||||
echo "UBSAN_OPTIONS=include=${GITHUB_WORKSPACE}/sanitizers/suppressions/runtime-ubsan-options.txt:suppressions=${GITHUB_WORKSPACE}/sanitizers/suppressions/ubsan.supp" >> ${GITHUB_ENV}
|
||||
echo "LSAN_OPTIONS=include=${GITHUB_WORKSPACE}/sanitizers/suppressions/runtime-lsan-options.txt:suppressions=${GITHUB_WORKSPACE}/sanitizers/suppressions/lsan.supp" >> ${GITHUB_ENV}
|
||||
echo "ASAN_OPTIONS=${ASAN_OPTS}" >>${GITHUB_ENV}
|
||||
echo "TSAN_OPTIONS=include=${GITHUB_WORKSPACE}/sanitizers/suppressions/runtime-tsan-options.txt:suppressions=${GITHUB_WORKSPACE}/sanitizers/suppressions/tsan.supp" >>${GITHUB_ENV}
|
||||
echo "UBSAN_OPTIONS=include=${GITHUB_WORKSPACE}/sanitizers/suppressions/runtime-ubsan-options.txt:suppressions=${GITHUB_WORKSPACE}/sanitizers/suppressions/ubsan.supp" >>${GITHUB_ENV}
|
||||
echo "LSAN_OPTIONS=include=${GITHUB_WORKSPACE}/sanitizers/suppressions/runtime-lsan-options.txt:suppressions=${GITHUB_WORKSPACE}/sanitizers/suppressions/lsan.supp" >>${GITHUB_ENV}
|
||||
|
||||
- name: Run the separate tests
|
||||
if: ${{ !inputs.build_only }}
|
||||
working-directory: ${{ env.BUILD_DIR }}
|
||||
# Windows locks some of the build files while running tests, and parallel jobs can collide
|
||||
env:
|
||||
BUILD_TYPE: ${{ inputs.build_type }}
|
||||
PARALLELISM: ${{ runner.os == 'Windows' && '1' || steps.nproc.outputs.nproc }}
|
||||
run: |
|
||||
ctest \
|
||||
--output-on-failure \
|
||||
-C "${BUILD_TYPE}" \
|
||||
-j "${PARALLELISM}"
|
||||
working-directory: ${{ runner.os == 'Windows' && format('{0}/{1}', env.BUILD_DIR, inputs.build_type) || env.BUILD_DIR }}
|
||||
run: ./xrpl_tests
|
||||
|
||||
- name: Run the embedded tests
|
||||
if: ${{ !inputs.build_only }}
|
||||
@@ -278,8 +306,26 @@ jobs:
|
||||
run: |
|
||||
set -o pipefail
|
||||
# Coverage builds are slower due to instrumentation; use fewer parallel jobs to avoid flakiness
|
||||
[ "$COVERAGE_ENABLED" = "true" ] && BUILD_NPROC=$(( BUILD_NPROC - 2 ))
|
||||
./xrpld --unittest --unittest-jobs "${BUILD_NPROC}" 2>&1 | tee unittest.log
|
||||
[ "$COVERAGE_ENABLED" = "true" ] && BUILD_NPROC=$((BUILD_NPROC - 2))
|
||||
|
||||
# The resolver/preload workaround is only correct for the ASan build:
|
||||
# a regular build doesn't hit the __dn_expand interceptor bug, and must
|
||||
# NOT have libasan injected. So only preload when xrpld is ASan-built.
|
||||
#
|
||||
# libresolv hosts getaddrinfo's resolver helpers (dn_expand, res_*). Under ASan
|
||||
# these are intercepted via dlsym(RTLD_NEXT, ...), which yields a NULL pointer
|
||||
# and crashes DNS resolution if libresolv isn't loaded. Linking it guarantees
|
||||
# the symbols are present; it's a harmless no-op on glibc >= 2.34 (merged into
|
||||
# libc) and is what the compiler driver already does for sanitizer builds.
|
||||
# https://github.com/llvm/llvm-project/issues/59007
|
||||
# https://github.com/google/sanitizers/issues/1592
|
||||
if ldd ./xrpld | grep -q libasan; then
|
||||
PRELOAD="$(gcc -print-file-name=libasan.so):/usr/lib/x86_64-linux-gnu/libresolv.so.2"
|
||||
else
|
||||
PRELOAD=""
|
||||
fi
|
||||
|
||||
LD_PRELOAD="$PRELOAD" ./xrpld --unittest --unittest-jobs "${BUILD_NPROC}" 2>&1 | tee unittest.log
|
||||
|
||||
- name: Show test failure summary
|
||||
if: ${{ failure() && !inputs.build_only }}
|
||||
@@ -287,19 +333,19 @@ jobs:
|
||||
WORKING_DIR: ${{ runner.os == 'Windows' && format('{0}\{1}', env.BUILD_DIR, inputs.build_type) || env.BUILD_DIR }}
|
||||
run: |
|
||||
if [ ! -d "${WORKING_DIR}" ]; then
|
||||
echo "Working directory '${WORKING_DIR}' does not exist."
|
||||
exit 0
|
||||
echo "Working directory '${WORKING_DIR}' does not exist."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
cd "${WORKING_DIR}"
|
||||
|
||||
if [ ! -f unittest.log ]; then
|
||||
echo "unittest.log not found; embedded tests may not have run."
|
||||
exit 0
|
||||
echo "unittest.log not found; embedded tests may not have run."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if ! grep -E "failed" unittest.log; then
|
||||
echo "Log present but no failure lines found in unittest.log."
|
||||
echo "Log present but no failure lines found in unittest.log."
|
||||
fi
|
||||
- name: Debug failure (Linux)
|
||||
if: ${{ failure() && runner.os == 'Linux' && !inputs.build_only }}
|
||||
@@ -317,14 +363,14 @@ jobs:
|
||||
BUILD_TYPE: ${{ inputs.build_type }}
|
||||
run: |
|
||||
cmake \
|
||||
--build . \
|
||||
--config "${BUILD_TYPE}" \
|
||||
--parallel "${BUILD_NPROC}" \
|
||||
--target coverage
|
||||
--build . \
|
||||
--config "${BUILD_TYPE}" \
|
||||
--parallel "${BUILD_NPROC}" \
|
||||
--target coverage
|
||||
|
||||
- name: Upload coverage report
|
||||
if: ${{ github.repository == 'XRPLF/rippled' && !inputs.build_only && env.COVERAGE_ENABLED == 'true' }}
|
||||
uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0
|
||||
uses: codecov/codecov-action@e79a6962e0d4c0c17b229090214935d2e33f8354 # v6.0.1
|
||||
with:
|
||||
disable_search: true
|
||||
disable_telem: true
|
||||
|
||||
12
.github/workflows/reusable-build-test.yml
vendored
12
.github/workflows/reusable-build-test.yml
vendored
@@ -19,13 +19,6 @@ on:
|
||||
required: true
|
||||
type: string
|
||||
|
||||
strategy_matrix:
|
||||
# TODO: Support additional strategies, e.g. "ubuntu" for generating all Ubuntu configurations.
|
||||
description: 'The strategy matrix to use for generating the configurations ("minimal", "all").'
|
||||
required: false
|
||||
type: string
|
||||
default: "minimal"
|
||||
|
||||
secrets:
|
||||
CODECOV_TOKEN:
|
||||
description: "The Codecov token to use for uploading coverage reports."
|
||||
@@ -37,7 +30,6 @@ jobs:
|
||||
uses: ./.github/workflows/reusable-strategy-matrix.yml
|
||||
with:
|
||||
os: ${{ inputs.os }}
|
||||
strategy_matrix: ${{ inputs.strategy_matrix }}
|
||||
|
||||
# Build and test the binary for each configuration.
|
||||
build-test-config:
|
||||
@@ -47,7 +39,6 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: ${{ github.event_name == 'merge_group' }}
|
||||
matrix: ${{ fromJson(needs.generate-matrix.outputs.matrix) }}
|
||||
max-parallel: 10
|
||||
with:
|
||||
build_only: ${{ matrix.build_only }}
|
||||
build_type: ${{ matrix.build_type }}
|
||||
@@ -55,8 +46,9 @@ jobs:
|
||||
cmake_args: ${{ matrix.cmake_args }}
|
||||
cmake_target: ${{ matrix.cmake_target }}
|
||||
runs_on: ${{ toJSON(matrix.architecture.runner) }}
|
||||
image: ${{ contains(matrix.architecture.platform, 'linux') && format('ghcr.io/xrplf/ci/{0}-{1}:{2}-{3}-sha-{4}', matrix.os.distro_name, matrix.os.distro_version, matrix.os.compiler_name, matrix.os.compiler_version, matrix.os.image_sha) || '' }}
|
||||
image: ${{ matrix.image || '' }}
|
||||
config_name: ${{ matrix.config_name }}
|
||||
sanitizers: ${{ matrix.sanitizers }}
|
||||
compiler: ${{ matrix.compiler || '' }}
|
||||
secrets:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- name: Check levelization
|
||||
run: python .github/scripts/levelization/generate.py
|
||||
- name: Check for differences
|
||||
@@ -38,9 +38,9 @@ jobs:
|
||||
run: |
|
||||
DIFF=$(git status --porcelain)
|
||||
if [ -n "${DIFF}" ]; then
|
||||
# Print the differences to give the contributor a hint about what to
|
||||
# expect when running levelization on their own machine.
|
||||
git diff
|
||||
echo "${MESSAGE}"
|
||||
exit 1
|
||||
# Print the differences to give the contributor a hint about what to
|
||||
# expect when running levelization on their own machine.
|
||||
git diff
|
||||
echo "${MESSAGE}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
12
.github/workflows/reusable-check-rename.yml
vendored
12
.github/workflows/reusable-check-rename.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- name: Check definitions
|
||||
run: .github/scripts/rename/definitions.sh .
|
||||
- name: Check copyright notices
|
||||
@@ -48,9 +48,9 @@ jobs:
|
||||
run: |
|
||||
DIFF=$(git status --porcelain)
|
||||
if [ -n "${DIFF}" ]; then
|
||||
# Print the differences to give the contributor a hint about what to
|
||||
# expect when running the renaming scripts on their own machine.
|
||||
git diff
|
||||
echo "${MESSAGE}"
|
||||
exit 1
|
||||
# Print the differences to give the contributor a hint about what to
|
||||
# expect when running the renaming scripts on their own machine.
|
||||
git diff
|
||||
echo "${MESSAGE}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
57
.github/workflows/reusable-clang-tidy.yml
vendored
57
.github/workflows/reusable-clang-tidy.yml
vendored
@@ -36,13 +36,13 @@ jobs:
|
||||
needs: [determine-files]
|
||||
if: ${{ always() && !cancelled() && (!inputs.check_only_changed || needs.determine-files.outputs.cpp_changed_files != '' || needs.determine-files.outputs.clang_tidy_config_changed == 'true') }}
|
||||
runs-on: ["self-hosted", "Linux", "X64", "heavy"]
|
||||
container: "ghcr.io/xrplf/ci/debian-trixie:clang-21-sha-53033a2"
|
||||
container: "ghcr.io/xrplf/xrpld/nix-debian:sha-8abe82e"
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
|
||||
- name: Prepare runner
|
||||
uses: XRPLF/actions/prepare-runner@90f11ee655d1687824fb8793db770477d52afbab
|
||||
@@ -56,6 +56,11 @@ jobs:
|
||||
uses: XRPLF/actions/get-nproc@cf0433aa74563aead044a1e395610c96d65a37cf
|
||||
id: nproc
|
||||
|
||||
- name: Set compiler environment
|
||||
uses: ./.github/actions/set-compiler-env
|
||||
with:
|
||||
compiler: clang
|
||||
|
||||
- name: Setup Conan
|
||||
uses: ./.github/actions/setup-conan
|
||||
|
||||
@@ -70,13 +75,13 @@ jobs:
|
||||
working-directory: ${{ env.BUILD_DIR }}
|
||||
run: |
|
||||
cmake \
|
||||
-G 'Ninja' \
|
||||
-DCMAKE_TOOLCHAIN_FILE:FILEPATH=build/generators/conan_toolchain.cmake \
|
||||
-DCMAKE_BUILD_TYPE="${BUILD_TYPE}" \
|
||||
-Dtests=ON \
|
||||
-Dwerr=ON \
|
||||
-Dxrpld=ON \
|
||||
..
|
||||
-G 'Ninja' \
|
||||
-DCMAKE_TOOLCHAIN_FILE:FILEPATH=build/generators/conan_toolchain.cmake \
|
||||
-DCMAKE_BUILD_TYPE="${BUILD_TYPE}" \
|
||||
-Dtests=ON \
|
||||
-Dwerr=ON \
|
||||
-Dxrpld=ON \
|
||||
..
|
||||
|
||||
# clang-tidy needs headers generated from proto files
|
||||
- name: Build libxrpl.libpb
|
||||
@@ -133,7 +138,7 @@ jobs:
|
||||
- name: Write issue header
|
||||
if: ${{ steps.run_clang_tidy.outcome != 'success' }}
|
||||
run: |
|
||||
cat > "${ISSUE_FILE}" <<EOF
|
||||
cat >"${ISSUE_FILE}" <<EOF
|
||||
## Clang-tidy Check Failed
|
||||
|
||||
### Clang-tidy Output:
|
||||
@@ -144,30 +149,30 @@ jobs:
|
||||
if: ${{ steps.run_clang_tidy.outcome != 'success' }}
|
||||
run: |
|
||||
if [ -f "${OUTPUT_FILE}" ]; then
|
||||
# Extract lines containing 'error:', 'warning:', or 'note:'
|
||||
grep -E '(error:|warning:|note:)' "${OUTPUT_FILE}" > filtered-output.txt || true
|
||||
# Extract lines containing 'error:', 'warning:', or 'note:'
|
||||
grep -E '(error:|warning:|note:)' "${OUTPUT_FILE}" >filtered-output.txt || true
|
||||
|
||||
# If filtered output is empty, use original (might be a different error format)
|
||||
if [ ! -s filtered-output.txt ]; then
|
||||
cp "${OUTPUT_FILE}" filtered-output.txt
|
||||
fi
|
||||
# If filtered output is empty, use original (might be a different error format)
|
||||
if [ ! -s filtered-output.txt ]; then
|
||||
cp "${OUTPUT_FILE}" filtered-output.txt
|
||||
fi
|
||||
|
||||
# Truncate if too large
|
||||
head -c 60000 filtered-output.txt >> "${ISSUE_FILE}"
|
||||
if [ "$(wc -c < filtered-output.txt)" -gt 60000 ]; then
|
||||
echo "" >> "${ISSUE_FILE}"
|
||||
echo "... (output truncated, see artifacts for full output)" >> "${ISSUE_FILE}"
|
||||
fi
|
||||
# Truncate if too large
|
||||
head -c 60000 filtered-output.txt >>"${ISSUE_FILE}"
|
||||
if [ "$(wc -c <filtered-output.txt)" -gt 60000 ]; then
|
||||
echo "" >>"${ISSUE_FILE}"
|
||||
echo "... (output truncated, see artifacts for full output)" >>"${ISSUE_FILE}"
|
||||
fi
|
||||
|
||||
rm filtered-output.txt
|
||||
rm filtered-output.txt
|
||||
else
|
||||
echo "No output file found" >> "${ISSUE_FILE}"
|
||||
echo "No output file found" >>"${ISSUE_FILE}"
|
||||
fi
|
||||
|
||||
- name: Append issue footer
|
||||
if: ${{ steps.run_clang_tidy.outcome != 'success' }}
|
||||
run: |
|
||||
cat >> "${ISSUE_FILE}" <<EOF
|
||||
cat >>"${ISSUE_FILE}" <<EOF
|
||||
\`\`\`
|
||||
|
||||
---
|
||||
@@ -176,7 +181,7 @@ jobs:
|
||||
|
||||
- name: Create issue
|
||||
if: ${{ steps.run_clang_tidy.outcome != 'success' && inputs.create_issue_on_failure }}
|
||||
uses: XRPLF/actions/create-issue@36d450d12d301e8410c1b7936e5de70c291cbe36
|
||||
uses: XRPLF/actions/create-issue@2b8bc36af85b88bca0dd7bfac2e2dc05f94ad712
|
||||
with:
|
||||
title: "Clang-tidy check failed"
|
||||
body_file: ${{ env.ISSUE_FILE }}
|
||||
|
||||
49
.github/workflows/reusable-package.yml
vendored
49
.github/workflows/reusable-package.yml
vendored
@@ -1,8 +1,7 @@
|
||||
# Build Linux packages (DEB and RPM) from pre-built binary artifacts.
|
||||
# Discovers which configurations to package from linux.json (os entries
|
||||
# with "package": true) and fans out one job per entry. Today only
|
||||
# linux/amd64 is emitted; the architecture is hardcoded both here
|
||||
# (runner) and in generate.py.
|
||||
# Discovers which configurations to package from linux.json (configs in
|
||||
# "package_configs") and fans out one job per distro. Only linux/amd64 is
|
||||
# supported; the runner is hardcoded in the job below.
|
||||
name: Package
|
||||
|
||||
on:
|
||||
@@ -28,18 +27,17 @@ jobs:
|
||||
matrix: ${{ steps.generate.outputs.matrix }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
with:
|
||||
python-version: 3.13
|
||||
python-version: "3.13"
|
||||
|
||||
- name: Generate packaging matrix
|
||||
id: generate
|
||||
working-directory: .github/scripts/strategy-matrix
|
||||
run: |
|
||||
./generate.py --packaging --config=linux.json >> "${GITHUB_OUTPUT}"
|
||||
run: ./generate.py --packaging >>"${GITHUB_OUTPUT}"
|
||||
|
||||
generate-version:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -47,7 +45,7 @@ jobs:
|
||||
version: ${{ steps.version.outputs.version }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
with:
|
||||
sparse-checkout: |
|
||||
.github/actions/generate-version
|
||||
@@ -58,6 +56,7 @@ jobs:
|
||||
|
||||
package:
|
||||
needs: [generate-matrix, generate-version]
|
||||
if: ${{ github.event.repository.visibility == 'public' }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix: ${{ fromJson(needs.generate-matrix.outputs.matrix) }}
|
||||
@@ -65,12 +64,37 @@ jobs:
|
||||
permissions:
|
||||
contents: read
|
||||
runs-on: ["self-hosted", "Linux", "X64", "heavy"]
|
||||
container: ${{ format('ghcr.io/xrplf/ci/{0}-{1}:{2}-{3}-sha-{4}', matrix.os.distro_name, matrix.os.distro_version, matrix.os.compiler_name, matrix.os.compiler_version, matrix.os.image_sha) }}
|
||||
container: ${{ matrix.image }}
|
||||
timeout-minutes: 30
|
||||
|
||||
steps:
|
||||
# Packaging runs in a vanilla distro image, so the tooling has to come
|
||||
# from the distro's archive: debhelper for deb, rpm-build (and the
|
||||
# systemd / find-debuginfo macros it depends on) for rpm. Run this
|
||||
# before actions/checkout so the latter can use git (real history) for
|
||||
# build_pkg.sh's SOURCE_DATE_EPOCH; otherwise it falls back to a tarball
|
||||
# download and the timestamp comes from wall-clock time.
|
||||
- name: Install packaging tooling (deb)
|
||||
if: ${{ matrix.distro == 'debian' }}
|
||||
run: |
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
apt-get update
|
||||
apt-get install -y --no-install-recommends \
|
||||
ca-certificates \
|
||||
debhelper \
|
||||
git
|
||||
|
||||
- name: Install packaging tooling (rpm)
|
||||
if: ${{ matrix.distro == 'rhel' }}
|
||||
run: |
|
||||
dnf install -y --setopt=install_weak_deps=False \
|
||||
git \
|
||||
rpm-build \
|
||||
redhat-rpm-config \
|
||||
systemd-rpm-macros
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
|
||||
- name: Download pre-built binary
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
@@ -88,8 +112,7 @@ jobs:
|
||||
run: ./package/build_pkg.sh
|
||||
|
||||
- name: Upload package artifact
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
if: ${{ github.event.repository.visibility == 'public' }}
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: ${{ matrix.artifact_name }}-pkg-${{ needs.generate-version.outputs.version }}
|
||||
path: |
|
||||
|
||||
17
.github/workflows/reusable-strategy-matrix.yml
vendored
17
.github/workflows/reusable-strategy-matrix.yml
vendored
@@ -4,15 +4,9 @@ on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
os:
|
||||
description: 'The operating system to use for the build ("linux", "macos", "windows").'
|
||||
description: 'The operating system to use for the build ("linux", "macos", "windows", or empty for all).'
|
||||
required: false
|
||||
type: string
|
||||
strategy_matrix:
|
||||
# TODO: Support additional strategies, e.g. "ubuntu" for generating all Ubuntu configurations.
|
||||
description: 'The strategy matrix to use for generating the configurations ("minimal", "all").'
|
||||
required: false
|
||||
type: string
|
||||
default: "minimal"
|
||||
outputs:
|
||||
matrix:
|
||||
description: "The generated strategy matrix."
|
||||
@@ -29,17 +23,16 @@ jobs:
|
||||
matrix: ${{ steps.generate.outputs.matrix }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
with:
|
||||
python-version: 3.13
|
||||
python-version: "3.13"
|
||||
|
||||
- name: Generate strategy matrix
|
||||
working-directory: .github/scripts/strategy-matrix
|
||||
id: generate
|
||||
env:
|
||||
GENERATE_CONFIG: ${{ inputs.os != '' && format('--config={0}.json', inputs.os) || '' }}
|
||||
GENERATE_OPTION: ${{ inputs.strategy_matrix == 'all' && '--all' || '' }}
|
||||
run: ./generate.py ${GENERATE_OPTION} ${GENERATE_CONFIG} >> "${GITHUB_OUTPUT}"
|
||||
GENERATE_CONFIG: ${{ inputs.os != '' && format('--config={0}', inputs.os) || '' }}
|
||||
run: ./generate.py ${GENERATE_CONFIG} >>"${GITHUB_OUTPUT}"
|
||||
|
||||
4
.github/workflows/reusable-upload-recipe.yml
vendored
4
.github/workflows/reusable-upload-recipe.yml
vendored
@@ -40,10 +40,10 @@ defaults:
|
||||
jobs:
|
||||
upload:
|
||||
runs-on: ubuntu-latest
|
||||
container: ghcr.io/xrplf/ci/ubuntu-noble:gcc-13-sha-5dd7158
|
||||
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-8abe82e
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
|
||||
- name: Generate build version number
|
||||
id: version
|
||||
|
||||
13
.github/workflows/upload-conan-deps.yml
vendored
13
.github/workflows/upload-conan-deps.yml
vendored
@@ -48,8 +48,6 @@ jobs:
|
||||
# Generate the strategy matrix to be used by the following job.
|
||||
generate-matrix:
|
||||
uses: ./.github/workflows/reusable-strategy-matrix.yml
|
||||
with:
|
||||
strategy_matrix: ${{ github.event_name == 'pull_request' && 'minimal' || 'all' }}
|
||||
|
||||
# Build and upload the dependencies for each configuration.
|
||||
run-upload-conan-deps:
|
||||
@@ -58,16 +56,15 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix: ${{ fromJson(needs.generate-matrix.outputs.matrix) }}
|
||||
max-parallel: 10
|
||||
runs-on: ${{ matrix.architecture.runner }}
|
||||
container: ${{ contains(matrix.architecture.platform, 'linux') && format('ghcr.io/xrplf/ci/{0}-{1}:{2}-{3}-sha-{4}', matrix.os.distro_name, matrix.os.distro_version, matrix.os.compiler_name, matrix.os.compiler_version, matrix.os.image_sha) || null }}
|
||||
container: ${{ matrix.image || null }}
|
||||
steps:
|
||||
- name: Cleanup workspace (macOS and Windows)
|
||||
if: ${{ runner.os == 'macOS' || runner.os == 'Windows' }}
|
||||
uses: XRPLF/actions/cleanup-workspace@c7d9ce5ebb03c752a354889ecd870cadfc2b1cd4
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
|
||||
- name: Prepare runner
|
||||
uses: XRPLF/actions/prepare-runner@90f11ee655d1687824fb8793db770477d52afbab
|
||||
@@ -83,6 +80,12 @@ jobs:
|
||||
with:
|
||||
subtract: ${{ env.NPROC_SUBTRACT }}
|
||||
|
||||
- name: Set compiler environment (Linux)
|
||||
if: ${{ runner.os == 'Linux' }}
|
||||
uses: ./.github/actions/set-compiler-env
|
||||
with:
|
||||
compiler: ${{ matrix.compiler }}
|
||||
|
||||
- name: Setup Conan
|
||||
env:
|
||||
SANITIZERS: ${{ matrix.sanitizers }}
|
||||
|
||||
@@ -37,37 +37,50 @@ repos:
|
||||
exclude: ^include/xrpl/protocol_autogen/(transactions|ledger_entries)/
|
||||
|
||||
- repo: https://github.com/pre-commit/mirrors-clang-format
|
||||
rev: cd481d7b0bfb5c7b3090c21846317f9a8262e891 # frozen: v22.1.0
|
||||
rev: dd18dad857d6133e90bbe478f4f2f22ec0030269 # frozen: v22.1.5
|
||||
hooks:
|
||||
- id: clang-format
|
||||
args: [--style=file]
|
||||
"types_or": [c++, c, proto]
|
||||
exclude: ^include/xrpl/protocol_autogen/(transactions|ledger_entries)/
|
||||
|
||||
- repo: https://github.com/BlankSpruce/gersemi
|
||||
rev: 0.26.0
|
||||
- repo: https://github.com/BlankSpruce/gersemi-pre-commit
|
||||
rev: faadd6a9d852369ca94f4d15b2404c967ba8cb01 # frozen: 0.27.6
|
||||
hooks:
|
||||
- id: gersemi
|
||||
|
||||
- repo: https://github.com/rbubley/mirrors-prettier
|
||||
rev: c2bc67fe8f8f549cc489e00ba8b45aa18ee713b1 # frozen: v3.8.1
|
||||
rev: 515f543f5718ebfd6ce22e16708bb32c68ff96e1 # frozen: v3.8.3
|
||||
hooks:
|
||||
- id: prettier
|
||||
args: [--end-of-line=auto]
|
||||
|
||||
- repo: https://github.com/psf/black-pre-commit-mirror
|
||||
rev: ea488cebbfd88a5f50b8bd95d5c829d0bb76feb8 # frozen: 26.1.0
|
||||
rev: 4160603246a6b365d4a2af661c6d71b0a0f50478 # frozen: 26.5.1
|
||||
hooks:
|
||||
- id: black
|
||||
|
||||
- repo: https://github.com/openstack/bashate
|
||||
rev: 5798d24d571676fc407e81df574c1ef57b520f23 # frozen: 2.1.1
|
||||
- repo: https://github.com/scop/pre-commit-shfmt
|
||||
rev: 05c1426671b9237fb5e1444dd63aa5731bec0dfb # frozen: v3.13.1-1
|
||||
hooks:
|
||||
- id: bashate
|
||||
args: ["--ignore=E006"]
|
||||
- id: shfmt
|
||||
args: [--write, --indent=4, --case-indent=true]
|
||||
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: format-inline-bash-workflows
|
||||
name: "format `run:` blocks in workflows/actions"
|
||||
entry: ./.github/scripts/format-inline-bash.py
|
||||
language: python
|
||||
files: ^\.github/(workflows|actions)/.*\.ya?ml$
|
||||
- id: format-inline-bash-markdown
|
||||
name: "format ```bash blocks in markdown"
|
||||
entry: ./.github/scripts/format-inline-bash.py
|
||||
language: python
|
||||
files: \.md$
|
||||
|
||||
- repo: https://github.com/streetsidesoftware/cspell-cli
|
||||
rev: a42085ade523f591dca134379a595e7859986445 # frozen: v9.7.0
|
||||
rev: 4643f154907327ee0a2c7038f0296e0dd77d9776 # frozen: v10.0.0
|
||||
hooks:
|
||||
- id: cspell # Spell check changed files
|
||||
exclude: |
|
||||
|
||||
15
BUILD.md
15
BUILD.md
@@ -151,8 +151,8 @@ git init
|
||||
git remote add origin git@github.com:XRPLF/conan-center-index.git
|
||||
git sparse-checkout init
|
||||
for recipe in "${recipes[@]}"; do
|
||||
echo "Checking out recipe '${recipe}'..."
|
||||
git sparse-checkout add recipes/${recipe}
|
||||
echo "Checking out recipe '${recipe}'..."
|
||||
git sparse-checkout add recipes/${recipe}
|
||||
done
|
||||
git fetch origin master
|
||||
git checkout master
|
||||
@@ -180,7 +180,7 @@ the new recipe will be automatically pulled from the official Conan Center.
|
||||
|
||||
If you see an error similar to the following after running `conan profile show`:
|
||||
|
||||
```bash
|
||||
```text
|
||||
ERROR: Invalid setting '17' is not a valid 'settings.compiler.version' value.
|
||||
Possible values are ['5.0', '5.1', '6.0', '6.1', '7.0', '7.3', '8.0', '8.1',
|
||||
'9.0', '9.1', '10.0', '11.0', '12.0', '13', '13.0', '13.1', '14', '14.0', '15',
|
||||
@@ -427,16 +427,19 @@ install ccache --version 4.11.3 --allow-downgrade`.
|
||||
Single-config generators:
|
||||
|
||||
```
|
||||
cmake --build .
|
||||
cmake --build . --parallel N
|
||||
```
|
||||
|
||||
Multi-config generators:
|
||||
|
||||
```
|
||||
cmake --build . --config Release
|
||||
cmake --build . --config Debug
|
||||
cmake --build . --config Release --parallel N
|
||||
cmake --build . --config Debug --parallel N
|
||||
```
|
||||
|
||||
Replace the `--parallel` parameter N with the desired number of parallel jobs. A common starting point is half of the number of available CPU
|
||||
cores.
|
||||
|
||||
5. Test xrpld.
|
||||
|
||||
Single-config generators:
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#!/bin/bash
|
||||
|
||||
if [[ $# -ne 1 || "$1" == "--help" || "$1" == "-h" ]]; then
|
||||
name=$( basename $0 )
|
||||
cat <<- USAGE
|
||||
name=$(basename $0)
|
||||
cat <<-USAGE
|
||||
Usage: $name <username>
|
||||
|
||||
Where <username> is the Github username of the upstream repo. e.g. XRPLF
|
||||
@@ -14,7 +14,7 @@ fi
|
||||
shift
|
||||
user="$1"
|
||||
# Get the origin URL. Expect it be an SSH-style URL
|
||||
origin=$( git remote get-url origin )
|
||||
origin=$(git remote get-url origin)
|
||||
if [[ "${origin}" == "" ]]; then
|
||||
echo Invalid origin remote >&2
|
||||
exit 1
|
||||
@@ -22,11 +22,11 @@ fi
|
||||
# echo "Origin: ${origin}"
|
||||
# Parse the origin
|
||||
ifs_orig="${IFS}"
|
||||
IFS=':' read remote originpath <<< "${origin}"
|
||||
IFS=':' read remote originpath <<<"${origin}"
|
||||
# echo "Remote: ${remote}, Originpath: ${originpath}"
|
||||
IFS='@' read sshuser server <<< "${remote}"
|
||||
IFS='@' read sshuser server <<<"${remote}"
|
||||
# echo "SSHUser: ${sshuser}, Server: ${server}"
|
||||
IFS='/' read originuser repo <<< "${originpath}"
|
||||
IFS='/' read originuser repo <<<"${originpath}"
|
||||
# echo "Originuser: ${originuser}, Repo: ${repo}"
|
||||
if [[ "${sshuser}" == "" || "${server}" == "" || "${originuser}" == "" || "${repo}" == "" ]]; then
|
||||
echo "Can't parse origin URL: ${origin}" >&2
|
||||
@@ -35,9 +35,9 @@ fi
|
||||
upstream="https://${server}/${user}/${repo}"
|
||||
upstreampush="${remote}:${user}/${repo}"
|
||||
upstreamgroup="upstream upstream-push"
|
||||
current=$( git remote get-url upstream 2>/dev/null )
|
||||
currentpush=$( git remote get-url upstream-push 2>/dev/null )
|
||||
currentgroup=$( git config remotes.upstreams )
|
||||
current=$(git remote get-url upstream 2>/dev/null)
|
||||
currentpush=$(git remote get-url upstream-push 2>/dev/null)
|
||||
currentgroup=$(git config remotes.upstreams)
|
||||
if [[ "${current}" == "${upstream}" ]]; then
|
||||
echo "Upstream already set up correctly. Skip"
|
||||
elif [[ -n "${current}" && "${current}" != "${upstream}" && "${current}" != "${upstreampush}" ]]; then
|
||||
@@ -45,9 +45,9 @@ elif [[ -n "${current}" && "${current}" != "${upstream}" && "${current}" != "${u
|
||||
else
|
||||
if [[ "${current}" == "${upstreampush}" ]]; then
|
||||
echo "Upstream set to dangerous push URL. Update."
|
||||
_run git remote rename upstream upstream-push || \
|
||||
_run git remote remove upstream
|
||||
currentpush=$( git remote get-url upstream-push 2>/dev/null )
|
||||
_run git remote rename upstream upstream-push ||
|
||||
_run git remote remove upstream
|
||||
currentpush=$(git remote get-url upstream-push 2>/dev/null)
|
||||
fi
|
||||
_run git remote add upstream "${upstream}"
|
||||
fi
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#!/bin/bash
|
||||
|
||||
if [[ $# -lt 3 || "$1" == "--help" || "$1" = "-h" ]]; then
|
||||
name=$( basename $0 )
|
||||
cat <<- USAGE
|
||||
name=$(basename $0)
|
||||
cat <<-USAGE
|
||||
Usage: $name workbranch base/branch user/branch [user/branch [...]]
|
||||
|
||||
* workbranch will be created locally from base/branch
|
||||
@@ -16,7 +16,7 @@ fi
|
||||
work="$1"
|
||||
shift
|
||||
|
||||
branches=( $( echo "${@}" | sed "s/:/\//" ) )
|
||||
branches=($(echo "${@}" | sed "s/:/\//"))
|
||||
base="${branches[0]}"
|
||||
unset branches[0]
|
||||
|
||||
@@ -24,10 +24,10 @@ set -e
|
||||
|
||||
users=()
|
||||
for b in "${branches[@]}"; do
|
||||
users+=( $( echo $b | cut -d/ -f1 ) )
|
||||
users+=($(echo $b | cut -d/ -f1))
|
||||
done
|
||||
|
||||
users=( $( printf '%s\n' "${users[@]}" | sort -u ) )
|
||||
users=($(printf '%s\n' "${users[@]}" | sort -u))
|
||||
|
||||
git fetch --multiple upstreams "${users[@]}"
|
||||
git checkout -B "$work" --no-track "$base"
|
||||
@@ -40,7 +40,7 @@ done
|
||||
# Make sure the commits look right
|
||||
git log --show-signature "$base..HEAD"
|
||||
|
||||
parts=( $( echo $base | sed "s/\// /" ) )
|
||||
parts=($(echo $base | sed "s/\// /"))
|
||||
repo="${parts[0]}"
|
||||
b="${parts[1]}"
|
||||
push=$repo
|
||||
@@ -50,7 +50,7 @@ fi
|
||||
if [[ "$repo" == "upstream" ]]; then
|
||||
repo="upstreams"
|
||||
fi
|
||||
cat << PUSH
|
||||
cat <<PUSH
|
||||
|
||||
-------------------------------------------------------------------
|
||||
This script will not push. Verify everything is correct, then push
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#!/bin/bash
|
||||
|
||||
if [[ $# -ne 3 || "$1" == "--help" || "$1" = "-h" ]]; then
|
||||
name=$( basename $0 )
|
||||
cat <<- USAGE
|
||||
name=$(basename $0)
|
||||
cat <<-USAGE
|
||||
Usage: $name workbranch base/branch version
|
||||
|
||||
* workbranch will be created locally from base/branch. If it exists,
|
||||
@@ -16,7 +16,7 @@ fi
|
||||
work="$1"
|
||||
shift
|
||||
|
||||
base=$( echo "$1" | sed "s/:/\//" )
|
||||
base=$(echo "$1" | sed "s/:/\//")
|
||||
shift
|
||||
|
||||
version=$1
|
||||
@@ -28,16 +28,16 @@ git fetch upstreams
|
||||
|
||||
git checkout -B "${work}" --no-track "${base}"
|
||||
|
||||
push=$( git rev-parse --abbrev-ref --symbolic-full-name '@{push}' \
|
||||
2>/dev/null ) || true
|
||||
push=$(git rev-parse --abbrev-ref --symbolic-full-name '@{push}' \
|
||||
2>/dev/null) || true
|
||||
if [[ "${push}" != "" ]]; then
|
||||
echo "Warning: ${push} may already exist."
|
||||
fi
|
||||
|
||||
build=$( find -name BuildInfo.cpp )
|
||||
sed 's/\(^.*versionString =\).*$/\1 "'${version}'"/' ${build} > version.cpp && \
|
||||
diff "${build}" version.cpp && exit 1 || \
|
||||
mv -vi version.cpp ${build}
|
||||
build=$(find -name BuildInfo.cpp)
|
||||
sed 's/\(^.*versionString =\).*$/\1 "'${version}'"/' ${build} >version.cpp &&
|
||||
diff "${build}" version.cpp && exit 1 ||
|
||||
mv -vi version.cpp ${build}
|
||||
|
||||
git diff
|
||||
|
||||
@@ -47,7 +47,7 @@ git commit -S -m "Set version to ${version}"
|
||||
|
||||
git log --oneline --first-parent ${base}^..
|
||||
|
||||
cat << PUSH
|
||||
cat <<PUSH
|
||||
|
||||
-------------------------------------------------------------------
|
||||
This script will not push. Verify everything is correct, then push
|
||||
|
||||
@@ -168,7 +168,13 @@ def main():
|
||||
if not os.environ.get("TIDY"):
|
||||
return 0
|
||||
|
||||
repo_root = Path(__file__).parent.parent
|
||||
repo_root = Path(
|
||||
subprocess.check_output(
|
||||
["git", "rev-parse", "--show-toplevel"],
|
||||
cwd=Path(__file__).parent,
|
||||
text=True,
|
||||
).strip()
|
||||
)
|
||||
files = staged_files(repo_root)
|
||||
if not files:
|
||||
return 0
|
||||
|
||||
@@ -953,6 +953,21 @@
|
||||
#
|
||||
# Optional keys for NuDB and RocksDB:
|
||||
#
|
||||
# cache_size Size of cache for database records. Default is 16384.
|
||||
# Setting this value to 0 will use the default value.
|
||||
#
|
||||
# cache_age Length of time in minutes to keep database records
|
||||
# cached. Default is 5 minutes. Setting this value to
|
||||
# 0 will use the default value.
|
||||
#
|
||||
# Note: if cache_size or cache_age is not specified,
|
||||
# default values will be used for the unspecified
|
||||
# parameter.
|
||||
#
|
||||
# Note: the cache will not be created if online_delete
|
||||
# is specified, because the rotating NodeStore does
|
||||
# not use this cache).
|
||||
#
|
||||
# fast_load Boolean. If set, load the last persisted ledger
|
||||
# from disk upon process start before syncing to
|
||||
# the network. This is likely to improve performance
|
||||
@@ -1466,10 +1481,7 @@ admin = 127.0.0.1
|
||||
protocol = http
|
||||
|
||||
[port_peer]
|
||||
# Many servers still use the legacy port of 51235, so for backward-compatibility
|
||||
# we maintain that port number here. However, for new servers we recommend
|
||||
# changing this to the default port of 2459.
|
||||
port = 51235
|
||||
port = 2459
|
||||
ip = 0.0.0.0
|
||||
# alternatively, to accept connections on IPv4 + IPv6, use:
|
||||
#ip = ::
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
include(isolate_headers)
|
||||
|
||||
function(xrpl_add_test name)
|
||||
set(target ${PROJECT_NAME}.test.${name})
|
||||
|
||||
file(
|
||||
GLOB_RECURSE sources
|
||||
CONFIGURE_DEPENDS
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/${name}/*.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/${name}.cpp"
|
||||
)
|
||||
add_executable(${target} ${ARGN} ${sources})
|
||||
|
||||
isolate_headers(
|
||||
${target}
|
||||
"${CMAKE_SOURCE_DIR}"
|
||||
"${CMAKE_SOURCE_DIR}/tests/${name}"
|
||||
PRIVATE
|
||||
)
|
||||
|
||||
add_test(NAME ${target} COMMAND ${target})
|
||||
endfunction()
|
||||
@@ -145,13 +145,39 @@ else()
|
||||
INTERFACE
|
||||
-rdynamic
|
||||
$<$<BOOL:${is_linux}>:-Wl,-z,relro,-z,now,--build-id>
|
||||
# link to static libc/c++ iff: * static option set and * NOT APPLE (AppleClang does not support static
|
||||
# libc/c++) and * NOT SANITIZERS (sanitizers typically don't work with static libc/c++)
|
||||
$<$<AND:$<BOOL:${static}>,$<NOT:$<BOOL:${APPLE}>>,$<NOT:$<BOOL:${SANITIZERS_ENABLED}>>>:
|
||||
# link to static libc/c++ if:
|
||||
# * static option set and
|
||||
# * NOT APPLE (AppleClang does not support static libc/c++)
|
||||
$<$<AND:$<BOOL:${static}>,$<NOT:$<BOOL:${APPLE}>>>:
|
||||
-static-libstdc++
|
||||
-static-libgcc
|
||||
>
|
||||
)
|
||||
|
||||
# Keep -stdlib=libstdc++ off the compile commands, but preserve it for linking.
|
||||
#
|
||||
# Conan turns `compiler.libcxx=libstdc++` into `-stdlib=libstdc++` and puts it in
|
||||
# CMAKE_CXX_FLAGS, which CMake passes to BOTH compile and link steps. On a normal Clang
|
||||
# the compile step consumes it while choosing the C++ stdlib include paths. The Nixpkgs
|
||||
# Clang wrapper supplies those paths itself (via -nostdinc++), so at compile time the
|
||||
# flag is unused -> Clang errors under our -Werror. At link time the flag IS consumed
|
||||
# (it selects the C++ runtime), so we move it there instead of dropping it entirely.
|
||||
get_filename_component(_cxx_real "${CMAKE_CXX_COMPILER}" REALPATH)
|
||||
if(
|
||||
_cxx_real MATCHES "^/nix/store/"
|
||||
AND is_linux
|
||||
AND is_clang
|
||||
AND CMAKE_CXX_FLAGS MATCHES "stdlib=libstdc"
|
||||
)
|
||||
string(
|
||||
REPLACE "-stdlib=libstdc++"
|
||||
""
|
||||
CMAKE_CXX_FLAGS
|
||||
"${CMAKE_CXX_FLAGS}"
|
||||
)
|
||||
string(STRIP "${CMAKE_CXX_FLAGS}" CMAKE_CXX_FLAGS)
|
||||
add_link_options($<$<LINK_LANGUAGE:CXX>:-stdlib=libstdc++>)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Antithesis instrumentation will only be built and deployed using machines running Linux.
|
||||
|
||||
@@ -47,7 +47,7 @@ setup_target_for_coverage_gcovr(
|
||||
"include/xrpl/beast/test"
|
||||
"include/xrpl/beast/unit_test"
|
||||
"${CMAKE_BINARY_DIR}/pb-xrpl.libpb"
|
||||
DEPENDENCIES xrpld xrpl.tests
|
||||
DEPENDENCIES xrpld xrpl_tests
|
||||
)
|
||||
|
||||
add_code_coverage_to_target(opts INTERFACE)
|
||||
|
||||
13
cmake/scripts/codegen/requirements.in
Normal file
13
cmake/scripts/codegen/requirements.in
Normal file
@@ -0,0 +1,13 @@
|
||||
# Python dependencies for XRP Ledger code generation scripts
|
||||
#
|
||||
# These packages are required to run the code generation scripts that
|
||||
# parse macro files and generate C++ wrapper classes.
|
||||
|
||||
# C preprocessor for Python - used to preprocess macro files
|
||||
pcpp>=1.30
|
||||
|
||||
# Parser combinator library - used to parse the macro DSL
|
||||
pyparsing>=3.0.0
|
||||
|
||||
# Template engine - used to generate C++ code from templates
|
||||
Mako>=1.2.2
|
||||
@@ -1,13 +1,105 @@
|
||||
# Python dependencies for XRP Ledger code generation scripts
|
||||
#
|
||||
# These packages are required to run the code generation scripts that
|
||||
# parse macro files and generate C++ wrapper classes.
|
||||
|
||||
# C preprocessor for Python - used to preprocess macro files
|
||||
pcpp>=1.30
|
||||
|
||||
# Parser combinator library - used to parse the macro DSL
|
||||
pyparsing>=3.0.0
|
||||
|
||||
# Template engine - used to generate C++ code from templates
|
||||
Mako>=1.2.2
|
||||
# This file was autogenerated by uv via the following command:
|
||||
# uv pip compile requirements.in --generate-hashes --output-file requirements.txt
|
||||
mako==1.3.12 \
|
||||
--hash=sha256:8f61569480282dbf557145ce441e4ba888be453c30989f879f0d652e39f53ea9 \
|
||||
--hash=sha256:9f778e93289bd410bb35daadeb4fc66d95a746f0b75777b942088b7fd7af550a
|
||||
# via -r requirements.in
|
||||
markupsafe==3.0.3 \
|
||||
--hash=sha256:0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f \
|
||||
--hash=sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a \
|
||||
--hash=sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf \
|
||||
--hash=sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19 \
|
||||
--hash=sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf \
|
||||
--hash=sha256:0f4b68347f8c5eab4a13419215bdfd7f8c9b19f2b25520968adfad23eb0ce60c \
|
||||
--hash=sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175 \
|
||||
--hash=sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219 \
|
||||
--hash=sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb \
|
||||
--hash=sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6 \
|
||||
--hash=sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab \
|
||||
--hash=sha256:15d939a21d546304880945ca1ecb8a039db6b4dc49b2c5a400387cdae6a62e26 \
|
||||
--hash=sha256:177b5253b2834fe3678cb4a5f0059808258584c559193998be2601324fdeafb1 \
|
||||
--hash=sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce \
|
||||
--hash=sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218 \
|
||||
--hash=sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634 \
|
||||
--hash=sha256:1ba88449deb3de88bd40044603fafffb7bc2b055d626a330323a9ed736661695 \
|
||||
--hash=sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad \
|
||||
--hash=sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73 \
|
||||
--hash=sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c \
|
||||
--hash=sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe \
|
||||
--hash=sha256:2a15a08b17dd94c53a1da0438822d70ebcd13f8c3a95abe3a9ef9f11a94830aa \
|
||||
--hash=sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559 \
|
||||
--hash=sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa \
|
||||
--hash=sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37 \
|
||||
--hash=sha256:3537e01efc9d4dccdf77221fb1cb3b8e1a38d5428920e0657ce299b20324d758 \
|
||||
--hash=sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f \
|
||||
--hash=sha256:38664109c14ffc9e7437e86b4dceb442b0096dfe3541d7864d9cbe1da4cf36c8 \
|
||||
--hash=sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d \
|
||||
--hash=sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c \
|
||||
--hash=sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97 \
|
||||
--hash=sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a \
|
||||
--hash=sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19 \
|
||||
--hash=sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9 \
|
||||
--hash=sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9 \
|
||||
--hash=sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc \
|
||||
--hash=sha256:591ae9f2a647529ca990bc681daebdd52c8791ff06c2bfa05b65163e28102ef2 \
|
||||
--hash=sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4 \
|
||||
--hash=sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354 \
|
||||
--hash=sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50 \
|
||||
--hash=sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698 \
|
||||
--hash=sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9 \
|
||||
--hash=sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b \
|
||||
--hash=sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc \
|
||||
--hash=sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115 \
|
||||
--hash=sha256:7c3fb7d25180895632e5d3148dbdc29ea38ccb7fd210aa27acbd1201a1902c6e \
|
||||
--hash=sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485 \
|
||||
--hash=sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f \
|
||||
--hash=sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12 \
|
||||
--hash=sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025 \
|
||||
--hash=sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009 \
|
||||
--hash=sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d \
|
||||
--hash=sha256:949b8d66bc381ee8b007cd945914c721d9aba8e27f71959d750a46f7c282b20b \
|
||||
--hash=sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a \
|
||||
--hash=sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5 \
|
||||
--hash=sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f \
|
||||
--hash=sha256:a320721ab5a1aba0a233739394eb907f8c8da5c98c9181d1161e77a0c8e36f2d \
|
||||
--hash=sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1 \
|
||||
--hash=sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287 \
|
||||
--hash=sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6 \
|
||||
--hash=sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f \
|
||||
--hash=sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581 \
|
||||
--hash=sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed \
|
||||
--hash=sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b \
|
||||
--hash=sha256:c0c0b3ade1c0b13b936d7970b1d37a57acde9199dc2aecc4c336773e1d86049c \
|
||||
--hash=sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026 \
|
||||
--hash=sha256:c4ffb7ebf07cfe8931028e3e4c85f0357459a3f9f9490886198848f4fa002ec8 \
|
||||
--hash=sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676 \
|
||||
--hash=sha256:d2ee202e79d8ed691ceebae8e0486bd9a2cd4794cec4824e1c99b6f5009502f6 \
|
||||
--hash=sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e \
|
||||
--hash=sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d \
|
||||
--hash=sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d \
|
||||
--hash=sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01 \
|
||||
--hash=sha256:df2449253ef108a379b8b5d6b43f4b1a8e81a061d6537becd5582fba5f9196d7 \
|
||||
--hash=sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419 \
|
||||
--hash=sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795 \
|
||||
--hash=sha256:e2103a929dfa2fcaf9bb4e7c091983a49c9ac3b19c9061b6d5427dd7d14d81a1 \
|
||||
--hash=sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5 \
|
||||
--hash=sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d \
|
||||
--hash=sha256:e8fc20152abba6b83724d7ff268c249fa196d8259ff481f3b1476383f8f24e42 \
|
||||
--hash=sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe \
|
||||
--hash=sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda \
|
||||
--hash=sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e \
|
||||
--hash=sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737 \
|
||||
--hash=sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523 \
|
||||
--hash=sha256:f42d0984e947b8adf7dd6dde396e720934d12c506ce84eea8476409563607591 \
|
||||
--hash=sha256:f71a396b3bf33ecaa1626c255855702aca4d3d9fea5e051b41ac59a9c1c41edc \
|
||||
--hash=sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a \
|
||||
--hash=sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50
|
||||
# via mako
|
||||
pcpp==1.30 \
|
||||
--hash=sha256:05fe08292b6da57f385001c891a87f40d6aa7f46787b03e8ba326d20a3297c6e \
|
||||
--hash=sha256:5af9fbce55f136d7931ae915fae03c34030a3b36c496e72d9636cedc8e2543a1
|
||||
# via -r requirements.in
|
||||
pyparsing==3.3.2 \
|
||||
--hash=sha256:850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d \
|
||||
--hash=sha256:c777f4d763f140633dcb6d8a3eda953bf7a214dc4eff598413c070bcdc117cbc
|
||||
# via -r requirements.in
|
||||
|
||||
@@ -33,7 +33,7 @@ public:
|
||||
* @brief Construct a ${name} ledger entry wrapper from an existing SLE object.
|
||||
* @throws std::runtime_error if the ledger entry type doesn't match.
|
||||
*/
|
||||
explicit ${name}(std::shared_ptr<SLE const> sle)
|
||||
explicit ${name}(SLE::const_pointer sle)
|
||||
: LedgerEntryBase(std::move(sle))
|
||||
{
|
||||
// Verify ledger entry type
|
||||
@@ -168,7 +168,7 @@ ${field['typeData']['setter_type']} ${field['paramName']}${',' if i < len(requir
|
||||
* @param sle The existing ledger entry to copy from.
|
||||
* @throws std::runtime_error if the ledger entry type doesn't match.
|
||||
*/
|
||||
${name}Builder(std::shared_ptr<SLE const> sle)
|
||||
${name}Builder(SLE::const_pointer sle)
|
||||
{
|
||||
if (sle->at(sfLedgerEntryType) != ${tag})
|
||||
{
|
||||
|
||||
@@ -1 +1,8 @@
|
||||
{% set os = detect_api.detect_os() %}
|
||||
include(sanitizers)
|
||||
|
||||
[conf]
|
||||
{% if os == "Linux" %}
|
||||
user.package:libc_version=2.31
|
||||
tools.info.package_id:confs+=["user.package:libc_version"]
|
||||
{% endif %}
|
||||
|
||||
@@ -50,6 +50,7 @@ words:
|
||||
- AMMXRP
|
||||
- amt
|
||||
- amts
|
||||
- archs
|
||||
- asnode
|
||||
- asynchrony
|
||||
- attestation
|
||||
@@ -93,6 +94,7 @@ words:
|
||||
- daria
|
||||
- dcmake
|
||||
- dearmor
|
||||
- dedented
|
||||
- deleteme
|
||||
- demultiplexer
|
||||
- deserializaton
|
||||
@@ -133,6 +135,7 @@ words:
|
||||
- iou
|
||||
- ious
|
||||
- isrdc
|
||||
- isystem
|
||||
- itype
|
||||
- jemalloc
|
||||
- jlog
|
||||
@@ -199,11 +202,13 @@ words:
|
||||
- nonxrp
|
||||
- noreplace
|
||||
- noripple
|
||||
- nostdinc
|
||||
- notifempty
|
||||
- nudb
|
||||
- nullptr
|
||||
- nunl
|
||||
- Nyffenegger
|
||||
- onlatest
|
||||
- ostr
|
||||
- pargs
|
||||
- partitioner
|
||||
@@ -254,6 +259,7 @@ words:
|
||||
- sfields
|
||||
- shamap
|
||||
- shamapitem
|
||||
- shfmt
|
||||
- shlibs
|
||||
- sidechain
|
||||
- SIGGOOD
|
||||
@@ -298,6 +304,7 @@ words:
|
||||
- unauthorizing
|
||||
- unergonomic
|
||||
- unfetched
|
||||
- unfindable
|
||||
- unflatten
|
||||
- unfund
|
||||
- unimpair
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
ARG BASE_IMAGE=nixos/nix:latest
|
||||
|
||||
# Nix builder
|
||||
FROM nixos/nix:latest AS builder-source
|
||||
|
||||
RUN mkdir -p ~/.config/nix && \
|
||||
echo "experimental-features = nix-command flakes" >> ~/.config/nix/nix.conf
|
||||
|
||||
# Copy our source and setup our working dir.
|
||||
COPY nix/ci-env.nix /tmp/build/nix/ci-env.nix
|
||||
COPY nix/packages.nix /tmp/build/nix/packages.nix
|
||||
COPY nix/utils.nix /tmp/build/nix/utils.nix
|
||||
COPY flake.nix /tmp/build/
|
||||
COPY flake.lock /tmp/build/
|
||||
WORKDIR /tmp/build
|
||||
|
||||
FROM builder-source AS builder
|
||||
|
||||
# Build our Nix CI environment (all build tools in a single store path)
|
||||
RUN nix \
|
||||
--option filter-syscalls false \
|
||||
build
|
||||
|
||||
# Copy the Nix store closure into a directory. The Nix store closure is the
|
||||
# entire set of Nix store values that we need for our build.
|
||||
RUN mkdir /tmp/nix-store-closure && \
|
||||
cp -R $(nix-store -qR result/) /tmp/nix-store-closure
|
||||
|
||||
# Final image
|
||||
FROM ${BASE_IMAGE}
|
||||
|
||||
# bash is not located at /bin/bash in nixos/nix, so we need to create a symlink to it.
|
||||
RUN if [ -d /nix ]; then \
|
||||
ln -s /root/.nix-profile/bin/bash /bin/bash; \
|
||||
fi
|
||||
|
||||
# Use Bash as the default shell for RUN commands, using the options
|
||||
# `set -o errexit -o pipefail`, and as the entrypoint.
|
||||
SHELL ["/bin/bash", "-e", "-o", "pipefail", "-c"]
|
||||
ENTRYPOINT ["/bin/bash"]
|
||||
|
||||
# Copy /nix/store and the env symlink tree
|
||||
COPY --from=builder /tmp/nix-store-closure /nix/store
|
||||
COPY --from=builder /tmp/build/result /nix/ci-env
|
||||
|
||||
ENV PATH="/nix/ci-env/bin:$PATH"
|
||||
|
||||
RUN <<EOF
|
||||
ccache --version
|
||||
clang-format --version
|
||||
cmake --version
|
||||
conan --version
|
||||
g++ --version
|
||||
gcc --version
|
||||
gcovr --version
|
||||
git --version
|
||||
make --version
|
||||
mold --version
|
||||
ninja --version
|
||||
perl --version
|
||||
pkg-config --version
|
||||
pre-commit --version
|
||||
python3 --version
|
||||
run-clang-tidy --help
|
||||
vim --version
|
||||
EOF
|
||||
10
flake.lock
generated
10
flake.lock
generated
@@ -2,11 +2,11 @@
|
||||
"nodes": {
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1777954456,
|
||||
"narHash": "sha256-hGdgeU2Nk87RAuZyYjyDjFL6LK7dAZN5RE9+hrDTkDU=",
|
||||
"lastModified": 1780243769,
|
||||
"narHash": "sha256-x5UQuRsH3MqI0U9afaXSNqzTPSeZlRLvFAav2Ux1pNw=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "549bd84d6279f9852cae6225e372cc67fb91a4c1",
|
||||
"rev": "331800de5053fcebacf6813adb5db9c9dca22a0c",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -15,7 +15,7 @@
|
||||
"type": "indirect"
|
||||
}
|
||||
},
|
||||
"nixpkgs-glibc231": {
|
||||
"nixpkgs-custom-glibc": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1593520194,
|
||||
@@ -35,7 +35,7 @@
|
||||
"root": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs",
|
||||
"nixpkgs-glibc231": "nixpkgs-glibc231"
|
||||
"nixpkgs-custom-glibc": "nixpkgs-custom-glibc"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -6,16 +6,16 @@
|
||||
# version — matches the system libc on Ubuntu 20.04 LTS. Imported
|
||||
# manually (flake = false) because this revision predates nixpkgs'
|
||||
# own flake.nix.
|
||||
nixpkgs-glibc231 = {
|
||||
nixpkgs-custom-glibc = {
|
||||
url = "github:NixOS/nixpkgs/9cd98386a38891d1074fc18036b842dc4416f562";
|
||||
flake = false;
|
||||
};
|
||||
};
|
||||
|
||||
outputs =
|
||||
{ nixpkgs, nixpkgs-glibc231, ... }:
|
||||
{ nixpkgs, nixpkgs-custom-glibc, ... }:
|
||||
let
|
||||
forEachSystem = import ./nix/utils.nix { inherit nixpkgs nixpkgs-glibc231; };
|
||||
forEachSystem = import ./nix/utils.nix { inherit nixpkgs nixpkgs-custom-glibc; };
|
||||
in
|
||||
{
|
||||
devShells = forEachSystem (import ./nix/devshell.nix);
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
{
|
||||
"args": [
|
||||
{
|
||||
"lineno": 13,
|
||||
"name": "src"
|
||||
},
|
||||
{
|
||||
"lineno": 13,
|
||||
"name": "dst"
|
||||
}
|
||||
],
|
||||
"classes": [],
|
||||
"description": "Header file declaring a function to extract a tar archive compressed with lz4 using Boost Filesystem, within the xrpl namespace.",
|
||||
"file_path": "workflow/XRPLF-rippled-develop/source/include/xrpl/basics/Archive.h",
|
||||
"functions": [
|
||||
{
|
||||
"args": [
|
||||
"src",
|
||||
"dst"
|
||||
],
|
||||
"lineno": 13,
|
||||
"name": "extractTarLz4"
|
||||
}
|
||||
],
|
||||
"language": "c header",
|
||||
"namespaces": [
|
||||
{
|
||||
"lineno": 4,
|
||||
"name": "xrpl"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
# `Archive.h` — Tar/LZ4 Archive Extraction
|
||||
|
||||
This header declares a single utility function within the `xrpl` namespace: `extractTarLz4`. Its purpose is narrowly scoped — providing the XRPL node software with the ability to unpack `.tar.lz4` archives to a target directory at runtime. The most natural use case is ledger database bootstrapping, where a node downloads a pre-built snapshot of the ledger state rather than replaying the entire transaction history from genesis.
|
||||
|
||||
## The Interface
|
||||
|
||||
```cpp
|
||||
void extractTarLz4(
|
||||
boost::filesystem::path const& src,
|
||||
boost::filesystem::path const& dst);
|
||||
```
|
||||
|
||||
Both parameters are `boost::filesystem::path` rather than `std::string` or `std::filesystem::path`. This is consistent with the broader `xrpl/basics` module (see `FileUtilities.h`), which predates C++17's standard filesystem library and relies on Boost.Filesystem throughout. The function throws `std::runtime_error` on any failure — there is no return value to check or error code to inspect.
|
||||
|
||||
## Implementation Design
|
||||
|
||||
The implementation in `Archive.cpp` delegates all archive I/O to **libarchive**, a portable C library (`<archive.h>`, `<archive_entry.h>`). This is a deliberate choice over rolling a custom tar/lz4 parser: libarchive handles format detection, streaming decompression, and sparse file support in a well-tested, security-audited way.
|
||||
|
||||
Resource management for the two libarchive handles — a reader (`ar`) and a disk writer (`aw`) — is handled via `std::unique_ptr` with custom deleters that call `archive_read_free` and `archive_write_free` respectively. This is the only safe pattern here: libarchive resources must be released even when intermediate steps throw, and wrapping them in `unique_ptr` ensures cleanup happens automatically as the stack unwinds.
|
||||
|
||||
The reader is configured explicitly for the tar format and the lz4 filter (rather than using libarchive's auto-detection). This prevents the function from silently accepting other archive formats, keeping the interface contract tight. The file is opened with a 10240-byte block size, which matches the canonical recommendation in libarchive documentation.
|
||||
|
||||
The disk writer is configured with `ARCHIVE_EXTRACT_TIME | ARCHIVE_EXTRACT_PERM | ARCHIVE_EXTRACT_ACL | ARCHIVE_EXTRACT_FFLAGS`, meaning extracted files faithfully preserve timestamps, permissions, access control lists, and BSD file flags from the archive. For a snapshot intended to be a drop-in replacement for a live ledger database directory, this fidelity matters: the consuming software may rely on mtime or permission bits being intact.
|
||||
|
||||
A non-obvious detail is the pathname rewriting on line 65: before writing each entry to disk, the function prepends `dst` to the entry's stored path using Boost.Filesystem's `/` operator. This is what places all extracted content under `dst` rather than at absolute paths embedded in the archive, and it prevents path traversal issues where a maliciously constructed archive might attempt to write files outside the intended directory tree.
|
||||
|
||||
## Error Handling
|
||||
|
||||
All errors are surfaced through `xrpl::Throw<std::runtime_error>`, defined in `contract.h`. Unlike a raw `throw`, `Throw` first calls `LogThrow` to capture a stack trace before the exception propagates. This means extraction failures produce actionable diagnostics in the node's log — important for diagnosing corrupted snapshots or filesystem problems during a bootstrap operation that might otherwise appear as a silent crash.
|
||||
|
||||
The function validates `src` is a regular file (not a directory or symlink) before opening it, providing a clear early error rather than letting libarchive fail with a less informative message.
|
||||
@@ -1,324 +0,0 @@
|
||||
{
|
||||
"args": [
|
||||
{
|
||||
"lineno": 15,
|
||||
"name": "name"
|
||||
},
|
||||
{
|
||||
"lineno": 0,
|
||||
"name": "src"
|
||||
},
|
||||
{
|
||||
"lineno": 0,
|
||||
"name": "dst"
|
||||
},
|
||||
{
|
||||
"lineno": 45,
|
||||
"name": "value"
|
||||
},
|
||||
{
|
||||
"lineno": 66,
|
||||
"name": "key"
|
||||
},
|
||||
{
|
||||
"lineno": 72,
|
||||
"name": "lines"
|
||||
},
|
||||
{
|
||||
"lineno": 78,
|
||||
"name": "line"
|
||||
},
|
||||
{
|
||||
"lineno": 94,
|
||||
"name": "other"
|
||||
},
|
||||
{
|
||||
"lineno": 156,
|
||||
"name": "section"
|
||||
},
|
||||
{
|
||||
"lineno": 193,
|
||||
"name": "sectionName"
|
||||
},
|
||||
{
|
||||
"lineno": 211,
|
||||
"name": "ifs"
|
||||
},
|
||||
{
|
||||
"lineno": 220,
|
||||
"name": "target"
|
||||
},
|
||||
{
|
||||
"lineno": 235,
|
||||
"name": "defaultValue"
|
||||
},
|
||||
{
|
||||
"lineno": 272,
|
||||
"name": "v"
|
||||
}
|
||||
],
|
||||
"classes": [
|
||||
{
|
||||
"args": [
|
||||
"name"
|
||||
],
|
||||
"lineno": 13,
|
||||
"name": "Section"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 140,
|
||||
"name": "BasicConfig"
|
||||
}
|
||||
],
|
||||
"description": "Defines classes and utility functions for handling configuration sections and key/value pairs, including parsing, storing, and retrieving configuration data for the xrpl project.",
|
||||
"file_path": "workflow/XRPLF-rippled-develop/source/include/xrpl/basics/BasicConfig.h",
|
||||
"functions": [
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 23,
|
||||
"name": "Section::name"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 30,
|
||||
"name": "Section::lines"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 37,
|
||||
"name": "Section::values"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"value"
|
||||
],
|
||||
"lineno": 44,
|
||||
"name": "Section::legacy"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 53,
|
||||
"name": "Section::legacy"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"key",
|
||||
"value"
|
||||
],
|
||||
"lineno": 65,
|
||||
"name": "Section::set"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"lines"
|
||||
],
|
||||
"lineno": 71,
|
||||
"name": "Section::append"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"line"
|
||||
],
|
||||
"lineno": 77,
|
||||
"name": "Section::append"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"name"
|
||||
],
|
||||
"lineno": 82,
|
||||
"name": "Section::exists"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"name"
|
||||
],
|
||||
"lineno": 85,
|
||||
"name": "Section::get"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"name",
|
||||
"other"
|
||||
],
|
||||
"lineno": 93,
|
||||
"name": "Section::value_or"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 101,
|
||||
"name": "Section::had_trailing_comments"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 110,
|
||||
"name": "Section::empty"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 115,
|
||||
"name": "Section::size"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 120,
|
||||
"name": "Section::begin"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 125,
|
||||
"name": "Section::cbegin"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 130,
|
||||
"name": "Section::end"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 135,
|
||||
"name": "Section::cend"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"name"
|
||||
],
|
||||
"lineno": 151,
|
||||
"name": "BasicConfig::exists"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"name"
|
||||
],
|
||||
"lineno": 155,
|
||||
"name": "BasicConfig::section"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"name"
|
||||
],
|
||||
"lineno": 158,
|
||||
"name": "BasicConfig::section"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"name"
|
||||
],
|
||||
"lineno": 161,
|
||||
"name": "BasicConfig::operator[]"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"name"
|
||||
],
|
||||
"lineno": 165,
|
||||
"name": "BasicConfig::operator[]"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"section",
|
||||
"key",
|
||||
"value"
|
||||
],
|
||||
"lineno": 171,
|
||||
"name": "BasicConfig::overwrite"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"section"
|
||||
],
|
||||
"lineno": 176,
|
||||
"name": "BasicConfig::deprecatedClearSection"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"section",
|
||||
"value"
|
||||
],
|
||||
"lineno": 183,
|
||||
"name": "BasicConfig::legacy"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"sectionName"
|
||||
],
|
||||
"lineno": 192,
|
||||
"name": "BasicConfig::legacy"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 201,
|
||||
"name": "BasicConfig::had_trailing_comments"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"ifs"
|
||||
],
|
||||
"lineno": 210,
|
||||
"name": "BasicConfig::build"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"target",
|
||||
"name",
|
||||
"section"
|
||||
],
|
||||
"lineno": 219,
|
||||
"name": "set"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"target",
|
||||
"defaultValue",
|
||||
"name",
|
||||
"section"
|
||||
],
|
||||
"lineno": 234,
|
||||
"name": "set"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"section",
|
||||
"name",
|
||||
"defaultValue"
|
||||
],
|
||||
"lineno": 247,
|
||||
"name": "get"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"section",
|
||||
"name",
|
||||
"defaultValue"
|
||||
],
|
||||
"lineno": 260,
|
||||
"name": "get"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"section",
|
||||
"name",
|
||||
"v"
|
||||
],
|
||||
"lineno": 271,
|
||||
"name": "get_if_exists"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"section",
|
||||
"name",
|
||||
"v"
|
||||
],
|
||||
"lineno": 277,
|
||||
"name": "get_if_exists<bool>"
|
||||
}
|
||||
],
|
||||
"language": "c header",
|
||||
"namespaces": [
|
||||
{
|
||||
"lineno": 10,
|
||||
"name": "xrpl"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
# `BasicConfig.h` — INI-Style Configuration Substrate
|
||||
|
||||
`BasicConfig.h` defines the foundational data model for the XRPL node's configuration system. It sits at the bottom of a two-layer design: this file provides the in-memory representation and query interface for section-based configuration data, while the concrete `Config` class (in `src/xrpld/core/Config.h`) inherits from `BasicConfig` and adds filesystem loading, application-specific typed fields, and validator management. The header comment on `Config` explicitly labels that derived class as deprecated, signaling that `BasicConfig`'s style — decentralized, per-module parsing — is the intended long-term direction.
|
||||
|
||||
## Data Model: Two Representations in One `Section`
|
||||
|
||||
The `Section` class maintains three parallel containers for the same underlying config content:
|
||||
|
||||
- `lookup_` — an `unordered_map<string, string>` for `key = value` pairs, used for named lookups
|
||||
- `values_` — a `vector<string>` of non-key-value lines (bare tokens like IP addresses or file paths)
|
||||
- `lines_` — a `vector<string>` containing every non-empty, non-comment line in canonical form
|
||||
|
||||
This triple storage isn't redundancy — it reflects the two distinct ways config sections are used in practice. Sections like `[server]` contain key=value pairs consumed by name; sections like `[validators]` contain bare values (one per line) iterated as a list. The `lines()` accessor preserves insertion order, which matters for list-type sections where positional meaning exists.
|
||||
|
||||
The `append()` method is where parsing happens. It applies a Boost regex matching `^key=value` to each incoming line. Lines that match go into `lookup_` via `set()`; non-matching lines go into `values_`. Both go into `lines_`. The same method also handles inline comment stripping: `#` characters are treated as comment delimiters unless escaped with `\`. The escape character is consumed when found (`val.erase(comment - 1, 1)`), allowing literal `#` characters in values. This detail is tracked via `had_trailing_comments_`, which bubbles up through `BasicConfig::had_trailing_comments()` via `std::any_of` — presumably to emit a deprecation warning to operators about ambiguous config syntax.
|
||||
|
||||
## The "Legacy" Pattern
|
||||
|
||||
Some older config sections hold a single freeform value rather than key-value pairs — for example `[node_db]` in its pre-structured form. The `legacy()` getter/setter pair accommodates this by treating the first entry of `lines_` as the canonical value. Reading a `Section` as legacy on a multi-line section intentionally throws `std::runtime_error` via `Throw<>()`, enforcing that this access path is only valid for single-line sections. This prevents silent misreads where code expecting one value silently gets only the first of many.
|
||||
|
||||
`BasicConfig` also exposes `legacy()` at the aggregate level, forwarding to the named section's `legacy()`. This provides `config.legacy("section_name")` as a convenience for the many legacy callsites in `Config.cpp`.
|
||||
|
||||
## `BasicConfig`: Container and Access Protocol
|
||||
|
||||
`BasicConfig` holds an `unordered_map<string, Section>`, keyed by section name. The critical behavioral difference between the const and non-const `section()` overloads reflects a deliberate design choice:
|
||||
|
||||
- Non-const `section()` calls `map_.emplace(name, name)` — it auto-creates an empty section on first access. This allows callers to unconditionally call `config["new_section"].set(...)` without precondition checks.
|
||||
- Const `section()` returns a reference to a `static Section const none("")` sentinel when the section doesn't exist. This avoids exceptions during read-only configuration queries and makes `operator[]` safe to call on a const `BasicConfig` even for absent sections.
|
||||
|
||||
The `overwrite()` method is specifically for command-line argument injection, layering CLI-provided values over whatever the config file contains. `deprecatedClearSection()` (name signals intent) wipes a section's content by replacing its `Section` object wholesale — used historically to clear sections before reloading.
|
||||
|
||||
The `build()` method is `protected`, not `public`. It consumes an `IniFileSections` (a `unordered_map<string, vector<string>>`), which is the raw pre-parsed form produced by `parseIniFile()` in `Config.cpp`. Subclasses call `build()` after obtaining this intermediate representation, keeping the file I/O and INI parsing out of `BasicConfig` itself.
|
||||
|
||||
## Free Function Query Layer
|
||||
|
||||
The file exports three sets of free functions designed for module-level configuration consumption:
|
||||
|
||||
`set(target, name, section)` reads a named key, casts it via `boost::lexical_cast<T>`, and assigns to `target` only on success — leaving `target` unchanged on missing key or bad cast. The two-argument variant adds an explicit default value applied on failure. Both return `bool` indicating whether the config file actually specified the value, which is important for distinguishing "user set this to the default" from "user didn't set this."
|
||||
|
||||
`get(section, name, defaultValue)` is a value-returning variant; it catches `bad_lexical_cast` and falls back to the default silently. An overload handles `char const*` defaults to avoid awkward template deduction with string literals.
|
||||
|
||||
`get_if_exists<bool>` is explicitly specialized to read boolean config values as integers (`0` or `1`) rather than as the string tokens `"true"` or `"false"`. This matches the XRPL config file convention where booleans are expressed numerically, and avoids `lexical_cast<bool>` which in Boost accepts `"true"` but not `"1"` depending on locale.
|
||||
|
||||
Together these three free functions provide a consistent, exception-safe pattern that modules throughout the codebase use to pull typed values from their respective config sections without having to handle parse failures individually.
|
||||
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"args": [],
|
||||
"classes": [],
|
||||
"description": "Defines a type alias 'Blob' for storing linear binary data as a vector of unsigned char within the xrpl namespace.",
|
||||
"file_path": "workflow/XRPLF-rippled-develop/source/include/xrpl/basics/Blob.h",
|
||||
"functions": [],
|
||||
"language": "c header",
|
||||
"namespaces": [
|
||||
{
|
||||
"lineno": 4,
|
||||
"name": "xrpl"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
# `include/xrpl/basics/Blob.h`
|
||||
|
||||
`Blob.h` introduces a single named type alias used throughout the XRPL codebase for owning, mutable binary data:
|
||||
|
||||
```cpp
|
||||
using Blob = std::vector<unsigned char>;
|
||||
```
|
||||
|
||||
Its role is to give raw byte sequences a meaningful, searchable name rather than leaving `std::vector<unsigned char>` scattered as an anonymous type across the protocol and serialization layers. `Blob` appears as the internal storage buffer inside `Serializer` (`mData`), as the return type of serialization helpers, and in `StringUtilities` for hex encoding and SQL blob literals.
|
||||
|
||||
`Blob` sits at one corner of the three-type binary data model in `xrpl::basics`:
|
||||
|
||||
- **`Blob`** (`std::vector<unsigned char>`) — mutable, dynamically resizable, owns its memory. The right choice when data is built up incrementally, as in `Serializer`.
|
||||
- **`Buffer`** — fixed-size block allocated with `unique_ptr<uint8_t[]>`, no capacity overhead, suitable when size is known upfront and resizing is not required.
|
||||
- **`Slice`** — a non-owning, read-only `(pointer, length)` view. Cheap to copy and pass; `makeSlice()` factory overloads accept both `Blob` and `Buffer` seamlessly.
|
||||
|
||||
The choice of `unsigned char` rather than `char` is deliberate: it avoids signed/unsigned arithmetic warnings when working with raw binary values and aligns with the `uint8_t` element type used by `Slice` and `Buffer`. Because `Blob` is simply a `std::vector`, callers get the full standard iterator interface, `push_back`, `resize`, and range-insert without any additional wrapper API.
|
||||
@@ -1,42 +0,0 @@
|
||||
{
|
||||
"args": [],
|
||||
"classes": [
|
||||
{
|
||||
"args": [
|
||||
"size",
|
||||
"data",
|
||||
"other",
|
||||
"s"
|
||||
],
|
||||
"lineno": 10,
|
||||
"name": "Buffer"
|
||||
}
|
||||
],
|
||||
"description": "Defines a Buffer class for managing dynamic byte arrays, similar to std::vector<char> but optimized for use as a BufferFactory, including copy/move semantics, assignment from slices, and comparison operators.",
|
||||
"file_path": "workflow/XRPLF-rippled-develop/source/include/xrpl/basics/Buffer.h",
|
||||
"functions": [
|
||||
{
|
||||
"args": [
|
||||
"lhs",
|
||||
"rhs"
|
||||
],
|
||||
"lineno": 120,
|
||||
"name": "operator=="
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"lhs",
|
||||
"rhs"
|
||||
],
|
||||
"lineno": 130,
|
||||
"name": "operator!="
|
||||
}
|
||||
],
|
||||
"language": "c header",
|
||||
"namespaces": [
|
||||
{
|
||||
"lineno": 7,
|
||||
"name": "xrpl"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
# `include/xrpl/basics/Buffer.h`
|
||||
|
||||
## Role in the System
|
||||
|
||||
`Buffer` is the XRPL codebase's canonical owning byte container. It occupies a distinct position alongside two other byte-handling types: `Slice`, which is a non-owning, immutable view into existing memory, and `Blob` (a typedef for `std::vector<unsigned char>`), which is a general-purpose growable sequence. `Buffer` fills the gap between these: it owns its memory exclusively, is mutable, but makes no provision for incremental growth. When you need to allocate a block of bytes, write into it once, and pass it around by move, `Buffer` is the right tool.
|
||||
|
||||
The class also satisfies an informal `BufferFactory` concept used by compression utilities — a callable that accepts a size and returns a `void*` to writable memory. This dual role as both a container and an allocator-callback is the most distinctive design choice in the file.
|
||||
|
||||
## Ownership and Internal Layout
|
||||
|
||||
The backing store is a `std::unique_ptr<std::uint8_t[]>`, giving the class clear exclusive ownership with automatic deallocation. The invariant enforced throughout is that an empty buffer (`size_ == 0`) always holds a null pointer — never a zero-byte allocation. This is visible in the size constructor: `new std::uint8_t[size]` is called only when `size` is non-zero, and `alloc()` resets to `nullptr` if `n == 0`. The test suite verifies this invariant explicitly via its `sane()` helper, which asserts `data() == nullptr` iff `empty()`. Treating null as the canonical empty state avoids any ambiguity at the call site and makes zero-initialization checks safe without checking both pointer and size.
|
||||
|
||||
## The `alloc()` Pattern — Discard, Don't Resize
|
||||
|
||||
The central API difference from `std::vector` is `alloc(std::size_t n)`, which reallocates the buffer to exactly `n` bytes and discards any existing content. Unlike `vector::resize()`, there is no attempt to preserve data. This is intentional: the primary workload for `Buffer` is receiving output from operations like decompression, where the caller pre-computes the required size and wants a fresh block to write into. Reallocation is skipped entirely if the requested size equals the current size, avoiding a pointless free/alloc cycle when the same `Buffer` is reused across calls of equal output length.
|
||||
|
||||
The `operator()(std::size_t n)` overload simply delegates to `alloc()` and returns a `void*`, satisfying the `BufferFactory` concept expected by `lz4Compress` in `CompressionAlgorithms.h`. That template function calls `bf(outCapacity)` to obtain the destination buffer — passing a `Buffer` object directly fills both roles (allocation and storage) in a single object.
|
||||
|
||||
## Slice Integration
|
||||
|
||||
`Buffer` is tightly coupled to `Slice`. It provides an implicit conversion `operator Slice() const noexcept`, so any `Buffer` can be passed wherever a `Slice` is expected without an explicit cast. The reverse — constructing a `Buffer` from a `Slice` — is marked `explicit`, preventing accidental copies of view-only data.
|
||||
|
||||
The `operator=(Slice)` assignment requires particular attention: before copying, it checks via `XRPL_ASSERT` that the source slice does not overlap with the `Buffer`'s own storage. The danger is that `alloc()` frees the old memory first, and if the incoming `Slice` pointed into that memory, the subsequent `memcpy` would be a use-after-free. The assertion guards against this specific self-overlapping scenario. Note that `operator=(Buffer const&)` uses a different path through `alloc()` + `memcpy`, which naturally handles self-assignment because `alloc()` is a no-op when sizes match — the existing pointer is reused and then `memcpy`-d over itself (which is defined behavior for `memcpy` with identical source and destination).
|
||||
|
||||
## Move Semantics
|
||||
|
||||
Both move constructor and move assignment are `noexcept`, a static guarantee the test suite verifies with `static_assert`. This ensures `Buffer` can be held in standard containers like `std::vector` without triggering copies on reallocation. After a move, the source is left in a valid empty state: `p_` is null (via `unique_ptr` move semantics) and `size_` is explicitly reset to zero.
|
||||
|
||||
## Comparison and Iteration
|
||||
|
||||
Equality comparison is implemented as a free function using `std::memcmp` after a size check. The class exposes only `const_iterator` (raw `uint8_t const*` pointers), meaning range-for loops and standard algorithms can consume the buffer's contents read-only. Mutable iteration is available only through `data()`, keeping the interface honest about the distinction between reading and writing into the buffer.
|
||||
|
||||
## Contrast with `Blob`
|
||||
|
||||
`Blob` (`std::vector<unsigned char>`) is still used extensively in the codebase for cases where the byte sequence grows incrementally, such as serialization output. `Buffer` is preferred when the size is known upfront, ownership transfer by move is the primary operation, or the `BufferFactory` pattern is required — for example, storing the output of an LZ4 decompression call without needing the capacity/size distinction that `vector` maintains internally.
|
||||
@@ -1,38 +0,0 @@
|
||||
{
|
||||
"args": [
|
||||
{
|
||||
"lineno": 6,
|
||||
"name": "value"
|
||||
},
|
||||
{
|
||||
"lineno": 13,
|
||||
"name": "value"
|
||||
}
|
||||
],
|
||||
"classes": [],
|
||||
"description": "Provides constexpr utility functions to convert values to kilobytes and megabytes within the xrpl namespace.",
|
||||
"file_path": "workflow/XRPLF-rippled-develop/source/include/xrpl/basics/ByteUtilities.h",
|
||||
"functions": [
|
||||
{
|
||||
"args": [
|
||||
"value"
|
||||
],
|
||||
"lineno": 6,
|
||||
"name": "kilobytes"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"value"
|
||||
],
|
||||
"lineno": 13,
|
||||
"name": "megabytes"
|
||||
}
|
||||
],
|
||||
"language": "c header",
|
||||
"namespaces": [
|
||||
{
|
||||
"lineno": 3,
|
||||
"name": "xrpl"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
# `ByteUtilities.h` — Compile-Time Byte-Size Helpers
|
||||
|
||||
`ByteUtilities.h` is a minimal, header-only utility in `xrpl/basics` that provides two `constexpr` template functions — `kilobytes()` and `megabytes()` — for expressing byte-count constants in human-readable units at compile time. The file exists purely to eliminate magic numbers from sites that configure buffer sizes, memory limits, and slab allocator parameters throughout the XRPL codebase.
|
||||
|
||||
## The Functions
|
||||
|
||||
`kilobytes(value)` multiplies its argument by 1024. `megabytes(value)` composes that twice — it calls `kilobytes(kilobytes(value))` — which gives the correct factor of 1,048,576 (2²⁰) without any separate literal. Both functions are templated on `T`, so they work with any integral or arithmetic type and return the same type that the arithmetic produces, letting the caller's type context drive the result without an explicit cast. Both are `constexpr` and `noexcept`, meaning the computation happens entirely at compile time and has no runtime overhead whatsoever.
|
||||
|
||||
The `static_assert` lines immediately below the definitions act as inline tests: they verify `kilobytes(2) == 2048` and `megabytes(3) == 3145728` during every compilation, preventing any silent regression if the implementation were ever accidentally changed.
|
||||
|
||||
## Design Rationale
|
||||
|
||||
The template design over a fixed `size_t` signature is deliberate. Call sites like `megabytes(std::size_t(60))` in `SHAMapItem.h` need to produce `std::size_t` results for slab allocator configuration, while other uses such as `megabytes(256)` in `RPCCall.cpp` are happy with `int`-width results for comparison. By letting `auto` return the natural result of the arithmetic, the functions avoid both unwanted narrowing conversions and unwanted widening that could paper over a type mismatch.
|
||||
|
||||
The composition `kilobytes(kilobytes(value))` for megabytes is a small but telling choice: it reuses the already-tested primitive rather than independently writing `value * 1024 * 1024`, keeping the chain of trust short and making the relationship between units self-documenting.
|
||||
|
||||
## Usage Across the Codebase
|
||||
|
||||
The functions appear at exactly the kinds of boundaries where misreading a magnitude would have serious consequences:
|
||||
|
||||
- **Overlay message cap**: `src/xrpld/overlay/Message.h` defines `constexpr std::size_t maximumMessageSize = megabytes(64)`, bounding the maximum peer-to-peer message size to 64 MiB.
|
||||
- **RPC reply limit**: `src/xrpld/rpc/detail/RPCCall.cpp` defines `constexpr auto RPC_REPLY_MAX_BYTES = megabytes(256)` to guard against unbounded JSON responses.
|
||||
- **Ledger and open-view buffers**: `include/xrpl/ledger/OpenView.h` and `include/xrpl/ledger/detail/RawStateTable.h` both set `initialBufferSize = kilobytes(256)` for their serialisation scratch buffers.
|
||||
- **ShaMap slab allocator**: `SHAMapItem.h` uses `megabytes()` to express the per-size-class allocation limits for the slab allocator pools (60 MB, 46 MB, etc.), and `TaggedPointer.ipp` uses `kilobytes(512)` for the slab block granularity.
|
||||
|
||||
The consistent use of these helpers rather than raw literals means that anyone reading any of those files immediately understands the intended scale without mental arithmetic, and the compiler catches any integer overflow that a bare literal might hide at the point of definition.
|
||||
@@ -1,93 +0,0 @@
|
||||
{
|
||||
"args": [
|
||||
{
|
||||
"lineno": 18,
|
||||
"name": "in"
|
||||
},
|
||||
{
|
||||
"lineno": 18,
|
||||
"name": "inSize"
|
||||
},
|
||||
{
|
||||
"lineno": 18,
|
||||
"name": "bf"
|
||||
},
|
||||
{
|
||||
"lineno": 41,
|
||||
"name": "in"
|
||||
},
|
||||
{
|
||||
"lineno": 41,
|
||||
"name": "inSizeUnchecked"
|
||||
},
|
||||
{
|
||||
"lineno": 41,
|
||||
"name": "decompressed"
|
||||
},
|
||||
{
|
||||
"lineno": 41,
|
||||
"name": "decompressedSizeUnchecked"
|
||||
},
|
||||
{
|
||||
"lineno": 62,
|
||||
"name": "in"
|
||||
},
|
||||
{
|
||||
"lineno": 62,
|
||||
"name": "inSize"
|
||||
},
|
||||
{
|
||||
"lineno": 62,
|
||||
"name": "decompressed"
|
||||
},
|
||||
{
|
||||
"lineno": 62,
|
||||
"name": "decompressedSize"
|
||||
}
|
||||
],
|
||||
"classes": [],
|
||||
"description": "Provides LZ4 block compression and decompression utilities, including template and inline functions for compressing and decompressing data buffers and streams within the xrpl::compression_algorithms namespace.",
|
||||
"file_path": "workflow/XRPLF-rippled-develop/source/include/xrpl/basics/CompressionAlgorithms.h",
|
||||
"functions": [
|
||||
{
|
||||
"args": [
|
||||
"in",
|
||||
"inSize",
|
||||
"bf"
|
||||
],
|
||||
"lineno": 18,
|
||||
"name": "lz4Compress"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"in",
|
||||
"inSizeUnchecked",
|
||||
"decompressed",
|
||||
"decompressedSizeUnchecked"
|
||||
],
|
||||
"lineno": 41,
|
||||
"name": "lz4Decompress"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"in",
|
||||
"inSize",
|
||||
"decompressed",
|
||||
"decompressedSize"
|
||||
],
|
||||
"lineno": 62,
|
||||
"name": "lz4Decompress"
|
||||
}
|
||||
],
|
||||
"language": "c header",
|
||||
"namespaces": [
|
||||
{
|
||||
"lineno": 9,
|
||||
"name": "xrpl"
|
||||
},
|
||||
{
|
||||
"lineno": 11,
|
||||
"name": "compression_algorithms"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
# `CompressionAlgorithms.h` — LZ4 Block Compression Primitives
|
||||
|
||||
This header lives in `include/xrpl/basics/` and provides the low-level LZ4 compression and decompression routines used by the XRPL peer overlay network. It sits one abstraction layer below `src/xrpld/overlay/Compression.h`, which adds algorithm-selection logic and error suppression on top of what this file exposes.
|
||||
|
||||
## Architectural Role
|
||||
|
||||
When XRPL nodes exchange P2P messages they can optionally compress the payload before transmission. The overlay layer negotiates compression during the connection handshake and then routes compressed messages through the functions defined here. `CompressionAlgorithms.h` isolates the raw LZ4 calls — the `int`-based C API hazards, buffer management, and stream chunking — from the policy-level decisions that live in `Compression.h`.
|
||||
|
||||
The functions are entirely in the `xrpl::compression_algorithms` namespace. There are no classes, no state, no singletons — just three free functions.
|
||||
|
||||
## `lz4Compress` — Template with BufferFactory
|
||||
|
||||
```cpp
|
||||
template <typename BufferFactory>
|
||||
std::size_t lz4Compress(void const* in, std::size_t inSize, BufferFactory&& bf)
|
||||
```
|
||||
|
||||
The design choice to accept a `BufferFactory` callable rather than returning a `std::vector` is deliberate and important. The caller knows its allocation context: in the overlay code it may be writing into a Protobuf `CodedOutputStream` region or a pooled buffer. The factory receives the worst-case compressed size from `LZ4_compressBound` and returns a raw pointer; the template accepts any callable that satisfies this contract without virtual dispatch overhead.
|
||||
|
||||
The sole pre-condition check guards against input larger than `UINT32_MAX`. LZ4's block API uses `int` internally, so exceeding that limit would silently truncate the size argument. The function throws via `Throw<std::runtime_error>`, which logs a call stack through `contract.h` before throwing — consistent with XRPL's "crash loudly with context" philosophy for invariant violations.
|
||||
|
||||
## `lz4Decompress` — Raw Buffer Overload
|
||||
|
||||
```cpp
|
||||
inline std::size_t lz4Decompress(
|
||||
std::uint8_t const* in, std::size_t inSizeUnchecked,
|
||||
std::uint8_t* decompressed, std::size_t decompressedSizeUnchecked)
|
||||
```
|
||||
|
||||
The `Unchecked` naming in the parameters is the code's way of signalling that the `size_t` → `int` narrowing has not yet been validated. The function immediately casts both sizes to `int` and checks for `<= 0`. This catches two distinct failure modes: a genuinely zero-length buffer, and a `size_t` value large enough that the narrowing wrap produces a non-positive `int`. Separating these checks with distinct error messages makes debugging easier.
|
||||
|
||||
`LZ4_decompress_safe` is used rather than the faster `LZ4_decompress_fast`. The safe variant takes the output buffer capacity as a bound and will not write past it even if the compressed data is malformed — essential when the input arrives from an untrusted peer on the network.
|
||||
|
||||
The function enforces an exact-size postcondition: if `LZ4_decompress_safe` returns anything other than the expected `decompressedSize` it throws. This reflects the fact that, in the overlay protocol, the original message size is transmitted in the message header; any mismatch means either corruption or a peer bug.
|
||||
|
||||
## `lz4Decompress` — Streaming ZeroCopyInputStream Overload
|
||||
|
||||
```cpp
|
||||
template <typename InputStream>
|
||||
std::size_t lz4Decompress(
|
||||
InputStream& in, std::size_t inSize,
|
||||
std::uint8_t* decompressed, std::size_t decompressedSize)
|
||||
```
|
||||
|
||||
This overload works with Protobuf-style `ZeroCopyInputStream` objects that expose data as a series of chunks rather than a single contiguous buffer. The key optimization is the fast path: if the very first chunk returned by `in.Next()` is at least `inSize` bytes long, the function uses that chunk's pointer directly and avoids any allocation. In practice, compressed P2P messages typically arrive in a single TCP read buffer, so this path is taken most of the time.
|
||||
|
||||
When the data spans multiple chunks, the function lazily allocates a `std::vector<std::uint8_t>` of exactly `inSize` bytes (note the `compressed.resize(inSize)` is only reached on the second iteration) and copies chunks into it until the full compressed message is assembled. After reading, any bytes that were consumed from the stream beyond `inSize` are returned via `in.BackUp()`, preserving the stream cursor for the next message in the framing protocol.
|
||||
|
||||
The final validation before delegating to the raw overload checks that the amount actually read matches what was requested. This guards against a stream that ends early — e.g., a truncated TCP connection or a framing bug where the declared size doesn't match the available data.
|
||||
|
||||
## Relationship to `Compression.h`
|
||||
|
||||
The overlay's `Compression.h` wraps these two functions inside `compress()` and `decompress()` functions that add an `Algorithm` enum parameter (currently `Algorithm::LZ4 = 0x90` or `Algorithm::None`). Those wrappers catch all exceptions from the functions here and return `0` on failure, converting the throw-on-error contract into a return-zero-on-error contract. The distinction is intentional: the raw primitives throw so that callers who want structured error handling can use them; the overlay wrapper normalises failures to a `0` return value to simplify the state machine in the peer message processing loop.
|
||||
@@ -1,88 +0,0 @@
|
||||
{
|
||||
"args": [
|
||||
{
|
||||
"lineno": 23,
|
||||
"name": "name"
|
||||
},
|
||||
{
|
||||
"lineno": 16,
|
||||
"name": "minimumThreshold"
|
||||
},
|
||||
{
|
||||
"lineno": 65,
|
||||
"name": "Object"
|
||||
}
|
||||
],
|
||||
"classes": [
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 7,
|
||||
"name": "CountedObjects"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"name"
|
||||
],
|
||||
"lineno": 22,
|
||||
"name": "Counter"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 65,
|
||||
"name": "CountedObject"
|
||||
}
|
||||
],
|
||||
"description": "Provides a mechanism to count and report the number of instances of various object types at runtime, using a lock-free linked list and atomic counters. Includes a base class for automatic instance counting.",
|
||||
"file_path": "workflow/XRPLF-rippled-develop/source/include/xrpl/basics/CountedObject.h",
|
||||
"functions": [
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 10,
|
||||
"name": "getInstance"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"minimumThreshold"
|
||||
],
|
||||
"lineno": 16,
|
||||
"name": "getCounts"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 36,
|
||||
"name": "increment"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 41,
|
||||
"name": "decrement"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 46,
|
||||
"name": "getCount"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 51,
|
||||
"name": "getNext"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 56,
|
||||
"name": "getName"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 71,
|
||||
"name": "getCounter"
|
||||
}
|
||||
],
|
||||
"language": "c header",
|
||||
"namespaces": [
|
||||
{
|
||||
"lineno": 6,
|
||||
"name": "xrpl"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
# `include/xrpl/basics/CountedObject.h`
|
||||
|
||||
## Purpose
|
||||
|
||||
This header provides a zero-per-instance-overhead mechanism for counting live objects of any given type throughout the rippled process lifetime. It exists for operational diagnostics: the `get_counts` admin RPC command interrogates `CountedObjects` to report how many instances of each tracked type are currently alive, helping operators identify memory growth, cache saturation, or unexpected object accumulation.
|
||||
|
||||
## Design Pattern — CRTP Instance Counting
|
||||
|
||||
The design uses the Curiously Recurring Template Pattern (CRTP). A class opts into counting by inheriting `CountedObject<Derived>`:
|
||||
|
||||
```cpp
|
||||
class SHAMapItem : public CountedObject<SHAMapItem> { ... };
|
||||
class NodeObject : public CountedObject<NodeObject> { ... };
|
||||
class Job : public CountedObject<Job> { ... };
|
||||
```
|
||||
|
||||
Across the codebase, roughly two dozen types follow this pattern — `STPathElement`, `STPath`, `InfoSub`, `HashRouter::Entry`, `Book`, `CanonicalTXSet`, and many more. Adding the base class is the entire integration cost; no other instrumentation is required.
|
||||
|
||||
The key insight that makes this zero-per-instance overhead is that `CountedObject<T>::getCounter()` returns a **function-local static** `Counter` object — one per template instantiation, not one per live instance. The only per-instance cost is two atomic increments (constructor and destructor) touching a shared counter.
|
||||
|
||||
## Three-Layer Architecture
|
||||
|
||||
**`CountedObject<T>`** (template base class) — the public-facing layer. Its default constructor, copy constructor, and destructor call `getCounter().increment()` / `decrement()` respectively. The copy constructor is explicitly defined to increment because a copy produces a new live object; the assignment operator is `= default` because assigning between two existing objects doesn't change the total number of live instances. There is no explicit move constructor, so moves fall back to the copy constructor, which correctly increments for the new object while the source's destructor later decrements for the old one.
|
||||
|
||||
**`CountedObjects::Counter`** (inner class) — the per-type bookkeeping node. Each `Counter` holds its type name (obtained via `beast::type_name<T>()`, which uses `typeid` plus GCC/Clang ABI demangling for a human-readable string), an `std::atomic<int>` live count, and a raw `Counter*` pointer to the next node in an intrusive singly-linked list.
|
||||
|
||||
**`CountedObjects`** (singleton) — the global registry. It owns the head of the lock-free linked list and a count of registered counter types.
|
||||
|
||||
## Lock-Free Registration
|
||||
|
||||
`Counter` objects self-register when they are first constructed — which happens at first use of any given type, during static initialization of `getCounter()`'s local static. Registration must be thread-safe without a mutex, because many types can be instantiated concurrently at startup:
|
||||
|
||||
```cpp
|
||||
Counter* head = nullptr;
|
||||
do {
|
||||
head = instance.m_head.load();
|
||||
next_ = head;
|
||||
} while (instance.m_head.exchange(this) != head);
|
||||
```
|
||||
|
||||
This is a classic CAS (compare-and-swap) insertion loop: load the current head, set `next_` to it, then atomically exchange the head with `this`. If the head changed between the load and the exchange, retry. Because `Counter` objects are permanent (static lifetime), they are never removed from the list, so traversal during `getCounts()` never encounters a dangling pointer regardless of whether other registrations are happening concurrently.
|
||||
|
||||
## `getCounts()` and the Reporting Path
|
||||
|
||||
`CountedObjects::getCounts(int minimumThreshold)` traverses the linked list and collects `(name, count)` pairs for any type whose live count is at or above the threshold. It pre-reserves the result vector using `m_count.load()` as a hint (the comment in the implementation acknowledges this can be temporarily under-counted under concurrency — it is only an optimization). The results are sorted alphabetically before return.
|
||||
|
||||
The `get_counts` admin RPC handler (`GetCounts.cpp`) calls this with a configurable `min_count` (defaulting to 10) and serializes the results into a JSON object, mixing them with cache statistics, database sizes, write load, and uptime. Object counts appear as top-level keys named by the demangled C++ type.
|
||||
|
||||
## Concurrency Properties
|
||||
|
||||
All per-type counts use `std::atomic<int>` with default sequential consistency, so `increment()` and `decrement()` are safe from any thread. The linked-list head pointer `m_head` is also `std::atomic<Counter*>`. There are no mutexes anywhere in this file. The only non-atomic operation is reading `Counter::next_` during traversal in `getCounts()`, which is safe because `next_` is written exactly once at construction time and never modified thereafter.
|
||||
|
||||
## Why Not Alternatives
|
||||
|
||||
A virtual-function approach (e.g., a pure virtual `typeName()` method) would require each instance to carry a vtable pointer and would not trivially aggregate counts across all instances of the same type without additional infrastructure. A manual registry with `std::map` would need a mutex. The CRTP-plus-static-counter approach achieves type safety, automatic demangled names, lock-free operation, and zero per-instance storage — at the cost of slightly surprising copy/move semantics that operators must understand when subclassing.
|
||||
@@ -1,115 +0,0 @@
|
||||
{
|
||||
"args": [
|
||||
{
|
||||
"lineno": 18,
|
||||
"name": "now"
|
||||
},
|
||||
{
|
||||
"lineno": 26,
|
||||
"name": "value"
|
||||
},
|
||||
{
|
||||
"lineno": 26,
|
||||
"name": "now"
|
||||
},
|
||||
{
|
||||
"lineno": 34,
|
||||
"name": "now"
|
||||
},
|
||||
{
|
||||
"lineno": 41,
|
||||
"name": "now"
|
||||
},
|
||||
{
|
||||
"lineno": 61,
|
||||
"name": "now"
|
||||
},
|
||||
{
|
||||
"lineno": 74,
|
||||
"name": "value"
|
||||
},
|
||||
{
|
||||
"lineno": 74,
|
||||
"name": "now"
|
||||
},
|
||||
{
|
||||
"lineno": 79,
|
||||
"name": "now"
|
||||
},
|
||||
{
|
||||
"lineno": 86,
|
||||
"name": "now"
|
||||
}
|
||||
],
|
||||
"classes": [
|
||||
{
|
||||
"args": [
|
||||
"time_point now"
|
||||
],
|
||||
"lineno": 10,
|
||||
"name": "DecayingSample"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"time_point now"
|
||||
],
|
||||
"lineno": 61,
|
||||
"name": "DecayWindow"
|
||||
}
|
||||
],
|
||||
"description": "Provides two template classes for sampling functions using exponential decay: DecayingSample (with a fixed window) and DecayWindow (with a half-life), useful for tracking decaying averages or statistics over time.",
|
||||
"file_path": "workflow/XRPLF-rippled-develop/source/include/xrpl/basics/DecayingSample.h",
|
||||
"functions": [
|
||||
{
|
||||
"args": [
|
||||
"value",
|
||||
"now"
|
||||
],
|
||||
"lineno": 26,
|
||||
"name": "DecayingSample::add"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"now"
|
||||
],
|
||||
"lineno": 34,
|
||||
"name": "DecayingSample::value"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"now"
|
||||
],
|
||||
"lineno": 41,
|
||||
"name": "DecayingSample::decay"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"value",
|
||||
"now"
|
||||
],
|
||||
"lineno": 74,
|
||||
"name": "DecayWindow::add"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"now"
|
||||
],
|
||||
"lineno": 79,
|
||||
"name": "DecayWindow::value"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"now"
|
||||
],
|
||||
"lineno": 86,
|
||||
"name": "DecayWindow::decay"
|
||||
}
|
||||
],
|
||||
"language": "c header",
|
||||
"namespaces": [
|
||||
{
|
||||
"lineno": 4,
|
||||
"name": "xrpl"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
# `DecayingSample.h` — Exponential Decay Accumulators
|
||||
|
||||
This header provides two small template classes that maintain a running accumulation of values that automatically decay over time. Both are used throughout the XRPL node to answer the question "how much activity has happened recently?" without needing to store timestamped histories — the decay does the windowing implicitly.
|
||||
|
||||
## `DecayingSample<Window, Clock>`
|
||||
|
||||
`DecayingSample` maintains an integer accumulator that decays by approximately `1/Window` of its current value each second, producing a rate estimate normalized over the window length. It drives the resource manager's per-peer charge tracking: `Entry.h` declares `local_balance` as `DecayingSample<decayWindowSeconds, clock_type>` where `decayWindowSeconds = 32` (a power of two, per a comment in `Tuning.h`, so the division can be optimized to a bit-shift by the compiler).
|
||||
|
||||
The core decay step is deliberately integer arithmetic with ceiling division:
|
||||
|
||||
```cpp
|
||||
m_value -= (m_value + Window - 1) / Window;
|
||||
```
|
||||
|
||||
This subtracts at least 1 when `m_value` is positive, so the value cannot stall at a non-zero integer indefinitely — a safety property important for rate limiting. Adding `Window - 1` before dividing implements ceiling division, meaning the decay rounds up rather than down. The practical effect is the balance decays slightly faster than the mathematically ideal `m_value *= (1 - 1/Window)^elapsed`, which is a conservative choice for load balancing: erring toward under-charging rather than over-charging.
|
||||
|
||||
The `decay()` fast-path cuts off long idle periods: if more than `4 * Window` seconds have elapsed since the last update (which would leave the value at less than ~2% of its original magnitude), `m_value` is simply zeroed. This prevents the per-second loop from iterating hundreds of times on a reconnecting peer.
|
||||
|
||||
`add()` ages the accumulator first, then adds the new sample, and returns `m_value / Window` — the normalized balance representing average load per second across the window. `value()` does the same without adding anything. Both methods demand a `time_point now` from the caller rather than reading a clock themselves; this makes the class testable and clock-agnostic.
|
||||
|
||||
## `DecayWindow<HalfLife, Clock>`
|
||||
|
||||
`DecayWindow` takes a different approach: it stores a `double` and applies the mathematically exact exponential half-life formula:
|
||||
|
||||
```cpp
|
||||
value_ *= std::pow(2.0, -elapsed / HalfLife);
|
||||
```
|
||||
|
||||
After exactly `HalfLife` seconds of inactivity, the accumulated value halves. After two half-lives it quarters, and so on. Unlike `DecayingSample`, which loops through whole seconds, `DecayWindow` casts the elapsed duration to `duration<double>`, giving it sub-second precision — appropriate when the caller's clock has higher resolution or when calls are frequent.
|
||||
|
||||
`InboundLedgers.cpp` uses this class as `DecayWindow<30, clock_type> fetchRate_` to measure the rate at which ledgers are being fetched from peers. Each fetch fires `fetchRate_.add(1, now)`. The `fetchRate()` accessor returns `60 * fetchRate_.value(now)`, converting the per-second average to a per-minute rate for reporting.
|
||||
|
||||
The `static_assert(HalfLife > 0)` guards against a zero divisor in `std::pow`, which would produce undefined floating-point behavior.
|
||||
|
||||
## Design Rationale: Two Classes Rather Than One
|
||||
|
||||
The two classes reflect different use cases that have incompatible requirements. `DecayingSample` works with integer `value_type` (derived from the clock's duration representation), which matters for the resource manager where charges are counted in discrete units and the result feeds integer comparison thresholds. Integer arithmetic also avoids floating-point instability in tight loops. `DecayWindow` accepts `double` inputs and uses `std::pow`, accepting the floating-point cost in exchange for smooth decay curves and sub-second accuracy — the right tradeoff when measuring continuous rates rather than discrete charges.
|
||||
|
||||
Neither class is thread-safe on its own; callers are responsible for synchronization. `InboundLedgersImp` wraps `fetchRate_` with `fetchRateMutex_`, and the resource `Entry` is similarly protected by the table's lock.
|
||||
@@ -1,109 +0,0 @@
|
||||
{
|
||||
"args": [
|
||||
{
|
||||
"lineno": 29,
|
||||
"name": "Impl"
|
||||
},
|
||||
{
|
||||
"lineno": 36,
|
||||
"name": "Impl"
|
||||
},
|
||||
{
|
||||
"lineno": 43,
|
||||
"name": "Impl"
|
||||
},
|
||||
{
|
||||
"lineno": 53,
|
||||
"name": "E"
|
||||
},
|
||||
{
|
||||
"lineno": 80,
|
||||
"name": "U"
|
||||
},
|
||||
{
|
||||
"lineno": 87,
|
||||
"name": "U"
|
||||
},
|
||||
{
|
||||
"lineno": 128,
|
||||
"name": "U"
|
||||
}
|
||||
],
|
||||
"classes": [
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 16,
|
||||
"name": "bad_expected_access"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 27,
|
||||
"name": "throw_policy"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"E const& e",
|
||||
"E&& e"
|
||||
],
|
||||
"lineno": 53,
|
||||
"name": "Unexpected"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"U&& r",
|
||||
"Unexpected<U> e"
|
||||
],
|
||||
"lineno": 77,
|
||||
"name": "Expected"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"Expected()",
|
||||
"Unexpected<U> e"
|
||||
],
|
||||
"lineno": 120,
|
||||
"name": "Expected<void, E>"
|
||||
}
|
||||
],
|
||||
"description": "This file provides an approximation of std::expected (proposed for C++23) using boost::outcome_v2::result, including custom error handling and policies for expected/unexpected result types.",
|
||||
"file_path": "workflow/XRPLF-rippled-develop/source/include/xrpl/basics/Expected.h",
|
||||
"functions": [
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 18,
|
||||
"name": "bad_expected_access"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"Impl&& self"
|
||||
],
|
||||
"lineno": 29,
|
||||
"name": "wide_value_check"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"Impl&& self"
|
||||
],
|
||||
"lineno": 36,
|
||||
"name": "wide_error_check"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"Impl&& self"
|
||||
],
|
||||
"lineno": 43,
|
||||
"name": "wide_exception_check"
|
||||
}
|
||||
],
|
||||
"language": "c header",
|
||||
"namespaces": [
|
||||
{
|
||||
"lineno": 7,
|
||||
"name": "xrpl"
|
||||
},
|
||||
{
|
||||
"lineno": 25,
|
||||
"name": "detail"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
# `include/xrpl/basics/Expected.h`
|
||||
|
||||
## Role and Motivation
|
||||
|
||||
This header provides `xrpl::Expected<T, E>`, a polyfill for the `std::expected<T, E>` type proposed for C++23 (P0323R10). At the time this code was written, `std::expected` was not yet available, so the implementation delegates all storage and state management to `boost::outcome_v2::result<T, E, Policy>` while exposing an API that closely mirrors the eventual standard — making a future migration straightforward.
|
||||
|
||||
`Expected<T, E>` represents a value that is *either* a success of type `T` or an error of type `E`. Unlike `std::optional`, which only signals absence, `Expected` carries diagnostic information about *why* a result is missing. Unlike exceptions, it forces callers to explicitly inspect the outcome. The `[[nodiscard]]` attribute on both the primary template and the `void` specialization guarantees at compile time that callers cannot silently drop a return value — a critical safety property in a financial ledger where ignored error returns could mean silent transaction corruption.
|
||||
|
||||
## Components
|
||||
|
||||
### `bad_expected_access`
|
||||
|
||||
A thin `std::runtime_error` subclass thrown whenever code tries to read the wrong half of an `Expected` — e.g., calling `value()` on an error-holding instance. It carries no additional data because the error value itself is available via `error()` and the point of failure is immediately clear from the stack trace. By inheriting from `std::runtime_error`, it integrates naturally with XRPL's existing exception hierarchy.
|
||||
|
||||
### `detail::throw_policy`
|
||||
|
||||
Boost.Outcome's policy mechanism controls what happens when the invariants of a `result` are violated. The default boost policy may assert or exhibit undefined behavior depending on build configuration; `throw_policy` replaces that with deterministic exception throwing. All three "wide" check entry points — `wide_value_check`, `wide_error_check`, and `wide_exception_check` — delegate to `Throw<bad_expected_access>()` (from `contract.h`) rather than a bare `throw`, so the violation is also logged before the exception propagates, consistent with XRPL's programming-by-contract philosophy.
|
||||
|
||||
### `Unexpected<E>`
|
||||
|
||||
A wrapper type that acts as an explicit tag for the error path. A function returning `Expected<T, E>` constructs the error branch by returning `Unexpected<E>(err)`, not a bare `E`. This prevents the implicit construction ambiguity that would arise if both `T` and `E` were, for example, `std::string`. The class provides all four value-category overloads of `value()` (lvalue/rvalue × const/non-const) for perfect forwarding into `Expected`'s constructor.
|
||||
|
||||
The deduction guide `Unexpected(E (&)[N]) -> Unexpected<E const*>` makes it ergonomic to pass string literals: `Unexpected("bad input")` deduces to `Unexpected<char const*>` rather than to a fixed-length array type, avoiding obscure template errors.
|
||||
|
||||
### `Expected<T, E>` (primary template)
|
||||
|
||||
Privately inherits from `boost::outcome_v2::result<T, E, detail::throw_policy>`. Private inheritance is intentional — it exposes only the `std::expected`-shaped API and hides the broader Outcome API (which includes channel-specific accessors and other facilities that would pollute the interface). The two constructors use `requires std::convertible_to` constraints so that implicit narrowing is rejected at compile time.
|
||||
|
||||
`operator bool`, `operator*`, and `operator->` map onto `has_value()` and `value()`, matching the pointer-like ergonomics of the standard proposal. Accessing `operator*` or `operator->` on an error-holding `Expected` triggers `throw_policy::wide_value_check`, which throws `bad_expected_access`. Similarly, calling `error()` on a value-holding instance triggers `wide_error_check`.
|
||||
|
||||
### `Expected<void, E>` (partial specialization)
|
||||
|
||||
Functions that either succeed (producing no value) or fail with a diagnostic use this specialization. Its default constructor calls `boost::outcome_v2::success()` to produce a successful instance — matching the proposed `std::expected<void, E>{}` default construction semantics. This is the pattern used in `STTx::checkSign()` and related signature-verification methods, which return `Expected<void, std::string>`: on success the caller simply checks `operator bool`; on failure the error string explains what went wrong.
|
||||
|
||||
## Usage Patterns in the Codebase
|
||||
|
||||
`tokens.h` defines a convenience alias `B58Result<T> = Expected<T, std::error_code>` for Base58Check encoding/decoding operations, where the error is a standard system error code. `base_uint.h` uses `Expected<decltype(data_), ParseResult>` for a `noexcept` hex-parsing path, capturing a per-character parse failure without throwing. `STTx.h` uses `Expected<void, std::string>` for all signature-check entry points — a natural fit because signature validation either passes silently or produces a human-readable error message.
|
||||
|
||||
## Design Trade-offs
|
||||
|
||||
Choosing `boost::outcome` over a hand-rolled type means the storage layout, move semantics, and triviality propagation are handled by a well-tested library, reducing the risk of subtle UB in low-level storage operations. The cost is a dependency on Boost and some mismatch between Outcome's three-state model (value / error / exception pointer) and `std::expected`'s two-state model; the `wide_exception_check` override in `throw_policy` handles the third state consistently by also throwing `bad_expected_access`, even though `Expected` itself never stores an exception pointer in practice. When C++23 `std::expected` becomes universally available, the migration path is clear: the public API is already a subset of the standard interface.
|
||||
@@ -1,58 +0,0 @@
|
||||
{
|
||||
"args": [
|
||||
{
|
||||
"lineno": 9,
|
||||
"name": "ec"
|
||||
},
|
||||
{
|
||||
"lineno": 10,
|
||||
"name": "sourcePath"
|
||||
},
|
||||
{
|
||||
"lineno": 11,
|
||||
"name": "maxSize"
|
||||
},
|
||||
{
|
||||
"lineno": 15,
|
||||
"name": "ec"
|
||||
},
|
||||
{
|
||||
"lineno": 16,
|
||||
"name": "destPath"
|
||||
},
|
||||
{
|
||||
"lineno": 17,
|
||||
"name": "contents"
|
||||
}
|
||||
],
|
||||
"classes": [],
|
||||
"description": "Provides utility functions for reading from and writing to files using Boost filesystem, with error handling and optional size limit.",
|
||||
"file_path": "workflow/XRPLF-rippled-develop/source/include/xrpl/basics/FileUtilities.h",
|
||||
"functions": [
|
||||
{
|
||||
"args": [
|
||||
"ec",
|
||||
"sourcePath",
|
||||
"maxSize"
|
||||
],
|
||||
"lineno": 8,
|
||||
"name": "getFileContents"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"ec",
|
||||
"destPath",
|
||||
"contents"
|
||||
],
|
||||
"lineno": 14,
|
||||
"name": "writeFileContents"
|
||||
}
|
||||
],
|
||||
"language": "c header",
|
||||
"namespaces": [
|
||||
{
|
||||
"lineno": 6,
|
||||
"name": "xrpl"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
# `include/xrpl/basics/FileUtilities.h`
|
||||
|
||||
This header declares two thin file I/O utilities — `getFileContents` and `writeFileContents` — that form the XRPL codebase's standard interface for synchronous file access. The design problem they solve is not the I/O itself, but the error-handling contract: the broader rippled codebase avoids exceptions in many subsystems and instead relies on `boost::system::error_code` for structured, non-throwing error propagation. These two functions provide a consistent, exception-free surface for the handful of places in the node that must read or write files.
|
||||
|
||||
## Interface Design
|
||||
|
||||
Both functions follow the same convention: an `error_code&` output parameter is the first argument, populated on failure while the function returns an empty result (or returns nothing for the write case). This is the classic Boost.Asio-style error-out-parameter pattern, chosen over exception-throwing I/O because the callers — configuration loading, validator list file reads, test scaffolding — operate in contexts where an error is a recoverable condition requiring a structured diagnostic path rather than a stack unwind.
|
||||
|
||||
`getFileContents` takes a `boost::filesystem::path` and an optional `std::size_t` upper bound. The `std::optional<std::size_t> maxSize` parameter is the key safety valve: it allows callers to cap memory usage before any bytes are read into a `std::string`. When absent, the file is read in full. When present, the function checks the on-disk file size before opening the stream and returns `file_too_large` immediately if the limit is exceeded. This pre-check is cheap and prevents unbounded allocation when reading untrusted or potentially large files.
|
||||
|
||||
`writeFileContents` takes a `boost::filesystem::path` and the string to write. It opens with `std::ios::out | std::ios::trunc`, guaranteeing the destination file is replaced atomically from the content's perspective — no partial appends. The function does not check or create intermediate directories; the caller is responsible for ensuring the destination path exists.
|
||||
|
||||
## Implementation Notes (from the `.cpp`)
|
||||
|
||||
`getFileContents` calls `boost::filesystem::canonical()` before doing anything else. This resolves symlinks and relative components into an absolute, normalized path, ensuring the subsequent `file_size()` check and stream open operate on the same physical file. Calling `canonical()` with the `ec` overload also intercepts path resolution errors (non-existent file, permission denied) through the same error-code channel rather than a filesystem exception.
|
||||
|
||||
After path resolution, the implementation reads the file via a `std::istreambuf_iterator` range construction directly into a `std::string`. This is idiomatic C++ for slurping a whole file but has a subtle implication: for text-mode streams on some platforms, newline translation may occur. The stream is opened in `std::ios::in` (text mode), consistent with the intended use cases — TOML/JSON configuration and validator list JSON — where the content is human-readable text rather than binary data.
|
||||
|
||||
Error checking is done at three points: path resolution failure, pre-open size check, and post-read `fileStream.bad()`. The `bad()` check (not `fail()`) specifically catches I/O errors during reading, not logical stream state issues, which is the correct guard for a hardware or OS-level read failure mid-stream.
|
||||
|
||||
## Callers in Context
|
||||
|
||||
The three primary call sites reveal the intended use scope:
|
||||
|
||||
- `src/xrpld/core/detail/Config.cpp` uses `getFileContents` twice: once to load the main configuration file and once to load the validators file specified within that config. These are startup-time reads on the main thread, where a missing file is a fatal misconfiguration.
|
||||
- `src/xrpld/app/misc/detail/WorkFile.h` uses `getFileContents` with a hard cap of `megabytes(1)` to read validator list files fetched from the network. The 1 MB cap is a deliberate denial-of-service defense against a maliciously large or corrupted file consuming unbounded memory.
|
||||
- `src/xrpld/app/misc/detail/ValidatorList.cpp` uses `writeFileContents` to persist the current validator list as styled JSON after an update.
|
||||
|
||||
The `maxSize` parameter's real motivation is visible in the `WorkFile` usage: without it, a 4 GB file at a validator list URL would allocate 4 GB of heap before the caller could inspect the error. The pre-check using `file_size()` is a TOCTOU (time-of-check/time-of-use) race in theory, but in practice the files involved are either local config files or freshly downloaded files in a controlled temp location, making the race window negligible.
|
||||
|
||||
## Relationship to the `basics` Module
|
||||
|
||||
Within `include/xrpl/basics/`, this header occupies the narrowest role: it is a leaf utility with no dependencies on other XRPL types. It depends only on Boost.Filesystem and `<optional>`, making it safe to include anywhere in the stack without pulling in heavier XRPL headers. The `ByteUtilities.h` header (which provides `kilobytes()` and `megabytes()`) is the natural companion when callers need to express size limits in readable units, as the test suite and `WorkFile` both demonstrate.
|
||||
@@ -1,115 +0,0 @@
|
||||
{
|
||||
"args": [
|
||||
{
|
||||
"lineno": 61,
|
||||
"name": "T* p"
|
||||
},
|
||||
{
|
||||
"lineno": 61,
|
||||
"name": "TAdoptTag"
|
||||
},
|
||||
{
|
||||
"lineno": 63,
|
||||
"name": "SharedIntrusive const& rhs"
|
||||
},
|
||||
{
|
||||
"lineno": 67,
|
||||
"name": "SharedIntrusive<TT> const& rhs"
|
||||
},
|
||||
{
|
||||
"lineno": 70,
|
||||
"name": "SharedIntrusive&& rhs"
|
||||
},
|
||||
{
|
||||
"lineno": 74,
|
||||
"name": "SharedIntrusive<TT>&& rhs"
|
||||
},
|
||||
{
|
||||
"lineno": 196,
|
||||
"name": "TT"
|
||||
},
|
||||
{
|
||||
"lineno": 196,
|
||||
"name": "Args&&... args"
|
||||
}
|
||||
],
|
||||
"classes": [
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 11,
|
||||
"name": "StaticCastTagSharedIntrusive"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 19,
|
||||
"name": "DynamicCastTagSharedIntrusive"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 27,
|
||||
"name": "SharedIntrusiveAdoptIncrementStrongTag"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 34,
|
||||
"name": "SharedIntrusiveAdoptNoIncrementTag"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"T* p, TAdoptTag",
|
||||
"SharedIntrusive const& rhs",
|
||||
"SharedIntrusive<TT> const& rhs",
|
||||
"SharedIntrusive&& rhs",
|
||||
"SharedIntrusive<TT>&& rhs",
|
||||
"StaticCastTagSharedIntrusive, SharedIntrusive<TT> const& rhs",
|
||||
"StaticCastTagSharedIntrusive, SharedIntrusive<TT>&& rhs",
|
||||
"DynamicCastTagSharedIntrusive, SharedIntrusive<TT> const& rhs",
|
||||
"DynamicCastTagSharedIntrusive, SharedIntrusive<TT>&& rhs"
|
||||
],
|
||||
"lineno": 54,
|
||||
"name": "SharedIntrusive"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"WeakIntrusive const& rhs",
|
||||
"WeakIntrusive&& rhs",
|
||||
"SharedIntrusive<T> const& rhs",
|
||||
"SharedIntrusive<T> const&& rhs"
|
||||
],
|
||||
"lineno": 151,
|
||||
"name": "WeakIntrusive"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"SharedWeakUnion const& rhs",
|
||||
"SharedIntrusive<TT> const& rhs",
|
||||
"SharedWeakUnion&& rhs",
|
||||
"SharedIntrusive<TT>&& rhs"
|
||||
],
|
||||
"lineno": 210,
|
||||
"name": "SharedWeakUnion"
|
||||
}
|
||||
],
|
||||
"description": "This file implements shared and weak intrusive pointer classes (SharedIntrusive, WeakIntrusive, SharedWeakUnion) for reference-counted memory management, including utilities for casting and pointer creation, primarily for use in the XRPL codebase.",
|
||||
"file_path": "workflow/XRPLF-rippled-develop/source/include/xrpl/basics/IntrusivePointer.h",
|
||||
"functions": [
|
||||
{
|
||||
"args": [
|
||||
"Args&&... args"
|
||||
],
|
||||
"lineno": 196,
|
||||
"name": "make_SharedIntrusive"
|
||||
}
|
||||
],
|
||||
"language": "c header",
|
||||
"namespaces": [
|
||||
{
|
||||
"lineno": 7,
|
||||
"name": "xrpl"
|
||||
},
|
||||
{
|
||||
"lineno": 222,
|
||||
"name": "intr_ptr"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
# `include/xrpl/basics/IntrusivePointer.h`
|
||||
|
||||
This header defines XRPL's custom intrusive smart pointer system: `SharedIntrusive<T>`, `WeakIntrusive<T>`, and `SharedWeakUnion<T>`. The system was designed specifically for `SHAMapInnerNode` — the inner nodes of the radix-16 Merkle trie at the heart of ledger state — but is general enough to serve other reference-counted types. The driver for building this rather than using `std::shared_ptr` is a lifecycle feature called the *partial destructor*, combined with a memory-efficient combined strong/weak pointer variant.
|
||||
|
||||
## Why Not `std::shared_ptr`?
|
||||
|
||||
The file's own comment names the key difference clearly. With `std::shared_ptr` created via `make_shared`, the control block (which contains both the strong and weak counts) lives alongside the object in a single allocation. That allocation is not reclaimed until both the strong *and* weak counts hit zero. So if something holds a `std::weak_ptr` to an inner node, the node's full allocation — including its 16 child pointers — stays live even after the last `shared_ptr` drops. For the SHAMap this is expensive: each inner node can hold up to 16 child `SharedIntrusive` pointers. The partial destructor mechanism exists specifically to release those children as soon as the strong count falls to zero, leaving only a shell waiting for the weak count to drain.
|
||||
|
||||
## Reference Count Layout
|
||||
|
||||
The actual counters live in `IntrusiveRefCounts` (`IntrusiveRefCounts.h`), which must be a base class of any type `T` used with these pointers. A single `std::atomic<uint32_t>` field encodes four things:
|
||||
|
||||
- **Bits 0–15**: strong count (up to 65535 owners)
|
||||
- **Bits 16–29**: weak count (14 bits, up to 16383 weak holders)
|
||||
- **Bit 30**: `partialDestroyStarted` flag
|
||||
- **Bit 31**: `partialDestroyFinished` flag
|
||||
|
||||
Packing counts and flags into one atomic integer means `releaseStrongRef()` can atomically decrement the count *and* set the `partialDestroyStarted` flag in a single CAS loop, avoiding a TOCTOU window where two threads could both decide to trigger partial destruction. The two flags are required to safely sequence concurrent partial- and full-destruction: the last weak pointer release spins on `atomic::wait()` if the partial destructor has started but not yet finished, preventing `delete` from racing with `partialDestructor()`.
|
||||
|
||||
## `SharedIntrusive<T>` — The Strong Pointer
|
||||
|
||||
`SharedIntrusive<T>` holds a raw `T* ptr_` whose lifetime is controlled by an intrusive strong count on `*ptr_`. Copy construction calls `ptr_->addStrongRef()`; move construction steals via `unsafeExchange(nullptr)` without touching the count. When the last strong holder releases (`unsafeReleaseAndStore(nullptr)` called from destructor or `reset()`), `releaseStrongRef()` returns one of three `ReleaseStrongRefAction` values:
|
||||
|
||||
- `noop` — other strong holders remain
|
||||
- `destroy` — both counts are zero; `delete prev`
|
||||
- `partialDestroy` — weak holders remain; call `prev->partialDestructor()` then `partialDestructorFinished(&prev)`
|
||||
|
||||
The call to `partialDestructorFinished` is the responsibility of the smart pointer class, not the pointee's `partialDestructor()`. This deliberate separation — noted in comments — forces every new `partialDestructor` implementation to explicitly arrange that call, making it harder to accidentally omit the step that wakes waiting threads.
|
||||
|
||||
The `unsafe*` private methods (`unsafeGetRawPtr`, `unsafeSetRawPtr`, `unsafeExchange`, `unsafeReleaseAndStore`) are named with the "unsafe" prefix not because they are dangerous in isolation, but as an architectural seam: the comment explicitly anticipates a future patch where `ptr_` might become `std::atomic<T*>`, and isolating all direct pointer access through these methods makes such a change localized.
|
||||
|
||||
## Adopt Tags and `make_SharedIntrusive`
|
||||
|
||||
Two tag types — `SharedIntrusiveAdoptIncrementStrongTag` and `SharedIntrusiveAdoptNoIncrementTag` — control whether adopting a raw pointer bumps the strong count. `make_SharedIntrusive<TT>()` allocates a new object with `new TT(...)` and wraps it with `NoIncrement`. This is correct because `IntrusiveRefCounts` initializes its atomic field to `strongDelta` (= 1), meaning the object is born with a strong count of one; incrementing again would be a double-count. The `static_assert` in `make_SharedIntrusive` verifies that the adopting constructor is `noexcept`, since a throw after the raw `new` but before the pointer is wrapped would leak the allocation.
|
||||
|
||||
## Cast Tags
|
||||
|
||||
`StaticCastTagSharedIntrusive` and `DynamicCastTagSharedIntrusive` are dispatch tags for cast-constructors, enabling the `intr_ptr::static_pointer_cast<T>()` and `intr_ptr::dynamic_pointer_cast<T>()` free functions. The move variant of the dynamic-cast constructor handles failure carefully: it uses `unsafeExchange` to steal the pointer from `rhs`, attempts `dynamic_cast`, and if it fails, exchanges the pointer back into `rhs` so ownership is not lost.
|
||||
|
||||
## `WeakIntrusive<T>` — The Weak Pointer
|
||||
|
||||
`WeakIntrusive<T>` mirrors the weak semantics of `std::weak_ptr`. Copy construction calls `ptr_->addWeakRef()`; the destructor calls `unsafeReleaseNoStore()` which invokes `releaseWeakRef()`. The interesting method is `lock()`: it calls `checkoutStrongRefFromWeak()`, a CAS loop that increments the strong count only if it is already non-zero. If the strong count has already hit zero the lock fails and an empty `SharedIntrusive` is returned. Note that copy assignment from a `WeakIntrusive` is deleted — the comment explains this was omitted to simplify the implementation since no current use case required it.
|
||||
|
||||
## `SharedWeakUnion<T>` — The Tagged Pointer
|
||||
|
||||
`SharedWeakUnion<T>` is the most architecturally unusual piece. It stores both the pointer value and a strong/weak discriminator inside a single `uintptr_t` field `tp_` by using pointer tagging: if the low bit is `1`, the pointer represents a weak reference; if it is `0`, a strong reference. This works because `alignof(T) >= 2` is statically asserted, guaranteeing the low bit of any valid `T*` is always zero.
|
||||
|
||||
The practical value is for tagged caches, where a cache slot should hold a strong pointer when the object is actively needed but can downgrade to a weak pointer to allow eviction without cache churn. `convertToStrong()` and `convertToWeak()` perform in-place promotion and demotion: `convertToStrong()` atomically promotes a weak checkout to a strong reference using `checkoutStrongRefFromWeak()` then releases the weak count; `convertToWeak()` uses the atomic `addWeakReleaseStrongRef()` operation to swap one strong count for one weak count in a single CAS loop, handling the `partialDestroy` case that arises if this was the very last strong pointer. The `lock()` method unifies weak and strong paths: if already strong, increment and return; if weak, attempt a checkout.
|
||||
|
||||
## `intr_ptr` Namespace
|
||||
|
||||
The nested `intr_ptr` namespace provides `std::shared_ptr`-style vocabulary aliases — `SharedPtr<T>`, `WeakPtr<T>`, `SharedWeakUnionPtr<T>`, `make_shared<T>()`, `static_pointer_cast<T>()`, `dynamic_pointer_cast<T>()` — used throughout the SHAMap subsystem. `SHAMapInnerNode` stores its 16 children as `intr_ptr::SharedPtr<SHAMapTreeNode>` and exposes `partialDestructor()` to reset them when the last strong holder drops.
|
||||
@@ -1,698 +0,0 @@
|
||||
{
|
||||
"args": [],
|
||||
"classes": [],
|
||||
"code_paths": [
|
||||
{
|
||||
"call_chain": [
|
||||
"SharedIntrusive<T>::SharedIntrusive(T* p, TAdoptTag)",
|
||||
"if constexpr (std::is_same_v<TAdoptTag, SharedIntrusiveAdoptIncrementStrongTag>)",
|
||||
"if (p)",
|
||||
"p->addStrongRef()"
|
||||
],
|
||||
"entry_point": "SharedIntrusive<T>::SharedIntrusive(T* p, TAdoptTag)",
|
||||
"purpose": "Constructs a SharedIntrusive from a raw pointer, optionally incrementing the strong ref count if adopting.",
|
||||
"validation_points": [
|
||||
"if (p) // Validates pointer before addStrongRef"
|
||||
]
|
||||
},
|
||||
{
|
||||
"call_chain": [
|
||||
"SharedIntrusive<T>::SharedIntrusive(SharedIntrusive const& rhs)",
|
||||
"rhs.unsafeGetRawPtr()",
|
||||
"if (p)",
|
||||
"p->addStrongRef()"
|
||||
],
|
||||
"entry_point": "SharedIntrusive<T>::SharedIntrusive(SharedIntrusive const& rhs)",
|
||||
"purpose": "Copy constructor, increments strong ref if pointer is not null.",
|
||||
"validation_points": [
|
||||
"if (p) // Validates pointer before addStrongRef"
|
||||
]
|
||||
},
|
||||
{
|
||||
"call_chain": [
|
||||
"SharedIntrusive<T>::operator=(SharedIntrusive const& rhs)",
|
||||
"if (this == &rhs)",
|
||||
"rhs.unsafeGetRawPtr()",
|
||||
"if (p)",
|
||||
"p->addStrongRef()",
|
||||
"unsafeReleaseAndStore(p)"
|
||||
],
|
||||
"entry_point": "SharedIntrusive<T>::operator=(SharedIntrusive const& rhs)",
|
||||
"purpose": "Assignment operator, handles self-assignment, increments ref if needed, releases old pointer.",
|
||||
"validation_points": [
|
||||
"if (this == &rhs) // Self-assignment check",
|
||||
"if (p) // Validates pointer before addStrongRef"
|
||||
]
|
||||
},
|
||||
{
|
||||
"call_chain": [
|
||||
"SharedIntrusive<T>::operator=(SharedIntrusive<TT> const& rhs)",
|
||||
"if constexpr (std::is_same_v<T, TT>)",
|
||||
"if (this == &rhs)",
|
||||
"rhs.unsafeGetRawPtr()",
|
||||
"if (p)",
|
||||
"p->addStrongRef()",
|
||||
"unsafeReleaseAndStore(p)"
|
||||
],
|
||||
"entry_point": "SharedIntrusive<T>::operator=(SharedIntrusive<TT> const& rhs)",
|
||||
"purpose": "Assignment from convertible type, handles self-assignment, increments ref if needed, releases old pointer.",
|
||||
"validation_points": [
|
||||
"if (this == &rhs) // Self-assignment check (if T == TT)",
|
||||
"if (p) // Validates pointer before addStrongRef"
|
||||
]
|
||||
},
|
||||
{
|
||||
"call_chain": [
|
||||
"SharedIntrusive<T>::adopt(T* p)",
|
||||
"if constexpr (std::is_same_v<TAdoptTag, SharedIntrusiveAdoptIncrementStrongTag>)",
|
||||
"if (p)",
|
||||
"p->addStrongRef()",
|
||||
"unsafeReleaseAndStore(p)"
|
||||
],
|
||||
"entry_point": "SharedIntrusive<T>::adopt(T* p)",
|
||||
"purpose": "Adopts a raw pointer, optionally increments ref count, releases old pointer.",
|
||||
"validation_points": [
|
||||
"if (p) // Validates pointer before addStrongRef"
|
||||
]
|
||||
}
|
||||
],
|
||||
"data_flows": [
|
||||
{
|
||||
"field": "ptr_",
|
||||
"flow": [
|
||||
"T* p (input or from rhs)",
|
||||
"if (p) validation",
|
||||
"p->addStrongRef() (if validated)",
|
||||
"ptr_ = p"
|
||||
],
|
||||
"origin": "Constructor argument (T* p) or rhs.unsafeGetRawPtr()",
|
||||
"transformations": [
|
||||
"Pointer is checked for null",
|
||||
"Reference count incremented if not null",
|
||||
"Stored in ptr_"
|
||||
],
|
||||
"validated_at": "if (p) before addStrongRef"
|
||||
},
|
||||
{
|
||||
"field": "rhs (SharedIntrusive const& rhs or SharedIntrusive<TT> const& rhs)",
|
||||
"flow": [
|
||||
"rhs (input)",
|
||||
"if (this == &rhs) validation (self-assignment)",
|
||||
"rhs.unsafeGetRawPtr()",
|
||||
"if (p) validation",
|
||||
"p->addStrongRef()",
|
||||
"unsafeReleaseAndStore(p)"
|
||||
],
|
||||
"origin": "Function argument",
|
||||
"transformations": [
|
||||
"Self-assignment check",
|
||||
"Pointer extracted",
|
||||
"Reference count incremented if not null",
|
||||
"Old pointer released and replaced"
|
||||
],
|
||||
"validated_at": "if (this == &rhs), if (p)"
|
||||
},
|
||||
{
|
||||
"field": "T* p (adopt)",
|
||||
"flow": [
|
||||
"T* p (input)",
|
||||
"if (p) validation",
|
||||
"p->addStrongRef() (if validated)",
|
||||
"unsafeReleaseAndStore(p)"
|
||||
],
|
||||
"origin": "adopt(T* p) argument",
|
||||
"transformations": [
|
||||
"Pointer is checked for null",
|
||||
"Reference count incremented if not null",
|
||||
"Old pointer released and replaced"
|
||||
],
|
||||
"validated_at": "if (p)"
|
||||
}
|
||||
],
|
||||
"description": "Implements the definitions for intrusive smart pointer types (SharedIntrusive, WeakIntrusive, SharedWeakUnion) used for reference-counted memory management in the xrpl namespace.",
|
||||
"false_positive_patterns": [
|
||||
{
|
||||
"applies_to": [
|
||||
"validation",
|
||||
"input_validation"
|
||||
],
|
||||
"confidence": 0.9,
|
||||
"detection_keywords": [
|
||||
"template type parameters (via static_assert and if constexpr)",
|
||||
"validation",
|
||||
"missing",
|
||||
"check"
|
||||
],
|
||||
"evidence": "Field template type parameters (via static_assert and if constexpr) validated by C++ type system, manual null checks, static_assert",
|
||||
"issue_pattern": "Missing validation for template type parameters (via static_assert and if constexpr)",
|
||||
"why_false_positive": "C++ type system, manual null checks, static_assert validates template type parameters (via static_assert and if constexpr) automatically"
|
||||
},
|
||||
{
|
||||
"applies_to": [
|
||||
"validation",
|
||||
"input_validation"
|
||||
],
|
||||
"confidence": 0.9,
|
||||
"detection_keywords": [
|
||||
"pointer nullness (via if (p))",
|
||||
"validation",
|
||||
"missing",
|
||||
"check"
|
||||
],
|
||||
"evidence": "Field pointer nullness (via if (p)) validated by C++ type system, manual null checks, static_assert",
|
||||
"issue_pattern": "Missing validation for pointer nullness (via if (p))",
|
||||
"why_false_positive": "C++ type system, manual null checks, static_assert validates pointer nullness (via if (p)) automatically"
|
||||
},
|
||||
{
|
||||
"applies_to": [
|
||||
"validation",
|
||||
"null_empty"
|
||||
],
|
||||
"confidence": 1.0,
|
||||
"detection_keywords": [
|
||||
"p (pointer to T)",
|
||||
"empty",
|
||||
"string",
|
||||
"validation"
|
||||
],
|
||||
"evidence": "if (p) at SharedIntrusive<T>::SharedIntrusive(T* p, TAdoptTag) noexcept",
|
||||
"issue_pattern": "Missing empty string validation for p (pointer to T)",
|
||||
"why_false_positive": "if (p) validates p (pointer to T) for empty strings"
|
||||
},
|
||||
{
|
||||
"applies_to": [
|
||||
"validation",
|
||||
"null_empty"
|
||||
],
|
||||
"confidence": 1.0,
|
||||
"detection_keywords": [
|
||||
"rhs (SharedIntrusive const& rhs)",
|
||||
"empty",
|
||||
"string",
|
||||
"validation"
|
||||
],
|
||||
"evidence": "if (p) at SharedIntrusive<T>::SharedIntrusive(SharedIntrusive const& rhs)",
|
||||
"issue_pattern": "Missing empty string validation for rhs (SharedIntrusive const& rhs)",
|
||||
"why_false_positive": "if (p) validates rhs (SharedIntrusive const& rhs) for empty strings"
|
||||
},
|
||||
{
|
||||
"applies_to": [
|
||||
"validation",
|
||||
"null_empty"
|
||||
],
|
||||
"confidence": 1.0,
|
||||
"detection_keywords": [
|
||||
"rhs (SharedIntrusive<TT> const& rhs)",
|
||||
"empty",
|
||||
"string",
|
||||
"validation"
|
||||
],
|
||||
"evidence": "if (p) at SharedIntrusive<T>::SharedIntrusive(SharedIntrusive<TT> const& rhs)",
|
||||
"issue_pattern": "Missing empty string validation for rhs (SharedIntrusive<TT> const& rhs)",
|
||||
"why_false_positive": "if (p) validates rhs (SharedIntrusive<TT> const& rhs) for empty strings"
|
||||
},
|
||||
{
|
||||
"applies_to": [
|
||||
"validation",
|
||||
"null_empty"
|
||||
],
|
||||
"confidence": 1.0,
|
||||
"detection_keywords": [
|
||||
"this and rhs (self-assignment)",
|
||||
"empty",
|
||||
"string",
|
||||
"validation"
|
||||
],
|
||||
"evidence": "if (this == &rhs) at SharedIntrusive<T>::operator=(SharedIntrusive const& rhs)",
|
||||
"issue_pattern": "Missing empty string validation for this and rhs (self-assignment)",
|
||||
"why_false_positive": "if (this == &rhs) validates this and rhs (self-assignment) for empty strings"
|
||||
},
|
||||
{
|
||||
"applies_to": [
|
||||
"validation",
|
||||
"null_empty"
|
||||
],
|
||||
"confidence": 1.0,
|
||||
"detection_keywords": [
|
||||
"this and rhs (self-assignment)",
|
||||
"empty",
|
||||
"string",
|
||||
"validation"
|
||||
],
|
||||
"evidence": "if (this == &rhs) at SharedIntrusive<T>::operator=(SharedIntrusive<TT> const& rhs)",
|
||||
"issue_pattern": "Missing empty string validation for this and rhs (self-assignment)",
|
||||
"why_false_positive": "if (this == &rhs) validates this and rhs (self-assignment) for empty strings"
|
||||
},
|
||||
{
|
||||
"applies_to": [
|
||||
"validation",
|
||||
"null_empty"
|
||||
],
|
||||
"confidence": 1.0,
|
||||
"detection_keywords": [
|
||||
"this and rhs (self-assignment)",
|
||||
"empty",
|
||||
"string",
|
||||
"validation"
|
||||
],
|
||||
"evidence": "if (this == &rhs) at SharedIntrusive<T>::operator=(SharedIntrusive&& rhs)",
|
||||
"issue_pattern": "Missing empty string validation for this and rhs (self-assignment)",
|
||||
"why_false_positive": "if (this == &rhs) validates this and rhs (self-assignment) for empty strings"
|
||||
},
|
||||
{
|
||||
"applies_to": [
|
||||
"validation",
|
||||
"null_empty"
|
||||
],
|
||||
"confidence": 1.0,
|
||||
"detection_keywords": [
|
||||
"TT (template type parameter)",
|
||||
"empty",
|
||||
"string",
|
||||
"validation"
|
||||
],
|
||||
"evidence": "static_assert(!std::is_same_v<T, TT>, ...) at SharedIntrusive<T>::operator=(SharedIntrusive<TT>&& rhs)",
|
||||
"issue_pattern": "Missing empty string validation for TT (template type parameter)",
|
||||
"why_false_positive": "static_assert(!std::is_same_v<T, TT>, ...) validates TT (template type parameter) for empty strings"
|
||||
},
|
||||
{
|
||||
"applies_to": [
|
||||
"validation",
|
||||
"type_safety"
|
||||
],
|
||||
"confidence": 0.85,
|
||||
"detection_keywords": [
|
||||
"TT (template type parameter)",
|
||||
"type",
|
||||
"validation",
|
||||
"check"
|
||||
],
|
||||
"evidence": "static_assert(!std::is_same_v<T, TT>, ...) at SharedIntrusive<T>::operator=(SharedIntrusive<TT>&& rhs)",
|
||||
"issue_pattern": "Missing type validation for TT (template type parameter)",
|
||||
"why_false_positive": "static_assert(!std::is_same_v<T, TT>, ...) validates TT (template type parameter) type"
|
||||
},
|
||||
{
|
||||
"applies_to": [
|
||||
"validation",
|
||||
"null_empty"
|
||||
],
|
||||
"confidence": 1.0,
|
||||
"detection_keywords": [
|
||||
"TAdoptTag (template type parameter)",
|
||||
"empty",
|
||||
"string",
|
||||
"validation"
|
||||
],
|
||||
"evidence": "if constexpr (std::is_same_v<TAdoptTag, SharedIntrusiveAdoptIncrementStrongTag>) at SharedIntrusive<T>::SharedIntrusive(T* p, TAdoptTag) noexcept and adopt(T* p)",
|
||||
"issue_pattern": "Missing empty string validation for TAdoptTag (template type parameter)",
|
||||
"why_false_positive": "if constexpr (std::is_same_v<TAdoptTag, SharedIntrusiveAdoptIncrementStrongTag>) validates TAdoptTag (template type parameter) for empty strings"
|
||||
},
|
||||
{
|
||||
"applies_to": [
|
||||
"validation",
|
||||
"type_safety"
|
||||
],
|
||||
"confidence": 0.85,
|
||||
"detection_keywords": [
|
||||
"TAdoptTag (template type parameter)",
|
||||
"type",
|
||||
"validation",
|
||||
"check"
|
||||
],
|
||||
"evidence": "if constexpr (std::is_same_v<TAdoptTag, SharedIntrusiveAdoptIncrementStrongTag>) at SharedIntrusive<T>::SharedIntrusive(T* p, TAdoptTag) noexcept and adopt(T* p)",
|
||||
"issue_pattern": "Missing type validation for TAdoptTag (template type parameter)",
|
||||
"why_false_positive": "if constexpr (std::is_same_v<TAdoptTag, SharedIntrusiveAdoptIncrementStrongTag>) validates TAdoptTag (template type parameter) type"
|
||||
}
|
||||
],
|
||||
"file_path": "workflow/XRPLF-rippled-develop/source/include/xrpl/basics/IntrusivePointer.ipp",
|
||||
"functions": [
|
||||
{
|
||||
"args": [
|
||||
"SharedIntrusive const& rhs"
|
||||
],
|
||||
"lineno": 61,
|
||||
"name": "SharedIntrusive<T>::operator="
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"SharedIntrusive<TT> const& rhs"
|
||||
],
|
||||
"lineno": 80,
|
||||
"name": "SharedIntrusive<T>::operator="
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"SharedIntrusive&& rhs"
|
||||
],
|
||||
"lineno": 99,
|
||||
"name": "SharedIntrusive<T>::operator="
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"SharedIntrusive<TT>&& rhs"
|
||||
],
|
||||
"lineno": 110,
|
||||
"name": "SharedIntrusive<T>::operator="
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"std::nullptr_t"
|
||||
],
|
||||
"lineno": 120,
|
||||
"name": "SharedIntrusive<T>::operator!="
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"std::nullptr_t"
|
||||
],
|
||||
"lineno": 126,
|
||||
"name": "SharedIntrusive<T>::operator=="
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"T* p"
|
||||
],
|
||||
"lineno": 132,
|
||||
"name": "SharedIntrusive<T>::adopt"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 142,
|
||||
"name": "SharedIntrusive<T>::~SharedIntrusive"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 170,
|
||||
"name": "SharedIntrusive<T>::operator*"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 176,
|
||||
"name": "SharedIntrusive<T>::operator->"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 182,
|
||||
"name": "SharedIntrusive<T>::operator bool"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 188,
|
||||
"name": "SharedIntrusive<T>::reset"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 193,
|
||||
"name": "SharedIntrusive<T>::get"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 198,
|
||||
"name": "SharedIntrusive<T>::use_count"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 206,
|
||||
"name": "SharedIntrusive<T>::unsafeGetRawPtr"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"T* p"
|
||||
],
|
||||
"lineno": 211,
|
||||
"name": "SharedIntrusive<T>::unsafeSetRawPtr"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"T* p"
|
||||
],
|
||||
"lineno": 216,
|
||||
"name": "SharedIntrusive<T>::unsafeExchange"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"T* next"
|
||||
],
|
||||
"lineno": 221,
|
||||
"name": "SharedIntrusive<T>::unsafeReleaseAndStore"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"SharedIntrusive<TT> const& rhs"
|
||||
],
|
||||
"lineno": 265,
|
||||
"name": "WeakIntrusive<T>::operator="
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"T* ptr"
|
||||
],
|
||||
"lineno": 274,
|
||||
"name": "WeakIntrusive<T>::adopt"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 280,
|
||||
"name": "WeakIntrusive<T>::~WeakIntrusive"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 285,
|
||||
"name": "WeakIntrusive<T>::lock"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 294,
|
||||
"name": "WeakIntrusive<T>::expired"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 299,
|
||||
"name": "WeakIntrusive<T>::reset"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 304,
|
||||
"name": "WeakIntrusive<T>::unsafeReleaseNoStore"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"SharedWeakUnion const& rhs"
|
||||
],
|
||||
"lineno": 353,
|
||||
"name": "SharedWeakUnion<T>::operator="
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"SharedIntrusive<TT> const& rhs"
|
||||
],
|
||||
"lineno": 380,
|
||||
"name": "SharedWeakUnion<T>::operator="
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"SharedIntrusive<TT>&& rhs"
|
||||
],
|
||||
"lineno": 391,
|
||||
"name": "SharedWeakUnion<T>::operator="
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 399,
|
||||
"name": "SharedWeakUnion<T>::~SharedWeakUnion"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 404,
|
||||
"name": "SharedWeakUnion<T>::getStrong"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 415,
|
||||
"name": "SharedWeakUnion<T>::operator bool"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 420,
|
||||
"name": "SharedWeakUnion<T>::reset"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 425,
|
||||
"name": "SharedWeakUnion<T>::get"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 430,
|
||||
"name": "SharedWeakUnion<T>::use_count"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 437,
|
||||
"name": "SharedWeakUnion<T>::expired"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 442,
|
||||
"name": "SharedWeakUnion<T>::lock"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 463,
|
||||
"name": "SharedWeakUnion<T>::isStrong"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 468,
|
||||
"name": "SharedWeakUnion<T>::isWeak"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 473,
|
||||
"name": "SharedWeakUnion<T>::convertToStrong"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 491,
|
||||
"name": "SharedWeakUnion<T>::convertToWeak"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 517,
|
||||
"name": "SharedWeakUnion<T>::unsafeGetRawPtr"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"T* p",
|
||||
"RefStrength rs"
|
||||
],
|
||||
"lineno": 522,
|
||||
"name": "SharedWeakUnion<T>::unsafeSetRawPtr"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"std::nullptr_t"
|
||||
],
|
||||
"lineno": 528,
|
||||
"name": "SharedWeakUnion<T>::unsafeSetRawPtr"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 532,
|
||||
"name": "SharedWeakUnion<T>::unsafeReleaseNoStore"
|
||||
}
|
||||
],
|
||||
"language": "cpp",
|
||||
"language_patterns": {
|
||||
"exception_patterns": [],
|
||||
"namespace_accessors": [],
|
||||
"raii_usage": [],
|
||||
"smart_pointers": [],
|
||||
"template_validation": []
|
||||
},
|
||||
"namespaces": [
|
||||
{
|
||||
"lineno": 6,
|
||||
"name": "xrpl"
|
||||
}
|
||||
],
|
||||
"test_coverage_notes": "The code is a low-level utility for intrusive reference counting. Typical tests would be in files like IntrusivePointer_test.cpp or SharedIntrusive_test.cpp, likely under the 'test' or 'unittest' directories. Tests should cover: construction from raw pointer (null and non-null), copy/move construction, assignment (including self-assignment), adopt(), and destruction. Gaps may exist in testing edge cases such as self-assignment, null pointer handling, and cross-type assignment. No direct evidence of test files is present in this snippet, so coverage should be verified in the codebase.",
|
||||
"validation_architecture": {
|
||||
"auto_validated_fields": [
|
||||
"template type parameters (via static_assert and if constexpr)",
|
||||
"pointer nullness (via if (p))"
|
||||
],
|
||||
"framework": "C++ type system, manual null checks, static_assert",
|
||||
"validation_layer": "business_logic"
|
||||
},
|
||||
"validations": [
|
||||
{
|
||||
"confidence": 1.0,
|
||||
"error_thrown": "none (conditional logic only)",
|
||||
"field": "p (pointer to T)",
|
||||
"location": "SharedIntrusive<T>::SharedIntrusive(T* p, TAdoptTag) noexcept",
|
||||
"validated_by": "if (p)",
|
||||
"validates": [
|
||||
"Checks if pointer p is not null before calling p->addStrongRef()"
|
||||
],
|
||||
"validation_type": "null check"
|
||||
},
|
||||
{
|
||||
"confidence": 1.0,
|
||||
"error_thrown": "none (conditional logic only)",
|
||||
"field": "rhs (SharedIntrusive const& rhs)",
|
||||
"location": "SharedIntrusive<T>::SharedIntrusive(SharedIntrusive const& rhs)",
|
||||
"validated_by": "if (p)",
|
||||
"validates": [
|
||||
"Checks if pointer p (from rhs.unsafeGetRawPtr()) is not null before calling p->addStrongRef()"
|
||||
],
|
||||
"validation_type": "null check"
|
||||
},
|
||||
{
|
||||
"confidence": 1.0,
|
||||
"error_thrown": "none (conditional logic only)",
|
||||
"field": "rhs (SharedIntrusive<TT> const& rhs)",
|
||||
"location": "SharedIntrusive<T>::SharedIntrusive(SharedIntrusive<TT> const& rhs)",
|
||||
"validated_by": "if (p)",
|
||||
"validates": [
|
||||
"Checks if pointer p (from rhs.unsafeGetRawPtr()) is not null before calling p->addStrongRef()"
|
||||
],
|
||||
"validation_type": "null check"
|
||||
},
|
||||
{
|
||||
"confidence": 1.0,
|
||||
"error_thrown": "none (early return)",
|
||||
"field": "this and rhs (self-assignment)",
|
||||
"location": "SharedIntrusive<T>::operator=(SharedIntrusive const& rhs)",
|
||||
"validated_by": "if (this == &rhs)",
|
||||
"validates": [
|
||||
"Checks for self-assignment to avoid unnecessary operations"
|
||||
],
|
||||
"validation_type": "business_logic"
|
||||
},
|
||||
{
|
||||
"confidence": 1.0,
|
||||
"error_thrown": "none (early return)",
|
||||
"field": "this and rhs (self-assignment)",
|
||||
"location": "SharedIntrusive<T>::operator=(SharedIntrusive<TT> const& rhs)",
|
||||
"validated_by": "if (this == &rhs)",
|
||||
"validates": [
|
||||
"Checks for self-assignment to avoid unnecessary operations (only if T == TT)"
|
||||
],
|
||||
"validation_type": "business_logic"
|
||||
},
|
||||
{
|
||||
"confidence": 1.0,
|
||||
"error_thrown": "none (early return)",
|
||||
"field": "this and rhs (self-assignment)",
|
||||
"location": "SharedIntrusive<T>::operator=(SharedIntrusive&& rhs)",
|
||||
"validated_by": "if (this == &rhs)",
|
||||
"validates": [
|
||||
"Checks for self-assignment to avoid unnecessary operations"
|
||||
],
|
||||
"validation_type": "business_logic"
|
||||
},
|
||||
{
|
||||
"confidence": 1.0,
|
||||
"error_thrown": "static_assert failure (compile-time error)",
|
||||
"field": "TT (template type parameter)",
|
||||
"location": "SharedIntrusive<T>::operator=(SharedIntrusive<TT>&& rhs)",
|
||||
"validated_by": "static_assert(!std::is_same_v<T, TT>, ...)",
|
||||
"validates": [
|
||||
"Ensures that this overload is not instantiated for T == TT"
|
||||
],
|
||||
"validation_type": "type"
|
||||
},
|
||||
{
|
||||
"confidence": 1.0,
|
||||
"error_thrown": "none (compile-time conditional)",
|
||||
"field": "TAdoptTag (template type parameter)",
|
||||
"location": "SharedIntrusive<T>::SharedIntrusive(T* p, TAdoptTag) noexcept and adopt(T* p)",
|
||||
"validated_by": "if constexpr (std::is_same_v<TAdoptTag, SharedIntrusiveAdoptIncrementStrongTag>)",
|
||||
"validates": [
|
||||
"Ensures that addStrongRef() is only called if TAdoptTag is SharedIntrusiveAdoptIncrementStrongTag"
|
||||
],
|
||||
"validation_type": "type"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
# `IntrusivePointer.ipp` — Intrusive Smart Pointer Method Definitions
|
||||
|
||||
This file provides the out-of-line template method bodies for three intrusive smart pointer classes declared in `IntrusivePointer.h`: `SharedIntrusive<T>`, `WeakIntrusive<T>`, and `SharedWeakUnion<T>`. As is conventional for C++ template implementations that must be visible to all translation units, the definitions live in a `.ipp` file that `IntrusivePointer.h` includes, rather than in a `.cpp` file.
|
||||
|
||||
## Why Not `std::shared_ptr`?
|
||||
|
||||
The comment on `SharedIntrusive` in the header explains the core motivation: the XRPL codebase needs a smart pointer that can release an object's *data* when the last strong reference drops, while deferring the *memory* release until the last weak reference also drops. `std::shared_ptr` guarantees that the destructor runs at strong-count zero, but an implementation using `make_shared` keeps the entire memory block alive until weak-count zero. More importantly, `std::shared_ptr` provides no hook between "last strong gone" and "last weak gone."
|
||||
|
||||
The intrusive design solves this by embedding reference counts directly in the pointee via `IntrusiveRefCounts` (a base struct the controlled type must inherit). When the strong count reaches zero, the pointer machinery calls a user-defined `partialDestructor()` — which can, for instance, reset `SHAMapInnerNode`'s child-pointer array to free the expensive working data — while the object shell continues to exist as long as any weak pointer holds on. Only when the weak count also reaches zero does `delete` run.
|
||||
|
||||
## `SharedIntrusive<T>` — The Strong Pointer
|
||||
|
||||
Construction and assignment follow a consistent pattern: acquire the new ref before releasing the old. The copy constructor uses a lambda initializer to atomically call `addStrongRef()` before storing the pointer in `ptr_`, ensuring the count is correct even if the lambda result is used to initialize a field directly:
|
||||
|
||||
```cpp
|
||||
ptr_{[&] {
|
||||
auto p = rhs.unsafeGetRawPtr();
|
||||
if (p) p->addStrongRef();
|
||||
return p;
|
||||
}()}
|
||||
```
|
||||
|
||||
Move construction is cheaper: it calls `unsafeExchange(nullptr)` on the source to steal the pointer without touching ref counts. The `static_assert` in the heterogeneous move-assignment operator enforces at compile time that the same-type case is handled by the homogeneous overload, preventing this overload from being instantiated for `T == TT`.
|
||||
|
||||
### The `TAdoptTag` Pattern
|
||||
|
||||
The `adopt(T* p)` method and the raw-pointer constructor both take a `TAdoptTag` template parameter constrained by the `CAdoptTag` concept. Two tags exist: `SharedIntrusiveAdoptIncrementStrongTag` increments the strong count (for absorbing a raw pointer that hasn't had its count bumped yet), and `SharedIntrusiveAdoptNoIncrementTag` adopts the pointer without incrementing. `make_SharedIntrusive` allocates via `new T` — `IntrusiveRefCounts` initializes the strong count to 1 — and then adopts with `SharedIntrusiveAdoptNoIncrementTag` to avoid a double-count. The `noexcept` guarantee on that constructor path is enforced with a `static_assert` inside `make_SharedIntrusive` to prevent memory leaks if construction were to throw after allocation.
|
||||
|
||||
### `unsafeReleaseAndStore` — The Core Destruction Path
|
||||
|
||||
Every operation that replaces the stored pointer funnels through `unsafeReleaseAndStore(T* next)`. It atomically swaps the new pointer in via `std::exchange`, then calls `releaseStrongRef()` on the evicted pointer. The return value is a `ReleaseStrongRefAction` enum with three values:
|
||||
|
||||
- `noop` — other strong pointers remain; do nothing.
|
||||
- `destroy` — no strong or weak pointers remain; call `delete`.
|
||||
- `partialDestroy` — the weak count is non-zero; call `partialDestructor()` then `partialDestructorFinished()`.
|
||||
|
||||
The `partialDestructorFinished` template friend function sets the `partialDestroyFinishedMask` bit atomically on `IntrusiveRefCounts::refCounts`, and if the weak count has already reached zero it calls `notify_one()` to wake any thread that is waiting in `releaseWeakRef()` for the partial destructor to complete before running the full destructor.
|
||||
|
||||
### Cast Constructors
|
||||
|
||||
`SharedIntrusive` supports both `static_cast` and `dynamic_cast` construction from a `SharedIntrusive<TT>`. For the move variant of `dynamic_cast`, there is a subtle correctness invariant: if `dynamic_cast<T*>` returns null (the cast fails), the source pointer is restored via `rhs.unsafeExchange(toSet)` to prevent the controlled object from leaking. A code comment notes that the `unsafeExchange` structure is also kept in anticipation of a future atomic pointer mode.
|
||||
|
||||
## `WeakIntrusive<T>` — The Weak Pointer
|
||||
|
||||
`WeakIntrusive` manages a non-owning reference via `addWeakRef()` and `releaseWeakRef()`. Two deliberate omissions in the interface are worth noting:
|
||||
|
||||
- Copy assignment from another `WeakIntrusive` is `delete`d. The header comment explains this is because there are currently no use cases, and omitting it simplifies implementation. It can be reintroduced if needed.
|
||||
- There is no move constructor from `SharedIntrusive<T>&&`. Moving a strong pointer into a weak pointer would require decrementing the strong count and adding a weak count, making it *more* expensive than copying the raw pointer and adding a weak ref. The deleted overload prevents this surprising hidden cost.
|
||||
|
||||
`lock()` calls `checkoutStrongRefFromWeak()` on the raw pointer, which uses a CAS loop to atomically increment the strong count only if it is currently non-zero. On success, the new `SharedIntrusive` is constructed with `SharedIntrusiveAdoptNoIncrementTag` — the checkout already performed the increment, so a second increment must not occur.
|
||||
|
||||
## `SharedWeakUnion<T>` — The Tagged-Pointer Union
|
||||
|
||||
`SharedWeakUnion` packs both a strong and weak reference into the space of a single pointer word. It stores the pointer as a `std::uintptr_t` called `tp_`, uses the low bit as a tag (1 = weak, 0 = strong), and recovers the raw pointer by masking with `ptrMask = ~1`. A `static_assert` on `alignof(T) >= 2` enforces that the actual pointer will never set the low bit, keeping the encoding sound.
|
||||
|
||||
`unsafeGetRawPtr()` applies the mask; `unsafeSetRawPtr(T*, RefStrength)` stores the pointer and conditionally ORs in the tag bit. `isStrong()` / `isWeak()` read the tag bit directly.
|
||||
|
||||
`convertToStrong()` and `convertToWeak()` allow in-place reference strength switching. `convertToWeak()` uses `addWeakReleaseStrongRef()` — an atomic operation on `IntrusiveRefCounts` that adds a weak delta and subtracts a strong delta in one CAS loop — to avoid a window where the strong count is zero but the weak count hasn't been incremented yet. If the result is `partialDestroy`, `convertToWeak` handles the two-phase partial destruction, including the `partialDestructorFinished` call that clears the pointer variable.
|
||||
|
||||
`get()` returns the raw pointer only if the union holds a strong reference; calling `get()` on a weak-tagged union returns null. `lock()` unifies both cases: if already strong, it bumps the strong count and returns; if weak, it attempts `checkoutStrongRefFromWeak()` and adopts without increment on success.
|
||||
|
||||
## Naming Convention for Primitives
|
||||
|
||||
All methods prefixed with `unsafe` are private and skip reference counting entirely. They manipulate the raw pointer field directly. This naming convention serves two purposes: it makes the separation between raw pointer mechanics and safe counted semantics immediately visible during code review, and the header comments note that these wrappers exist in anticipation of a future patch to support atomic pointer storage (which would require replacing `std::exchange` with `std::atomic::exchange` inside these one-line helpers).
|
||||
@@ -1,118 +0,0 @@
|
||||
{
|
||||
"args": [
|
||||
{
|
||||
"lineno": 272,
|
||||
"name": "v"
|
||||
},
|
||||
{
|
||||
"lineno": 282,
|
||||
"name": "s"
|
||||
},
|
||||
{
|
||||
"lineno": 282,
|
||||
"name": "w"
|
||||
},
|
||||
{
|
||||
"lineno": 299,
|
||||
"name": "o"
|
||||
}
|
||||
],
|
||||
"classes": [
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 38,
|
||||
"name": "IntrusiveRefCounts"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"FieldType v",
|
||||
"CountType s, CountType w"
|
||||
],
|
||||
"lineno": 101,
|
||||
"name": "IntrusiveRefCounts::RefCountPair"
|
||||
}
|
||||
],
|
||||
"description": "Implements atomic reference counting for intrusive smart pointers, including strong and weak reference management, partial destruction, and thread-safe operations for use in the XRPL codebase.",
|
||||
"file_path": "workflow/XRPLF-rippled-develop/source/include/xrpl/basics/IntrusiveRefCounts.h",
|
||||
"functions": [
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 120,
|
||||
"name": "IntrusiveRefCounts::addStrongRef"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 125,
|
||||
"name": "IntrusiveRefCounts::addWeakRef"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 130,
|
||||
"name": "IntrusiveRefCounts::releaseStrongRef"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 170,
|
||||
"name": "IntrusiveRefCounts::addWeakReleaseStrongRef"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 210,
|
||||
"name": "IntrusiveRefCounts::releaseWeakRef"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 235,
|
||||
"name": "IntrusiveRefCounts::checkoutStrongRefFromWeak"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 248,
|
||||
"name": "IntrusiveRefCounts::expired"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 253,
|
||||
"name": "IntrusiveRefCounts::use_count"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 258,
|
||||
"name": "IntrusiveRefCounts::~IntrusiveRefCounts"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"IntrusiveRefCounts::FieldType v"
|
||||
],
|
||||
"lineno": 271,
|
||||
"name": "IntrusiveRefCounts::RefCountPair::RefCountPair"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"IntrusiveRefCounts::CountType s",
|
||||
"IntrusiveRefCounts::CountType w"
|
||||
],
|
||||
"lineno": 281,
|
||||
"name": "IntrusiveRefCounts::RefCountPair::RefCountPair"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 289,
|
||||
"name": "IntrusiveRefCounts::RefCountPair::combinedValue"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"T** o"
|
||||
],
|
||||
"lineno": 299,
|
||||
"name": "partialDestructorFinished"
|
||||
}
|
||||
],
|
||||
"language": "c header",
|
||||
"namespaces": [
|
||||
{
|
||||
"lineno": 6,
|
||||
"name": "xrpl"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
# `IntrusiveRefCounts.h` — Atomic Reference Counting for Intrusive Smart Pointers
|
||||
|
||||
## Role in the System
|
||||
|
||||
`IntrusiveRefCounts` is the reference-counting backbone for XRPL's custom intrusive smart pointer family: `SharedIntrusive<T>`, `WeakIntrusive<T>`, and `SharedWeakUnion<T>`, all defined in `IntrusivePointer.h`. A class participates in this system simply by inheriting from `IntrusiveRefCounts`; the notable consumer is `SHAMapInnerNode`, whose billions-of-ops-per-second traversal patterns make every byte and every allocation matter.
|
||||
|
||||
The design answers a specific criticism of `std::shared_ptr`: with `make_shared`, the control block and the object are co-allocated, which is efficient, but the memory cannot be reclaimed until the weak count also hits zero. XRPL's intrusive design embeds the counts directly in the object, saves the separate control-block allocation, and — crucially — introduces a *partial destruction* protocol that lets the object shed its expensive payload (e.g., child node pointers) the moment the strong count reaches zero, even while weak pointers keep the shell alive.
|
||||
|
||||
## The Packed Atomic Field
|
||||
|
||||
All state lives in a single `mutable std::atomic<uint32_t> refCounts`. The 32 bits are divided as follows:
|
||||
|
||||
| Bits | Field |
|
||||
|------|-------|
|
||||
| 0–15 | Strong count (16 bits) |
|
||||
| 16–29 | Weak count (14 bits) |
|
||||
| 30 | `partialDestroyStartedBit` |
|
||||
| 31 | `partialDestroyFinishedBit` |
|
||||
|
||||
Packing everything into one atomic word means that decrementing a count *and* setting a flag can be done as a single compare-and-swap, eliminating any window between the two operations that a second atomic would expose. The helper struct `RefCountPair` wraps the unpack/repack logic: its constructor extracts each field by masking and shifting, and `combinedValue()` reassembles them.
|
||||
|
||||
## Destruction Lifecycle and the Partial Destructor
|
||||
|
||||
When `releaseStrongRef()` finds that the strong count is dropping to exactly one (i.e., to zero), it branches on whether any weak references exist:
|
||||
|
||||
- **No weak refs**: returns `ReleaseStrongRefAction::destroy`. The caller (in `IntrusivePointer.ipp`) runs `delete ptr`, calling the full destructor.
|
||||
- **Weak refs present**: atomically sets `partialDestroyStartedBit` *in the same CAS that decrements the count*, then returns `partialDestroy`. The caller invokes `ptr->partialDestructor()`, then calls the free function `partialDestructorFinished(&ptr)`.
|
||||
|
||||
This CAS loop in `releaseStrongRef()` almost always executes once; looping is necessary only if another thread modifies `refCounts` between the `load` and `compare_exchange_weak`.
|
||||
|
||||
`partialDestructorFinished()` is a `friend` function template declared in the class. It atomically sets `partialDestroyFinishedBit` via `fetch_or`, then — if the weak count is already zero at that point — calls `refCounts.notify_one()` to wake any thread blocked in `releaseWeakRef()`. The intentional `T**` (double-pointer) signature is a deliberate API ergonomic signal: after the call, `*o` is set to `nullptr` to discourage use-after-free, because another thread may immediately delete the object upon seeing the finished bit.
|
||||
|
||||
## Why Two Bits for Partial Destroy
|
||||
|
||||
There is a genuine race that a single bit cannot handle. Consider:
|
||||
|
||||
1. Thread A: last strong pointer releases → strong count goes to zero, weak count is 1, `partialDestroyStarted` is about to be set (but has not been set yet).
|
||||
2. Thread B: last weak pointer releases → sees `strong == 0`, `weak == 1 → 0` → would naively call the full destructor, racing with Thread A's partial destructor.
|
||||
|
||||
The `partialDestroyStarted` bit, set atomically in the same CAS that decrements the strong count, prevents Thread B from proceeding until the partial destructor's state is stable. The `partialDestroyFinished` bit then lets `releaseWeakRef()` know it is safe to destroy. When neither bit is set, `releaseWeakRef()` calls `refCounts.wait(…)` — a futex-style block on the atomic value — and rechecks upon wake-up.
|
||||
|
||||
## Key Operations
|
||||
|
||||
**`addStrongRef()` / `addWeakRef()`** use `fetch_add` with `acq_rel` ordering — the common fast path that requires no CAS loop.
|
||||
|
||||
**`addWeakReleaseStrongRef()`** is an atomic composite operation needed when `SharedWeakUnion` converts itself from a strong to a weak reference. Doing these as two separate operations would create a transient moment where the object has neither kind of reference holding it, which could trigger premature destruction. The implementation computes `weakDelta - strongDelta` and applies it as one delta in a CAS loop, while still setting `partialDestroyStartedBit` if appropriate.
|
||||
|
||||
**`checkoutStrongRefFromWeak()`** implements `lock()` semantics: it atomically increments the strong count, but only if the strong count is currently nonzero. If it reaches zero between load and CAS, the loop exits with `false`, signalling that the object is already being destroyed.
|
||||
|
||||
## Design Notes and Tradeoffs
|
||||
|
||||
The `addStrongRef()` is `noexcept` by requirement: `make_SharedIntrusive` calls it immediately after `new T(...)`, and if it could throw, the freshly allocated object would leak. The `static_assert` in `make_SharedIntrusive` enforces this.
|
||||
|
||||
The `uint16_t` strong count cap of 65 535 and 14-bit weak count cap of 16 383 are annotated with a `TODO`: if audit reveals these are insufficient, both types would need to widen to `uint32_t`, moving the entire atomic to `uint64_t`. The `checkStrongMaxValue` and `checkWeakMaxValue` constants leave a 32-unit margin below the hard cap, and debug-mode assertions in `RefCountPair`'s constructors fire before the actual overflow, providing early warning.
|
||||
|
||||
The `partialDestructorFinished()` free function is deliberately *not* called at the end of the `partialDestructor()` virtual method itself. This means any class that inherits `IntrusiveRefCounts` and implements its own `partialDestructor()` must explicitly call `partialDestructorFinished()` when done. The comment explains this was chosen to make the protocol visible at the call site rather than hiding it inside the smart-pointer machinery, reducing the chance that new subclasses silently skip the required notification.
|
||||
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"args": [],
|
||||
"classes": [],
|
||||
"description": "Defines a type alias KeyCache as a TaggedCache specialized for uint256 keys and int values within the xrpl namespace.",
|
||||
"file_path": "workflow/XRPLF-rippled-develop/source/include/xrpl/basics/KeyCache.h",
|
||||
"functions": [],
|
||||
"language": "c header",
|
||||
"namespaces": [
|
||||
{
|
||||
"lineno": 5,
|
||||
"name": "xrpl"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
# `KeyCache.h` — Key-Only Cache Type Alias
|
||||
|
||||
`KeyCache.h` provides a single-line type alias that expresses a common, narrow caching pattern throughout the XRPL codebase: track the *presence* of `uint256` keys over time, without attaching any value to them.
|
||||
|
||||
```cpp
|
||||
using KeyCache = TaggedCache<uint256, int, true>;
|
||||
```
|
||||
|
||||
The three template arguments to `TaggedCache` are: the key type (`uint256`, the 256-bit hash used pervasively in XRPL), a value type placeholder (`int`), and the `IsKeyCache` boolean flag set to `true`. That third argument is the critical one. Inside `TaggedCache`, `IsKeyCache = true` activates a compile-time branch via `std::conditional` that substitutes `KeyOnlyEntry` — a struct holding nothing but a `last_access` timestamp — in place of the full `ValueEntry` that carries a `shared_ptr` to an actual object. The `int` value type is therefore never stored or accessed; it exists only to satisfy the template parameter list.
|
||||
|
||||
This design lets callers answer a single yes/no question efficiently: *"Have I seen this hash recently enough that I don't need to re-check it?"* The primary consumer is `FullBelowCache` in `include/xrpl/shamap/FullBelowCache.h`, which uses a `KeyCache` to remember which SHAMap tree nodes have all their descendants resident in the database. When a node is marked "full below," the ledger acquisition machinery can skip redundant subtree traversals, a meaningful performance win during sync.
|
||||
|
||||
Because `KeyCache` is built directly on `TaggedCache`, it inherits the full sweep-based expiry mechanism, thread-safe access under `std::recursive_mutex`, and the `touch_if_exists` / `insert` interface — with `insert` enabled only in the key-only overload path when `IsKeyCache` is `true`.
|
||||
@@ -1,76 +0,0 @@
|
||||
{
|
||||
"args": [
|
||||
{
|
||||
"lineno": 34,
|
||||
"name": "lvs"
|
||||
},
|
||||
{
|
||||
"lineno": 53,
|
||||
"name": "Args... args"
|
||||
}
|
||||
],
|
||||
"classes": [
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 9,
|
||||
"name": "LocalValues"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 15,
|
||||
"name": "BasicValue"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"T",
|
||||
"t"
|
||||
],
|
||||
"lineno": 21,
|
||||
"name": "Value"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"Args... args"
|
||||
],
|
||||
"lineno": 52,
|
||||
"name": "LocalValue"
|
||||
}
|
||||
],
|
||||
"description": "Implements a thread- and coroutine-local storage utility for storing values specific to the calling thread or coroutine, using boost::thread_specific_ptr and custom value wrappers.",
|
||||
"file_path": "workflow/XRPLF-rippled-develop/source/include/xrpl/basics/LocalValue.h",
|
||||
"functions": [
|
||||
{
|
||||
"args": [
|
||||
"lvs"
|
||||
],
|
||||
"lineno": 34,
|
||||
"name": "cleanup"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 41,
|
||||
"name": "getLocalValues"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 67,
|
||||
"name": "operator*"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 61,
|
||||
"name": "operator->"
|
||||
}
|
||||
],
|
||||
"language": "c header",
|
||||
"namespaces": [
|
||||
{
|
||||
"lineno": 6,
|
||||
"name": "xrpl"
|
||||
},
|
||||
{
|
||||
"lineno": 8,
|
||||
"name": "detail"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
# `LocalValue.h` — Coroutine-Aware Local Storage
|
||||
|
||||
## Role and Motivation
|
||||
|
||||
Standard `thread_local` storage is insufficient in a system that runs cooperative coroutines on a thread pool. XRPL's `JobQueue::Coro` coroutines (backed by `boost::coroutines2`) are created on one thread, suspended with `yield()`, and resumed — potentially on a different thread. If two coroutines share a worker thread, a naive `thread_local` variable would expose the state left behind by a previously suspended coroutine, producing subtle, hard-to-reproduce bugs.
|
||||
|
||||
`LocalValue<T>` solves this by providing **coroutine-aware local storage**: each coroutine (or plain thread, as a fallback) gets its own independent copy of a `T`, keyed to its execution context rather than its OS thread.
|
||||
|
||||
## Architecture
|
||||
|
||||
The design has two interlocking layers: a per-coroutine dictionary (`LocalValues`) managed via a thread-local pointer, and a typed wrapper (`LocalValue<T>`) that uses its own address as a dictionary key.
|
||||
|
||||
### `detail::LocalValues`
|
||||
|
||||
`LocalValues` is a runtime dictionary that maps `void const*` keys to heap-allocated `BasicValue` instances. The keys are the addresses of `LocalValue<T>` objects; the values hold a type-erased `Value<T>` (a concrete subclass of `BasicValue`) accessed through a virtual `get()` that returns `void*`. This type-erasure pattern lets a single heterogeneous map hold values of arbitrary types without any registry or type-ID scheme — the `LocalValue<T>` template handles all casts.
|
||||
|
||||
The `onCoro` flag distinguishes two ownership modes. When `onCoro == true`, the `LocalValues` is embedded directly in a `Coro` object (`detail::LocalValues lvs_`) and its lifetime is tied to the coroutine's. When `onCoro == false`, the `LocalValues` was heap-allocated for a plain thread worker, and the `cleanup()` deleter passed to `boost::thread_specific_ptr` will `delete` it on thread exit.
|
||||
|
||||
### Thread-Local Pointer Swap in `Coro::resume()`
|
||||
|
||||
The critical mechanism lives in `Coro.ipp`. Every time a coroutine is resumed, `Coro::resume()` performs a pointer swap:
|
||||
|
||||
```cpp
|
||||
auto saved = detail::getLocalValues().release();
|
||||
detail::getLocalValues().reset(&lvs_);
|
||||
// ... run coroutine body ...
|
||||
detail::getLocalValues().release();
|
||||
detail::getLocalValues().reset(saved);
|
||||
```
|
||||
|
||||
This installs the coroutine's private `lvs_` as the active `LocalValues` for the duration of the coroutine's time slice, then restores the previous state. Any `LocalValue<T>` dereference during that time slice will therefore see the coroutine's private dictionary. Because `lvs_` is owned by the `Coro` object and the `thread_specific_ptr` merely borrows it (it will not delete it thanks to `cleanup()`'s `onCoro` guard), there is no double-free risk.
|
||||
|
||||
### `LocalValue<T>` — The Public Interface
|
||||
|
||||
`LocalValue<T>` is a global or static object that holds a single "prototype" value `t_` set at construction time. The prototype is never mutated; it only serves as the initializer for per-context copies.
|
||||
|
||||
`operator*()` implements the lookup:
|
||||
|
||||
1. Call `detail::getLocalValues().get()` to retrieve (or lazily create) the `LocalValues` for the current context.
|
||||
2. If no `LocalValues` exists yet, allocate one with `onCoro = false` (plain thread path) and register it with the `thread_specific_ptr`.
|
||||
3. Search for `this` in `lvs->values`. On a hit, cast the `void*` back to `T&` and return it.
|
||||
4. On a miss, emplace a new `Value<T>` copy-initialized from `t_`, then return a reference to that new copy.
|
||||
|
||||
`operator->()` is a trivial forwarding wrapper to `operator*()`.
|
||||
|
||||
## Concrete Usage
|
||||
|
||||
In `IOUAmount.cpp`, `LocalValue<bool>` governs whether IOU arithmetic uses the newer `STNumber` code path or the legacy one. Wrapping this flag in a `LocalValue` rather than `thread_local` ensures that two coroutines executing concurrently on the same thread pool can independently select their arithmetic mode without one overwriting the other's flag:
|
||||
|
||||
```cpp
|
||||
static LocalValue<bool> r{true};
|
||||
```
|
||||
|
||||
The coroutine test in `Coroutine_test.cpp` verifies the isolation property directly: four coroutines each set `*lv = id` (their own integer ID), interleave via yields, and confirm that no coroutine ever sees another's value. A plain-thread job running on the same pool also sees an independent copy.
|
||||
|
||||
## Key Design Decisions
|
||||
|
||||
**Address-keyed map instead of a registry.** Using `this` (the `LocalValue<T>*`) as the map key avoids any global registry or static ID counter. Each `LocalValue<T>` is naturally unique by address, and the approach requires no synchronization beyond what the `thread_specific_ptr` already provides.
|
||||
|
||||
**Lazy allocation.** The per-context copy is created on first dereference rather than at construction. This keeps coroutine startup cheap when only some `LocalValue` instances are actually accessed.
|
||||
|
||||
**`void*` type erasure with `unique_ptr<BasicValue>`.** A single `unordered_map` can hold values of unrelated types (`bool`, `int`, custom structs) because ownership and destruction are managed through the virtual destructor of `BasicValue`. The `LocalValue<T>` template retains type knowledge and performs the cast safely — the key equality guarantees that the `void*` behind a given key will always be a `T*`.
|
||||
|
||||
**`onCoro` ownership flag.** The asymmetry between coroutine-owned and thread-owned `LocalValues` is handled by a single boolean rather than two separate code paths or a custom deleter per instance. The `boost::thread_specific_ptr` deleter is fixed at static-initialization time, so the flag is the only extensible hook available.
|
||||
@@ -1,62 +0,0 @@
|
||||
{
|
||||
"args": [
|
||||
{
|
||||
"lineno": 36,
|
||||
"name": "partition"
|
||||
},
|
||||
{
|
||||
"lineno": 36,
|
||||
"name": "thresh"
|
||||
},
|
||||
{
|
||||
"lineno": 36,
|
||||
"name": "logs"
|
||||
}
|
||||
],
|
||||
"classes": [
|
||||
{
|
||||
"args": [
|
||||
"level"
|
||||
],
|
||||
"lineno": 27,
|
||||
"name": "Logs"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"partition",
|
||||
"thresh",
|
||||
"logs"
|
||||
],
|
||||
"lineno": 33,
|
||||
"name": "Logs::Sink"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 61,
|
||||
"name": "Logs::File"
|
||||
}
|
||||
],
|
||||
"description": "This file defines logging utilities for the XRPL project, including log severity levels, a Logs manager class for handling log partitions and files, and macros/utilities for debug logging.",
|
||||
"file_path": "workflow/XRPLF-rippled-develop/source/include/xrpl/basics/Log.h",
|
||||
"functions": [
|
||||
{
|
||||
"args": [
|
||||
"sink"
|
||||
],
|
||||
"lineno": 168,
|
||||
"name": "setDebugLogSink"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 175,
|
||||
"name": "debugLog"
|
||||
}
|
||||
],
|
||||
"language": "c header",
|
||||
"namespaces": [
|
||||
{
|
||||
"lineno": 18,
|
||||
"name": "xrpl"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
# `include/xrpl/basics/Log.h` — Logging Infrastructure
|
||||
|
||||
## Role in the System
|
||||
|
||||
`Log.h` is the entry point for all structured logging in the XRPL C++ server. It defines the central `Logs` class, which acts as a factory and registry for named logging partitions, manages the output file, and controls severity thresholds. Every component in the server — consensus, ledger management, networking, transaction processing — acquires a `beast::Journal` handle from `Logs`, then uses that handle to write messages at the appropriate severity level without coupling to the output destination.
|
||||
|
||||
The header sits at the boundary between xrpl's own types and the `beast` logging primitives it builds on. `beast::Journal` (from `xrpl/beast/utility/Journal.h`) is the lightweight, copyable handle that individual subsystems carry; `Logs` is the stateful root that backs all those handles.
|
||||
|
||||
## The `beast::Journal` Layer
|
||||
|
||||
Before understanding `Logs`, it helps to understand what it produces. A `beast::Journal` is a non-owning, copyable pointer to a `beast::Journal::Sink`. The `Sink` is an abstract base providing two key operations: `write()` (filtered by threshold) and `writeAlways()` (bypasses the threshold check). The `Journal` itself offers named severity accessors — `trace()`, `debug()`, `info()`, `warn()`, `error()`, `fatal()` — each returning a `Journal::Stream` that implements `operator bool()` returning whether the level is currently active.
|
||||
|
||||
This architecture makes cheap caller-side checks possible: every `Stream` can be tested for activity before any string formatting happens.
|
||||
|
||||
## The `JLOG` Macro and Lazy Evaluation
|
||||
|
||||
The `JLOG` macro is the primary idiom for logging throughout the codebase:
|
||||
|
||||
```cpp
|
||||
JLOG(j.warn()) << "Computed value: " << expensiveFunction();
|
||||
```
|
||||
|
||||
Expanding to an `if (!x) {} else x` pattern, this ensures that if the `warn()` stream is below threshold, the `<<` chain is never evaluated. This is architecturally significant: without it, even a disabled `trace()` call would still serialize all arguments into a string, burning CPU in a hot path. The macro rather than an inline function avoids any overhead whatsoever at the disabled level — the compiler simply skips the branch. `CLOG` is a similar guard for optional pointer-style stream objects (used when the stream may be null).
|
||||
|
||||
## The `Logs` Class
|
||||
|
||||
`Logs` is constructed once per process with an initial severity threshold and acts as the single routing point for all log output. It maintains a `std::map<std::string, unique_ptr<Sink>>` keyed by partition name (subsystem label), with a `boost::beast::iless` comparator for case-insensitive lookup. This means code can request `"Ledger"` or `"ledger"` and get the same sink.
|
||||
|
||||
### Partition Sinks
|
||||
|
||||
The inner `Logs::Sink` class extends `beast::Journal::Sink`, carrying a partition name and a back-reference to its parent `Logs`. When `write()` is called on a sink, it delegates immediately to `Logs::write()`, which holds the shared `mutex_`, formats the message, writes to the log file, and — unless `silent_` is set — writes to `std::cerr`. The indirection through `Logs::write()` is what makes it possible to serialize all partition output through one lock and one file handle.
|
||||
|
||||
Callers acquire sinks via `Logs::get()` or `Logs::journal()`. The `get()` implementation uses `emplace()` on the map: if the partition already exists the existing sink is returned, otherwise a new one is created via the virtual `makeSink()` factory method. Because `makeSink()` is virtual, tests can subclass `Logs` to inject mock sinks without touching the rest of the system.
|
||||
|
||||
Changing the global threshold via `Logs::threshold(Severity)` updates `thresh_` and then iterates all existing sinks to propagate the change. This means a run-time config reload can silence verbose partitions or turn on trace output without restarting the server.
|
||||
|
||||
### File Output and Log Rotation
|
||||
|
||||
The nested `Logs::File` class wraps a `std::ofstream` opened in append mode. The design is intentionally minimal: `open()`, `close()`, `write()`, `writeln()`. The critical method is `closeAndReopen()`, which is the POSIX log rotation idiom: external tools like `logrotate(8)` rename the live log file and then send a signal to the server; the server responds by calling `Logs::rotate()`, which closes its file handle and reopens the path, creating a fresh file at the original location. The `File` class stores `m_path` specifically to enable this reopen without any external coordination.
|
||||
|
||||
### Message Formatting and Security Scrubbing
|
||||
|
||||
`Logs::format()` — a private static method called unconditionally before any write — prepends a timestamp and renders the severity as a three-letter tag (`TRC`, `DBG`, `NFO`, `WRN`, `ERR`, `FTL`), followed by the partition name. It then truncates messages exceeding 12 KB to prevent runaway log lines.
|
||||
|
||||
Critically, `format()` also performs **sensitive data redaction**. After composing the full output string, it scans for JSON keys `"seed"`, `"seed_hex"`, `"secret"`, `"master_key"`, `"master_seed"`, `"master_seed_hex"`, and `"passphrase"`, and replaces the associated quoted values with asterisks. This is a defensive measure to prevent operator errors from leaking wallet secrets into log files — even if a developer mistakenly logs a raw JSON RPC request containing signing keys.
|
||||
|
||||
## Deprecated `LogSeverity` Enum
|
||||
|
||||
The `LogSeverity` enum (`lsTRACE`, `lsDEBUG`, etc.) is a legacy mapping that predates the `beast::severities::Severity` enum. It is marked deprecated but kept for backwards compatibility, with `fromSeverity()` and `toSeverity()` providing the translation. The `fromString()` method accepts several aliases (`"warn"`, `"warning"`, `"warnings"`) using case-insensitive comparison, which serves the config file parser.
|
||||
|
||||
## Debug Logging
|
||||
|
||||
`setDebugLogSink()` and `debugLog()` provide a secondary, process-global logging channel backed by a thread-safe `DebugSink` Meyers singleton (defined in `Log.cpp`). This channel defaults to a null sink and is primarily for test code that wants to capture log output without constructing a full `Logs` instance. The documentation explicitly warns that output from `debugLog()` may never be seen — it is unsuitable for any information that matters operationally.
|
||||
|
||||
## Thread Safety
|
||||
|
||||
All mutable state in `Logs` — the sink map, the file handle, and the threshold — is protected by a single `mutable mutex_`. `beast::Journal` and `Stream` objects are copyable and safe to use across threads because they only dereference a `Sink*` for reads. The `DebugSink` in `Log.cpp` has its own separate mutex for swapping the debug sink atomically while concurrent `debugLog()` calls may be in flight.
|
||||
@@ -1,32 +0,0 @@
|
||||
{
|
||||
"args": [
|
||||
{
|
||||
"lineno": 54,
|
||||
"name": "tag"
|
||||
},
|
||||
{
|
||||
"lineno": 54,
|
||||
"name": "journal"
|
||||
}
|
||||
],
|
||||
"classes": [],
|
||||
"description": "Provides a facility to attempt to return freed memory to the operating system by invoking malloc_trim on Linux/glibc, along with a report structure for before/after memory metrics.",
|
||||
"file_path": "workflow/XRPLF-rippled-develop/source/include/xrpl/basics/MallocTrim.h",
|
||||
"functions": [
|
||||
{
|
||||
"args": [
|
||||
"tag",
|
||||
"journal"
|
||||
],
|
||||
"lineno": 54,
|
||||
"name": "mallocTrim"
|
||||
}
|
||||
],
|
||||
"language": "c header",
|
||||
"namespaces": [
|
||||
{
|
||||
"lineno": 6,
|
||||
"name": "xrpl"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
# `include/xrpl/basics/MallocTrim.h`
|
||||
|
||||
## Role in the System
|
||||
|
||||
This header is a targeted memory-pressure relief valve for the XRPL node process. On long-running servers, glibc's ptmalloc allocator can accumulate free pages in its internal arenas without returning them to the OS, causing the process's Resident Set Size (RSS) to drift upward over time even when application-level memory usage has genuinely declined. `MallocTrim.h` exposes a controlled wrapper around `::malloc_trim(0)` that reclaims that slack, paired with a diagnostic report so callers can observe what the operation actually accomplished.
|
||||
|
||||
## `MallocTrimReport`
|
||||
|
||||
The `MallocTrimReport` struct is the observability surface for a single trim invocation. All fields default to sentinel values (`-1` or `false`) that signal "not populated" rather than zero, which is important because a real RSS reading or page-fault count of zero is valid and meaningful. The fields capture:
|
||||
|
||||
- **`supported`** — whether the current platform supports trimming at all. This lets callers distinguish "trim ran and did nothing" from "trim was never attempted."
|
||||
- **`trimResult`** — the raw return value from `::malloc_trim`: `1` if the allocator actually released pages, `0` if there was nothing to release.
|
||||
- **`rssBeforeKB` / `rssAfterKB`** — process RSS read from `/proc/self/statm` bracketing the trim call, in kilobytes.
|
||||
- **`durationUs`** — wall-clock duration of the `::malloc_trim` call in microseconds, useful for detecting trim latency spikes.
|
||||
- **`minfltDelta` / `majfltDelta`** — thread-level minor and major page fault deltas (via `RUSAGE_THREAD`) across the trim, making it possible to detect cases where trimming caused unexpected kernel re-faulting of pages.
|
||||
|
||||
The inline `deltaKB()` method returns the signed RSS change (negative means memory was freed, positive means RSS grew). It guards against uninitialized sentinel values by returning `0` if either RSS reading is negative.
|
||||
|
||||
## `mallocTrim()` Function
|
||||
|
||||
The function signature is:
|
||||
|
||||
```cpp
|
||||
MallocTrimReport mallocTrim(std::string_view tag, beast::Journal journal);
|
||||
```
|
||||
|
||||
The `tag` is a caller-supplied identifier that appears in log output, allowing multiple call sites to be distinguished in diagnostics. The actual implementation in `MallocTrim.cpp` is compiled conditionally: the entire trim path is gated on `#if defined(__GLIBC__) && BOOST_OS_LINUX`. On any other platform, the function is a no-op that returns a default-constructed report with `supported = false`.
|
||||
|
||||
When active, the implementation makes a deliberate performance tradeoff gated on the journal's debug severity. If debug logging is disabled, only `::malloc_trim(0)` is called and the report stays mostly at sentinel values — no `/proc` reads, no rusage calls, minimal overhead. If debug logging is enabled, the function reads `/proc/self/statm` before and after, calls `getrusage(RUSAGE_THREAD, ...)` before and after, and measures wall time. This layered instrumentation is only paid when someone is actively debugging memory behavior, which makes the function cheap enough for production call sites.
|
||||
|
||||
The trim padding constant is hardcoded to `TRIM_PAD = 0`. A comment in the implementation documents that this choice came from 12-hour empirical testing on Mainnet across four padding values (0, 256 KB, 1 MB, 16 MB): zero delivered the best balance of RSS reduction and trim-latency stability without adding a tuning surface. This is a non-obvious design choice that prevents the parameter from becoming a configuration footgun.
|
||||
|
||||
## Allocator Compatibility and Scope Limits
|
||||
|
||||
The header's comment block is worth reading carefully: `malloc_trim` only affects glibc's own `sbrk`-based arenas. Large allocations backed by `mmap` are returned to the OS when freed, regardless of trimming. More critically, if jemalloc or tcmalloc is linked or `LD_PRELOAD`ed, calling `::malloc_trim` from glibc's symbol is harmless but does not touch those allocators' arenas. The function has no way to detect this at runtime, so the `supported` flag is set based on compiler/OS detection only, not on whether the active allocator will actually respond.
|
||||
|
||||
## Call Site
|
||||
|
||||
The function is invoked in `Application.cpp`'s `doSweep()` method, immediately after cache sweeps and ledger cleanup:
|
||||
|
||||
```cpp
|
||||
mallocTrim("doSweep", m_journal);
|
||||
```
|
||||
|
||||
This is the canonical usage pattern: call at a known point after significant bulk-free operations, rather than on a timer. The header comment recommends rate-limiting calls to avoid churn, which `doSweep`'s existing sweep timer naturally provides.
|
||||
@@ -1,32 +0,0 @@
|
||||
{
|
||||
"args": [
|
||||
{
|
||||
"lineno": 16,
|
||||
"name": "count"
|
||||
},
|
||||
{
|
||||
"lineno": 16,
|
||||
"name": "total"
|
||||
}
|
||||
],
|
||||
"classes": [],
|
||||
"description": "Provides a constexpr function to calculate the percentage of one number divided by another, rounded up and capped between 0 and 100, with static_assert unit tests.",
|
||||
"file_path": "workflow/XRPLF-rippled-develop/source/include/xrpl/basics/MathUtilities.h",
|
||||
"functions": [
|
||||
{
|
||||
"args": [
|
||||
"count",
|
||||
"total"
|
||||
],
|
||||
"lineno": 16,
|
||||
"name": "calculatePercent"
|
||||
}
|
||||
],
|
||||
"language": "c header",
|
||||
"namespaces": [
|
||||
{
|
||||
"lineno": 6,
|
||||
"name": "xrpl"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
# `MathUtilities.h` — Integer Percentage Utility
|
||||
|
||||
This header lives in the `xrpl::basics` utility layer and provides a single function, `calculatePercent`, for computing integer percentages with ceiling rounding and range clamping. It exists because this specific combination of behaviors — ceiling division, overflow-safe clamping, and compile-time verifiability — recurs in the XRPL codebase wherever a count-over-total ratio needs to be expressed as a human-readable integer percentage without floating point.
|
||||
|
||||
## `calculatePercent`
|
||||
|
||||
```cpp
|
||||
constexpr std::size_t calculatePercent(std::size_t count, std::size_t total)
|
||||
```
|
||||
|
||||
The function answers: "what percent of `total` is `count`, rounded up to the nearest whole percent, and never exceeding 100?" The ceiling rounding is intentional: a fraction like 1/99 (≈1.01%) rounds up to 2, signalling that *some* nonzero fraction is present. This is the conservative, safety-oriented direction for thresholding use cases — if you're checking whether at least N% of validators have seen something, you want to err on the side of reporting a higher fraction rather than losing signal in truncation.
|
||||
|
||||
The arithmetic `((std::min(count, total) * 100) + total - 1) / total` encodes three distinct behaviors in one expression. The `std::min(count, total)` clamp prevents the numerator from exceeding `total * 100`, which both enforces the 100% cap and guards against the case where `count > total` (e.g., a validator set that temporarily grows). The `+ total - 1` addend is the standard ceiling-division trick: it causes any non-zero remainder in the integer division to round up rather than truncate. The whole expression stays in unsigned integer arithmetic with no floating point, making it safe across all platforms and constexpr-eligible.
|
||||
|
||||
The `assert(total != 0)` guard catches division-by-zero in debug builds. The comment explains why `XRPL_ASSERT` is not used here: the function is `constexpr`, and `XRPL_ASSERT` is not constexpr-compatible, so the plain `assert` is the appropriate defence at runtime while still permitting compile-time evaluation.
|
||||
|
||||
## Real-World Use
|
||||
|
||||
The primary call site is in `LedgerMaster.cpp`, which uses `calculatePercent` to decide whether to warn operators about a potential software upgrade need. Two separate thresholds are evaluated: whether at least 90% of the UNL (Unique Node List) validators have sent validation messages, and whether at least 60% of xrpld-running validators are on a higher version. Both checks use `calculatePercent` with named constants (`reportingPercent`, `cutoffPercent`) rather than raw literals, and the thresholding via `>=` naturally benefits from ceiling rounding — a value just above a threshold boundary will not be falsely excluded by truncation.
|
||||
|
||||
## Compile-Time Tests
|
||||
|
||||
The `static_assert` block at namespace scope doubles as both documentation and zero-cost test coverage. Fourteen cases are checked at compile time, covering the zero numerator, full 100% match, over-100% input, boundary rounding cases (1/99, 1/64, near-100% fractions), and large-scale inputs up to 100 million. If the implementation is ever changed and any of these properties breaks, the build fails immediately — there is no need for a separate test binary or runtime fixture. This pattern is well-suited to a pure arithmetic utility that has no external dependencies.
|
||||
@@ -1,60 +0,0 @@
|
||||
{
|
||||
"args": [
|
||||
{
|
||||
"lineno": 11,
|
||||
"name": "ProtectedDataType"
|
||||
},
|
||||
{
|
||||
"lineno": 11,
|
||||
"name": "MutexType"
|
||||
},
|
||||
{
|
||||
"lineno": 15,
|
||||
"name": "LockType"
|
||||
}
|
||||
],
|
||||
"classes": [
|
||||
{
|
||||
"args": [
|
||||
"MutexType& mutex, ProtectedDataType& data"
|
||||
],
|
||||
"lineno": 18,
|
||||
"name": "Lock"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"ProtectedDataType data"
|
||||
],
|
||||
"lineno": 56,
|
||||
"name": "Mutex"
|
||||
}
|
||||
],
|
||||
"description": "Provides a thread-safe container (Mutex) for protecting data with a mutex, inspired by Rust's Mutex, and a Lock class for accessing the protected data safely.",
|
||||
"file_path": "workflow/XRPLF-rippled-develop/source/include/xrpl/basics/Mutex.hpp",
|
||||
"functions": [
|
||||
{
|
||||
"args": [
|
||||
"Args&&... args"
|
||||
],
|
||||
"lineno": 66,
|
||||
"name": "Mutex::make"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 81,
|
||||
"name": "Mutex::lock (const)"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 92,
|
||||
"name": "Mutex::lock"
|
||||
}
|
||||
],
|
||||
"language": "cpp header",
|
||||
"namespaces": [
|
||||
{
|
||||
"lineno": 8,
|
||||
"name": "xrpl"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
# `include/xrpl/basics/Mutex.hpp`
|
||||
|
||||
## Purpose and Motivation
|
||||
|
||||
This header introduces a Rust-inspired `Mutex<T>` idiom to the XRPL C++ codebase. The core problem it solves is a well-known C++ anti-pattern: separating a mutex from the data it protects, which lets callers accidentally read or write the data without holding the lock. The standard library provides `std::mutex` and RAII guards, but the data itself remains a separate, freely-accessible member. Nothing in the type system stops code from doing `data_ = x;` without ever calling `lock()`.
|
||||
|
||||
The Rust model solves this by making the lock guard the only path to the data. `Mutex.hpp` reproduces that guarantee in C++: the protected value lives inside `Mutex<T>`, is entirely private, and the only way to reach it is through a `Lock` object returned by `Mutex::lock()`. A `Lock` holds both the RAII lock and a reference to the data, so when the `Lock` goes out of scope the mutex is released and the reference becomes unreachable simultaneously.
|
||||
|
||||
The file is credited to the Clio project (`https://github.com/XRPLF/clio`) and lives under the `xrpl` namespace in `include/xrpl/basics/`.
|
||||
|
||||
## `Lock<ProtectedDataType, LockType, MutexType>`
|
||||
|
||||
`Lock` is a thin RAII wrapper that bundles a lock instance (`LockType<MutexType> lock_`) with a reference to the protected data (`ProtectedDataType& data_`). It exposes `operator*`, `get()`, and `operator->` — all in both const and non-const variants — so users interact with the guarded value naturally.
|
||||
|
||||
The constructor `Lock(MutexType& mutex, ProtectedDataType& data)` is `private`, with `friend class Mutex<std::remove_const_t<ProtectedDataType>, MutexType>` as the sole access point. The `std::remove_const_t` strip is intentional: when the `Mutex` is const it instantiates `Lock<ProtectedDataType const, ...>`, whose friend declaration must still point back to `Mutex<ProtectedDataType>` (non-const), not the non-existent `Mutex<ProtectedDataType const>`. This single line of `friend` is what makes the safety guarantee airtight — no code path outside `Mutex::lock()` can construct a `Lock`.
|
||||
|
||||
The conversion operators `operator LockType<MutexType>&()` and `operator LockType<MutexType> const&()` allow a `Lock` to decay to a reference to the underlying lock object. This matters in practice when `std::unique_lock` is chosen as the `LockType` and the lock needs to be passed to `std::condition_variable::wait()`, which accepts a `std::unique_lock<std::mutex>&`. The conversion keeps the idiom composable with the standard library's synchronisation utilities.
|
||||
|
||||
## `Mutex<ProtectedDataType, MutexType>`
|
||||
|
||||
`Mutex` stores a `mutable MutexType mutex_` and a `ProtectedDataType data_{}`. The `mutable` qualifier is deliberate: acquiring a lock is logically a non-mutating operation on the container, so it must be callable on a `const Mutex` instance. Without `mutable` the const `lock()` overload could not take `mutex_` by non-const reference as required by any standard lock type.
|
||||
|
||||
Two `lock()` overloads handle const-correctness propagation:
|
||||
|
||||
```cpp
|
||||
// const Mutex → Lock<ProtectedDataType const, ...> (read-only access)
|
||||
Lock<ProtectedDataType const, LockType, MutexType> lock() const;
|
||||
|
||||
// non-const Mutex → Lock<ProtectedDataType, ...> (read-write access)
|
||||
Lock<ProtectedDataType, LockType, MutexType> lock();
|
||||
```
|
||||
|
||||
Calling `lock()` on a `const Mutex` yields a `Lock` whose `operator*` and `operator->` return `const` references. The compiler enforces this — there is no cast or escape hatch. The test suite verifies it with `static_assert(std::is_const_v<...>)`.
|
||||
|
||||
Both overloads are templated on `LockType`, which defaults to `std::lock_guard`. Callers can substitute `std::unique_lock` for deferred or timed locking, or combine a `std::shared_mutex` as the `MutexType` with `std::shared_lock` as the `LockType` to implement reader-writer semantics with the same ergonomic API. The test in `src/tests/libxrpl/basics/Mutex.cpp` demonstrates exactly this: multiple shared readers coexist while an exclusive writer uses `std::unique_lock`.
|
||||
|
||||
The `make()` static factory provides perfect-forwarding in-place construction of the protected object:
|
||||
|
||||
```cpp
|
||||
auto m = Mutex<MyStruct>::make(arg1, arg2); // forwards to MyStruct(arg1, arg2)
|
||||
```
|
||||
|
||||
This avoids a move-construct-then-copy sequence when constructing from arguments and also supports move-only types like `std::unique_ptr<T>`.
|
||||
|
||||
## Design Trade-offs
|
||||
|
||||
The main cost of this pattern is that the `Lock` object must remain alive for the entire scope of any access. Code that needs to unlock mid-scope, or pass just the protected value to a function, requires `std::unique_lock` as the `LockType` so the conversion operator can expose the lock for manual `unlock()`/`lock()` calls. The default `std::lock_guard` is intentionally non-flexible — it signals "lock for this scope, nothing else" and prevents premature unlocking.
|
||||
|
||||
There is no `try_lock()` surface exposed through `Mutex` itself. A caller wanting try-lock semantics must drop to `std::unique_lock` (for `try_lock()`) or interact with the raw mutex type outside this wrapper, which is a deliberate friction point rather than an oversight. The design prioritises the common, safe path — RAII lock for the lifetime of a `Lock` object — over less common, riskier patterns.
|
||||
|
||||
The file is the sole resident of the `basics/` header directory that was found, suggesting it was added as a focused utility pulled from Clio rather than part of a larger local module. Its test coverage in `Mutex.cpp` is comprehensive: default construction, value construction, `make()` with forwarding, const and non-const access, custom lock types, `std::shared_mutex` reader-writer patterns, and move-only protected types.
|
||||
@@ -2,12 +2,16 @@
|
||||
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <optional>
|
||||
#include <ostream>
|
||||
#include <set>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
@@ -38,17 +42,58 @@ isPowerOfTen(T value)
|
||||
return logTen(value).has_value();
|
||||
}
|
||||
|
||||
namespace detail {
|
||||
|
||||
/** Builds a table of the powers of 10
|
||||
*
|
||||
* This function is marked consteval, so it can only be run in
|
||||
* a constexpr context. This assures that it is and can only be run at
|
||||
* compile time. Doing it at runtime would be pretty wasteful and
|
||||
* inefficient.
|
||||
*/
|
||||
constexpr std::size_t kInt64Digits = 20;
|
||||
consteval std::array<std::uint64_t, kInt64Digits>
|
||||
buildPowersOfTen()
|
||||
{
|
||||
std::array<std::uint64_t, kInt64Digits> result{};
|
||||
|
||||
std::uint64_t power = 1;
|
||||
std::size_t exponent = 0;
|
||||
// end the loop early so it doesn't overflow;
|
||||
for (; exponent < result.size() - 1; ++exponent, power *= 10)
|
||||
{
|
||||
result[exponent] = power;
|
||||
if (power > std::numeric_limits<std::uint64_t>::max() / 10)
|
||||
throw std::logic_error("Power of 10 table is too big");
|
||||
}
|
||||
result[exponent] = power;
|
||||
if (power < std::numeric_limits<std::uint64_t>::max() / 10)
|
||||
throw std::logic_error("Power of 10 table is not big enough for the uint64_t type");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
constexpr std::array<std::uint64_t, detail::kInt64Digits> kPowerOfTen = detail::buildPowersOfTen();
|
||||
|
||||
static_assert(kPowerOfTen[0] == 1);
|
||||
static_assert(kPowerOfTen[1] == 10);
|
||||
static_assert(kPowerOfTen[10] == 10'000'000'000);
|
||||
static_assert(
|
||||
isPowerOfTen(kPowerOfTen.back()) && *logTen(kPowerOfTen.back()) == detail::kInt64Digits - 1);
|
||||
|
||||
/** MantissaRange defines a range for the mantissa of a normalized Number.
|
||||
*
|
||||
* The mantissa is in the range [min, max], where
|
||||
* * min is a power of 10, and
|
||||
* * max = min * 10 - 1.
|
||||
*
|
||||
* The mantissa_scale enum indicates whether the range is "small" or "large".
|
||||
* This intentionally restricts the number of MantissaRanges that can be
|
||||
* instantiated to two: one for each scale.
|
||||
* The MantissaScale enum indicates properties of the range: size, and some behavioral
|
||||
* options. This intentionally restricts the number of unique MantissaRanges that can
|
||||
* be instantiated: one for each scale.
|
||||
*
|
||||
* The "small" scale is based on the behavior of STAmount for IOUs. It has a min
|
||||
* The "Small" scale is based on the behavior of STAmount for IOUs. It has a min
|
||||
* value of 10^15, and a max value of 10^16-1. This was sufficient for
|
||||
* uses before Lending Protocol was implemented, mostly related to AMM.
|
||||
*
|
||||
@@ -59,46 +104,100 @@ isPowerOfTen(T value)
|
||||
* STNumber field type, and for internal calculations. That necessitated the
|
||||
* "large" scale.
|
||||
*
|
||||
* The "large" scale is intended to represent all values that can be represented
|
||||
* The "Large" scales are intended to represent all values that can be represented
|
||||
* by an STAmount - IOUs, XRP, and MPTs. It has a min value of 10^18, and a max
|
||||
* value of 10^19-1.
|
||||
* value of 10^19-1. "LargeLegacy" is like "Large", but preserves
|
||||
* a rounding error when a computation results in a mantissa of
|
||||
* Number::kMaxRep that needs to be rounded up, but rounds down
|
||||
* instead. It will maintain consistent behavior until the fixCleanup3_2_0
|
||||
* amendment is enabled.
|
||||
*
|
||||
* Note that if the mentioned amendments are eventually retired, this class
|
||||
* should be left in place, but the "small" scale option should be removed. This
|
||||
* should be left in place, but the "Small" scale option should be removed. This
|
||||
* will allow for future expansion beyond 64-bits if it is ever needed.
|
||||
*/
|
||||
struct MantissaRange
|
||||
struct MantissaRange final
|
||||
{
|
||||
using rep = std::uint64_t;
|
||||
enum class MantissaScale { Small, Large };
|
||||
|
||||
explicit constexpr MantissaRange(MantissaScale scale)
|
||||
: min(getMin(scale)), log(logTen(min).value_or(-1)), scale(scale)
|
||||
enum class MantissaScale {
|
||||
Small,
|
||||
// LargeLegacy can be removed when fixCleanup3_2_0 is retired
|
||||
LargeLegacy,
|
||||
Large,
|
||||
};
|
||||
|
||||
// This entire enum can be removed when fixCleanup3_2_0 is retired
|
||||
enum class CuspRoundingFix : bool {
|
||||
Disabled = false,
|
||||
Enabled = true,
|
||||
};
|
||||
|
||||
explicit constexpr MantissaRange(MantissaScale sc) : scale(sc)
|
||||
{
|
||||
}
|
||||
|
||||
rep min;
|
||||
rep max{(min * 10) - 1};
|
||||
int log;
|
||||
MantissaScale scale;
|
||||
MantissaScale const scale;
|
||||
int const log{getExponent(scale)};
|
||||
rep const min{getMin(scale, log)};
|
||||
rep const max{(min * 10) - 1};
|
||||
CuspRoundingFix const cuspRoundingFixEnabled{isCuspFixEnabled(scale)};
|
||||
|
||||
static MantissaRange const&
|
||||
getMantissaRange(MantissaScale scale);
|
||||
|
||||
static std::set<MantissaScale> const&
|
||||
getAllScales();
|
||||
|
||||
private:
|
||||
static constexpr rep
|
||||
getMin(MantissaScale scale)
|
||||
static constexpr int
|
||||
getExponent(MantissaScale scale)
|
||||
{
|
||||
switch (scale)
|
||||
{
|
||||
case MantissaScale::Small:
|
||||
return 1'000'000'000'000'000ULL;
|
||||
return 15;
|
||||
case MantissaScale::LargeLegacy:
|
||||
case MantissaScale::Large:
|
||||
return 1'000'000'000'000'000'000ULL;
|
||||
return 18;
|
||||
// LCOV_EXCL_START
|
||||
default:
|
||||
// Since this can never be called outside a non-constexpr
|
||||
// context, this throw assures that the build fails if an
|
||||
// If called in a constexpr context, this throw assures that the build fails if an
|
||||
// invalid scale is used.
|
||||
throw std::runtime_error("Unknown mantissa scale");
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
}
|
||||
|
||||
// Keep this function for future use with different ways to compute
|
||||
// the ranges.
|
||||
static constexpr rep
|
||||
getMin(MantissaScale scale, int exponent)
|
||||
{
|
||||
if (exponent < 0 || exponent >= kPowerOfTen.size())
|
||||
throw std::runtime_error("Invalid exponent"); // LCOV_EXCL_LINE
|
||||
return kPowerOfTen[exponent];
|
||||
}
|
||||
|
||||
static constexpr CuspRoundingFix
|
||||
isCuspFixEnabled(MantissaScale scale)
|
||||
{
|
||||
switch (scale)
|
||||
{
|
||||
case MantissaScale::Small:
|
||||
case MantissaScale::LargeLegacy:
|
||||
return CuspRoundingFix::Disabled;
|
||||
case MantissaScale::Large:
|
||||
return CuspRoundingFix::Enabled;
|
||||
default:
|
||||
// If called in a constexpr context, this throw assures that the build fails if an
|
||||
// invalid scale is used.
|
||||
throw std::runtime_error("Unknown mantissa scale"); // LCOV_EXCL_LINE
|
||||
}
|
||||
}
|
||||
|
||||
static std::unordered_map<MantissaScale, MantissaRange> const&
|
||||
getRanges();
|
||||
};
|
||||
|
||||
// Like std::integral, but only 64-bit integral types.
|
||||
@@ -203,7 +302,7 @@ concept Integral64 = std::is_same_v<T, std::int64_t> || std::is_same_v<T, std::u
|
||||
* amendments are enabled to determine which result to expect.
|
||||
*
|
||||
*/
|
||||
class Number
|
||||
class Number final
|
||||
{
|
||||
using rep = std::int64_t;
|
||||
using internalrep = MantissaRange::rep;
|
||||
@@ -424,49 +523,28 @@ public:
|
||||
return kRange.get().log;
|
||||
}
|
||||
|
||||
/// oneSmall is needed because the ranges are private
|
||||
static constexpr Number
|
||||
oneSmall();
|
||||
/// oneLarge is needed because the ranges are private
|
||||
static constexpr Number
|
||||
oneLarge();
|
||||
|
||||
// And one is needed because it needs to choose between oneSmall and
|
||||
// oneLarge based on the current range
|
||||
static Number
|
||||
one();
|
||||
|
||||
template <Integral64 T>
|
||||
template <
|
||||
auto MinMantissa,
|
||||
auto MaxMantissa,
|
||||
Integral64 T = std::decay_t<decltype(MinMantissa)>>
|
||||
[[nodiscard]]
|
||||
std::pair<T, int>
|
||||
normalizeToRange(T minMantissa, T maxMantissa) const;
|
||||
normalizeToRange() const;
|
||||
|
||||
private:
|
||||
static thread_local RoundingMode mode;
|
||||
// The available ranges for mantissa
|
||||
|
||||
static constexpr MantissaRange kSmallRange{MantissaRange::MantissaScale::Small};
|
||||
static_assert(isPowerOfTen(kSmallRange.min));
|
||||
static_assert(kSmallRange.min == 1'000'000'000'000'000LL);
|
||||
static_assert(kSmallRange.max == 9'999'999'999'999'999LL);
|
||||
static_assert(kSmallRange.log == 15);
|
||||
static_assert(kSmallRange.min < kMaxRep);
|
||||
static_assert(kSmallRange.max < kMaxRep);
|
||||
static constexpr MantissaRange kLargeRange{MantissaRange::MantissaScale::Large};
|
||||
static_assert(isPowerOfTen(kLargeRange.min));
|
||||
static_assert(kLargeRange.min == 1'000'000'000'000'000'000ULL);
|
||||
static_assert(kLargeRange.max == internalrep(9'999'999'999'999'999'999ULL));
|
||||
static_assert(kLargeRange.log == 18);
|
||||
static_assert(kLargeRange.min < kMaxRep);
|
||||
static_assert(kLargeRange.max > kMaxRep);
|
||||
|
||||
// The range for the mantissa when normalized.
|
||||
// Use reference_wrapper to avoid making copies, and prevent accidentally
|
||||
// changing the values inside the range.
|
||||
static thread_local std::reference_wrapper<MantissaRange const> kRange;
|
||||
|
||||
void
|
||||
normalize();
|
||||
normalize(MantissaRange const& range);
|
||||
|
||||
/** Normalize Number components to an arbitrary range.
|
||||
*
|
||||
@@ -481,7 +559,8 @@ private:
|
||||
T& mantissa,
|
||||
int& exponent,
|
||||
internalrep const& minMantissa,
|
||||
internalrep const& maxMantissa);
|
||||
internalrep const& maxMantissa,
|
||||
MantissaRange::CuspRoundingFix cuspRoundingFixEnabled);
|
||||
|
||||
template <class T>
|
||||
friend void
|
||||
@@ -490,7 +569,9 @@ private:
|
||||
T& mantissa,
|
||||
int& exponent,
|
||||
MantissaRange::rep const& minMantissa,
|
||||
MantissaRange::rep const& maxMantissa);
|
||||
MantissaRange::rep const& maxMantissa,
|
||||
MantissaRange::CuspRoundingFix cuspRoundingFixEnabled,
|
||||
bool dropped);
|
||||
|
||||
[[nodiscard]] bool
|
||||
isnormal() const noexcept;
|
||||
@@ -526,7 +607,7 @@ static constexpr Number kNumZero{};
|
||||
inline Number::Number(bool negative, internalrep mantissa, int exponent, Normalized)
|
||||
: Number(negative, mantissa, exponent, Unchecked{})
|
||||
{
|
||||
normalize();
|
||||
normalize(kRange);
|
||||
}
|
||||
|
||||
inline Number::Number(internalrep mantissa, int exponent, Normalized)
|
||||
@@ -696,10 +777,21 @@ Number::isnormal() const noexcept
|
||||
kMinExponent <= exponent_ && exponent_ <= kMaxExponent);
|
||||
}
|
||||
|
||||
template <Integral64 T>
|
||||
template <auto MinMantissa, auto MaxMantissa, Integral64 T>
|
||||
std::pair<T, int>
|
||||
Number::normalizeToRange(T minMantissa, T maxMantissa) const
|
||||
Number::normalizeToRange() const
|
||||
{
|
||||
static_assert(std::is_same_v<T, std::uint64_t> || std::is_same_v<T, std::int64_t>);
|
||||
static_assert(std::is_same_v<T, std::decay_t<decltype(MinMantissa)>>);
|
||||
static_assert(std::is_same_v<T, std::decay_t<decltype(MaxMantissa)>>);
|
||||
auto constexpr kMIN = static_cast<T>(MinMantissa);
|
||||
auto constexpr kMAX = static_cast<T>(MaxMantissa);
|
||||
static_assert(kMIN > 0);
|
||||
static_assert(kMIN % 10 == 0);
|
||||
static_assert(isPowerOfTen(kMIN));
|
||||
static_assert(kMAX % 10 == 9);
|
||||
static_assert((kMAX + 1) / 10 == kMIN);
|
||||
|
||||
bool negative = negative_;
|
||||
internalrep mantissa = mantissa_;
|
||||
int exponent = exponent_;
|
||||
@@ -711,7 +803,10 @@ Number::normalizeToRange(T minMantissa, T maxMantissa) const
|
||||
"xrpl::Number::normalizeToRange",
|
||||
"Number is non-negative for unsigned range.");
|
||||
}
|
||||
Number::normalize(negative, mantissa, exponent, minMantissa, maxMantissa);
|
||||
// Don't need to worry about the cuspRounding fix because rounding up will never take the
|
||||
// mantissa over maxMantissa with a ones digit value other than 0. 0 can safely be truncated.
|
||||
Number::normalize(
|
||||
negative, mantissa, exponent, kMIN, kMAX, MantissaRange::CuspRoundingFix::Disabled);
|
||||
|
||||
auto const sign = negative ? -1 : 1;
|
||||
return std::make_pair(static_cast<T>(sign * mantissa), exponent);
|
||||
@@ -763,6 +858,8 @@ to_string(MantissaRange::MantissaScale const& scale)
|
||||
{
|
||||
case MantissaRange::MantissaScale::Small:
|
||||
return "small";
|
||||
case MantissaRange::MantissaScale::LargeLegacy:
|
||||
return "largeLegacy";
|
||||
case MantissaRange::MantissaScale::Large:
|
||||
return "large";
|
||||
default:
|
||||
|
||||
@@ -1,363 +0,0 @@
|
||||
{
|
||||
"args": [
|
||||
{
|
||||
"lineno": 10,
|
||||
"name": "amount"
|
||||
},
|
||||
{
|
||||
"lineno": 13,
|
||||
"name": "value"
|
||||
},
|
||||
{
|
||||
"lineno": 31,
|
||||
"name": "scale_"
|
||||
},
|
||||
{
|
||||
"lineno": 119,
|
||||
"name": "mantissa"
|
||||
},
|
||||
{
|
||||
"lineno": 119,
|
||||
"name": "exponent"
|
||||
},
|
||||
{
|
||||
"lineno": 117,
|
||||
"name": "negative"
|
||||
},
|
||||
{
|
||||
"lineno": 373,
|
||||
"name": "mode"
|
||||
},
|
||||
{
|
||||
"lineno": 407,
|
||||
"name": "scale"
|
||||
}
|
||||
],
|
||||
"classes": [
|
||||
{
|
||||
"args": [
|
||||
"mantissa_scale scale_"
|
||||
],
|
||||
"lineno": 28,
|
||||
"name": "MantissaRange"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 81,
|
||||
"name": "Number"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 120,
|
||||
"name": "Number::unchecked"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 127,
|
||||
"name": "Number::normalized"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"Number::rounding_mode mode"
|
||||
],
|
||||
"lineno": 370,
|
||||
"name": "saveNumberRoundMode"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"Number::rounding_mode mode"
|
||||
],
|
||||
"lineno": 386,
|
||||
"name": "NumberRoundModeGuard"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"MantissaRange::mantissa_scale scale"
|
||||
],
|
||||
"lineno": 404,
|
||||
"name": "NumberMantissaScaleGuard"
|
||||
}
|
||||
],
|
||||
"description": "Defines the xrpl::Number class, a floating point type for representing a wide range of asset values with precise mantissa and exponent control, including normalization, rounding, and mantissa range switching. Also provides related utilities, guards, and helper functions.",
|
||||
"file_path": "workflow/XRPLF-rippled-develop/source/include/xrpl/basics/Number.h",
|
||||
"functions": [
|
||||
{
|
||||
"args": [
|
||||
"Number const& amount"
|
||||
],
|
||||
"lineno": 10,
|
||||
"name": "to_string"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"T value"
|
||||
],
|
||||
"lineno": 13,
|
||||
"name": "logTen"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"T value"
|
||||
],
|
||||
"lineno": 24,
|
||||
"name": "isPowerOfTen"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"Number const& x",
|
||||
"Number const& y"
|
||||
],
|
||||
"lineno": 154,
|
||||
"name": "operator=="
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"Number const& x",
|
||||
"Number const& y"
|
||||
],
|
||||
"lineno": 160,
|
||||
"name": "operator!="
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"Number const& x",
|
||||
"Number const& y"
|
||||
],
|
||||
"lineno": 165,
|
||||
"name": "operator<"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 186,
|
||||
"name": "signum"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 191,
|
||||
"name": "truncate"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"Number const& x",
|
||||
"Number const& y"
|
||||
],
|
||||
"lineno": 194,
|
||||
"name": "operator>"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"Number const& x",
|
||||
"Number const& y"
|
||||
],
|
||||
"lineno": 198,
|
||||
"name": "operator<="
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"Number const& x",
|
||||
"Number const& y"
|
||||
],
|
||||
"lineno": 202,
|
||||
"name": "operator>="
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"std::ostream& os",
|
||||
"Number const& x"
|
||||
],
|
||||
"lineno": 206,
|
||||
"name": "operator<<"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"Number const& amount"
|
||||
],
|
||||
"lineno": 211,
|
||||
"name": "to_string"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"Number f",
|
||||
"unsigned d"
|
||||
],
|
||||
"lineno": 213,
|
||||
"name": "root"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"Number f"
|
||||
],
|
||||
"lineno": 215,
|
||||
"name": "root2"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 220,
|
||||
"name": "getround"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"rounding_mode mode"
|
||||
],
|
||||
"lineno": 222,
|
||||
"name": "setround"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 229,
|
||||
"name": "getMantissaScale"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"MantissaRange::mantissa_scale scale"
|
||||
],
|
||||
"lineno": 234,
|
||||
"name": "setMantissaScale"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 238,
|
||||
"name": "minMantissa"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 243,
|
||||
"name": "maxMantissa"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 248,
|
||||
"name": "mantissaLog"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 254,
|
||||
"name": "oneSmall"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 256,
|
||||
"name": "oneLarge"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 260,
|
||||
"name": "one"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"T minMantissa",
|
||||
"T maxMantissa"
|
||||
],
|
||||
"lineno": 264,
|
||||
"name": "normalizeToRange"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 282,
|
||||
"name": "normalize"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"bool& negative",
|
||||
"T& mantissa",
|
||||
"int& exponent",
|
||||
"internalrep const& minMantissa",
|
||||
"internalrep const& maxMantissa"
|
||||
],
|
||||
"lineno": 287,
|
||||
"name": "normalize"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"bool& negative",
|
||||
"T& mantissa_",
|
||||
"int& exponent_",
|
||||
"MantissaRange::rep const& minMantissa",
|
||||
"MantissaRange::rep const& maxMantissa"
|
||||
],
|
||||
"lineno": 295,
|
||||
"name": "doNormalize"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"lineno": 299,
|
||||
"name": "isnormal"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"int exponentDelta"
|
||||
],
|
||||
"lineno": 304,
|
||||
"name": "shiftExponent"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"rep mantissa"
|
||||
],
|
||||
"lineno": 309,
|
||||
"name": "externalToInternal"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"Number x"
|
||||
],
|
||||
"lineno": 324,
|
||||
"name": "abs"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"Number const& f",
|
||||
"unsigned n"
|
||||
],
|
||||
"lineno": 334,
|
||||
"name": "power"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"Number f",
|
||||
"unsigned d"
|
||||
],
|
||||
"lineno": 338,
|
||||
"name": "root"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"Number f"
|
||||
],
|
||||
"lineno": 341,
|
||||
"name": "root2"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"Number const& f",
|
||||
"unsigned n",
|
||||
"unsigned d"
|
||||
],
|
||||
"lineno": 344,
|
||||
"name": "power"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"Number const& x",
|
||||
"Number const& limit"
|
||||
],
|
||||
"lineno": 348,
|
||||
"name": "squelch"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"MantissaRange::mantissa_scale const& scale"
|
||||
],
|
||||
"lineno": 355,
|
||||
"name": "to_string"
|
||||
}
|
||||
],
|
||||
"language": "c header",
|
||||
"namespaces": [
|
||||
{
|
||||
"lineno": 8,
|
||||
"name": "xrpl"
|
||||
}
|
||||
]
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user