diff --git a/.clang-tidy b/.clang-tidy index 6a967532db..b23d7ccbff 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -1,11 +1,10 @@ --- -# This entire group of checks was applied to all cpp files but not all header files. -# --- Checks: "-*, bugprone-argument-comment, bugprone-assert-side-effect, bugprone-bad-signal-to-kill-thread, bugprone-bool-pointer-implicit-conversion, + bugprone-capturing-this-in-member-variable, bugprone-casting-through-void, bugprone-chained-comparison, bugprone-compare-pointer-to-member-virtual-function, @@ -25,6 +24,7 @@ Checks: "-*, bugprone-lambda-function-name, bugprone-macro-parentheses, bugprone-macro-repeated-side-effects, + bugprone-misleading-setter-of-reference, bugprone-misplaced-operator-in-strlen-in-alloc, bugprone-misplaced-pointer-arithmetic-in-alloc, bugprone-misplaced-widening-cast, @@ -73,10 +73,10 @@ Checks: "-*, bugprone-unhandled-self-assignment, bugprone-unique-ptr-array-mismatch, bugprone-unsafe-functions, - bugprone-use-after-move, + bugprone-unused-local-non-trivial-variable, bugprone-unused-raii, bugprone-unused-return-value, - bugprone-unused-local-non-trivial-variable, + bugprone-use-after-move, bugprone-virtual-near-miss, cppcoreguidelines-init-variables, cppcoreguidelines-misleading-capture-default-by-value, @@ -85,8 +85,10 @@ Checks: "-*, cppcoreguidelines-pro-type-static-cast-downcast, cppcoreguidelines-rvalue-reference-param-not-moved, cppcoreguidelines-use-default-member-init, + cppcoreguidelines-use-enum-class, cppcoreguidelines-virtual-class-destructor, hicpp-ignored-remove-result, + llvm-namespace-comment, misc-const-correctness, misc-definitions-in-headers, misc-header-include-cycle, @@ -98,6 +100,7 @@ Checks: "-*, misc-unused-alias-decls, misc-unused-using-decls, modernize-concat-nested-namespaces, + modernize-deprecated-headers, modernize-make-shared, modernize-make-unique, modernize-pass-by-value, @@ -109,11 +112,10 @@ Checks: "-*, modernize-use-nodiscard, modernize-use-override, modernize-use-ranges, + modernize-use-scoped-lock, modernize-use-starts-ends-with, modernize-use-std-numbers, modernize-use-using, - modernize-deprecated-headers, - llvm-namespace-comment, performance-faster-string-find, performance-for-range-copy, performance-implicit-conversion-in-loop, @@ -122,6 +124,7 @@ Checks: "-*, performance-move-constructor-init, performance-no-automatic-move, performance-trivially-destructible, + readability-ambiguous-smartptr-reset-call, readability-avoid-nested-conditional-operator, readability-avoid-return-with-void-value, readability-braces-around-statements, @@ -132,6 +135,7 @@ Checks: "-*, readability-duplicate-include, readability-else-after-return, readability-enum-initial-value, + readability-identifier-naming, readability-implicit-bool-conversion, readability-make-member-function-const, readability-math-missing-parentheses, @@ -149,52 +153,52 @@ Checks: "-*, readability-use-std-min-max " # --- -# other checks that have issues that need to be resolved: -# # 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-identifier-naming, # https://github.com/XRPLF/rippled/pull/6571 # --- -# + CheckOptions: - readability-braces-around-statements.ShortStatementLines: 2 - # readability-identifier-naming.MacroDefinitionCase: UPPER_CASE - # readability-identifier-naming.ClassCase: CamelCase - # readability-identifier-naming.StructCase: CamelCase - # readability-identifier-naming.UnionCase: CamelCase - # readability-identifier-naming.EnumCase: CamelCase - # readability-identifier-naming.EnumConstantCase: CamelCase - # readability-identifier-naming.ScopedEnumConstantCase: CamelCase - # readability-identifier-naming.GlobalConstantCase: UPPER_CASE - # readability-identifier-naming.GlobalConstantPrefix: "k" - # readability-identifier-naming.GlobalVariableCase: CamelCase - # readability-identifier-naming.GlobalVariablePrefix: "g" - # readability-identifier-naming.ConstexprFunctionCase: camelBack - # readability-identifier-naming.ConstexprMethodCase: camelBack - # readability-identifier-naming.ClassMethodCase: camelBack - # readability-identifier-naming.ClassMemberCase: camelBack - # readability-identifier-naming.ClassConstantCase: UPPER_CASE - # readability-identifier-naming.ClassConstantPrefix: "k" - # readability-identifier-naming.StaticConstantCase: UPPER_CASE - # readability-identifier-naming.StaticConstantPrefix: "k" - # readability-identifier-naming.StaticVariableCase: UPPER_CASE - # readability-identifier-naming.StaticVariablePrefix: "k" - # readability-identifier-naming.ConstexprVariableCase: UPPER_CASE - # readability-identifier-naming.ConstexprVariablePrefix: "k" - # readability-identifier-naming.LocalConstantCase: camelBack - # readability-identifier-naming.LocalVariableCase: camelBack - # readability-identifier-naming.TemplateParameterCase: CamelCase - # readability-identifier-naming.ParameterCase: camelBack - # readability-identifier-naming.FunctionCase: camelBack - # readability-identifier-naming.MemberCase: camelBack - # readability-identifier-naming.PrivateMemberSuffix: _ - # readability-identifier-naming.ProtectedMemberSuffix: _ - # readability-identifier-naming.PublicMemberSuffix: "" - # readability-identifier-naming.FunctionIgnoredRegexp: ".*tag_invoke.*" bugprone-unsafe-functions.ReportMoreUnsafeFunctions: true bugprone-unused-return-value.CheckedReturnTypes: ::std::error_code;::std::error_condition;::std::errc + misc-include-cleaner.IgnoreHeaders: ".*/(detail|impl)/.*;.*fwd\\.h(pp)?;time.h;stdlib.h;sqlite3.h;netinet/in\\.h;sys/resource\\.h;sys/sysinfo\\.h;linux/sysinfo\\.h;__chrono/.*;bits/.*;_abort\\.h;boost/uuid/uuid_hash.hpp;boost/beast/core/flat_buffer\\.hpp;boost/beast/http/field\\.hpp;boost/beast/http/dynamic_body\\.hpp;boost/beast/http/message\\.hpp;boost/beast/http/read\\.hpp;boost/beast/http/write\\.hpp;openssl/obj_mac\\.h" -# -HeaderFilterRegex: '^.*/(test|xrpl|xrpld)/.*\.(h|hpp)$' + + readability-braces-around-statements.ShortStatementLines: 2 + readability-identifier-naming.MacroDefinitionCase: UPPER_CASE + readability-identifier-naming.ClassCase: CamelCase + readability-identifier-naming.StructCase: CamelCase + readability-identifier-naming.UnionCase: CamelCase + readability-identifier-naming.EnumCase: CamelCase + readability-identifier-naming.EnumConstantCase: CamelCase + readability-identifier-naming.ScopedEnumConstantCase: CamelCase + readability-identifier-naming.GlobalConstantCase: CamelCase + readability-identifier-naming.GlobalConstantPrefix: "k" + readability-identifier-naming.GlobalVariableCase: CamelCase + readability-identifier-naming.GlobalVariablePrefix: "g" + readability-identifier-naming.ConstexprFunctionCase: camelBack + readability-identifier-naming.ConstexprMethodCase: camelBack + readability-identifier-naming.ClassMethodCase: camelBack + readability-identifier-naming.ClassMemberCase: camelBack + readability-identifier-naming.ClassConstantCase: CamelCase + readability-identifier-naming.ClassConstantPrefix: "k" + readability-identifier-naming.StaticConstantCase: CamelCase + readability-identifier-naming.StaticConstantPrefix: "k" + readability-identifier-naming.StaticVariableCase: camelBack + readability-identifier-naming.ConstexprVariableCase: camelBack + readability-identifier-naming.LocalConstantCase: camelBack + readability-identifier-naming.LocalVariableCase: camelBack + readability-identifier-naming.TemplateParameterCase: CamelCase + 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: '^.*/(tests?|xrpl|xrpld)/.*\.(h|hpp|ipp)$' ExcludeHeaderFilterRegex: '^.*/protocol_autogen/.*\.(h|hpp)$' WarningsAsErrors: "*" diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs index 0cf704b051..d61cab7e03 100644 --- a/.git-blame-ignore-revs +++ b/.git-blame-ignore-revs @@ -5,6 +5,8 @@ # This file is sorted in reverse chronological order, with the most recent commits at the top. # The commits listed here are ignored by git blame, which is useful for formatting-only commits that would otherwise obscure the history of changes to a file. +# refactor: Enable clang-tidy `readability-identifier-naming` check (#6571) +8995564ed6b9e453e144bb663303072a3c1ba305 # refactor: Enable remaining clang-tidy `cppcoreguidelines` checks (#6538) 72f4cb097f626b08b02fc3efcb4aa11cb2e7adb8 # refactor: Rename system name from 'ripple' to 'xrpld' (#6347) diff --git a/.github/actions/build-deps/action.yml b/.github/actions/build-deps/action.yml index 9d52be1998..0891d56dfa 100644 --- a/.github/actions/build-deps/action.yml +++ b/.github/actions/build-deps/action.yml @@ -37,12 +37,12 @@ runs: 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 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}" \ + . diff --git a/.github/actions/generate-version/action.yml b/.github/actions/generate-version/action.yml index 8edb7920c6..50b3166596 100644 --- a/.github/actions/generate-version/action.yml +++ b/.github/actions/generate-version/action.yml @@ -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}" diff --git a/.github/actions/print-env/action.yml b/.github/actions/print-env/action.yml deleted file mode 100644 index 3527ca6f02..0000000000 --- a/.github/actions/print-env/action.yml +++ /dev/null @@ -1,43 +0,0 @@ -name: Print build environment -description: "Print environment and some tooling versions" - -runs: - using: composite - steps: - - name: Check configuration (Windows) - if: ${{ runner.os == 'Windows' }} - shell: bash - run: | - echo 'Checking environment variables.' - set - - - name: Check configuration (Linux and macOS) - if: ${{ runner.os == 'Linux' || runner.os == 'macOS' }} - shell: bash - run: | - echo 'Checking path.' - echo ${PATH} | tr ':' '\n' - - echo 'Checking environment variables.' - env | sort - - echo 'Checking compiler version.' - ${{ runner.os == 'Linux' && '${CC}' || 'clang' }} --version - - echo 'Checking Ninja version.' - ninja --version - - echo 'Checking nproc version.' - nproc --version - - - name: Check configuration (all) - shell: bash - run: | - echo 'Checking Ccache version.' - ccache --version - - echo 'Checking CMake version.' - cmake --version - - echo 'Checking Conan version.' - conan --version diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 66e319e0e7..0e6b840fe7 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -33,17 +33,6 @@ updates: prefix: "ci: [DEPENDABOT] " target-branch: develop - - package-ecosystem: github-actions - directory: .github/actions/print-env/ - 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/ schedule: diff --git a/.github/scripts/format-inline-bash.py b/.github/scripts/format-inline-bash.py new file mode 100755 index 0000000000..423c78109c --- /dev/null +++ b/.github/scripts/format-inline-bash.py @@ -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 ` (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[ \t]*(?:- )?)run:[ \t]*\|[+-]?[ \t]*$") +RUN_INLINE_RE = re.compile( + r"^(?P[ \t]*(?:- )?)run:[ \t]+" r"(?P(?!\|[+-]?[ \t]*$)\S.*?)[ \t]*$" +) +MD_BASH_OPEN_RE = re.compile(r"^(?P[ ]{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 "::: ". + 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:])) diff --git a/.github/scripts/levelization/results/ordering.txt b/.github/scripts/levelization/results/ordering.txt index 5a2307b1be..b77b1315a8 100644 --- a/.github/scripts/levelization/results/ordering.txt +++ b/.github/scripts/levelization/results/ordering.txt @@ -191,12 +191,18 @@ test.toplevel > xrpl.json test.unit_test > xrpl.basics test.unit_test > xrpl.protocol tests.libxrpl > xrpl.basics +tests.libxrpl > xrpl.core tests.libxrpl > xrpld.telemetry tests.libxrpl > xrpl.json +tests.libxrpl > xrpl.ledger tests.libxrpl > xrpl.net +tests.libxrpl > xrpl.nodestore tests.libxrpl > xrpl.protocol tests.libxrpl > xrpl.protocol_autogen +tests.libxrpl > xrpl.server +tests.libxrpl > xrpl.shamap tests.libxrpl > xrpl.telemetry +tests.libxrpl > xrpl.tx xrpl.conditions > xrpl.basics xrpl.conditions > xrpl.protocol xrpl.core > xrpl.basics diff --git a/.github/scripts/rename/binary.sh b/.github/scripts/rename/binary.sh index cdce6db4ba..89d884538c 100755 --- a/.github/scripts/rename/binary.sh +++ b/.github/scripts/rename/binary.sh @@ -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 diff --git a/.github/scripts/rename/cmake.sh b/.github/scripts/rename/cmake.sh index 9c91e8f277..28bf777fed 100755 --- a/.github/scripts/rename/cmake.sh +++ b/.github/scripts/rename/cmake.sh @@ -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 @@ -74,10 +74,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 diff --git a/.github/scripts/rename/config.sh b/.github/scripts/rename/config.sh index b7aff82cdf..ac9debb154 100755 --- a/.github/scripts/rename/config.sh +++ b/.github/scripts/rename/config.sh @@ -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,7 +62,7 @@ ${SED_COMMAND} -i 's@ripple/@xrpld/@g' src/test/core/Config_test.cpp ${SED_COMMAND} -i 's/Rippled/File/g' src/test/core/Config_test.cpp # Restore the old config file name in the code that maintains support for now. -${SED_COMMAND} -i 's/configLegacyName = "xrpld.cfg"/configLegacyName = "rippled.cfg"/g' src/xrpld/core/detail/Config.cpp +${SED_COMMAND} -i 's/kConfigLegacyName = "xrpld.cfg"/kConfigLegacyName = "rippled.cfg"/g' src/xrpld/core/detail/Config.cpp # Restore an URL. ${SED_COMMAND} -i 's/connect-your-xrpld-to-the-xrp-test-net.html/connect-your-rippled-to-the-xrp-test-net.html/g' cfg/xrpld-example.cfg diff --git a/.github/scripts/rename/copyright.sh b/.github/scripts/rename/copyright.sh index 9ebdad1e89..09bc5a8926 100755 --- a/.github/scripts/rename/copyright.sh +++ b/.github/scripts/rename/copyright.sh @@ -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 \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 \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 \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 \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 \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 \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 \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 \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. diff --git a/.github/scripts/rename/definitions.sh b/.github/scripts/rename/definitions.sh index 5e004afe39..daa5d01e80 100755 --- a/.github/scripts/rename/definitions.sh +++ b/.github/scripts/rename/definitions.sh @@ -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 diff --git a/.github/scripts/rename/docs.sh b/.github/scripts/rename/docs.sh index 59cc5665bf..9f080b06e5 100755 --- a/.github/scripts/rename/docs.sh +++ b/.github/scripts/rename/docs.sh @@ -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 @@ -90,7 +90,7 @@ ${SED_COMMAND} -i 's/www.ripple.com/www.xrpl.org/g' src/test/protocol/Seed_test. # Restore specific changes. ${SED_COMMAND} -i 's@b5efcc/src/xrpld@b5efcc/src/ripple@' include/xrpl/protocol/README.md ${SED_COMMAND} -i 's/dbPrefix_ = "xrpldb"/dbPrefix_ = "rippledb"/' src/xrpld/app/misc/SHAMapStoreImp.h # cspell: disable-line -${SED_COMMAND} -i 's/configLegacyName = "xrpld.cfg"/configLegacyName = "rippled.cfg"/' src/xrpld/core/detail/Config.cpp +${SED_COMMAND} -i 's/kConfigLegacyName = "xrpld.cfg"/kConfigLegacyName = "rippled.cfg"/' src/xrpld/core/detail/Config.cpp popd echo "Renaming complete." diff --git a/.github/scripts/rename/namespace.sh b/.github/scripts/rename/namespace.sh index aba193b0cf..bb186bc8bc 100755 --- a/.github/scripts/rename/namespace.sh +++ b/.github/scripts/rename/namespace.sh @@ -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 diff --git a/.github/scripts/strategy-matrix/generate.py b/.github/scripts/strategy-matrix/generate.py index dec41a2610..6eccfcc6be 100755 --- a/.github/scripts/strategy-matrix/generate.py +++ b/.github/scripts/strategy-matrix/generate.py @@ -32,7 +32,32 @@ We will further set additional CMake arguments as follows: """ -def generate_strategy_matrix(all: bool, config: Config) -> list: +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) + + +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. + """ + return [ + { + "artifact_name": f"xrpld-{build_config_name(os, 'linux/amd64', 'Release')}", + "os": os, + } + for os in config.os + if os.get("package", False) + ] + + +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 @@ -72,7 +97,7 @@ def generate_strategy_matrix(all: bool, config: Config) -> list: skip = False if ( f"{os['compiler_name']}-{os['compiler_version']}" == "gcc-15" - and build_type == "Debug" + and build_type == "Release" and architecture["platform"] == "linux/amd64" ): skip = False @@ -90,8 +115,9 @@ def generate_strategy_matrix(all: bool, config: Config) -> list: ): 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-20" + f"{os['compiler_name']}-{os['compiler_version']}" == "clang-22" and build_type == "Debug" and architecture["platform"] == "linux/amd64" ): @@ -100,14 +126,15 @@ def generate_strategy_matrix(all: bool, config: Config) -> list: continue # RHEL: - # - 9 using GCC 12: Debug on linux/amd64. + # - 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 == "Debug" + and build_type in ["Debug", "Release"] and architecture["platform"] == "linux/amd64" ): skip = False @@ -122,7 +149,8 @@ def generate_strategy_matrix(all: bool, config: Config) -> list: continue # Ubuntu: - # - Jammy using GCC 12: Debug on linux/arm64. + # - 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. @@ -135,6 +163,12 @@ def generate_strategy_matrix(all: bool, config: Config) -> list: 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" @@ -188,8 +222,9 @@ def generate_strategy_matrix(all: bool, config: Config) -> list: # We skip all clang 20+ on arm64 due to Boost build error. if ( - f"{os['compiler_name']}-{os['compiler_version']}" - in ["clang-20", "clang-21"] + os["compiler_name"] == "clang" + and os["compiler_version"].isdigit() + and int(os["compiler_version"]) >= 20 and architecture["platform"] == "linux/arm64" ): continue @@ -216,17 +251,7 @@ def generate_strategy_matrix(all: bool, config: Config) -> list: # Generate a unique name for the configuration, e.g. macos-arm64-debug # or debian-bookworm-gcc-12-amd64-release. - config_name = os["distro_name"] - if (n := os["distro_version"]) != "": - config_name += f"-{n}" - if (n := os["compiler_name"]) != "": - config_name += f"-{n}" - if (n := os["compiler_version"]) != "": - config_name += f"-{n}" - config_name += ( - f"-{architecture['platform'][architecture['platform'].find('/')+1:]}" - ) - config_name += f"-{build_type.lower()}" + 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: @@ -238,13 +263,14 @@ def generate_strategy_matrix(all: bool, config: Config) -> list: # 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']}" in [ - "gcc-15", - "clang-20", - ]: - # Add ASAN configuration. + 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", @@ -257,7 +283,6 @@ def generate_strategy_matrix(all: bool, config: Config) -> list: "sanitizers": "address", } ) - # Add UBSAN configuration. configurations.append( { "config_name": config_name + "-ubsan", @@ -330,10 +355,19 @@ if __name__ == "__main__": required=False, type=Path, ) + parser.add_argument( + "-p", + "--packaging", + help="Emit the packaging matrix (derived from the 'package' field on os entries) instead of the build/test matrix.", + action="store_true", + ) args = parser.parse_args() matrix = [] - if args.config is None or args.config == "": + 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") ) diff --git a/.github/scripts/strategy-matrix/linux.json b/.github/scripts/strategy-matrix/linux.json index 4943579be8..4f090a81a3 100644 --- a/.github/scripts/strategy-matrix/linux.json +++ b/.github/scripts/strategy-matrix/linux.json @@ -15,196 +15,205 @@ "distro_version": "bookworm", "compiler_name": "gcc", "compiler_version": "12", - "image_sha": "ab4d1f0" + "image_sha": "4c086b9" }, { "distro_name": "debian", "distro_version": "bookworm", "compiler_name": "gcc", "compiler_version": "13", - "image_sha": "ab4d1f0" + "image_sha": "4c086b9" }, { "distro_name": "debian", "distro_version": "bookworm", "compiler_name": "gcc", "compiler_version": "14", - "image_sha": "ab4d1f0" + "image_sha": "4c086b9" }, { "distro_name": "debian", "distro_version": "bookworm", "compiler_name": "gcc", "compiler_version": "15", - "image_sha": "ab4d1f0" + "image_sha": "4c086b9" }, { "distro_name": "debian", "distro_version": "bookworm", "compiler_name": "clang", "compiler_version": "16", - "image_sha": "ab4d1f0" + "image_sha": "4c086b9" }, { "distro_name": "debian", "distro_version": "bookworm", "compiler_name": "clang", "compiler_version": "17", - "image_sha": "ab4d1f0" + "image_sha": "4c086b9" }, { "distro_name": "debian", "distro_version": "bookworm", "compiler_name": "clang", "compiler_version": "18", - "image_sha": "ab4d1f0" + "image_sha": "4c086b9" }, { "distro_name": "debian", "distro_version": "bookworm", "compiler_name": "clang", "compiler_version": "19", - "image_sha": "ab4d1f0" + "image_sha": "4c086b9" }, { "distro_name": "debian", "distro_version": "bookworm", "compiler_name": "clang", "compiler_version": "20", - "image_sha": "ab4d1f0" + "image_sha": "4c086b9" }, { "distro_name": "debian", "distro_version": "trixie", "compiler_name": "gcc", "compiler_version": "14", - "image_sha": "ab4d1f0" + "image_sha": "4c086b9" }, { "distro_name": "debian", "distro_version": "trixie", "compiler_name": "gcc", "compiler_version": "15", - "image_sha": "ab4d1f0" + "image_sha": "4c086b9" }, { "distro_name": "debian", "distro_version": "trixie", "compiler_name": "clang", "compiler_version": "20", - "image_sha": "ab4d1f0" + "image_sha": "4c086b9" }, { "distro_name": "debian", "distro_version": "trixie", "compiler_name": "clang", "compiler_version": "21", - "image_sha": "ab4d1f0" + "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": "ab4d1f0" + "image_sha": "4c086b9" }, { "distro_name": "rhel", "distro_version": "8", "compiler_name": "clang", "compiler_version": "any", - "image_sha": "ab4d1f0" + "image_sha": "4c086b9" }, { "distro_name": "rhel", "distro_version": "9", "compiler_name": "gcc", "compiler_version": "12", - "image_sha": "ab4d1f0" + "image_sha": "4c086b9", + "package": true }, { "distro_name": "rhel", "distro_version": "9", "compiler_name": "gcc", "compiler_version": "13", - "image_sha": "ab4d1f0" + "image_sha": "4c086b9" }, { "distro_name": "rhel", "distro_version": "9", "compiler_name": "gcc", "compiler_version": "14", - "image_sha": "ab4d1f0" + "image_sha": "4c086b9" }, { "distro_name": "rhel", "distro_version": "9", "compiler_name": "clang", "compiler_version": "any", - "image_sha": "ab4d1f0" + "image_sha": "4c086b9" }, { "distro_name": "rhel", "distro_version": "10", "compiler_name": "gcc", "compiler_version": "14", - "image_sha": "ab4d1f0" + "image_sha": "4c086b9" }, { "distro_name": "rhel", "distro_version": "10", "compiler_name": "clang", "compiler_version": "any", - "image_sha": "ab4d1f0" + "image_sha": "4c086b9" }, { "distro_name": "ubuntu", "distro_version": "jammy", "compiler_name": "gcc", "compiler_version": "12", - "image_sha": "ab4d1f0" + "image_sha": "4c086b9", + "package": true }, { "distro_name": "ubuntu", "distro_version": "noble", "compiler_name": "gcc", "compiler_version": "13", - "image_sha": "ab4d1f0" + "image_sha": "4c086b9" }, { "distro_name": "ubuntu", "distro_version": "noble", "compiler_name": "gcc", "compiler_version": "14", - "image_sha": "ab4d1f0" + "image_sha": "4c086b9" }, { "distro_name": "ubuntu", "distro_version": "noble", "compiler_name": "clang", "compiler_version": "16", - "image_sha": "ab4d1f0" + "image_sha": "4c086b9" }, { "distro_name": "ubuntu", "distro_version": "noble", "compiler_name": "clang", "compiler_version": "17", - "image_sha": "ab4d1f0" + "image_sha": "4c086b9" }, { "distro_name": "ubuntu", "distro_version": "noble", "compiler_name": "clang", "compiler_version": "18", - "image_sha": "ab4d1f0" + "image_sha": "4c086b9" }, { "distro_name": "ubuntu", "distro_version": "noble", "compiler_name": "clang", "compiler_version": "19", - "image_sha": "ab4d1f0" + "image_sha": "4c086b9" } ], "build_type": ["Debug", "Release"], diff --git a/.github/workflows/build-nix-image.yml b/.github/workflows/build-nix-image.yml new file mode 100644 index 0000000000..bae4cfd437 --- /dev/null +++ b/.github/workflows/build-nix-image.yml @@ -0,0 +1,109 @@ +name: Build Nix Docker image + +on: + push: + branches: + - develop + paths: + - ".github/workflows/build-nix-image.yml" + - ".github/workflows/reusable-build-docker-image.yml" + - "docker/**" + - "flake.nix" + - "flake.lock" + - "nix/**" + pull_request: + paths: + - ".github/workflows/build-nix-image.yml" + - ".github/workflows/reusable-build-docker-image.yml" + - "docker/**" + - "flake.nix" + - "flake.lock" + - "nix/**" + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +defaults: + run: + shell: bash + +jobs: + build: + name: Build ${{ matrix.distro.name }} (${{ matrix.target.platform }}) + 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: rhel + base_image: registry.access.redhat.com/ubi9/ubi:latest + - name: debian + base_image: debian:bookworm + 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: ghcr.io/xrplf/xrpld/nix-${{ matrix.distro.name }} + dockerfile: docker/nix.Dockerfile + base_image: ${{ matrix.distro.base_image }} + platform: ${{ matrix.target.platform }} + runner: ${{ matrix.target.runner }} + push: ${{ github.repository == 'XRPLF/rippled' && github.event_name == 'push' }} + + merge: + name: Merge ${{ matrix.distro }} manifest + needs: build + if: ${{ github.repository == 'XRPLF/rippled' && github.event_name == 'push' }} + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + strategy: + fail-fast: false + matrix: + distro: [nixos, ubuntu, rhel, debian] + env: + IMAGE_NAME: ghcr.io/xrplf/xrpld/nix-${{ matrix.distro }} + + 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: ${{ env.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 + 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 + run: | + docker buildx imagetools inspect "${IMAGE_NAME}:${{ steps.meta.outputs.version }}" diff --git a/.github/workflows/check-pr-description.yml b/.github/workflows/check-pr-description.yml index f6eee50291..ff28220171 100644 --- a/.github/workflows/check-pr-description.yml +++ b/.github/workflows/check-pr-description.yml @@ -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: @@ -20,11 +29,11 @@ jobs: 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 diff --git a/.github/workflows/check-pr-title.yml b/.github/workflows/check-pr-title.yml index 6d7bdefa08..4b5f679df1 100644 --- a/.github/workflows/check-pr-title.yml +++ b/.github/workflows/check-pr-title.yml @@ -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@a5d8dd35be543365e90a11358447130c8763871d + uses: XRPLF/actions/.github/workflows/check-pr-title.yml@cba1f0891650baf1a9c88624dc2d72573be2eb81 diff --git a/.github/workflows/on-pr.yml b/.github/workflows/on-pr.yml index 28299a1264..db3c8667e5 100644 --- a/.github/workflows/on-pr.yml +++ b/.github/workflows/on-pr.yml @@ -58,20 +58,19 @@ jobs: # Keep the paths below in sync with those in `on-trigger.yml`. .github/actions/build-deps/** - .github/actions/build-test/** .github/actions/generate-version/** .github/actions/setup-conan/** .github/scripts/strategy-matrix/** - .github/workflows/reusable-build.yml .github/workflows/reusable-build-test-config.yml .github/workflows/reusable-build-test.yml .github/workflows/reusable-clang-tidy.yml - .github/workflows/reusable-clang-tidy-files.yml + .github/workflows/reusable-package.yml .github/workflows/reusable-strategy-matrix.yml .github/workflows/reusable-test.yml .github/workflows/reusable-upload-recipe.yml .clang-tidy .codecov.yml + cfg/** cmake/** conan/** external/** @@ -81,6 +80,10 @@ jobs: CMakeLists.txt conanfile.py conan.lock + LICENSE.md + package/** + README.md + - name: Check whether to run # This step determines whether the rest of the workflow should # run. The rest of the workflow will run if this job runs AND at @@ -95,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' }} @@ -137,6 +140,11 @@ jobs: secrets: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + package: + needs: [should-run, build-test] + if: ${{ needs.should-run.outputs.go == 'true' }} + uses: ./.github/workflows/reusable-package.yml + upload-recipe: needs: - should-run @@ -160,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() @@ -171,9 +179,10 @@ jobs: - check-rename - clang-tidy - build-test + - package - upload-recipe - notify-clio runs-on: ubuntu-latest steps: - name: Fail - run: false + run: exit 1 diff --git a/.github/workflows/on-tag.yml b/.github/workflows/on-tag.yml index e570a0e119..b7517ccf11 100644 --- a/.github/workflows/on-tag.yml +++ b/.github/workflows/on-tag.yml @@ -1,5 +1,5 @@ -# This workflow uploads the libxrpl recipe to the Conan remote when a versioned -# tag is pushed. +# This workflow uploads the libxrpl recipe to the Conan remote and builds +# release packages when a versioned tag is pushed. name: Tag on: @@ -22,3 +22,22 @@ jobs: secrets: remote_username: ${{ secrets.CONAN_REMOTE_USERNAME }} remote_password: ${{ secrets.CONAN_REMOTE_PASSWORD }} + + build-test: + if: ${{ github.repository == 'XRPLF/rippled' }} + uses: ./.github/workflows/reusable-build-test.yml + strategy: + fail-fast: true + matrix: + os: [linux] + with: + ccache_enabled: false + os: ${{ matrix.os }} + strategy_matrix: minimal + secrets: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + + package: + if: ${{ github.repository == 'XRPLF/rippled' }} + needs: build-test + uses: ./.github/workflows/reusable-package.yml diff --git a/.github/workflows/on-trigger.yml b/.github/workflows/on-trigger.yml index 5856c67bd3..803ba3c87b 100644 --- a/.github/workflows/on-trigger.yml +++ b/.github/workflows/on-trigger.yml @@ -15,20 +15,19 @@ on: # Keep the paths below in sync with those in `on-pr.yml`. - ".github/actions/build-deps/**" - - ".github/actions/build-test/**" - ".github/actions/generate-version/**" - ".github/actions/setup-conan/**" - ".github/scripts/strategy-matrix/**" - - ".github/workflows/reusable-build.yml" - ".github/workflows/reusable-build-test-config.yml" - ".github/workflows/reusable-build-test.yml" - ".github/workflows/reusable-clang-tidy.yml" - - ".github/workflows/reusable-clang-tidy-files.yml" + - ".github/workflows/reusable-package.yml" - ".github/workflows/reusable-strategy-matrix.yml" - ".github/workflows/reusable-test.yml" - ".github/workflows/reusable-upload-recipe.yml" - ".clang-tidy" - ".codecov.yml" + - "cfg/**" - "cmake/**" - "conan/**" - "external/**" @@ -38,6 +37,9 @@ on: - "CMakeLists.txt" - "conanfile.py" - "conan.lock" + - "LICENSE.md" + - "package/**" + - "README.md" # Run at 06:32 UTC on every day of the week from Monday through Friday. This # will force all dependencies to be rebuilt, which is useful to verify that @@ -98,3 +100,7 @@ jobs: secrets: remote_username: ${{ secrets.CONAN_REMOTE_USERNAME }} remote_password: ${{ secrets.CONAN_REMOTE_PASSWORD }} + + package: + needs: build-test + uses: ./.github/workflows/reusable-package.yml diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index 89255f0e47..de6a4f40b4 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -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@9307df762265e15c745ddcdb38a581c989f7f349 + 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" }' diff --git a/.github/workflows/reusable-build-docker-image.yml b/.github/workflows/reusable-build-docker-image.yml new file mode 100644 index 0000000000..c3795e56fa --- /dev/null +++ b/.github/workflows/reusable-build-docker-image.yml @@ -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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - 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 }} diff --git a/.github/workflows/reusable-build-test-config.yml b/.github/workflows/reusable-build-test-config.yml index c2c862d73f..31457bb892 100644 --- a/.github/workflows/reusable-build-test-config.yml +++ b/.github/workflows/reusable-build-test-config.yml @@ -113,10 +113,10 @@ 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: ./.github/actions/print-env + uses: XRPLF/actions/print-build-env@59dec886e4afb05a1724443af08baccbc045b574 - name: Get number of processors uses: XRPLF/actions/get-nproc@cf0433aa74563aead044a1e395610c96d65a37cf @@ -143,15 +143,14 @@ jobs: working-directory: ${{ env.BUILD_DIR }} env: BUILD_TYPE: ${{ inputs.build_type }} - SANITIZERS: ${{ inputs.sanitizers }} 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 }} @@ -173,10 +172,10 @@ 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 @@ -187,18 +186,18 @@ jobs: 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}" - 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) @@ -215,7 +214,7 @@ jobs: 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' }} @@ -232,10 +231,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) @@ -251,12 +250,12 @@ 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 }} @@ -267,9 +266,9 @@ jobs: PARALLELISM: ${{ runner.os == 'Windows' && '1' || steps.nproc.outputs.nproc }} run: | ctest \ - --output-on-failure \ - -C "${BUILD_TYPE}" \ - -j "${PARALLELISM}" + --output-on-failure \ + -C "${BUILD_TYPE}" \ + -j "${PARALLELISM}" - name: Run the embedded tests if: ${{ !inputs.build_only }} @@ -279,20 +278,28 @@ 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 )) + [ "$COVERAGE_ENABLED" = "true" ] && BUILD_NPROC=$((BUILD_NPROC - 2)) ./xrpld --unittest --unittest-jobs "${BUILD_NPROC}" 2>&1 | tee unittest.log - name: Show test failure summary if: ${{ failure() && !inputs.build_only }} - working-directory: ${{ runner.os == 'Windows' && format('{0}/{1}', env.BUILD_DIR, inputs.build_type) || env.BUILD_DIR }} + env: + 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 + 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 }} @@ -310,14 +317,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 diff --git a/.github/workflows/reusable-check-levelization.yml b/.github/workflows/reusable-check-levelization.yml index 4efe3e1138..b5d57a177a 100644 --- a/.github/workflows/reusable-check-levelization.yml +++ b/.github/workflows/reusable-check-levelization.yml @@ -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 diff --git a/.github/workflows/reusable-check-rename.yml b/.github/workflows/reusable-check-rename.yml index 56a1a3e637..7aa5b80594 100644 --- a/.github/workflows/reusable-check-rename.yml +++ b/.github/workflows/reusable-check-rename.yml @@ -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 diff --git a/.github/workflows/reusable-clang-tidy-files.yml b/.github/workflows/reusable-clang-tidy-files.yml deleted file mode 100644 index 9b99f418b1..0000000000 --- a/.github/workflows/reusable-clang-tidy-files.yml +++ /dev/null @@ -1,175 +0,0 @@ -name: Run clang-tidy on files - -on: - workflow_call: - inputs: - files: - description: "List of files to check (empty means check all files)" - type: string - default: "" - create_issue_on_failure: - description: "Whether to create an issue if the check failed" - type: boolean - default: false - -defaults: - run: - shell: bash - -env: - # Conan installs the generators in the build/generators directory, see the - # layout() method in conanfile.py. We then run CMake from the build directory. - BUILD_DIR: build - BUILD_TYPE: Release - -jobs: - run-clang-tidy: - name: Run clang tidy - runs-on: ["self-hosted", "Linux", "X64", "heavy"] - container: "ghcr.io/xrplf/ci/debian-trixie:clang-21-sha-53033a2" - permissions: - issues: write - contents: read - steps: - - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - - name: Prepare runner - uses: XRPLF/actions/prepare-runner@90f11ee655d1687824fb8793db770477d52afbab - with: - enable_ccache: false - - - name: Print build environment - uses: ./.github/actions/print-env - - - name: Get number of processors - uses: XRPLF/actions/get-nproc@cf0433aa74563aead044a1e395610c96d65a37cf - id: nproc - - - name: Setup Conan - uses: ./.github/actions/setup-conan - - - name: Build dependencies - uses: ./.github/actions/build-deps - with: - build_nproc: ${{ steps.nproc.outputs.nproc }} - build_type: ${{ env.BUILD_TYPE }} - log_verbosity: verbose - - - name: Configure CMake - 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 \ - .. - - # clang-tidy needs headers generated from proto files - - name: Build libxrpl.libpb - working-directory: ${{ env.BUILD_DIR }} - run: | - ninja -j ${{ steps.nproc.outputs.nproc }} xrpl.libpb - - - name: Run clang tidy - id: run_clang_tidy - continue-on-error: true - env: - TARGETS: ${{ inputs.files != '' && inputs.files || 'src tests' }} - run: | - run-clang-tidy -j ${{ steps.nproc.outputs.nproc }} -p "${BUILD_DIR}" -quiet -fix -allow-no-checks ${TARGETS} 2>&1 | tee clang-tidy-output.txt - - - name: Upload clang-tidy output - if: ${{ github.event.repository.visibility == 'public' && steps.run_clang_tidy.outcome != 'success' }} - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 - with: - name: clang-tidy-results - path: clang-tidy-output.txt - retention-days: 30 - - - name: Generate git diff - if: ${{ steps.run_clang_tidy.outcome != 'success' }} - run: | - git diff | tee clang-tidy-git-diff.txt - - - name: Upload clang-tidy diff output - if: ${{ github.event.repository.visibility == 'public' && steps.run_clang_tidy.outcome != 'success' }} - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 - with: - name: clang-tidy-git-diff - path: clang-tidy-git-diff.txt - retention-days: 30 - - - name: Create an issue - if: ${{ steps.run_clang_tidy.outcome != 'success' && inputs.create_issue_on_failure }} - id: create_issue - shell: bash - env: - GH_TOKEN: ${{ github.token }} - run: | - # Prepare issue body with clang-tidy output - cat > issue.md < filtered-output.txt || true - - # If filtered output is empty, use original (might be a different error format) - if [ ! -s filtered-output.txt ]; then - cp clang-tidy-output.txt filtered-output.txt - fi - - # Truncate if too large - head -c 60000 filtered-output.txt >> issue.md - if [ "$(wc -c < filtered-output.txt)" -gt 60000 ]; then - echo "" >> issue.md - echo "... (output truncated, see artifacts for full output)" >> issue.md - fi - - rm filtered-output.txt - else - echo "No output file found" >> issue.md - fi - - cat >> issue.md < create_issue.log - - created_issue="$(sed 's|.*/||' create_issue.log)" - echo "created_issue=$created_issue" >> $GITHUB_OUTPUT - echo "Created issue #$created_issue" - - rm -f create_issue.log issue.md clang-tidy-output.txt - - - name: Fail the workflow if clang-tidy failed - if: ${{ steps.run_clang_tidy.outcome != 'success' }} - run: | - echo "Clang-tidy check failed!" - exit 1 diff --git a/.github/workflows/reusable-clang-tidy.yml b/.github/workflows/reusable-clang-tidy.yml index 7a8bf6de57..8be1db5fb2 100644 --- a/.github/workflows/reusable-clang-tidy.yml +++ b/.github/workflows/reusable-clang-tidy.yml @@ -1,4 +1,4 @@ -name: Clang-tidy check +name: Run clang-tidy on files on: workflow_call: @@ -16,40 +16,175 @@ defaults: run: shell: bash +env: + BUILD_DIR: build + BUILD_TYPE: Debug # Debug so that ASSERTS and such participate in clang-tidy check + + OUTPUT_FILE: clang-tidy-output.txt + DIFF_FILE: clang-tidy-git-diff.txt + ISSUE_FILE: clang-tidy-issue.md + jobs: determine-files: - name: Determine files to check if: ${{ inputs.check_only_changed }} - runs-on: ubuntu-latest - outputs: - clang_tidy_config_changed: ${{ steps.changed_clang_tidy.outputs.any_changed }} - any_cpp_changed: ${{ steps.changed_files.outputs.any_changed }} - all_changed_files: ${{ steps.changed_files.outputs.all_changed_files }} + permissions: + contents: read + uses: XRPLF/actions/.github/workflows/determine-tidy-files.yml@224f3c48d3014d082a1129237b8291ff0b0a331f + + run-clang-tidy: + name: Run clang tidy + 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" + permissions: + contents: read + issues: write steps: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - name: Get changed C++ files - id: changed_files - uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6 + - name: Prepare runner + uses: XRPLF/actions/prepare-runner@90f11ee655d1687824fb8793db770477d52afbab with: - files: | - **/*.cpp - **/*.h - **/*.ipp - separator: " " + enable_ccache: false - - name: Get changed clang-tidy configuration - id: changed_clang_tidy - uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6 + - name: Print build environment + uses: XRPLF/actions/print-build-env@59dec886e4afb05a1724443af08baccbc045b574 + + - name: Get number of processors + uses: XRPLF/actions/get-nproc@cf0433aa74563aead044a1e395610c96d65a37cf + id: nproc + + - name: Setup Conan + uses: ./.github/actions/setup-conan + + - name: Build dependencies + uses: ./.github/actions/build-deps with: - files: | - .clang-tidy + build_nproc: ${{ steps.nproc.outputs.nproc }} + build_type: ${{ env.BUILD_TYPE }} + log_verbosity: verbose - run-clang-tidy: - needs: [determine-files] - if: ${{ always() && !cancelled() && (!inputs.check_only_changed || needs.determine-files.outputs.any_cpp_changed == 'true' || needs.determine-files.outputs.clang_tidy_config_changed == 'true') }} - uses: ./.github/workflows/reusable-clang-tidy-files.yml - with: - files: ${{ (needs.determine-files.outputs.clang_tidy_config_changed != 'true' && inputs.check_only_changed) && needs.determine-files.outputs.all_changed_files || '' }} - create_issue_on_failure: ${{ inputs.create_issue_on_failure }} + - name: Configure CMake + 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 \ + .. + + # clang-tidy needs headers generated from proto files + - name: Build libxrpl.libpb + working-directory: ${{ env.BUILD_DIR }} + run: | + ninja -j ${{ steps.nproc.outputs.nproc }} xrpl.libpb + + - name: Run clang tidy + id: run_clang_tidy + continue-on-error: true + env: + TARGETS: ${{ (needs.determine-files.outputs.clang_tidy_config_changed != 'true' && inputs.check_only_changed) && needs.determine-files.outputs.cpp_changed_files || 'src tests' }} + run: | + set -o pipefail + run-clang-tidy -j ${{ steps.nproc.outputs.nproc }} -p "${BUILD_DIR}" -quiet -fix -allow-no-checks ${TARGETS} 2>&1 | tee "${OUTPUT_FILE}" + + - name: Print errors + if: ${{ steps.run_clang_tidy.outcome != 'success' }} + run: | + sed '/error\||/!d' "${OUTPUT_FILE}" + + - name: Upload clang-tidy output + if: ${{ github.event.repository.visibility == 'public' && steps.run_clang_tidy.outcome != 'success' }} + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + path: ${{ env.OUTPUT_FILE }} + archive: false + retention-days: 30 + + - name: Check for changes + id: files_changed + continue-on-error: true + run: | + git diff --exit-code + + - name: Fix style + if: ${{ steps.files_changed.outcome != 'success' }} + run: | + pre-commit run --all-files || true + + - name: Generate git diff + if: ${{ steps.files_changed.outcome != 'success' }} + run: | + git diff | tee "${DIFF_FILE}" + + - name: Upload clang-tidy diff output + if: ${{ github.event.repository.visibility == 'public' && steps.files_changed.outcome != 'success' }} + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + path: ${{ env.DIFF_FILE }} + archive: false + retention-days: 30 + + - name: Write issue header + if: ${{ steps.run_clang_tidy.outcome != 'success' }} + run: | + cat >"${ISSUE_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 + + # Truncate if too large + head -c 60000 filtered-output.txt >>"${ISSUE_FILE}" + if [ "$(wc -c >"${ISSUE_FILE}" + echo "... (output truncated, see artifacts for full output)" >>"${ISSUE_FILE}" + fi + + rm filtered-output.txt + else + echo "No output file found" >>"${ISSUE_FILE}" + fi + + - name: Append issue footer + if: ${{ steps.run_clang_tidy.outcome != 'success' }} + run: | + cat >>"${ISSUE_FILE}" <>"${GITHUB_OUTPUT}" + + generate-version: + runs-on: ubuntu-latest + outputs: + version: ${{ steps.version.outputs.version }} + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + sparse-checkout: | + .github/actions/generate-version + src/libxrpl/protocol/BuildInfo.cpp + - name: Generate version + id: version + uses: ./.github/actions/generate-version + + package: + needs: [generate-matrix, generate-version] + if: ${{ github.event.repository.visibility == 'public' }} + strategy: + fail-fast: false + matrix: ${{ fromJson(needs.generate-matrix.outputs.matrix) }} + name: "${{ matrix.artifact_name }}" + 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) }} + timeout-minutes: 30 + + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Download pre-built binary + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: ${{ matrix.artifact_name }} + path: ${{ env.BUILD_DIR }} + + - name: Make binary executable + run: chmod +x "${BUILD_DIR}/xrpld" + + - name: Build package + env: + PKG_VERSION: ${{ needs.generate-version.outputs.version }} + PKG_RELEASE: ${{ inputs.pkg_release }} + run: ./package/build_pkg.sh + + - name: Upload package artifact + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: ${{ matrix.artifact_name }}-pkg-${{ needs.generate-version.outputs.version }} + path: | + ${{ env.BUILD_DIR }}/debbuild/*.deb + ${{ env.BUILD_DIR }}/debbuild/*.ddeb + ${{ env.BUILD_DIR }}/rpmbuild/RPMS/**/*.rpm + if-no-files-found: error diff --git a/.github/workflows/reusable-strategy-matrix.yml b/.github/workflows/reusable-strategy-matrix.yml index b1232a138f..62d65ad3fa 100644 --- a/.github/workflows/reusable-strategy-matrix.yml +++ b/.github/workflows/reusable-strategy-matrix.yml @@ -42,4 +42,4 @@ jobs: 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}" + run: ./generate.py ${GENERATE_OPTION} ${GENERATE_CONFIG} >>"${GITHUB_OUTPUT}" diff --git a/.github/workflows/upload-conan-deps.yml b/.github/workflows/upload-conan-deps.yml index f14efde05b..34dce28334 100644 --- a/.github/workflows/upload-conan-deps.yml +++ b/.github/workflows/upload-conan-deps.yml @@ -75,7 +75,7 @@ jobs: enable_ccache: false - name: Print build environment - uses: ./.github/actions/print-env + uses: XRPLF/actions/print-build-env@59dec886e4afb05a1724443af08baccbc045b574 - name: Get number of processors uses: XRPLF/actions/get-nproc@cf0433aa74563aead044a1e395610c96d65a37cf diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1c0dc94550..c9dec89435 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -37,40 +37,57 @@ 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: (.config/cspell.config.yaml|^include/xrpl/protocol_autogen/(transactions|ledger_entries)/) + exclude: | + (?x)^( + .config/cspell.config.yaml| + include/xrpl/protocol_autogen/(transactions|ledger_entries)/.* + )$ - id: cspell # Spell check the commit message name: check commit message spelling args: diff --git a/API-CHANGELOG.md b/API-CHANGELOG.md index d5faaf70af..56a45c132a 100644 --- a/API-CHANGELOG.md +++ b/API-CHANGELOG.md @@ -28,6 +28,8 @@ This section contains changes targeting a future version. ### Additions +- `ledger_entry`, `account_objects`: The `Delegate` ledger entry now includes an optional `DestinationNode` field, which stores the index into the authorized account's owner directory. This field is present on entries created after bidirectional directory tracking was introduced and may appear in RPC responses for those entries. ([#6681](https://github.com/XRPLF/rippled/pull/6681)) + - `server_definitions`: Added the following new sections to the response ([#6321](https://github.com/XRPLF/rippled/pull/6321)): - `TRANSACTION_FORMATS`: Describes the fields and their optionality for each transaction type, including common fields shared across all transactions. - `LEDGER_ENTRY_FORMATS`: Describes the fields and their optionality for each ledger entry type, including common fields shared across all ledger entries. @@ -40,6 +42,14 @@ This section contains changes targeting a future version. - Peer Crawler: The `port` field in `overlay.active[]` now consistently returns an integer instead of a string for outbound peers. [#6318](https://github.com/XRPLF/rippled/pull/6318) - `ping`: The `ip` field is no longer returned as an empty string for proxied connections without a forwarded-for header. It is now omitted, consistent with the behavior for identified connections. [#6730](https://github.com/XRPLF/rippled/pull/6730) - gRPC `GetLedgerDiff`: Fixed error message that incorrectly said "base ledger not validated" when the desired ledger was not validated. [#6730](https://github.com/XRPLF/rippled/pull/6730) +- `account_channels`: The `destination_account` field now returns an error if the value is not a string. [#6529](https://github.com/XRPLF/rippled/pull/6529) +- `subscribe`: The `taker` field in the `books` array now returns an error if the value is not a string. [#6529](https://github.com/XRPLF/rippled/pull/6529) +- `account_info`: The `urlgravatar` field now uses HTTPS instead of HTTP. [#6529](https://github.com/XRPLF/rippled/pull/6529) +- `ledger`: The `full`, `accounts`, `transactions`, `expand`, `binary`, `owner_funds`, and `queue` fields now return an error if the value is not a boolean. [#6529](https://github.com/XRPLF/rippled/pull/6529) +- `ledger_data`: The `binary` field now returns an error if the value is not a boolean. [#6529](https://github.com/XRPLF/rippled/pull/6529) +- `submit`: The `fail_hard` field now returns an error if the value is not a boolean. [#6529](https://github.com/XRPLF/rippled/pull/6529) +- `subscribe`: The `taker` field in the `books` array now returns `actMalformed` instead of `badIssuer` if the value is not a valid account. [#6529](https://github.com/XRPLF/rippled/pull/6529) +- Fixed a bug in `Forwarded` HTTP header parsing where the extracted IP address could be incorrect when no comma or semicolon delimiter follows the address. This could cause the server to misidentify a client's IP address when operating behind a reverse proxy. [#6529](https://github.com/XRPLF/rippled/pull/6529) ## XRP Ledger server version 3.1.0 diff --git a/BUILD.md b/BUILD.md index cf0a685abd..1d3fc8f774 100644 --- a/BUILD.md +++ b/BUILD.md @@ -141,7 +141,7 @@ Alternatively, you can pull our recipes from the repository and export them loca ```bash # Define which recipes to export. -recipes=('abseil' 'ed25519' 'grpc' 'm4' 'mpt-crypto' 'openssl' 'secp256k1' 'snappy' 'soci' 'wasm-xrplf' 'wasmi') +recipes=('abseil' 'ed25519' 'mpt-crypto' 'openssl' 'secp256k1' 'snappy' 'soci' 'wasm-xrplf' 'wasmi') # Selectively check out the recipes from our CCI fork. cd external @@ -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: @@ -530,16 +533,16 @@ stored inside the build directory, as either of: ## Sanitizers To build dependencies and xrpld with sanitizer instrumentation, set the -`SANITIZERS` environment variable (only once before running conan and cmake) and use the `sanitizers` profile in conan: +`SANITIZERS` environment variable when running `conan install` and use the `sanitizers` profile: ```bash export SANITIZERS=address,undefinedbehavior conan install .. --output-folder . --profile:all sanitizers --build missing --settings build_type=Debug - -cmake -DCMAKE_TOOLCHAIN_FILE:FILEPATH=build/generators/conan_toolchain.cmake -DCMAKE_BUILD_TYPE=Debug -Dxrpld=ON -Dtests=ON .. ``` +You can then build and test as usual, with the generated `xrpld` binary containing the sanitizer instrumentation. When you run it, it will report any sanitizer errors it detects in the console output. + See [Sanitizers docs](./docs/build/sanitizers.md) for more details. ## Options diff --git a/CMakeLists.txt b/CMakeLists.txt index 26189b4981..7f877e07dd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -146,6 +146,7 @@ endif() include(XrplCore) include(XrplProtocolAutogen) include(XrplInstall) +include(XrplPackaging) include(XrplValidatorKeys) if(tests) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 56d3b48057..25dd7ac059 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -348,8 +348,8 @@ For this reason: - Contract description for `UNREACHABLE` should describe the _unexpected_ situation which caused the line to have been reached. - Example good name for an - `UNREACHABLE` macro `"Json::operator==(Value, Value) : invalid type"`; example - good name for an `XRPL_ASSERT` macro `"Json::Value::asCString : valid type"`. + `UNREACHABLE` macro `"json::operator==(Value, Value) : invalid type"`; example + good name for an `XRPL_ASSERT` macro `"json::Value::asCString : valid type"`. - Example **bad** name `"RFC1751::insert(char* s, int x, int start, int length) : length is greater than or equal zero"` (missing namespace, unnecessary full function signature, description too verbose). diff --git a/OpenTelemetryPlan/02-design-decisions.md b/OpenTelemetryPlan/02-design-decisions.md index a0269e6379..16b0161dd3 100644 --- a/OpenTelemetryPlan/02-design-decisions.md +++ b/OpenTelemetryPlan/02-design-decisions.md @@ -362,20 +362,20 @@ resource::SemanticConventions::SERVICE_INSTANCE_ID = The following table summarizes what data is collected by category: -| Category | Attributes Collected | Purpose | -| --------------- | ---------------------------------------------------------------------- | ---------------------------- | -| **Transaction** | `tx.hash`, `tx.type`, `tx.result`, `tx.fee`, `ledger_index` | Trace transaction lifecycle | -| **Consensus** | `round`, `phase`, `mode`, `proposers` (public keys), `duration_ms` | Analyze consensus timing | -| **RPC** | `command`, `version`, `status`, `duration_ms` | Monitor RPC performance | -| **Peer** | `peer.id` (public key), `latency_ms`, `message.type`, `message.size` | Network topology analysis | -| **Ledger** | `ledger.hash`, `ledger.index`, `close_time`, `tx_count` | Ledger progression tracking | -| **Job** | `job.type`, `queue_ms`, `worker` | JobQueue performance | -| **PathFinding** | `pathfind.source_currency`, `dest_currency`, `path_count`, `cache_hit` | Payment path analysis | -| **TxQ** | `txq.queue_depth`, `fee_level`, `eviction_reason` | Queue depth and fee tracking | -| **Fee** | `fee.load_factor`, `escalation_level` | Fee escalation monitoring | -| **Validator** | `validator.list_size`, `list_age_sec` | UNL health monitoring | -| **Amendment** | `amendment.name`, `status` | Protocol upgrade tracking | -| **SHAMap** | `shamap.type`, `missing_nodes`, `duration_ms` | State tree sync performance | +| Category | Attributes Collected | Purpose | +| --------------- | ---------------------------------------------------------------------------------------------------------------- | ---------------------------- | +| **Transaction** | `tx.hash`, `tx.type`, `tx.result`, `tx.fee`, `ledger_index` | Trace transaction lifecycle | +| **Consensus** | `round`, `phase`, `mode`, `proposers` (public keys), `duration_ms` | Analyze consensus timing | +| **RPC** | `command`, `version`, `status`, `duration_ms` | Monitor RPC performance | +| **Peer** | `peer.id` (public key), `latency_ms`, `message.type`, `message.size` | Network topology analysis | +| **Ledger** | `ledger.hash`, `ledger.index`, `close_time`, `tx_count` | Ledger progression tracking | +| **Job** | `job.type`, `queue_ms`, `worker` | JobQueue performance | +| **PathFinding** | `pathfind_fast`, `pathfind_search_level`, `pathfind_num_paths`, `pathfind_ledger_index`, `pathfind_num_requests` | Payment path analysis | +| **TxQ** | `txq.queue_depth`, `fee_level`, `eviction_reason` | Queue depth and fee tracking | +| **Fee** | `fee.load_factor`, `escalation_level` | Fee escalation monitoring | +| **Validator** | `validator.list_size`, `list_age_sec` | UNL health monitoring | +| **Amendment** | `amendment.name`, `status` | Protocol upgrade tracking | +| **SHAMap** | `shamap.type`, `missing_nodes`, `duration_ms` | State tree sync performance | ### 2.4.4 Privacy & Sensitive Data Policy @@ -449,6 +449,8 @@ redact_peer_address=1 # Remove peer IP addresses > **Key Principle**: Telemetry collects **operational metadata** (timing, counts, hashes) — never **sensitive content** (keys, balances, amounts, raw payloads). +> **See also**: [Securing the OTel Pipeline](./secure-OTel.md) covers transport-level protection for telemetry leaving the node — mTLS to the collector and validation of incoming peer trace context. Privacy controls in this section keep sensitive data out of spans; the security doc keeps the spans themselves out of untrusted hands. + --- ## 2.5 Context Propagation Design diff --git a/OpenTelemetryPlan/04-code-samples.md b/OpenTelemetryPlan/04-code-samples.md index d4d5c0bdc0..d242de1764 100644 --- a/OpenTelemetryPlan/04-code-samples.md +++ b/OpenTelemetryPlan/04-code-samples.md @@ -165,12 +165,12 @@ public: // Factory functions std::unique_ptr -make_Telemetry( +makeTelemetry( Telemetry::Setup const& setup, beast::Journal journal); Telemetry::Setup -setup_Telemetry( +setupTelemetry( Section const& section, std::string const& nodePublicKey, std::string const& version); diff --git a/OpenTelemetryPlan/05-configuration-reference.md b/OpenTelemetryPlan/05-configuration-reference.md index e32449b64a..b1d6bfaa8a 100644 --- a/OpenTelemetryPlan/05-configuration-reference.md +++ b/OpenTelemetryPlan/05-configuration-reference.md @@ -128,7 +128,7 @@ namespace xrpl { namespace telemetry { Telemetry::Setup -setup_Telemetry( +setupTelemetry( Section const& section, std::string const& nodePublicKey, std::string const& version) @@ -213,8 +213,8 @@ class ApplicationImp : public Application, public BasicApp // Member initializer list (excerpt): // ... // , telemetry_( - // telemetry::make_Telemetry( - // telemetry::setup_Telemetry( + // telemetry::makeTelemetry( + // telemetry::setupTelemetry( // config_->section("telemetry"), // "", // Updated later via setServiceInstanceId() // BuildInfo::getVersionString()), @@ -405,6 +405,8 @@ endif() > **OTLP** = OpenTelemetry Protocol | **APM** = Application Performance Monitoring +> **Production hardening**: The configurations in this section are starting points. For production deployments where xrpld ships telemetry across a network to a centrally-hosted collector, see [Securing the OTel Pipeline](./secure-OTel.md) for the required mTLS receiver config, NetworkPolicy, and peer trace-context validation. + ### 5.5.1 Development Configuration ```yaml @@ -627,8 +629,8 @@ flowchart TB end subgraph init["Initialization"] - parse["setup_Telemetry()"] - factory["make_Telemetry()"] + parse["setupTelemetry()"] + factory["makeTelemetry()"] end subgraph runtime["Runtime Components"] @@ -661,7 +663,7 @@ flowchart TB **Reading the diagram:** - **Configuration Sources**: `xrpld.cfg` provides runtime settings (endpoint, sampling) while the CMake flag controls whether telemetry is compiled in at all. -- **Initialization**: `setup_Telemetry()` parses config values, then `make_Telemetry()` constructs the provider, processor, and exporter objects. +- **Initialization**: `setupTelemetry()` parses config values, then `makeTelemetry()` constructs the provider, processor, and exporter objects. - **Runtime Components**: The `TracerProvider` creates spans, the `BatchProcessor` buffers them, and the `OTLP Exporter` serializes and sends them over the wire. - **OTLP arrow to Collector**: Trace data leaves the xrpld process via OTLP (gRPC or HTTP) and enters the external Collector pipeline. - **Collector Pipeline**: `Receivers` ingest OTLP data, `Processors` apply sampling/filtering/enrichment, and `Exporters` forward traces to storage backends (Tempo, etc.). diff --git a/OpenTelemetryPlan/07-observability-backends.md b/OpenTelemetryPlan/07-observability-backends.md index 94124a62fe..a1c303b545 100644 --- a/OpenTelemetryPlan/07-observability-backends.md +++ b/OpenTelemetryPlan/07-observability-backends.md @@ -19,10 +19,10 @@ ```bash # Start Tempo with OTLP support docker run -d --name tempo \ - -p 3200:3200 \ - -p 4317:4317 \ - -p 4318:4318 \ - grafana/tempo:2.6.1 + -p 3200:3200 \ + -p 4317:4317 \ + -p 4318:4318 \ + grafana/tempo:2.6.1 ``` --- diff --git a/OpenTelemetryPlan/08-appendix.md b/OpenTelemetryPlan/08-appendix.md index b5cfbc6ae8..4968317658 100644 --- a/OpenTelemetryPlan/08-appendix.md +++ b/OpenTelemetryPlan/08-appendix.md @@ -170,20 +170,21 @@ flowchart TB ### Plan Documents -| Document | Description | -| -------------------------------------------------------------------- | -------------------------------------------- | -| [OpenTelemetryPlan.md](./OpenTelemetryPlan.md) | Master overview and executive summary | -| [00-tracing-fundamentals.md](./00-tracing-fundamentals.md) | Distributed tracing concepts and OTel primer | -| [01-architecture-analysis.md](./01-architecture-analysis.md) | xrpld architecture and trace points | -| [02-design-decisions.md](./02-design-decisions.md) | SDK selection, exporters, span conventions | -| [03-implementation-strategy.md](./03-implementation-strategy.md) | Directory structure, performance analysis | -| [04-code-samples.md](./04-code-samples.md) | C++ code examples for all components | -| [05-configuration-reference.md](./05-configuration-reference.md) | xrpld config, CMake, Collector configs | -| [06-implementation-phases.md](./06-implementation-phases.md) | Timeline, tasks, risks, success metrics | -| [07-observability-backends.md](./07-observability-backends.md) | Backend selection and architecture | -| [08-appendix.md](./08-appendix.md) | Glossary, references, version history | -| [09-data-collection-reference.md](./09-data-collection-reference.md) | Span/metric/dashboard inventory | -| [presentation.md](./presentation.md) | Slide deck for OTel plan overview | +| Document | Description | +| -------------------------------------------------------------------- | -------------------------------------------------- | +| [OpenTelemetryPlan.md](./OpenTelemetryPlan.md) | Master overview and executive summary | +| [00-tracing-fundamentals.md](./00-tracing-fundamentals.md) | Distributed tracing concepts and OTel primer | +| [01-architecture-analysis.md](./01-architecture-analysis.md) | xrpld architecture and trace points | +| [02-design-decisions.md](./02-design-decisions.md) | SDK selection, exporters, span conventions | +| [03-implementation-strategy.md](./03-implementation-strategy.md) | Directory structure, performance analysis | +| [04-code-samples.md](./04-code-samples.md) | C++ code examples for all components | +| [05-configuration-reference.md](./05-configuration-reference.md) | xrpld config, CMake, Collector configs | +| [06-implementation-phases.md](./06-implementation-phases.md) | Timeline, tasks, risks, success metrics | +| [07-observability-backends.md](./07-observability-backends.md) | Backend selection and architecture | +| [08-appendix.md](./08-appendix.md) | Glossary, references, version history | +| [secure-OTel.md](./secure-OTel.md) | Threat model and hardening (mTLS, peer validation) | +| [09-data-collection-reference.md](./09-data-collection-reference.md) | Span/metric/dashboard inventory | +| [presentation.md](./presentation.md) | Slide deck for OTel plan overview | ### Task Lists diff --git a/OpenTelemetryPlan/OpenTelemetryPlan.md b/OpenTelemetryPlan/OpenTelemetryPlan.md index aa91133861..ceca27b5e0 100644 --- a/OpenTelemetryPlan/OpenTelemetryPlan.md +++ b/OpenTelemetryPlan/OpenTelemetryPlan.md @@ -54,6 +54,7 @@ flowchart TB phases["06-implementation-phases.md"] backends["07-observability-backends.md"] appendix["08-appendix.md"] + secure["secure-OTel.md"] poc["POC_taskList.md"] dataref["09-data-collection-reference.md"] end @@ -71,6 +72,7 @@ flowchart TB config --> phases phases --> backends backends --> appendix + backends --> secure phases --> poc appendix --> dataref @@ -88,6 +90,7 @@ flowchart TB style phases fill:#4a148c,stroke:#2e0d57,color:#fff style backends fill:#4a148c,stroke:#2e0d57,color:#fff style appendix fill:#4a148c,stroke:#2e0d57,color:#fff + style secure fill:#4a148c,stroke:#2e0d57,color:#fff style poc fill:#4a148c,stroke:#2e0d57,color:#fff style dataref fill:#4a148c,stroke:#2e0d57,color:#fff ``` @@ -110,6 +113,7 @@ flowchart TB | **7** | [Observability Backends](./07-observability-backends.md) | Backend selection guide and production architecture | | **8** | [Appendix](./08-appendix.md) | Glossary, references, version history | | **9** | [Data Collection Reference](./09-data-collection-reference.md) | Complete inventory of spans, attributes, metrics, and dashboards | +| **Sec** | [Securing the OTel Pipeline](./secure-OTel.md) | Threat model and hardening (mTLS, peer trace-context validation) | | **POC** | [POC Task List](./POC_taskList.md) | Proof of concept tasks for RPC tracing end-to-end demo | --- @@ -234,6 +238,14 @@ A single-source-of-truth reference documenting every piece of telemetry data col --- +## Securing the OTel Pipeline + +Threat model and hardening guidance for production deployments where xrpld nodes ship telemetry to a centrally-hosted collector across an untrusted network. Covers the two attack surfaces (collector ingress and peer trace-context spoofing) and the chosen defenses: mTLS as primary collector auth, NetworkPolicy as defense-in-depth, and source-side validation plus per-peer rate limiting for the `protocol::TraceContext` field on peer messages. + +➡️ **[View Securing the OTel Pipeline](./secure-OTel.md)** + +--- + ## POC Task List A step-by-step task list for building a minimal end-to-end proof of concept that demonstrates distributed tracing in xrpld. The POC scope is limited to RPC tracing — showing request traces flowing from xrpld through an OpenTelemetry Collector into Tempo, viewable in Grafana. diff --git a/OpenTelemetryPlan/POC_taskList.md b/OpenTelemetryPlan/POC_taskList.md index 5f93886200..112c0359fe 100644 --- a/OpenTelemetryPlan/POC_taskList.md +++ b/OpenTelemetryPlan/POC_taskList.md @@ -143,8 +143,8 @@ - `virtual bool shouldTraceRpc() const = 0;` - `virtual bool shouldTraceTransactions() const = 0;` - `virtual bool shouldTraceConsensus() const = 0;` - - Factory: `std::unique_ptr make_Telemetry(Setup const&, beast::Journal);` - - Config parser: `Telemetry::Setup setup_Telemetry(Section const&, std::string const& nodePublicKey, std::string const& version);` + - Factory: `std::unique_ptr makeTelemetry(Setup const&, beast::Journal);` + - Config parser: `Telemetry::Setup setupTelemetry(Section const&, std::string const& nodePublicKey, std::string const& version);` - Create `include/xrpl/telemetry/SpanGuard.h`: - RAII guard with static factory methods (`rpcSpan()`, `txSpan()`, `consensusSpan()`, etc.) that access the global `Telemetry::getInstance()` singleton internally. @@ -196,10 +196,10 @@ - `shouldTraceRpc()` etc. read from `Setup` fields - Create `src/libxrpl/telemetry/TelemetryConfig.cpp`: - - `setup_Telemetry()` parses the `[telemetry]` config section from `xrpld.cfg` + - `setupTelemetry()` parses the `[telemetry]` config section from `xrpld.cfg` - Maps config keys: `enabled`, `exporter`, `endpoint`, `sampling_ratio`, `trace_rpc`, `trace_transactions`, `trace_consensus`, `trace_peer` -- Wire `make_Telemetry()` factory: +- Wire `makeTelemetry()` factory: - If `setup.enabled` is true AND `XRPL_ENABLE_TELEMETRY` is defined: return `TelemetryImpl` - Otherwise: return `NullTelemetry` @@ -217,7 +217,7 @@ **Reference**: - [04-code-samples.md §4.1](./04-code-samples.md) — `Telemetry` interface that `TelemetryImpl` must implement -- [05-configuration-reference.md §5.2](./05-configuration-reference.md) — `setup_Telemetry()` config parser implementation +- [05-configuration-reference.md §5.2](./05-configuration-reference.md) — `setupTelemetry()` config parser implementation - [02-design-decisions.md §2.2](./02-design-decisions.md) — OTLP/gRPC exporter config (endpoint, TLS options) - [02-design-decisions.md §2.4.1](./02-design-decisions.md) — Resource attributes: `service.name`, `service.version`, `service.instance.id`, `xrpl.network.id` - [03-implementation-strategy.md §3.4](./03-implementation-strategy.md) — Per-operation CPU costs and overhead budget for span creation @@ -242,8 +242,8 @@ `serviceInstanceId` (node identity is not yet known): ```cpp , telemetry_( - telemetry::make_Telemetry( - telemetry::setup_Telemetry( + telemetry::makeTelemetry( + telemetry::setupTelemetry( config_->section("telemetry"), "", // Updated later via setServiceInstanceId() BuildInfo::getVersionString()), @@ -453,18 +453,18 @@ ```bash # server_info curl -s -X POST http://localhost:5005 \ - -H "Content-Type: application/json" \ - -d '{"method":"server_info","params":[{}]}' + -H "Content-Type: application/json" \ + -d '{"method":"server_info","params":[{}]}' # ledger curl -s -X POST http://localhost:5005 \ - -H "Content-Type: application/json" \ - -d '{"method":"ledger","params":[{"ledger_index":"current"}]}' + -H "Content-Type: application/json" \ + -d '{"method":"ledger","params":[{"ledger_index":"current"}]}' # account_info (will error in standalone, that's fine — we trace errors too) curl -s -X POST http://localhost:5005 \ - -H "Content-Type: application/json" \ - -d '{"method":"account_info","params":[{"account":"rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh"}]}' + -H "Content-Type: application/json" \ + -d '{"method":"account_info","params":[{"account":"rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh"}]}' ``` 6. **Verify in Grafana (Tempo)**: diff --git a/OpenTelemetryPlan/Phase2_taskList.md b/OpenTelemetryPlan/Phase2_taskList.md index eb9c1aaa07..f473617cf8 100644 --- a/OpenTelemetryPlan/Phase2_taskList.md +++ b/OpenTelemetryPlan/Phase2_taskList.md @@ -1,8 +1,8 @@ # Phase 2: RPC Tracing Completion Task List -> **Goal**: Complete RPC tracing coverage with unit tests, Grafana search filters, node health attributes, and config hardening. Build on the Phase 1c SpanGuard factory foundation to achieve production-quality RPC observability. +> **Goal**: Complete RPC tracing coverage with unit tests, Grafana search filters, PathFind instrumentation, and config hardening. Build on the Phase 1c SpanGuard factory foundation to achieve production-quality RPC observability. > -> **Scope**: Unit tests for core telemetry, Grafana Tempo search filters, node health span attributes, config validation (`std::clamp`). +> **Scope**: Unit tests for core telemetry, Grafana Tempo search filters, PathFind RPC tracing, config validation (`std::clamp`). > > **Branch**: `pratik/otel-phase2-rpc-tracing` (from `pratik/otel-phase1c-rpc-integration`) @@ -67,7 +67,7 @@ - `src/tests/libxrpl/telemetry/TelemetryConfig.cpp`: - Test Setup defaults (all fields have correct initial values) - - Test `setup_Telemetry` config parser (empty section, full section, edge cases) + - Test `setupTelemetry` config parser (empty section, full section, edge cases) - Test `samplingRatio` clamping (values outside 0.0-1.0) - `src/tests/libxrpl/telemetry/SpanGuardFactory.cpp`: @@ -121,42 +121,9 @@ These can be added later if dashboard queries specifically need them. The node h ## Task 2.8: RPC Span Attribute Enrichment — Node Health Context -> **Source**: [External Dashboard Parity](../docs/superpowers/specs/2026-03-30-external-dashboard-parity-design.md) — adds node-level health context inspired by the community [xrpl-validator-dashboard](https://github.com/realgrapedrop/xrpl-validator-dashboard). -> -> **Downstream**: Phase 7 (MetricsRegistry uses these attributes for alerting context), Phase 10 (validation checks for these attributes). +**Status**: DROPPED. -**Objective**: Add node-level health state to every `rpc.command.*` span so operators can correlate RPC behavior with node state in Tempo. - -**What to do**: - -- Edit `src/xrpld/rpc/detail/RPCHandler.cpp`: - - In the `rpc.command.*` span creation block (after existing `setAttribute` calls for `command`, `version`, etc.): - - Node health attrs (`xrpl.node.amendment_blocked`, `xrpl.node.server_state`) are now resource-level attrs, not per-span. They are set at Tracer init. - -**New span attributes**: - -| Attribute | Type | Source | Example | -| ----------------------------- | ------ | ------------------------------------------- | -------- | -| `xrpl.node.amendment_blocked` | bool | `context.app.getOPs().isAmendmentBlocked()` | `true` | -| `xrpl.node.server_state` | string | `context.app.getOPs().strOperatingMode()` | `"full"` | - -**Rationale**: When a node is amendment-blocked or in a degraded state, every RPC response is suspect. Tagging spans with this state enables Tempo TraceQL queries like: - -``` -{name=~"rpc.command.*"} | xrpl.node.amendment_blocked = true -``` - -This surfaces all RPCs served during a blocked period — critical for post-incident analysis. - -**Key modified files**: - -- `src/xrpld/rpc/detail/RPCHandler.cpp` - -**Exit Criteria**: - -- [ ] `rpc.command.server_info` spans carry `xrpl.node.amendment_blocked` and `xrpl.node.server_state` attributes -- [ ] No measurable latency impact (attribute values are cached atomics, not computed per-call) -- [ ] Attributes appear in Tempo trace detail view +Node health (`amendment_blocked`, `server_state`) is not part of the telemetry surface. Operators consume the same data via the existing `server_info` / `server_state` RPC commands, so duplicating it on traces adds storage and cardinality cost without new value. The OTel C++ SDK 1.18.0 also does not support runtime updates to the resource, ruling out resource-level emission of these dynamic-by-nature flags. --- @@ -169,10 +136,11 @@ This surfaces all RPCs served during a blocked period — critical for post-inci **Spans added**: - `pathfind.request` — wraps `doPathFind()` and `doRipplePathFind()` RPC handlers -- `pathfind.compute` — wraps `PathRequest::doUpdate()` (fast/normal attr) -- `pathfind.update_all` — wraps `PathRequestManager::updateAll()` on ledger close (ledger_index attr) -- `pathfind.discover` — wraps `Pathfinder::findPaths()` graph exploration (search_level attr) -- `pathfind.rank` — wraps `Pathfinder::computePathRanks()` liquidity validation (num_paths attr) +- `pathfind.compute` — wraps `PathRequest::doUpdate()` (`pathfind_fast` attr) +- `pathfind.update_all` — wraps `PathRequestManager::updateAll()` on ledger close (`pathfind_ledger_index`, `pathfind_num_requests` attrs; emitted only when active subscriptions exist) +- `pathfind.discover` — wraps the entire per-source-asset loop in `PathRequest::findPaths()` (`pathfind_search_level`, `pathfind_num_paths` attrs). One span per RPC call instead of N (one per source asset). Trade-off: per-asset breakdown is lost; storage and cardinality bounded. + +**Attribute namespacing**: All pathfind attributes use the `pathfind_*` underscore form per the Phase 1c naming-spec rule 5. **New file**: `src/xrpld/rpc/detail/PathFindSpanNames.h` @@ -197,11 +165,12 @@ This surfaces all RPCs served during a blocked period — critical for post-inci | 2.5 | Enhanced RPC span attributes (HTTP-level) | Deferred | Low value; span duration covers timing natively | | 2.6 | Build verification and performance baseline | Complete | Verified in CI on Phase 1c | | 2.7 | Grafana Tempo search filters | Complete | rpc-command, rpc-status, rpc-role filters | -| 2.8 | RPC span attribute enrichment (node health) | Complete | amendment_blocked + server_state | -| 2.9 | PathFind RPC instrumentation (5 spans) | Complete | request, compute, update_all, discover, rank | +| 2.8 | RPC span attribute enrichment (node health) | Dropped | Available via `server_info`/`server_state` RPC | +| 2.9 | PathFind RPC instrumentation | Complete | request, compute, update_all, discover | -**Delivered in this branch**: Tasks 2.4, 2.7, 2.8, 2.9. +**Delivered in this branch**: Tasks 2.4, 2.7, 2.9. **Deferred with rationale**: Tasks 2.1 (→Phase 3), 2.5 (low priority). +**Dropped**: Task 2.8 (node health not duplicated on traces). **Superseded**: Task 2.2 (Phase 1c SpanGuard factory covers this). --- diff --git a/OpenTelemetryPlan/Phase3_taskList.md b/OpenTelemetryPlan/Phase3_taskList.md index d0a7657bf2..f352a1f862 100644 --- a/OpenTelemetryPlan/Phase3_taskList.md +++ b/OpenTelemetryPlan/Phase3_taskList.md @@ -89,7 +89,7 @@ - In `onMessage(TMTransaction)` / `handleTransaction()`: - Extract parent trace context from incoming `TMTransaction::trace_context` field (if present) - Create `tx.receive` span as child of extracted context (or new root if none) - - Set attributes: `xrpl.tx.hash`, `xrpl.peer.id`, `tx_status` + - Set attributes: `tx_hash`, `peer_id`, `tx_status` - On HashRouter suppression (duplicate): set `suppressed=true`, add `tx.duplicate` event - Wrap validation call with child span `tx.validate` - Wrap relay with `tx.relay` span @@ -121,7 +121,7 @@ - Edit `src/xrpld/app/misc/NetworkOPs.cpp`: - In `processTransaction()`: - Create `tx.process` span - - Set attributes: `xrpl.tx.hash`, `tx_type`, `local` (whether from RPC or peer) + - Set attributes: `tx_hash`, `tx_type`, `local` (whether from RPC or peer) - Record whether sync or async path is taken - In `doTransactionAsync()`: @@ -256,7 +256,7 @@ **What to do**: - Edit `src/xrpld/overlay/detail/PeerImp.cpp`: - - In the `tx.receive` span block (after existing `xrpl.peer.id` setAttribute call): + - In the `tx.receive` span block (after existing `peer_id` setAttribute call): - Add `peer_version` (string) — from `this->getVersion()` - Only set if `getVersion()` returns a non-empty string (avoid empty-string attributes) diff --git a/OpenTelemetryPlan/Phase4_taskList.md b/OpenTelemetryPlan/Phase4_taskList.md index 6d084c5934..4c93f7f1ee 100644 --- a/OpenTelemetryPlan/Phase4_taskList.md +++ b/OpenTelemetryPlan/Phase4_taskList.md @@ -445,8 +445,8 @@ direct method calls, which is cleaner and avoids macro control-flow issues. // Actual usage in Consensus.h::updateOurPositions(): span.addEvent( "dispute.resolve", - {{cons_span::attr::txId, to_string(txId)}, - {cons_span::attr::disputeOurVote, dispute.getOurVote() ? "yes" : "no"}}); + {{consensus::span::attr::txId, to_string(txId)}, + {consensus::span::attr::disputeOurVote, dispute.getOurVote() ? "yes" : "no"}}); ``` 2. **Span link support** — implemented via `SpanGuard::linkedSpan()` static factory @@ -624,10 +624,10 @@ details. ```cpp span.addEvent( "dispute.resolve", - {{cons_span::attr::txId, to_string(txId)}, - {cons_span::attr::disputeOurVote, dispute.getOurVote() ? "yes" : "no"}, - {cons_span::attr::disputeYays, std::to_string(dispute.getYays())}, - {cons_span::attr::disputeNays, std::to_string(dispute.getNays())}}); + {{consensus::span::attr::txId, to_string(txId)}, + {consensus::span::attr::disputeOurVote, dispute.getOurVote() ? "yes" : "no"}, + {consensus::span::attr::disputeYays, std::to_string(dispute.getYays())}, + {consensus::span::attr::disputeNays, std::to_string(dispute.getNays())}}); ``` **Not implemented**: @@ -687,8 +687,8 @@ wrongLedger, switchedLedger). ```cpp auto span = telemetry::SpanGuard::span( telemetry::TraceCategory::Consensus, telemetry::seg::consensus, "mode_change"); - span.setAttribute(cons_span::attr::modeOld, to_string(before).c_str()); // "mode_old" - span.setAttribute(cons_span::attr::modeNew, to_string(after).c_str()); // "mode_new" + span.setAttribute(consensus::span::attr::modeOld, to_string(before).c_str()); // "mode_old" + span.setAttribute(consensus::span::attr::modeNew, to_string(after).c_str()); // "mode_new" ``` - `MonitoredMode::set()` in `Consensus.h` calls `adaptor_.onModeChange(before, after)`. diff --git a/OpenTelemetryPlan/secure-OTel.md b/OpenTelemetryPlan/secure-OTel.md new file mode 100644 index 0000000000..ad76b0b3b5 --- /dev/null +++ b/OpenTelemetryPlan/secure-OTel.md @@ -0,0 +1,243 @@ +# Securing OpenTelemetry Against Trace Context Spoofing + +> **Part of**: [OpenTelemetry Implementation Plan](./OpenTelemetryPlan.md) — see also [Design Decisions § Privacy](./02-design-decisions.md#244-privacy--sensitive-data-policy) (what we don't collect) and [Configuration Reference § 5.5](./05-configuration-reference.md#55-opentelemetry-collector-configuration) (collector base config). + +Trace context spoofing (or poisoning) occurs when untrusted actors inject tampered or stale trace IDs into your system. If these requests are processed, the spans are appended to historical trace buckets, stretching trace durations, ruining p99 latency metrics, and breaking Grafana dashboards. + +This guide outlines two categories of defense: mitigating tampered contexts and locking down the OpenTelemetry (OTel) Collector to trusted clients only. + +--- + +## Part 1: Mitigating Tampered Trace Contexts + +### 1. Perimeter Defense: Strip Headers at the API Gateway + +The most effective way to prevent spoofing from external sources is to treat your API Gateway (Envoy, NGINX, AWS ALB) as a hard boundary. Strip incoming W3C tracing headers (`traceparent`, `tracestate`) from public traffic so the gateway is forced to generate a fresh, legitimate `trace_id`. + +**NGINX Example (Stripping Headers):** + +Code output + +File generated successfully. + +```nginx +server { + listen 80; + + location { + # Clear out untrusted incoming trace headers + proxy_set_header traceparent ""; + proxy_set_header tracestate ""; + + proxy_pass http://backend_service; + } +} +``` + +### **2. Timestamp-Anchored Trace IDs and OTTL Filtering** + +If you use a custom trace ID generator that embeds a timestamp in the first few bytes (like AWS X-Ray or UUIDv7), you can use the OTel Collector's OpenTelemetry Transform Language (OTTL) to detect anomalies. +**Collector Configuration (Conceptual OTTL Filter):** + +```yaml +processors: + filter/stale_traces: + error_mode: ignore + traces: + span: + # Example: Drop spans where the start time is significantly different + # from an expected parameter or embedded timestamp logic. + # Note: Standard W3C trace IDs do not contain timestamps by default. + - 'Keep out-of-bounds spans: time.sub(start_time, now()) > duration("1h")' +``` + +## **Part 2: Restricting Access to the OTel Collector** + +Locking down the Collector ensures that only authenticated, trusted clients can submit telemetry data. + +### **Approach A: Network Layer Security (Kubernetes Network Policies)** + +Ensure your Collector is not exposed to the public internet. If running in Kubernetes, use a NetworkPolicy to restrict ingress traffic to specific namespaces. +**Kubernetes NetworkPolicy Example:** + +```yaml +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-internal-otel + namespace: observability +spec: + podSelector: + matchLabels: + app: opentelemetry-collector + policyTypes: + - Ingress + ingress: + - from: + - namespaceSelector: + matchLabels: + environment: production + ports: + - protocol: TCP + port: 4317 # gRPC + - protocol: TCP + port: 4318 # HTTP +``` + +### **Approach B: Transport Layer Security (Mutual TLS / mTLS)** + +Require clients to present a valid cryptographic certificate to connect to the Collector. +**Collector Configuration (mTLS):** + +```yaml +receivers: + otlp: + protocols: + grpc: + endpoint: 0.0.0.0:4317 + tls: + client_ca_file: /certs/client_ca.pem # CA that signs trusted client certs + cert_file: /certs/collector.pem + key_file: /certs/collector.key + auth_type: require_and_verify_client_cert # Rejects unauthorized clients +``` + +### **Approach C: Application Layer Authentication (Basic Auth Extension)** + +Use the Collector's extension system to require an API key or Basic Auth credentials. +**Collector Configuration (Basic Auth):** + +```yaml +extensions: + basicauth/collector: + htpasswd: + inline: | + # username:trusted-client, password:SecurePassword123 + trusted-client:$apr1$4v8p76o6$DMTX5Wv6uOmrFAZp2X1N1. + +receivers: + otlp: + protocols: + grpc: + endpoint: 0.0.0.0:4317 + auth: + authenticator: basicauth/collector + +processors: + batch: + +exporters: + otlp: + endpoint: my-backend-storage:4317 + +service: + extensions: [basicauth/collector] + pipelines: + traces: + receivers: [otlp] + processors: [batch] + exporters: [otlp] +``` + +**Client Setup (Environment Variables):** +Developers must pass the authentication header using the standard OTel SDK environment variables: + +```bash +# Base64 encoded "trusted-client:SecurePassword123" +export OTEL_EXPORTER_OTLP_HEADERS="Authorization=Basic dHJ1c3RlZC1jbGllbnQ6U2VjdXJlUGFzc3dvcmQxMjM=" +``` + +--- + +Available routes to build on top of: https://github.com/XRPLF/rippled/pull/6425#discussion_r3234751995 + +--- + +# Analysis: Applying the Guide to xrpld + +The guide above is written for HTTP-fronted web services. xrpld is a P2P node daemon, so the threat model and the applicable defenses differ. This section captures how each approach maps to xrpld and the chosen direction. + +## Threat Model + +xrpld has **two distinct attack surfaces**, not one. The original guide conflates them under "trace context spoofing"; for xrpld they need separate defenses. + +| Surface | Attacker | Vector | Defense | +| ----------------------------------------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------- | +| **Collector ingress** (xrpld → collector) | Anyone who can reach `4317`/`4318` on the collector host | Forged OTLP traffic, telemetry exfiltration, DoS on collector | mTLS + network policy | +| **Peer trace context** (peer → xrpld) | Malicious peer in the XRPL overlay | Crafted `protocol::TraceContext` field inside peer protobuf messages (TMTransaction, consensus, etc.) — used to forge `trace_id`/`span_id`, pollute p99, attach spans to historical traces | Validate + rate-limit at the receive boundary | + +**Deployment context:** Across-network. xrpld nodes (potentially run by external operators or in different DCs) ship telemetry to a centrally-hosted collector across an untrusted network. The collector is NOT on the same host or private VPC as every node. + +``` + ┌── peer (untrusted) ── TMTransaction{trace_context} ──▶ xrpld + │ │ + │ [validate + rate-limit] + │ │ + │ ▼ + │ SpanGuard (clean) + │ │ + │ │ OTLP/gRPC + │ │ + mTLS + │ ▼ + └───────────────────────────────────────── [require_and_verify_client_cert] + OTel Collector + (in private subnet, NetPol) +``` + +## Part 1 Applicability — Peer Trace-Context Validation + +The guide's NGINX header stripping and OTTL stale-span filtering target HTTP gateways and post-hoc cleanup. Neither fits xrpld directly: + +- **NGINX header stripping** — N/A. There is no HTTP gateway between peers and xrpld; trace context arrives inside protobuf peer messages (`protocol::TraceContext`), not as W3C `traceparent` headers. See [src/xrpld/telemetry/PropagationHelpers.h](../src/xrpld/telemetry/PropagationHelpers.h). +- **OTTL stale-span filtering** — Weak fit. Post-hoc cleanup at the collector loses peer identity (you can't tell _which_ peer poisoned the trace). Validation at the receive site is stronger. + +**xrpld-specific Part 1 mitigations:** + +1. **Validate extracted context at the boundary** in [src/xrpld/telemetry/ConsensusReceiveTracing.h](../src/xrpld/telemetry/ConsensusReceiveTracing.h) and any other peer-message receive site. Reject if `trace_id` is all-zero, wrong length, or fails W3C format checks. Treat invalid context as "no propagated context" — start a fresh span — rather than dropping the message. +2. **Per-peer sample rate limiting** so a hostile peer cannot flood the collector with spans bearing a fabricated `trace_id`. Use probabilistic sampling on the receive path keyed by peer identity. + +## Part 2 — Comparison of Collector Hardening Approaches + +Evaluated for the across-network deployment shape: + +| Approach | Across-network fit | Cost | Verdict | +| ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- | ---------------------------------- | +| **A. NetworkPolicy / firewall** | Necessary baseline (don't expose `4317`/`4318` to the internet), but insufficient on its own when traffic genuinely crosses networks — you cannot NetworkPolicy the public internet. | Cheap. | **Defense-in-depth, not primary.** | +| **B. mTLS** | Strongest fit. Every xrpld node holds a client cert; collector verifies with `require_and_verify_client_cert`. Encrypts in transit (raw OTLP over the internet leaks transaction patterns and validator identity). Compromised node = revoke one cert, no shared secret to rotate everywhere. | Cert issuance + rotation pipeline. | **Primary.** | +| **C. Basic Auth** | Worst shape for this topology. Single shared password across all xrpld nodes — one leaked node config compromises the whole fleet. Doesn't encrypt; you'd need TLS underneath anyway, at which point you're 80% of the way to mTLS. | Cheap to set up, expensive to operate (rotation across N operators). | **Skip.** | + +## Decision + +**Primary defense:** mTLS (Approach B) on the collector's OTLP receivers, with `auth_type: require_and_verify_client_cert`. + +**Defense-in-depth:** NetworkPolicy / firewall rules (Approach A) so `4317`/`4318` are never reachable from outside the expected operator subnets even if mTLS were misconfigured. + +**Skipped:** Basic Auth (Approach C) — wrong shape for an across-network, multi-operator topology. + +**Plus xrpld-specific Part 1 work:** trace-context validation and per-peer rate limiting at peer-message receive sites. + +## Decisions Made + +| Decision | Choice | Rationale | +| -------------------- | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Cert source for mTLS | **Reuse XRPL node identity key** | One identity per node, no separate PKI to operate. Fits XRPL's existing trust model; requires small CA tooling step to derive/sign the OTel client cert from the node key. | +| Part 1 scope | **Include in this spec** | Collector hardening and peer trace-context validation share one threat model. Coherent design doc; can still be split into multiple PRs at implementation. | +| Dev impact | **Production-only** | Local `docker/telemetry/docker-compose.yml` keeps `insecure: true` and no auth for fast iteration. Only production deployment manifests gain mTLS. Accepted risk: minor dev/prod drift, mitigated by integration tests against a TLS-enabled collector in CI. | + +## Out of Scope + +- NGINX/Envoy header stripping (no HTTP gateway in front of xrpld-to-collector traffic). +- OTTL stale-span filtering at the collector (weaker than source validation; loses peer identity). +- Local development docker-compose hardening. +- Telemetry backend (Tempo) hardening — separate concern, downstream of the collector. + +## Next Step + +Write this up as a design doc with full sections covering: + +1. Threat model & architecture (this section, expanded) +2. Collector hardening — mTLS config, NetworkPolicy +3. Cert pipeline — deriving OTel client cert from XRPL node key +4. Peer trace-context validation — receive-site checks in `ConsensusReceiveTracing.h` +5. Per-peer span rate limiting +6. Testing & rollout diff --git a/bin/git/setup-upstreams.sh b/bin/git/setup-upstreams.sh index 57c3f935f9..97c84f5507 100755 --- a/bin/git/setup-upstreams.sh +++ b/bin/git/setup-upstreams.sh @@ -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 Where 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 diff --git a/bin/git/squash-branches.sh b/bin/git/squash-branches.sh index eb4aefe23c..ba63f9c148 100755 --- a/bin/git/squash-branches.sh +++ b/bin/git/squash-branches.sh @@ -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 </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 < +# +# If set to 0, the server will skip validation of endpoint +# addresses received in TMEndpoints peer protocol messages, +# allowing addresses that are not publicly routable or have a +# port of 0. The default is 1 (verification enabled). +# +# WARNING: Disabling this option is a security risk and should +# only be used for local testing and debugging. Do not disable +# on mainnet. +# # # [transaction_queue] EXPERIMENTAL # @@ -1258,7 +1269,7 @@ # default. Don't change this without understanding the consequences. # # Example: -# account_reserve = 10000000 # 10 XRP +# account_reserve = 1000000 # 1 XRP # # owner_reserve = # @@ -1270,7 +1281,7 @@ # default. Don't change this without understanding the consequences. # # Example: -# owner_reserve = 2000000 # 2 XRP +# owner_reserve = 200000 # 0.2 XRP # #------------------------------------------------------------------------------- # @@ -1455,10 +1466,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 = :: diff --git a/cmake/XrplInterface.cmake b/cmake/XrplInterface.cmake index 7add613f5a..825cb63310 100644 --- a/cmake/XrplInterface.cmake +++ b/cmake/XrplInterface.cmake @@ -23,7 +23,6 @@ target_compile_definitions( BOOST_FILESYSTEM_NO_DEPRECATED > $<$>: - BOOST_COROUTINES2_NO_DEPRECATION_WARNING BOOST_BEAST_ALLOW_DEPRECATED BOOST_FILESYSTEM_DEPRECATED > diff --git a/cmake/XrplPackaging.cmake b/cmake/XrplPackaging.cmake new file mode 100644 index 0000000000..fe885c200c --- /dev/null +++ b/cmake/XrplPackaging.cmake @@ -0,0 +1,44 @@ +#[===================================================================[ + Linux packaging support: 'package' target. + + The packaging script (package/build_pkg.sh) installs to FHS-standard + paths (/usr/bin, /etc/xrpld, etc.) regardless of CMAKE_INSTALL_PREFIX, + so no prefix guard is needed here. +#]===================================================================] +if(NOT is_linux) + message(STATUS "Packaging not supported on non-Linux hosts") + return() +endif() + +if(NOT DEFINED pkg_release) + set(pkg_release 1) +endif() + +find_program(RPMBUILD_EXECUTABLE rpmbuild) +find_program(DPKG_BUILDPACKAGE_EXECUTABLE dpkg-buildpackage) + +if(NOT (RPMBUILD_EXECUTABLE OR DPKG_BUILDPACKAGE_EXECUTABLE)) + message( + STATUS + "Neither rpmbuild nor dpkg-buildpackage found; 'package' target not available" + ) + return() +endif() + +set(package_env + SRC_DIR=${CMAKE_SOURCE_DIR} + BUILD_DIR=${CMAKE_BINARY_DIR} + PKG_VERSION=${xrpld_version} + PKG_RELEASE=${pkg_release} +) + +add_custom_target( + package + COMMAND + ${CMAKE_COMMAND} -E env ${package_env} + ${CMAKE_SOURCE_DIR}/package/build_pkg.sh + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + DEPENDS xrpld + COMMENT "Building Linux package (deb/rpm inferred from host tooling)" + VERBATIM +) diff --git a/cmake/XrplSanitizers.cmake b/cmake/XrplSanitizers.cmake index f9630f6856..64f1841bfb 100644 --- a/cmake/XrplSanitizers.cmake +++ b/cmake/XrplSanitizers.cmake @@ -1,138 +1,33 @@ #[===================================================================[ - Configure sanitizers based on environment variables. + Apply sanitizer flags built by the Conan profile. - This module reads the following environment variables: - - SANITIZERS: The sanitizers to enable. Possible values: - - "address" - - "address,undefinedbehavior" - - "thread" - - "thread,undefinedbehavior" - - "undefinedbehavior" + Parsing, validation, and flag construction are performed in conan/profiles/sanitizers. + This module reads the following CMake variables injected by the Conan toolchain via extra_variables: - The compiler type and platform are detected in CompilationEnv.cmake. - The sanitizer compile options are applied to the 'common' interface library - which is linked to all targets in the project. + - SANITIZERS: The active sanitizers (e.g. "address,undefinedbehavior"). + - SANITIZERS_COMPILER_FLAGS: Space-separated compiler flags. + - SANITIZERS_LINKER_FLAGS: Space-separated linker flags. - Internal flag variables set by this module: - - - SANITIZER_TYPES: List of sanitizer types to enable (e.g., "address", - "thread", "undefined"). And two more flags for undefined behavior sanitizer (e.g., "float-divide-by-zero", "unsigned-integer-overflow"). - This list is joined with commas and passed to -fsanitize=. - - - SANITIZERS_COMPILE_FLAGS: Compiler flags for sanitizer instrumentation. - Includes: - * -fno-omit-frame-pointer: Preserves frame pointers for stack traces - * -O1: Minimum optimization for reasonable performance - * -fsanitize=: Enables sanitizer instrumentation - * -fsanitize-ignorelist=: (Clang only) Compile-time ignorelist - * -mcmodel=large/medium: (GCC only) Code model for large binaries - * -Wno-stringop-overflow: (GCC only) Suppresses false positive warnings - * -Wno-tsan: (For GCC TSAN combination only) Suppresses atomic_thread_fence warnings - - - SANITIZERS_LINK_FLAGS: Linker flags for sanitizer runtime libraries. - Includes: - * -fsanitize=: Links sanitizer runtime libraries - * -mcmodel=large/medium: (GCC only) Matches compile-time code model - - - SANITIZERS_RELOCATION_FLAGS: (GCC only) Code model flags for linking. - Used to handle large instrumented binaries on x86_64: - * -mcmodel=large: For AddressSanitizer (prevents relocation errors) - * -mcmodel=medium: For ThreadSanitizer (large model is incompatible) + The flags are applied to the 'common' interface library which is linked to all targets in the project. #]===================================================================] +include_guard(GLOBAL) include(CompilationEnv) -# Read environment variable -set(SANITIZERS "") -if(DEFINED ENV{SANITIZERS}) - set(SANITIZERS "$ENV{SANITIZERS}") -endif() - -# Set SANITIZERS_ENABLED flag for use in other modules -if(SANITIZERS MATCHES "address|thread|undefinedbehavior") - set(SANITIZERS_ENABLED TRUE) -else() +if(NOT DEFINED SANITIZERS) set(SANITIZERS_ENABLED FALSE) return() endif() +set(SANITIZERS_ENABLED TRUE) -# Sanitizers are not supported on Windows/MSVC -if(is_msvc) - message( - FATAL_ERROR - "Sanitizers are not supported on Windows/MSVC. " - "Please unset the SANITIZERS environment variable." - ) -endif() +message(STATUS "=== Configuring Sanitizers ===") +message(STATUS " SANITIZERS: ${SANITIZERS}") +message(STATUS " Compile flags: ${SANITIZERS_COMPILER_FLAGS}") +message(STATUS " Link flags: ${SANITIZERS_LINKER_FLAGS}") -message(STATUS "Configuring sanitizers: ${SANITIZERS}") - -# Parse SANITIZERS value to determine which sanitizers to enable -set(enable_asan FALSE) -set(enable_tsan FALSE) -set(enable_ubsan FALSE) - -# Normalize SANITIZERS into a list -set(san_list "${SANITIZERS}") -string(REPLACE "," ";" san_list "${san_list}") -separate_arguments(san_list) - -foreach(san IN LISTS san_list) - if(san STREQUAL "address") - set(enable_asan TRUE) - elseif(san STREQUAL "thread") - set(enable_tsan TRUE) - elseif(san STREQUAL "undefinedbehavior") - set(enable_ubsan TRUE) - else() - message( - FATAL_ERROR - "Unsupported sanitizer type: ${san}" - "Supported: address, thread, undefinedbehavior and their combinations." - ) - endif() -endforeach() - -# Validate sanitizer compatibility -if(enable_asan AND enable_tsan) - message( - FATAL_ERROR - "AddressSanitizer and ThreadSanitizer are incompatible and cannot be enabled simultaneously. " - "Use 'address' or 'thread', optionally with 'undefinedbehavior'." - ) -endif() - -# Frame pointer is required for meaningful stack traces. Sanitizers recommend minimum of -O1 for reasonable performance -set(SANITIZERS_COMPILE_FLAGS "-fno-omit-frame-pointer" "-O1") - -# Build the sanitizer flags list -set(SANITIZER_TYPES) - -if(enable_asan) - list(APPEND SANITIZER_TYPES "address") -elseif(enable_tsan) - list(APPEND SANITIZER_TYPES "thread") -endif() - -if(enable_ubsan) - # UB sanitizer flags - list(APPEND SANITIZER_TYPES "undefined" "float-divide-by-zero") - if(is_clang) - # Clang supports additional UB checks. More info here - # https://clang.llvm.org/docs/UndefinedBehaviorSanitizer.html - list(APPEND SANITIZER_TYPES "unsigned-integer-overflow") - endif() -endif() - -# Configure code model for GCC on amd64 Use large code model for ASAN to avoid relocation errors Use medium code model -# for TSAN (large is not compatible with TSAN) -set(SANITIZERS_RELOCATION_FLAGS) - -# Compiler-specific configuration +# GCC with sanitizers is incompatible with mold, gold, and lld linkers. +# Namely, the instrumented binary exceeds size limits imposed by these linkers. if(is_gcc) - # Disable mold, gold and lld linkers for GCC with sanitizers Use default linker (bfd/ld) which is more lenient with - # mixed code models This is needed since the size of instrumented binary exceeds the limits set by mold, lld and - # gold linkers set(use_mold OFF CACHE BOOL "Use mold linker" FORCE) set(use_gold OFF CACHE BOOL "Use gold linker" FORCE) set(use_lld OFF CACHE BOOL "Use lld linker" FORCE) @@ -140,80 +35,62 @@ if(is_gcc) STATUS " Disabled mold, gold, and lld linkers for GCC with sanitizers" ) - - # Suppress false positive warnings in GCC with stringop-overflow - list(APPEND SANITIZERS_COMPILE_FLAGS "-Wno-stringop-overflow") - - if(is_amd64 AND enable_asan) - message(STATUS " Using large code model (-mcmodel=large)") - list(APPEND SANITIZERS_COMPILE_FLAGS "-mcmodel=large") - list(APPEND SANITIZERS_RELOCATION_FLAGS "-mcmodel=large") - elseif(enable_tsan) - # GCC doesn't support atomic_thread_fence with tsan. Suppress warnings. - list(APPEND SANITIZERS_COMPILE_FLAGS "-Wno-tsan") - message(STATUS " Using medium code model (-mcmodel=medium)") - list(APPEND SANITIZERS_COMPILE_FLAGS "-mcmodel=medium") - list(APPEND SANITIZERS_RELOCATION_FLAGS "-mcmodel=medium") - endif() - - # Join sanitizer flags with commas for -fsanitize option - list(JOIN SANITIZER_TYPES "," SANITIZER_TYPES_STR) - - # Add sanitizer to compile and link flags - list(APPEND SANITIZERS_COMPILE_FLAGS "-fsanitize=${SANITIZER_TYPES_STR}") - set(SANITIZERS_LINK_FLAGS - "${SANITIZERS_RELOCATION_FLAGS}" - "-fsanitize=${SANITIZER_TYPES_STR}" - ) -elseif(is_clang) - # Add ignorelist for Clang (GCC doesn't support this) Use CMAKE_SOURCE_DIR to get the path to the ignorelist - set(IGNORELIST_PATH - "${CMAKE_SOURCE_DIR}/sanitizers/suppressions/sanitizer-ignorelist.txt" - ) - if(NOT EXISTS "${IGNORELIST_PATH}") - message( - FATAL_ERROR - "Sanitizer ignorelist not found: ${IGNORELIST_PATH}" - ) - endif() - - list( - APPEND SANITIZERS_COMPILE_FLAGS - "-fsanitize-ignorelist=${IGNORELIST_PATH}" - ) - message(STATUS " Using sanitizer ignorelist: ${IGNORELIST_PATH}") - - # Join sanitizer flags with commas for -fsanitize option - list(JOIN SANITIZER_TYPES "," SANITIZER_TYPES_STR) - - # Add sanitizer to compile and link flags - list(APPEND SANITIZERS_COMPILE_FLAGS "-fsanitize=${SANITIZER_TYPES_STR}") - set(SANITIZERS_LINK_FLAGS "-fsanitize=${SANITIZER_TYPES_STR}") endif() -message(STATUS " Compile flags: ${SANITIZERS_COMPILE_FLAGS}") -message(STATUS " Link flags: ${SANITIZERS_LINK_FLAGS}") +# Flags arrive as space-separated strings; split into CMake lists before use +separate_arguments( + sanitizers_compiler_flags + UNIX_COMMAND + "${SANITIZERS_COMPILER_FLAGS}" +) +separate_arguments( + sanitizers_linker_flags + UNIX_COMMAND + "${SANITIZERS_LINKER_FLAGS}" +) -# Apply the sanitizer flags to the 'common' interface library This is the same library used by XrplCompiler.cmake target_compile_options( common INTERFACE - $<$:${SANITIZERS_COMPILE_FLAGS}> - $<$:${SANITIZERS_COMPILE_FLAGS}> + $<$:${sanitizers_compiler_flags}> + $<$:${sanitizers_compiler_flags}> ) +target_link_options(common INTERFACE ${sanitizers_linker_flags}) -# Apply linker flags -target_link_options(common INTERFACE ${SANITIZERS_LINK_FLAGS}) +# This module appends -fsanitize-ignorelist= for Clang builds. +# The ignorelist path contains CMAKE_SOURCE_DIR, so it must be set here, rather than in the Conan profile. +# GCC does not support -fsanitize-ignorelist. +if(is_clang) + set(ignorelist_path + "${CMAKE_SOURCE_DIR}/sanitizers/suppressions/sanitizer-ignorelist.txt" + ) + if(NOT EXISTS "${ignorelist_path}") + message( + FATAL_ERROR + "Sanitizer ignorelist not found: ${ignorelist_path}" + ) + endif() + target_compile_options( + common + INTERFACE + $<$:-fsanitize-ignorelist=${ignorelist_path}> + $<$:-fsanitize-ignorelist=${ignorelist_path}> + ) + message(STATUS " Ignorelist: ${ignorelist_path}") +endif() # Define SANITIZERS macro for BuildInfo.cpp set(sanitizers_list) -if(enable_asan) +if(SANITIZERS MATCHES "address") + set(enable_asan ON) list(APPEND sanitizers_list "ASAN") endif() -if(enable_tsan) +if(SANITIZERS MATCHES "thread") + set(enable_tsan ON) list(APPEND sanitizers_list "TSAN") endif() -if(enable_ubsan) +if(SANITIZERS MATCHES "undefinedbehavior") + set(enable_ubsan ON) list(APPEND sanitizers_list "UBSAN") endif() diff --git a/cmake/scripts/codegen/macro_parser_common.py b/cmake/scripts/codegen/macro_parser_common.py index eae4df39db..40cd9f50c5 100644 --- a/cmake/scripts/codegen/macro_parser_common.py +++ b/cmake/scripts/codegen/macro_parser_common.py @@ -152,10 +152,10 @@ def parse_sfields_macro(sfields_path): def create_field_list_parser(): """Create a pyparsing parser for field lists like '({...})'.""" - # A field identifier (e.g., sfDestination, soeREQUIRED, soeMPTSupported) + # A field identifier (e.g., sfDestination, SoeRequired, SoeMptSupported) field_identifier = pp.Word(pp.alphas + "_", pp.alphanums + "_") - # A single field definition: {sfName, soeREQUIRED, ...} + # A single field definition: {sfName, SoeRequired, ...} # Allow optional trailing comma inside the braces field_def = ( pp.Suppress("{") @@ -185,8 +185,8 @@ def parse_field_list(fields_str): Args: fields_str: A string like '({ - {sfDestination, soeREQUIRED}, - {sfAmount, soeREQUIRED, soeMPTSupported} + {sfDestination, SoeRequired}, + {sfAmount, SoeRequired, SoeMptSupported} })' Returns: @@ -205,7 +205,7 @@ def parse_field_list(fields_str): field_name = field_parts[0] requirement = field_parts[1] flags = list(field_parts[2:]) if len(field_parts) > 2 else [] - supports_mpt = "soeMPTSupported" in flags + supports_mpt = "SoeMptSupported" in flags fields.append( { diff --git a/cmake/scripts/codegen/requirements.in b/cmake/scripts/codegen/requirements.in new file mode 100644 index 0000000000..d799fd60fd --- /dev/null +++ b/cmake/scripts/codegen/requirements.in @@ -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 diff --git a/cmake/scripts/codegen/requirements.txt b/cmake/scripts/codegen/requirements.txt index 40b472078d..ff37548c7b 100644 --- a/cmake/scripts/codegen/requirements.txt +++ b/cmake/scripts/codegen/requirements.txt @@ -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.0 +# 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 diff --git a/cmake/scripts/codegen/templates/LedgerEntry.h.mako b/cmake/scripts/codegen/templates/LedgerEntry.h.mako index fdb55a973a..31029cd311 100644 --- a/cmake/scripts/codegen/templates/LedgerEntry.h.mako +++ b/cmake/scripts/codegen/templates/LedgerEntry.h.mako @@ -52,13 +52,13 @@ public: % if field.get('mpt_support'): * MPT Support: ${field['mpt_support']} % endif -% if field['requirement'] == 'soeREQUIRED': +% if field['requirement'] == 'SoeRequired': * @return The field value. % else: * @return The field value, or std::nullopt if not present. % endif */ -% if field['requirement'] == 'soeREQUIRED': +% if field['requirement'] == 'SoeRequired': [[nodiscard]] ${field['typeData']['return_type']} get${field['name'][2:]}() const @@ -94,13 +94,13 @@ public: * MPT Support: ${field['mpt_support']} % endif * @note This is an untyped field (${field.get('cppType', 'unknown')}). -% if field['requirement'] == 'soeREQUIRED': +% if field['requirement'] == 'SoeRequired': * @return The field value. % else: * @return The field value, or std::nullopt if not present. % endif */ -% if field['requirement'] == 'soeREQUIRED': +% if field['requirement'] == 'SoeRequired': [[nodiscard]] ${field['typeData']['return_type']} get${field['name'][2:]}() const @@ -133,13 +133,13 @@ public: }; <% - required_fields = [f for f in fields if f['requirement'] == 'soeREQUIRED'] + required_fields = [f for f in fields if f['requirement'] == 'SoeRequired'] %>\ /** * @brief Builder for ${name} ledger entries. * * Provides a fluent interface for constructing ledger entries with method chaining. - * Uses Json::Value internally for flexible ledger entry construction. + * Uses STObject internally for flexible ledger entry construction. * Inherits common field setters from LedgerEntryBuilderBase. */ class ${name}Builder : public LedgerEntryBuilderBase<${name}Builder> diff --git a/cmake/scripts/codegen/templates/LedgerEntryTests.cpp.mako b/cmake/scripts/codegen/templates/LedgerEntryTests.cpp.mako index 35ce57f17b..011cf933dd 100644 --- a/cmake/scripts/codegen/templates/LedgerEntryTests.cpp.mako +++ b/cmake/scripts/codegen/templates/LedgerEntryTests.cpp.mako @@ -1,7 +1,7 @@ // Auto-generated unit tests for ledger entry ${name} <% - required_fields = [f for f in fields if f["requirement"] == "soeREQUIRED"] - optional_fields = [f for f in fields if f["requirement"] != "soeREQUIRED"] + required_fields = [f for f in fields if f["requirement"] == "SoeRequired"] + optional_fields = [f for f in fields if f["requirement"] != "SoeRequired"] def canonical_expr(field): return f"canonical_{field['stiSuffix']}()" diff --git a/cmake/scripts/codegen/templates/Transaction.h.mako b/cmake/scripts/codegen/templates/Transaction.h.mako index 62c51a5c97..d3b303d9d6 100644 --- a/cmake/scripts/codegen/templates/Transaction.h.mako +++ b/cmake/scripts/codegen/templates/Transaction.h.mako @@ -54,13 +54,13 @@ public: % if field.get('supports_mpt'): * @note This field supports MPT (Multi-Purpose Token) amounts. % endif -% if field['requirement'] == 'soeREQUIRED': +% if field['requirement'] == 'SoeRequired': * @return The field value. % else: * @return The field value, or std::nullopt if not present. % endif */ -% if field['requirement'] == 'soeREQUIRED': +% if field['requirement'] == 'SoeRequired': [[nodiscard]] ${field['typeData']['return_type']} get${field['name'][2:]}() const @@ -97,13 +97,13 @@ public: * @note This field supports MPT (Multi-Purpose Token) amounts. % endif * @note This is an untyped field. -% if field['requirement'] == 'soeREQUIRED': +% if field['requirement'] == 'SoeRequired': * @return The field value. % else: * @return The field value, or std::nullopt if not present. % endif */ -% if field['requirement'] == 'soeREQUIRED': +% if field['requirement'] == 'SoeRequired': [[nodiscard]] ${field['typeData']['return_type']} get${field['name'][2:]}() const @@ -136,13 +136,13 @@ public: }; <% - required_fields = [f for f in fields if f['requirement'] == 'soeREQUIRED'] + required_fields = [f for f in fields if f['requirement'] == 'SoeRequired'] %>\ /** * @brief Builder for ${name} transactions. * * Provides a fluent interface for constructing transactions with method chaining. - * Uses Json::Value internally for flexible transaction construction. + * Uses STObject internally for flexible transaction construction. * Inherits common field setters from TransactionBuilderBase. */ class ${name}Builder : public TransactionBuilderBase<${name}Builder> diff --git a/cmake/scripts/codegen/templates/TransactionTests.cpp.mako b/cmake/scripts/codegen/templates/TransactionTests.cpp.mako index b6bd5cd831..14ceaffd09 100644 --- a/cmake/scripts/codegen/templates/TransactionTests.cpp.mako +++ b/cmake/scripts/codegen/templates/TransactionTests.cpp.mako @@ -1,7 +1,7 @@ // Auto-generated unit tests for transaction ${name} <% - required_fields = [f for f in fields if f["requirement"] == "soeREQUIRED"] - optional_fields = [f for f in fields if f["requirement"] != "soeREQUIRED"] + required_fields = [f for f in fields if f["requirement"] == "SoeRequired"] + optional_fields = [f for f in fields if f["requirement"] != "SoeRequired"] def canonical_expr(field): return f"canonical_{field['stiSuffix']}()" @@ -33,7 +33,7 @@ TEST(Transactions${name}Tests, BuilderSettersRoundTrip) { // Generate a deterministic keypair for signing auto const [publicKey, secretKey] = - generateKeyPair(KeyType::secp256k1, generateSeed("test${name}")); + generateKeyPair(KeyType::Secp256k1, generateSeed("test${name}")); // Common transaction fields auto const accountValue = calcAccountID(publicKey); @@ -101,7 +101,7 @@ TEST(Transactions${name}Tests, BuilderFromStTxRoundTrip) { // Generate a deterministic keypair for signing auto const [publicKey, secretKey] = - generateKeyPair(KeyType::secp256k1, generateSeed("test${name}FromTx")); + generateKeyPair(KeyType::Secp256k1, generateSeed("test${name}FromTx")); // Common transaction fields auto const accountValue = calcAccountID(publicKey); @@ -168,7 +168,7 @@ TEST(Transactions${name}Tests, WrapperThrowsOnWrongTxType) { // Build a valid transaction of a different type auto const [pk, sk] = - generateKeyPair(KeyType::secp256k1, generateSeed("testWrongType")); + generateKeyPair(KeyType::Secp256k1, generateSeed("testWrongType")); auto const account = calcAccountID(pk); % if wrong_tx_include == "AccountSet": @@ -186,7 +186,7 @@ TEST(Transactions${name}Tests, BuilderThrowsOnWrongTxType) { // Build a valid transaction of a different type auto const [pk, sk] = - generateKeyPair(KeyType::secp256k1, generateSeed("testWrongTypeBuilder")); + generateKeyPair(KeyType::Secp256k1, generateSeed("testWrongTypeBuilder")); auto const account = calcAccountID(pk); % if wrong_tx_include == "AccountSet": @@ -205,7 +205,7 @@ TEST(Transactions${name}Tests, OptionalFieldsReturnNullopt) { // Generate a deterministic keypair for signing auto const [publicKey, secretKey] = - generateKeyPair(KeyType::secp256k1, generateSeed("test${name}Nullopt")); + generateKeyPair(KeyType::Secp256k1, generateSeed("test${name}Nullopt")); // Common transaction fields auto const accountValue = calcAccountID(publicKey); diff --git a/conan.lock b/conan.lock index ce15ead4a2..f83d3eccc6 100644 --- a/conan.lock +++ b/conan.lock @@ -1,45 +1,45 @@ { "version": "0.5", "requires": [ - "zlib/1.3.1#cac0f6daea041b0ccf42934163defb20%1774439233.809", + "zlib/1.3.2#1cb806da49011867778ffb6ac7190fcb%1778091116.056", "xxhash/0.8.3#681d36a0a6111fc56e5e45ea182c19cc%1765850149.987", - "sqlite3/3.51.0#66aa11eabd0e34954c5c1c061ad44abe%1774467355.988", + "sqlite3/3.53.0#324ada52333108388a9a6108bfa96734%1778091117.311", "soci/4.0.3#fe32b9ad5eb47e79ab9e45a68f363945%1774450067.231", "snappy/1.1.10#968fef506ff261592ec30c574d4a7809%1765850147.878", "secp256k1/0.7.1#481881709eb0bdd0185a12b912bbe8ad%1770910500.329", "rocksdb/10.5.1#4a197eca381a3e5ae8adf8cffa5aacd0%1765850186.86", "re2/20251105#8579cfd0bda4daf0683f9e3898f964b4%1774398111.888", "protobuf/6.33.5#d96d52ba5baaaa532f47bda866ad87a5%1774467363.12", - "opentelemetry-cpp/1.18.0#efd9851e173f8a13b9c7d35232de8cf1%1750409186.472", - "openssl/3.6.1#e6399de266349245a4542fc5f6c71552%1774458290.139", - "nudb/2.0.9#11149c73f8f2baff9a0198fe25971fc7%1774883011.384", + "opentelemetry-cpp/1.26.0#9d81768342c78cb897345fd419b358d2%1776934712.672", + "openssl/3.6.2#4789bbf131b77d0515d15e094c8f697f%1778071755.506", + "nudb/2.0.9#11149c73f8f2baff9a0198fe25971fc7%1775040983.408", "nlohmann_json/3.11.3#45828be26eb619a2e04ca517bb7b828d%1701220705.259", "lz4/1.10.0#59fc63cac7f10fbe8e05c7e62c2f3504%1765850143.914", "libiconv/1.17#1e65319e945f2d31941a9d28cc13c058%1765842973.492", - "libcurl/8.18.0#364bc3755cb9ef84ed9a7ae9c7efc1c1%1770984390.024", + "libcurl/8.20.0#465ac276192c197ddc6a9f4494004278%1779353234.048", "libbacktrace/cci.20210118#a7691bfccd8caaf66309df196790a5a1%1765842973.03", - "libarchive/3.8.1#ffee18995c706e02bf96e7a2f7042e0d%1765850144.736", - "jemalloc/5.3.0#e951da9cf599e956cebc117880d2d9f8%1729241615.244", + "libarchive/3.8.7#c446109bd1f1d8ba7936c94189bc50e6%1778091117.848", + "jemalloc/5.3.1#1fc58d55316041f10fbc1e8a2eae632a%1776700028.228", "gtest/1.17.0#5224b3b3ff3b4ce1133cbdd27d53ee7d%1768312129.152", "grpc/1.78.1#b1a9e74b145cc471bed4dc64dc6eb2c1%1774467387.342", "ed25519/2015.03#ae761bdc52730a843f0809bdf6c1b1f6%1765850143.772", "date/3.0.4#862e11e80030356b53c2c38599ceb32b%1765850143.772", "c-ares/1.34.6#545240bb1c40e2cacd4362d6b8967650%1774439234.681", "bzip2/1.0.8#c470882369c2d95c5c77e970c0c7e321%1765850143.837", - "boost/1.90.0#d5e8defe7355494953be18524a7f135b%1769454080.269", + "boost/1.91.0#ea540ca2133d831b560036aa24dece3c%1778091165.282", "abseil/20250127.0#bb0baf1f362bc4a725a24eddd419b8f7%1774365460.196" ], "build_requires": [ - "zlib/1.3.1#cac0f6daea041b0ccf42934163defb20%1774439233.809", + "zlib/1.3.2#1cb806da49011867778ffb6ac7190fcb%1778091116.056", "strawberryperl/5.32.1.1#8d114504d172cfea8ea1662d09b6333e%1774447376.964", "protobuf/6.33.5#d96d52ba5baaaa532f47bda866ad87a5%1774467363.12", "pkgconf/2.5.1#93c2051284cba1279494a43a4fcfeae2%1757684701.089", - "opentelemetry-proto/1.4.0#4096a3b05916675ef9628f3ffd571f51%1732731336.11", + "opentelemetry-proto/1.7.0#ed6d5bd761bef0afb0ba09676420b9ea%1749461220.268", "ninja/1.13.2#c8c5dc2a52ed6e4e42a66d75b4717ceb%1764096931.974", "nasm/2.16.01#31e26f2ee3c4346ecd347911bd126904%1765850144.707", "msys2/cci.latest#d22fe7b2808f5fd34d0a7923ace9c54f%1770657326.649", - "meson/1.10.0#60786758ea978964c24525de19603cf4%1768294926.103", - "m4/1.4.19#5d7a4994e5875d76faf7acf3ed056036%1774365463.87", + "meson/1.10.2#9d2d10681fe7fe61c788c58626c89b25%1775558003.754", + "m4/1.4.19#4523e4347b55cd26ae918bd5770cab9a%1778062762.471", "libtool/2.4.7#14e7739cc128bc1623d2ed318008e47e%1755679003.847", "gnu-config/cci.20210814#466e9d4d7779e1c142443f7ea44b4284%1762363589.329", "cmake/4.3.0#b939a42e98f593fb34d3a8c5cc860359%1774439249.183", @@ -57,16 +57,19 @@ "lz4/1.10.0" ], "boost/[>=1.83.0 <1.91.0]": [ - "boost/1.90.0" + "boost/1.91.0" ], "sqlite3/[>=3.44 <4]": [ - "sqlite3/3.51.0" + "sqlite3/3.53.0" ], "boost/1.83.0": [ - "boost/1.90.0" + "boost/1.91.0" ], "lz4/[>=1.9.4 <2]": [ "lz4/1.10.0#59fc63cac7f10fbe8e05c7e62c2f3504" + ], + "protobuf/[>=4.25.3 <7]": [ + "protobuf/6.33.5#d96d52ba5baaaa532f47bda866ad87a5" ] }, "config_requires": [] diff --git a/conan/global.conf b/conan/global.conf index 37b329a5c5..cc803dc801 100644 --- a/conan/global.conf +++ b/conan/global.conf @@ -3,3 +3,5 @@ core:non_interactive=True core.download:parallel={{ os.cpu_count() }} core.upload:parallel={{ os.cpu_count() }} +tools.files.download:retry=5 +tools.files.download:retry_wait=10 diff --git a/conan/profiles/ci b/conan/profiles/ci index c4c0898ad5..ae93187026 100644 --- a/conan/profiles/ci +++ b/conan/profiles/ci @@ -1 +1 @@ - include(sanitizers) +include(sanitizers) diff --git a/conan/profiles/sanitizers b/conan/profiles/sanitizers index 6d37425f43..4a05fda734 100644 --- a/conan/profiles/sanitizers +++ b/conan/profiles/sanitizers @@ -1,86 +1,122 @@ include(default) {% set compiler, version, compiler_exe = detect_api.detect_default_compiler() %} +{% set arch = detect_api.detect_arch() %} {% set sanitizers = os.getenv("SANITIZERS") %} -[conf] -{% if sanitizers %} - {% if compiler == "gcc" %} - {% if "address" in sanitizers or "thread" in sanitizers or "undefinedbehavior" in sanitizers %} - {% set sanitizer_list = [] %} - {% set defines = [] %} - {% set model_code = "" %} - {% set extra_cxxflags = ["-fno-omit-frame-pointer", "-O1", "-Wno-stringop-overflow"] %} +{% if not sanitizers %} +{# Sanitizers not configured; no additional settings needed #} +{% else %} - {% if "address" in sanitizers %} - {% set _ = sanitizer_list.append("address") %} - {% set model_code = "-mcmodel=large" %} - {% set _ = defines.append("BOOST_USE_ASAN")%} - {% set _ = defines.append("BOOST_USE_UCONTEXT")%} - {% elif "thread" in sanitizers %} - {% set _ = sanitizer_list.append("thread") %} - {% set model_code = "-mcmodel=medium" %} - {% set _ = extra_cxxflags.append("-Wno-tsan") %} - {% set _ = defines.append("BOOST_USE_TSAN")%} - {% set _ = defines.append("BOOST_USE_UCONTEXT")%} - {% endif %} +{% if compiler == "msvc" %} + {{ "Sanitizers are not supported on Windows/MSVC. Please unset the SANITIZERS environment variable." }} +{% endif %} - {% if "undefinedbehavior" in sanitizers %} - {% set _ = sanitizer_list.append("undefined") %} - {% set _ = sanitizer_list.append("float-divide-by-zero") %} - {% endif %} +{% set known_sanitizers = ["address", "thread", "undefinedbehavior"] %} +{% set provided_sanitizers = [] %} +{% for san in sanitizers.split(",") %} + {% set san = san.strip() %} + {% if san not in known_sanitizers %} + {{ "Unknown sanitizer in SANITIZERS: " ~ san }} + {% endif %} + {% set _ = provided_sanitizers.append(san) %} +{% endfor %} - {% set sanitizer_flags = "-fsanitize=" ~ ",".join(sanitizer_list) ~ " " ~ model_code %} +{% set enable_asan = "address" in provided_sanitizers %} +{% set enable_tsan = "thread" in provided_sanitizers %} +{% set enable_ubsan = "undefinedbehavior" in provided_sanitizers %} - tools.build:cxxflags+=['{{sanitizer_flags}} {{" ".join(extra_cxxflags)}}'] - tools.build:sharedlinkflags+=['{{sanitizer_flags}}'] - tools.build:exelinkflags+=['{{sanitizer_flags}}'] - tools.build:defines+={{defines}} - {% endif %} - {% elif compiler == "apple-clang" or compiler == "clang" %} - {% if "address" in sanitizers or "thread" in sanitizers or "undefinedbehavior" in sanitizers %} - {% set sanitizer_list = [] %} - {% set defines = [] %} - {% set extra_cxxflags = ["-fno-omit-frame-pointer", "-O1"] %} +{% if enable_asan and enable_tsan %} + {{ "AddressSanitizer and ThreadSanitizer are incompatible and cannot be enabled simultaneously." }} +{% endif %} - {% if "address" in sanitizers %} - {% set _ = sanitizer_list.append("address") %} - {% set _ = defines.append("BOOST_USE_ASAN")%} - {% set _ = defines.append("BOOST_USE_UCONTEXT")%} - {% elif "thread" in sanitizers %} - {% set _ = sanitizer_list.append("thread") %} - {% set _ = defines.append("BOOST_USE_TSAN")%} - {% set _ = defines.append("BOOST_USE_UCONTEXT")%} - {% endif %} +{% set sanitizer_types = [] %} +{% set defines = [] %} - {% if "undefinedbehavior" in sanitizers %} - {% set _ = sanitizer_list.append("undefined") %} - {% set _ = sanitizer_list.append("float-divide-by-zero") %} - {% set _ = sanitizer_list.append("unsigned-integer-overflow") %} - {% endif %} +{% if enable_asan %} + {% set _ = sanitizer_types.append("address") %} + {% set _ = defines.append("BOOST_USE_ASAN") %} + {% set _ = defines.append("BOOST_USE_UCONTEXT") %} +{% elif enable_tsan %} + {% set _ = sanitizer_types.append("thread") %} + {% set _ = defines.append("BOOST_USE_TSAN") %} + {% set _ = defines.append("BOOST_USE_UCONTEXT") %} +{% endif %} - {% set sanitizer_flags = "-fsanitize=" ~ ",".join(sanitizer_list) %} - - tools.build:cxxflags+=['{{sanitizer_flags}} {{" ".join(extra_cxxflags)}}'] - tools.build:sharedlinkflags+=['{{sanitizer_flags}}'] - tools.build:exelinkflags+=['{{sanitizer_flags}}'] - tools.build:defines+={{defines}} - {% endif %} +{% if enable_ubsan %} + {% set _ = sanitizer_types.append("undefined") %} + {% set _ = sanitizer_types.append("float-divide-by-zero") %} + {# Clang supports additional UB checks beyond the GCC baseline #} + {% if compiler == "clang" or compiler == "apple-clang" %} + {% set _ = sanitizer_types.append("unsigned-integer-overflow") %} {% endif %} {% endif %} +{# Frame pointer required for meaningful stack traces; -O1 for reasonable performance #} +{% set compile_flags = ["-fno-omit-frame-pointer", "-O1"] %} + +{% if compiler == "gcc" %} + {# Suppress false positive warnings with GCC #} + {% set _ = compile_flags.append("-Wno-stringop-overflow") %} + + {% set relocation_flags = [] %} + + {% if arch == "x86_64" and enable_asan %} + {# Large code model prevents relocation errors in instrumented ASAN binaries #} + {% set _ = compile_flags.append("-mcmodel=large") %} + {% set _ = relocation_flags.append("-mcmodel=large") %} + {% elif enable_tsan %} + {# GCC doesn't support atomic_thread_fence with TSAN; suppress warnings #} + {% set _ = compile_flags.append("-Wno-tsan") %} + {% if arch == "x86_64" %} + {# Medium code model for TSAN; large is incompatible #} + {% set _ = compile_flags.append("-mcmodel=medium") %} + {% set _ = relocation_flags.append("-mcmodel=medium") %} + {% endif %} + {% endif %} + + {% set fsanitize = "-fsanitize=" ~ ",".join(sanitizer_types) %} + {% set _ = compile_flags.append(fsanitize) %} + {% set _ = relocation_flags.append(fsanitize) %} + + {% set sanitizer_compiler_flags = " ".join(compile_flags) %} + {% set sanitizer_linker_flags = " ".join(relocation_flags) %} +{% elif compiler == "clang" or compiler == "apple-clang" %} + {% set fsanitize = "-fsanitize=" ~ ",".join(sanitizer_types) %} + {% set _ = compile_flags.append(fsanitize) %} + + {% set sanitizer_compiler_flags = " ".join(compile_flags) %} + {% set sanitizer_linker_flags = fsanitize %} +{% endif %} + +[conf] +tools.build:defines+={{defines}} +tools.build:cxxflags+=['{{sanitizer_compiler_flags}}'] +tools.build:sharedlinkflags+=['{{sanitizer_linker_flags}}'] +tools.build:exelinkflags+=['{{sanitizer_linker_flags}}'] + tools.info.package_id:confs+=["tools.build:cxxflags", "tools.build:exelinkflags", "tools.build:sharedlinkflags", "tools.build:defines"] +# &: means "apply only to the consumer/root package" +&:tools.cmake.cmaketoolchain:extra_variables={"SANITIZERS": "{{sanitizers}}", "SANITIZERS_COMPILER_FLAGS": "{{sanitizer_compiler_flags}}", "SANITIZERS_LINKER_FLAGS": "{{sanitizer_linker_flags}}"} + [options] -{% if sanitizers %} - {% if "address" in sanitizers %} - # Build Boost.Context with ucontext backend (not fcontext) so that - # ASAN fiber-switching annotations (__sanitizer_start/finish_switch_fiber) - # are compiled into the library. fcontext (assembly) has no ASAN support. - # define=BOOST_USE_ASAN=1 is critical: it must be defined when building - # Boost.Context itself so the ucontext backend compiles in the ASAN annotations. - boost/*:extra_b2_flags=context-impl=ucontext address-sanitizer=on define=BOOST_USE_ASAN=1 - boost/*:without_context=False - # Boost stacktrace fails to build with some sanitizers - boost/*:without_stacktrace=True - {% endif %} +{% if enable_asan %} + # Build Boost.Context with ucontext backend (not fcontext) so that + # ASAN fiber-switching annotations (__sanitizer_start/finish_switch_fiber) + # are compiled into the library. fcontext (assembly) has no ASAN support. + # define=BOOST_USE_ASAN=1 is critical: it must be defined when building + # Boost.Context itself so the ucontext backend compiles in the ASAN annotations. + boost/*:extra_b2_flags=context-impl=ucontext address-sanitizer=on define=BOOST_USE_ASAN=1 + boost/*:without_context=False + # Boost stacktrace fails to build with some sanitizers + boost/*:without_stacktrace=True +{% elif enable_tsan %} + # Build Boost.Context with ucontext backend for TSAN. fcontext (assembly) + # has no TSAN annotations, so without this the BOOST_USE_TSAN/BOOST_USE_UCONTEXT + # defines in [conf] would be ineffective. + boost/*:extra_b2_flags=context-impl=ucontext thread-sanitizer=on define=BOOST_USE_TSAN=1 + boost/*:without_context=False + boost/*:without_stacktrace=True +{% endif %} + {% endif %} diff --git a/conanfile.py b/conanfile.py index 9630238ef6..36b50e4b4e 100644 --- a/conanfile.py +++ b/conanfile.py @@ -1,4 +1,3 @@ -import os import re from conan.tools.cmake import CMake, CMakeToolchain, cmake_layout @@ -31,12 +30,12 @@ class Xrpl(ConanFile): requires = [ "ed25519/2015.03", "grpc/1.78.1", - "libarchive/3.8.1", + "libarchive/3.8.7", "nudb/2.0.9", - "openssl/3.6.1", + "openssl/3.6.2", "secp256k1/0.7.1", "soci/4.0.3", - "zlib/1.3.1", + "zlib/1.3.2", ] test_requires = [ @@ -59,6 +58,7 @@ class Xrpl(ConanFile): "tests": False, "unity": False, "xrpld": False, + "boost/*:without_cobalt": True, "boost/*:without_context": False, "boost/*:without_coroutine": True, "boost/*:without_coroutine2": False, @@ -131,26 +131,20 @@ class Xrpl(ConanFile): if self.settings.compiler in ["clang", "gcc"]: self.options["boost"].without_cobalt = True - # Check if environment variable exists - if "SANITIZERS" in os.environ: - sanitizers = os.environ["SANITIZERS"] - if "address" in sanitizers.lower(): - self.default_options["fPIC"] = False - def requirements(self): - self.requires("boost/1.90.0", force=True, transitive_headers=True) + self.requires("boost/1.91.0", force=True, transitive_headers=True) self.requires("date/3.0.4", transitive_headers=True) self.requires("lz4/1.10.0", force=True) self.requires("protobuf/6.33.5", force=True) - self.requires("sqlite3/3.51.0", force=True) + self.requires("sqlite3/3.53.0", force=True) if self.options.jemalloc: - self.requires("jemalloc/5.3.0") + self.requires("jemalloc/5.3.1") if self.options.rocksdb: self.requires("rocksdb/10.5.1") # OpenTelemetry C++ SDK for distributed tracing (optional). # Provides OTLP/HTTP exporter, batch span processor, and trace API. if self.options.telemetry: - self.requires("opentelemetry-cpp/1.18.0") + self.requires("opentelemetry-cpp/1.26.0") self.requires("xxhash/0.8.3", transitive_headers=True) exports_sources = ( diff --git a/cspell.config.yaml b/cspell.config.yaml index 8a158d6ff5..4e62e503ad 100644 --- a/cspell.config.yaml +++ b/cspell.config.yaml @@ -63,7 +63,9 @@ words: - Bougalis - Britto - Btrfs + - Buildx - canonicality + - CGNAT - changespq - checkme - choco @@ -71,6 +73,7 @@ words: - citardauq - clawback - clawbacks + - cmaketoolchain - coeffs - coldwallet - compr @@ -91,6 +94,7 @@ words: - daria - dcmake - dearmor + - dedented - Dedup - dedup - deleteme @@ -99,13 +103,17 @@ words: - desync - desynced - determ + - disablerepo - distro - doxyfile - dxrpl - enabled + - enablerepo - endmacro - EOCFG - exceptioned + - EXPECT_STREQ + - exfiltration - Falco - fcontext - finalizers @@ -113,14 +121,18 @@ words: - fmtdur - fsanitize - funclets + - gantt + - Gantt - gcov - gcovr - ghead - Gnutella + - godexsoft - gpgcheck - gpgkey - hotwallet - hicpp + - htpasswd - hwaddress - hwrap - ifndef @@ -155,13 +167,14 @@ words: - lseq - lsmf - ltype + - mathbunnyru - mcmodel - MEMORYSTATUSEX - - MPTAMM - - MPTDEX - Merkle - Metafuncton - misprediction + - missingok + - MPTAMM - mptbalance - MPTDEX - mptflags @@ -194,13 +207,18 @@ words: - NOLINT - NOLINTNEXTLINE - nonxrp + - noreplace - noripple - nostd + - nostdinc + - notifempty - nudb - nullptr - nunl - Nyffenegger + - onlatest - ostr + - otelc - pargs - partitioner - paychan @@ -219,6 +237,7 @@ words: - preauthorize - preauthorizes - preclaim + - preun - protobuf - protos - ptrs @@ -255,12 +274,15 @@ words: - sfields - shamap - shamapitem + - shfmt + - shlibs - sidechain - SIGGOOD - sle - sles - soci - socidb + - SRPMS - sslws - statsd - STATSDCOLLECTOR @@ -280,6 +302,7 @@ words: - takerpays - ters - TMEndpointv2 + - traceql - trixie - tx - txid @@ -289,8 +312,8 @@ words: - txns - txqueue - txs - - UBSAN - ubsan + - UBSAN - umant - unacquired - unambiguity @@ -298,6 +321,7 @@ words: - unauthorizing - unergonomic - unfetched + - unfindable - unflatten - unfund - unimpair @@ -327,11 +351,6 @@ words: - xbridge - xchain - ximinez - - EXPECT_STREQ - - Gantt - - gantt - - otelc - - traceql - XMACRO - xrpkuwait - xrpl diff --git a/docker/check-sanitizers.sh b/docker/check-sanitizers.sh new file mode 100755 index 0000000000..38ccaed560 --- /dev/null +++ b/docker/check-sanitizers.sh @@ -0,0 +1,48 @@ +#!/bin/bash + +# Sanity-check that the sanitizer runtimes shipped with g++/clang++ work +# end-to-end against the system loader: compile each example with both +# compilers, run it, and confirm the expected diagnostic is emitted. + +set -eo pipefail + +cpp_files_dir="${1:?usage: $0 }" + +case "$(uname -m)" in + x86_64) loader=/lib64/ld-linux-x86-64.so.2 ;; + aarch64) loader=/lib/ld-linux-aarch64.so.1 ;; + *) + echo "Unsupported arch: $(uname -m)" >&2 + exit 1 + ;; +esac + +declare -A sanitize=( + [asan]="-fsanitize=address" + [tsan]="-fsanitize=thread" + [ubsan]="-fsanitize=undefined" +) +declare -A expect=( + [asan]="heap-use-after-free" + [tsan]="data race" + [ubsan]="signed integer overflow" +) + +for compiler in g++ clang++; do + for name in asan tsan ubsan; do + bin="/tmp/${name}-${compiler}" + echo "=== Build ${name} with ${compiler} ===" + "$compiler" -std=c++20 -O1 -g ${sanitize[$name]} \ + -Wl,--dynamic-linker=$loader \ + "${cpp_files_dir}/${name}.cpp" -o "$bin" + echo "=== Run ${name}-${compiler} ===" + output=$("$bin" 2>&1) || true + echo "$output" + echo "$output" | grep -q "${expect[$name]}" || + { + echo "expected '${expect[$name]}' from $bin" + exit 1 + } + rm -f "$bin" + done +done diff --git a/docker/cpp_files/asan.cpp b/docker/cpp_files/asan.cpp new file mode 100644 index 0000000000..8347f58d37 --- /dev/null +++ b/docker/cpp_files/asan.cpp @@ -0,0 +1,28 @@ +#include +#include +#include + +#if defined(__clang__) || defined(__GNUC__) +__attribute__((noinline)) +#elif defined(_MSC_VER) +__declspec(noinline) +#endif +int +read_after_free(volatile int* array, std::size_t index) +{ + std::atomic_signal_fence(std::memory_order_seq_cst); + int value = array[index]; + std::atomic_signal_fence(std::memory_order_seq_cst); + return value; +} + +int +main() +{ + int* array = new int[5]{10, 20, 30, 40, 50}; + delete[] array; + + std::cout << "Value at index 2: " << read_after_free(array, 2) << std::endl; + + return 0; +} diff --git a/docker/cpp_files/tsan.cpp b/docker/cpp_files/tsan.cpp new file mode 100644 index 0000000000..34b0990a6d --- /dev/null +++ b/docker/cpp_files/tsan.cpp @@ -0,0 +1,26 @@ +#include +#include + +static int kCounter = 0; + +void +increment() +{ + for (int i = 0; i < 100'000; ++i) + { + ++kCounter; + } +} + +int +main() +{ + std::thread t1(increment); + std::thread t2(increment); + + t1.join(); + t2.join(); + + std::cout << "Final counter value: " << kCounter << std::endl; + return 0; +} diff --git a/docker/cpp_files/ubsan.cpp b/docker/cpp_files/ubsan.cpp new file mode 100644 index 0000000000..db86119070 --- /dev/null +++ b/docker/cpp_files/ubsan.cpp @@ -0,0 +1,13 @@ +#include +#include + +int +main() +{ + int maxInt = std::numeric_limits::max(); + int volatile one = 1; + std::cout << "Current max: " << maxInt << std::endl; + int overflowed = maxInt + one; + std::cout << "Overflowed result: " << overflowed << std::endl; + return 0; +} diff --git a/docker/nix.Dockerfile b/docker/nix.Dockerfile new file mode 100644 index 0000000000..690f0b76bd --- /dev/null +++ b/docker/nix.Dockerfile @@ -0,0 +1,95 @@ +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" + +# Externally-built dynamically-linked ELF binaries hard-code the loader path +# (e.g. /lib64/ld-linux-x86-64.so.2) in their PT_INTERP header. Copy the +# loader from the Nix store to that path when the base image doesn't already +# provide one (i.e. on nixos/nix). +RUN <&2; exit 1 ;; +esac +if [ ! -e "$target" ]; then + # Use the loader from the same glibc that gcc links libc against, so + # ld-linux and libc/libpthread share GLIBC_PRIVATE symbols at runtime. + src="$(dirname "$(gcc -print-file-name=libc.so.6)")/$(basename "$target")" + [ -e "$src" ] || { echo "ld-linux not found at $src" >&2; exit 1; } + mkdir -p "$(dirname "$target")" + cp "$src" "$target" +fi +EOF + +RUN </dev/null && /tmp/check-sanitizers.sh /tmp/cpp_files || true diff --git a/docker/telemetry/grafana/provisioning/datasources/tempo.yaml b/docker/telemetry/grafana/provisioning/datasources/tempo.yaml index dac00303a7..8c65e82fee 100644 --- a/docker/telemetry/grafana/provisioning/datasources/tempo.yaml +++ b/docker/telemetry/grafana/provisioning/datasources/tempo.yaml @@ -108,20 +108,9 @@ datasources: operator: "=" scope: span type: dynamic - # Phase 2: Node health filters (Task 2.8) — resource attributes - - id: node-amendment-blocked - tag: xrpl.node.amendment_blocked - operator: "=" - scope: resource - type: static - - id: node-server-state - tag: xrpl.node.server_state - operator: "=" - scope: resource - type: dynamic # Phase 3: Transaction tracing filters - id: tx-hash - tag: xrpl.tx.hash + tag: tx_hash operator: "=" scope: span type: static diff --git a/docs/build/sanitizers.md b/docs/build/sanitizers.md index 7677775a3d..6e7284fd06 100644 --- a/docs/build/sanitizers.md +++ b/docs/build/sanitizers.md @@ -1,15 +1,17 @@ # Sanitizer Configuration for Xrpld -This document explains how to properly configure and run sanitizers (AddressSanitizer, undefinedbehaviorSanitizer, ThreadSanitizer) with the xrpld project. +This document explains how to properly configure and run sanitizers (`AddressSanitizer`, `UndefinedBehaviorSanitizer`, `ThreadSanitizer`) with the xrpld project. Corresponding suppression files are located in the `sanitizers/suppressions` directory. +> [!CAUTION] +> Do not mix Address and Thread sanitizers - they are incompatible. +> Also, we don't yet support MSVC sanitizers, so this is only for Clang/GCC builds. + - [Sanitizer Configuration for Xrpld](#sanitizer-configuration-for-xrpld) - [Building with Sanitizers](#building-with-sanitizers) - [Summary](#summary) - [Build steps:](#build-steps) - [Install dependencies](#install-dependencies) - - [Call CMake](#call-cmake) - - [Build](#build) - [Running Tests with Sanitizers](#running-tests-with-sanitizers) - [AddressSanitizer (ASAN)](#addresssanitizer-asan) - [ThreadSanitizer (TSan)](#threadsanitizer-tsan) @@ -33,9 +35,13 @@ Corresponding suppression files are located in the `sanitizers/suppressions` dir Follow the same instructions as mentioned in [BUILD.md](../../BUILD.md) but with the following changes: 1. Make sure you have a clean build directory. -2. Set the `SANITIZERS` environment variable before calling conan install and cmake. Only set it once. Make sure both conan and cmake read the same values. +2. Set the `SANITIZERS` environment variable before calling `conan install`. Only set it once. Example: `export SANITIZERS=address,undefinedbehavior` -3. Optionally use `--profile:all sanitizers` with Conan to build dependencies with sanitizer instrumentation. [!NOTE]Building with sanitizer-instrumented dependencies is slower but produces fewer false positives. +3. Use `--profile:all sanitizers` with Conan to build dependencies with sanitizer instrumentation. + + > [!NOTE] + > Building with sanitizer-instrumented dependencies is slower but produces fewer false positives. + 4. Set `ASAN_OPTIONS`, `LSAN_OPTIONS`, `UBSAN_OPTIONS` and `TSAN_OPTIONS` environment variables to configure sanitizer behavior when running executables. [More details below](#running-tests-with-sanitizers). --- @@ -51,36 +57,13 @@ cd .build #### Install dependencies -The `SANITIZERS` environment variable is used by both Conan and CMake. +The `SANITIZERS` environment variable is used during `conan install` command. ```bash -export SANITIZERS=address,undefinedbehavior -# Standard build (without instrumenting dependencies) -conan install .. --output-folder . --build missing --settings build_type=Debug - -# Or with sanitizer-instrumented dependencies (takes longer but fewer false positives) -conan install .. --output-folder . --profile:all sanitizers --build missing --settings build_type=Debug +SANITIZERS=address,undefinedbehavior conan install .. --output-folder . --build missing --settings build_type=Debug --profile:all sanitizers ``` -[!CAUTION] -Do not mix Address and Thread sanitizers - they are incompatible. - -Since you already set the `SANITIZERS` environment variable when running Conan, same values will be read for the next part. - -#### Call CMake - -```bash -cmake .. -G Ninja \ - -DCMAKE_TOOLCHAIN_FILE:FILEPATH=build/generators/conan_toolchain.cmake \ - -DCMAKE_BUILD_TYPE=Debug \ - -Dtests=ON -Dxrpld=ON -``` - -#### Build - -```bash -cmake --build . --parallel 4 -``` +Proceed with the rest of the build instructions as mentioned in [BUILD.md](../../BUILD.md). ## Running Tests with Sanitizers @@ -98,7 +81,7 @@ export LSAN_OPTIONS="include=sanitizers/suppressions/runtime-lsan-options.txt:su **Why `detect_container_overflow=0`?** -- Boost intrusive containers (used in `aged_unordered_container`) trigger false positives +- Boost intrusive containers (used in `AgedUnorderedContainer`) trigger false positives - Boost context switching (used in `Workers.cpp`) confuses ASAN's stack tracking - Since we usually don't build Boost (because we don't want to instrument Boost and detect issues in Boost code) with ASAN but use Boost containers in ASAN instrumented xrpld code, it generates false positives. - Building dependencies with ASAN instrumentation reduces false positives. But we don't want to instrument dependencies like Boost with ASAN because it is slow (to compile as well as run tests) and not necessary. diff --git a/docs/build/telemetry.md b/docs/build/telemetry.md index 1e6e715353..ed585cb64f 100644 --- a/docs/build/telemetry.md +++ b/docs/build/telemetry.md @@ -36,7 +36,8 @@ such as Grafana Tempo. Telemetry is **off by default** at both compile time and runtime: - **Compile time**: The Conan option `telemetry` and CMake option `telemetry` must be set to `True`/`ON`. - When disabled, all tracing macros compile to `((void)0)` with zero overhead. + When disabled, all `SpanGuard` calls compile to inline no-ops (defined in `SpanGuard.h`) + with zero overhead — no OTel SDK dependency required. - **Runtime**: The `[telemetry]` config section must set `enabled=1`. When disabled at runtime, a no-op implementation is used. @@ -63,7 +64,7 @@ cd .build #### Install dependencies -The `telemetry` option adds `opentelemetry-cpp/1.18.0` as a dependency. +The `telemetry` option adds `opentelemetry-cpp/1.26.0` as a dependency. If the Conan lockfile does not yet include this package, bypass it with `--lockfile=""`. ```bash @@ -235,7 +236,7 @@ curl -s -X POST http://127.0.0.1:5005/ \ ### Conan lockfile error -If you see `ERROR: Requirement 'opentelemetry-cpp/1.18.0' not in lockfile 'requires'`, +If you see `ERROR: Requirement 'opentelemetry-cpp/1.26.0' not in lockfile 'requires'`, the lockfile was generated without the telemetry dependency. Pass `--lockfile=""` to bypass the lockfile, or regenerate it with telemetry enabled. @@ -257,7 +258,7 @@ The Conan package provides a single umbrella target | `include/xrpl/telemetry/SpanGuard.h` | RAII span guard with `discard()` for dropping unwanted spans | | `include/xrpl/telemetry/DiscardFlag.h` | Thread-local discard flag (zero-dependency header) | | `src/libxrpl/telemetry/Telemetry.cpp` | OTel SDK setup, `FilteringSpanProcessor`, provider lifecycle | -| `src/libxrpl/telemetry/TelemetryConfig.cpp` | Config parser (`setup_Telemetry()`) | +| `src/libxrpl/telemetry/TelemetryConfig.cpp` | Config parser (`setupTelemetry()`) | | `src/libxrpl/telemetry/NullTelemetry.cpp` | No-op implementation (used when disabled) | | `src/libxrpl/telemetry/SpanGuard.cpp` | Pimpl implementation for SpanGuard (all OTel types confined) | | `src/xrpld/rpc/detail/ServerHandler.cpp` | RPC entry point instrumentation | diff --git a/docs/consensus.md b/docs/consensus.md index 0da23b708a..2850cf784e 100644 --- a/docs/consensus.md +++ b/docs/consensus.md @@ -477,7 +477,7 @@ struct Ledger // The parent ledger's close time NetClock::time_point parentCloseTime() const; - Json::Value getJson() const; + json::Value getJson() const; //... implementation specific }; diff --git a/flake.lock b/flake.lock index fd43f5b683..3149f3feed 100644 --- a/flake.lock +++ b/flake.lock @@ -2,11 +2,11 @@ "nodes": { "nixpkgs": { "locked": { - "lastModified": 1769461804, - "narHash": "sha256-6h5sROT/3CTHvzPy9koKBmoCa2eJKh4fzQK8eYFEgl8=", + "lastModified": 1777954456, + "narHash": "sha256-hGdgeU2Nk87RAuZyYjyDjFL6LK7dAZN5RE9+hrDTkDU=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "b579d443b37c9c5373044201ea77604e37e748c8", + "rev": "549bd84d6279f9852cae6225e372cc67fb91a4c1", "type": "github" }, "original": { @@ -15,9 +15,27 @@ "type": "indirect" } }, + "nixpkgs-custom-glibc": { + "flake": false, + "locked": { + "lastModified": 1593520194, + "narHash": "sha256-+TZW+2I7kLL9JglPNOagm1ywjf9ua0JYGoptq/dzVn0=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "9cd98386a38891d1074fc18036b842dc4416f562", + "type": "github" + }, + "original": { + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "9cd98386a38891d1074fc18036b842dc4416f562", + "type": "github" + } + }, "root": { "inputs": { - "nixpkgs": "nixpkgs" + "nixpkgs": "nixpkgs", + "nixpkgs-custom-glibc": "nixpkgs-custom-glibc" } } }, diff --git a/flake.nix b/flake.nix index 4c500f1933..3b3ec7ea08 100644 --- a/flake.nix +++ b/flake.nix @@ -2,15 +2,24 @@ description = "Nix related things for xrpld"; inputs = { nixpkgs.url = "nixpkgs/nixos-unstable"; + # nixpkgs snapshot (2020-06-30) that shipped glibc 2.31 as the primary + # version — matches the system libc on Ubuntu 20.04 LTS. Imported + # manually (flake = false) because this revision predates nixpkgs' + # own flake.nix. + nixpkgs-custom-glibc = { + url = "github:NixOS/nixpkgs/9cd98386a38891d1074fc18036b842dc4416f562"; + flake = false; + }; }; outputs = - { nixpkgs, ... }: + { nixpkgs, nixpkgs-custom-glibc, ... }: let - forEachSystem = (import ./nix/utils.nix { inherit nixpkgs; }).forEachSystem; + forEachSystem = import ./nix/utils.nix { inherit nixpkgs nixpkgs-custom-glibc; }; in { devShells = forEachSystem (import ./nix/devshell.nix); + packages = forEachSystem (import ./nix/ci-env.nix); formatter = forEachSystem ({ pkgs, ... }: pkgs.nixfmt); }; } diff --git a/include/xrpl/basics/BasicConfig.h b/include/xrpl/basics/BasicConfig.h index 0639fb6c4d..5a82f7d081 100644 --- a/include/xrpl/basics/BasicConfig.h +++ b/include/xrpl/basics/BasicConfig.h @@ -27,7 +27,7 @@ private: std::unordered_map lookup_; std::vector lines_; std::vector values_; - bool had_trailing_comments_ = false; + bool hadTrailingComments_ = false; using const_iterator = decltype(lookup_)::const_iterator; @@ -133,7 +133,7 @@ public: /// Returns a value if present, else another value. template [[nodiscard]] T - value_or(std::string const& name, T const& other) const + valueOr(std::string const& name, T const& other) const { auto const v = get(name); return v.has_value() ? *v : other; @@ -142,9 +142,9 @@ public: // indicates if trailing comments were seen // during the appending of any lines/values [[nodiscard]] bool - had_trailing_comments() const + hadTrailingComments() const { - return had_trailing_comments_; + return hadTrailingComments_; } friend std::ostream& @@ -273,9 +273,9 @@ public: // indicates if trailing comments were seen // in any loaded Sections [[nodiscard]] bool - had_trailing_comments() const + hadTrailingComments() const { - return std::ranges::any_of(map_, [](auto s) { return s.second.had_trailing_comments(); }); + return std::ranges::any_of(map_, [](auto s) { return s.second.hadTrailingComments(); }); } protected: @@ -294,17 +294,17 @@ template bool set(T& target, std::string const& name, Section const& section) { - bool found_and_valid = false; + bool foundAndValid = false; try { auto const val = section.get(name); - if ((found_and_valid = val.has_value())) + if ((foundAndValid = val.has_value())) target = *val; } catch (boost::bad_lexical_cast const&) // NOLINT(bugprone-empty-catch) { } - return found_and_valid; + return foundAndValid; } /** Set a value from a configuration Section @@ -316,10 +316,10 @@ template bool set(T& target, T const& defaultValue, std::string const& name, Section const& section) { - bool const found_and_valid = set(target, name, section); - if (!found_and_valid) + bool const foundAndValid = set(target, name, section); + if (!foundAndValid) target = defaultValue; - return found_and_valid; + return foundAndValid; } /** Retrieve a key/value pair from a section. @@ -333,7 +333,7 @@ get(Section const& section, std::string const& name, T const& defaultValue = T{} { try { - return section.value_or(name, defaultValue); + return section.valueOr(name, defaultValue); } catch (boost::bad_lexical_cast const&) // NOLINT(bugprone-empty-catch) { @@ -358,17 +358,17 @@ get(Section const& section, std::string const& name, char const* defaultValue) template bool -get_if_exists(Section const& section, std::string const& name, T& v) +getIfExists(Section const& section, std::string const& name, T& v) { return set(v, name, section); } template <> inline bool -get_if_exists(Section const& section, std::string const& name, bool& v) +getIfExists(Section const& section, std::string const& name, bool& v) { int intVal = 0; - auto stat = get_if_exists(section, name, intVal); + auto stat = getIfExists(section, name, intVal); if (stat) v = bool(intVal); return stat; diff --git a/include/xrpl/basics/CountedObject.h b/include/xrpl/basics/CountedObject.h index 379fd49837..275894673e 100644 --- a/include/xrpl/basics/CountedObject.h +++ b/include/xrpl/basics/CountedObject.h @@ -38,11 +38,11 @@ public: do { - head = instance.m_head.load(); + head = instance.head_.load(); next_ = head; - } while (instance.m_head.exchange(this) != head); + } while (instance.head_.exchange(this) != head); - ++instance.m_count; + ++instance.count_; } ~Counter() noexcept = default; @@ -88,8 +88,8 @@ private: ~CountedObjects() noexcept = default; private: - std::atomic m_count; - std::atomic m_head; + std::atomic count_; + std::atomic head_; }; //------------------------------------------------------------------------------ @@ -108,8 +108,8 @@ private: static auto& getCounter() noexcept { - static CountedObjects::Counter c{beast::type_name()}; - return c; + static CountedObjects::Counter kC{beast::typeName()}; + return kC; } CountedObject() noexcept diff --git a/include/xrpl/basics/DecayingSample.h b/include/xrpl/basics/DecayingSample.h index 8f6e729acf..d1861ebc4a 100644 --- a/include/xrpl/basics/DecayingSample.h +++ b/include/xrpl/basics/DecayingSample.h @@ -20,7 +20,7 @@ public: /** @param now Start time of DecayingSample. */ - explicit DecayingSample(time_point now) : m_value(value_type()), m_when(now) + explicit DecayingSample(time_point now) : value_(value_type()), when_(now) { } @@ -31,8 +31,8 @@ public: add(value_type value, time_point now) { decay(now); - m_value += value; - return m_value / Window; + value_ += value; + return value_ / Window; } /** Retrieve the current value in normalized units. @@ -42,7 +42,7 @@ public: value(time_point now) { decay(now); - return m_value / Window; + return value_ / Window; } private: @@ -50,38 +50,38 @@ private: void decay(time_point now) { - if (now == m_when) + if (now == when_) return; - if (m_value != value_type()) + if (value_ != value_type()) { std::size_t elapsed = - std::chrono::duration_cast(now - m_when).count(); + std::chrono::duration_cast(now - when_).count(); // A span larger than four times the window decays the // value to an insignificant amount so just reset it. // if (elapsed > 4 * Window) { - m_value = value_type(); + value_ = value_type(); } else { for (; elapsed > 0; --elapsed) { - m_value -= (m_value + Window - 1) / Window; + value_ -= (value_ + Window - 1) / Window; } } } - m_when = now; + when_ = now; } // Current value in exponential units - value_type m_value; + value_type value_; // Last time the aging function was applied - time_point m_when; + time_point when_; }; //------------------------------------------------------------------------------ diff --git a/include/xrpl/basics/Expected.h b/include/xrpl/basics/Expected.h index f4d8e5019a..3796151777 100644 --- a/include/xrpl/basics/Expected.h +++ b/include/xrpl/basics/Expected.h @@ -16,9 +16,9 @@ namespace xrpl { */ // Exception thrown by an invalid access to Expected. -struct bad_expected_access : public std::runtime_error +struct BadExpectedAccess : public std::runtime_error { - bad_expected_access() : runtime_error("bad expected access") + BadExpectedAccess() : runtime_error("bad expected access") { } }; @@ -26,30 +26,33 @@ struct bad_expected_access : public std::runtime_error namespace detail { // Custom policy for Expected. Always throw on an invalid access. -struct throw_policy : public boost::outcome_v2::policy::base +struct ThrowPolicy : public boost::outcome_v2::policy::base { template static constexpr void + // NOLINTNEXTLINE(readability-identifier-naming) wide_value_check(Impl&& self) { if (!base::_has_value(std::forward(self))) - Throw(); + Throw(); } template static constexpr void + // NOLINTNEXTLINE(readability-identifier-naming) wide_error_check(Impl&& self) { if (!base::_has_error(std::forward(self))) - Throw(); + Throw(); } template static constexpr void + // NOLINTNEXTLINE(readability-identifier-naming) wide_exception_check(Impl&& self) { if (!base::_has_exception(std::forward(self))) - Throw(); + Throw(); } }; @@ -107,9 +110,9 @@ Unexpected(E (&)[N]) -> Unexpected; // Definition of Expected. All of the machinery comes from boost::result. template -class [[nodiscard]] Expected : private boost::outcome_v2::result +class [[nodiscard]] Expected : private boost::outcome_v2::result { - using Base = boost::outcome_v2::result; + using Base = boost::outcome_v2::result; public: template @@ -126,6 +129,7 @@ public: } [[nodiscard]] constexpr bool + // NOLINTNEXTLINE(readability-identifier-naming) has_value() const { return Base::has_value(); @@ -144,17 +148,23 @@ public: } [[nodiscard]] constexpr E const& - error() const + error() const& { return Base::error(); } - constexpr E& - error() + [[nodiscard]] constexpr E& + error() & { return Base::error(); } + [[nodiscard]] constexpr E&& + error() && + { + return std::move(Base::error()); + } + constexpr explicit operator bool() const { @@ -193,9 +203,9 @@ public: // (without a value) or the reason for the failure. template class [[nodiscard]] -Expected : private boost::outcome_v2::result +Expected : private boost::outcome_v2::result { - using Base = boost::outcome_v2::result; + using Base = boost::outcome_v2::result; public: // The default constructor makes a successful Expected. @@ -211,17 +221,23 @@ public: } [[nodiscard]] constexpr E const& - error() const + error() const& { return Base::error(); } - constexpr E& - error() + [[nodiscard]] constexpr E& + error() & { return Base::error(); } + [[nodiscard]] constexpr E&& + error() && + { + return std::move(Base::error()); + } + constexpr explicit operator bool() const { diff --git a/include/xrpl/basics/IntrusivePointer.h b/include/xrpl/basics/IntrusivePointer.h index 019e71a727..d66c340d3f 100644 --- a/include/xrpl/basics/IntrusivePointer.h +++ b/include/xrpl/basics/IntrusivePointer.h @@ -164,11 +164,11 @@ public: /** Return the strong count */ [[nodiscard]] std::size_t - use_count() const; + useCount() const; template friend SharedIntrusive - make_SharedIntrusive(Args&&... args); + makeSharedIntrusive(Args&&... args); template friend class SharedIntrusive; @@ -364,7 +364,7 @@ public: * return 0 */ [[nodiscard]] std::size_t - use_count() const; + useCount() const; /** Return true if there is a non-zero strong count. */ [[nodiscard]] bool @@ -406,8 +406,8 @@ private: // pointer. The low bit must be masked to zero when converting back to a // pointer. If the low bit is '1', this is a weak pointer. std::uintptr_t tp_{0}; - static constexpr std::uintptr_t tagMask = 1; - static constexpr std::uintptr_t ptrMask = ~tagMask; + static constexpr std::uintptr_t kTagMask = 1; + static constexpr std::uintptr_t kPtrMask = ~kTagMask; private: /** Return the raw pointer held by this object. @@ -415,7 +415,7 @@ private: [[nodiscard]] T* unsafeGetRawPtr() const; - enum class RefStrength { strong, weak }; + enum class RefStrength { Strong, Weak }; /** Set the raw pointer and tag bit directly. */ void @@ -442,7 +442,7 @@ private: */ template SharedIntrusive -make_SharedIntrusive(Args&&... args) +makeSharedIntrusive(Args&&... args) { auto p = new TT(std::forward(args)...); @@ -469,21 +469,21 @@ using SharedWeakUnionPtr = SharedWeakUnion; template SharedPtr -make_shared(A&&... args) +makeShared(A&&... args) { - return make_SharedIntrusive(std::forward(args)...); + return makeSharedIntrusive(std::forward(args)...); } template SharedPtr -static_pointer_cast(TT const& v) +staticPointerCast(TT const& v) { return SharedPtr(StaticCastTagSharedIntrusive{}, v); } template SharedPtr -dynamic_pointer_cast(TT const& v) +dynamicPointerCast(TT const& v) { return SharedPtr(DynamicCastTagSharedIntrusive{}, v); } diff --git a/include/xrpl/basics/IntrusivePointer.ipp b/include/xrpl/basics/IntrusivePointer.ipp index 59caf5a931..8344a3e613 100644 --- a/include/xrpl/basics/IntrusivePointer.ipp +++ b/include/xrpl/basics/IntrusivePointer.ipp @@ -43,14 +43,16 @@ SharedIntrusive::SharedIntrusive(SharedIntrusive const& rhs) } template -SharedIntrusive::SharedIntrusive(SharedIntrusive&& rhs) : ptr_{rhs.unsafeExchange(nullptr)} +SharedIntrusive::SharedIntrusive(SharedIntrusive&& rhs) + : ptr_{std::move(rhs).unsafeExchange(nullptr)} { } template template requires std::convertible_to -SharedIntrusive::SharedIntrusive(SharedIntrusive&& rhs) : ptr_{rhs.unsafeExchange(nullptr)} +SharedIntrusive::SharedIntrusive(SharedIntrusive&& rhs) + : ptr_{std::move(rhs).unsafeExchange(nullptr)} { } template @@ -93,7 +95,7 @@ SharedIntrusive::operator=(SharedIntrusive&& rhs) if (this == &rhs) return *this; - unsafeReleaseAndStore(rhs.unsafeExchange(nullptr)); + unsafeReleaseAndStore(std::move(rhs).unsafeExchange(nullptr)); return *this; } @@ -105,7 +107,7 @@ SharedIntrusive::operator=(SharedIntrusive&& rhs) { static_assert(!std::is_same_v, "This overload should not be instantiated for T == TT"); - unsafeReleaseAndStore(rhs.unsafeExchange(nullptr)); + unsafeReleaseAndStore(std::move(rhs).unsafeExchange(nullptr)); return *this; } @@ -157,7 +159,7 @@ SharedIntrusive::SharedIntrusive(StaticCastTagSharedIntrusive, SharedIntrusiv template template SharedIntrusive::SharedIntrusive(StaticCastTagSharedIntrusive, SharedIntrusive&& rhs) - : ptr_{static_cast(rhs.unsafeExchange(nullptr))} + : ptr_{static_cast(std::move(rhs).unsafeExchange(nullptr))} { } @@ -184,8 +186,10 @@ SharedIntrusive::SharedIntrusive(DynamicCastTagSharedIntrusive, SharedIntrusi { ptr_ = dynamic_cast(toSet); if (!ptr_) + { // need to set the pointer back or will leak - rhs.unsafeExchange(toSet); + std::move(rhs).unsafeExchange(toSet); + } } } @@ -226,10 +230,10 @@ SharedIntrusive::get() const template std::size_t -SharedIntrusive::use_count() const +SharedIntrusive::useCount() const { if (auto p = unsafeGetRawPtr()) - return p->use_count(); + return p->useCount(); return 0; } @@ -266,12 +270,12 @@ SharedIntrusive::unsafeReleaseAndStore(T* next) auto action = prev->releaseStrongRef(); switch (action) { - case noop: + case NoOp: break; - case destroy: + case Destroy: delete prev; break; - case partialDestroy: + case PartialDestroy: prev->partialDestructor(); partialDestructorFinished(&prev); // prev is null and may no longer be used @@ -345,7 +349,7 @@ template bool WeakIntrusive::expired() const { - return (!ptr_ || ptr_->expired()); + return ((ptr_ == nullptr) || ptr_->expired()); } template @@ -360,16 +364,16 @@ template void WeakIntrusive::unsafeReleaseNoStore() { - if (!ptr_) + if (ptr_ == nullptr) return; using enum ReleaseWeakRefAction; auto action = ptr_->releaseWeakRef(); switch (action) { - case noop: + case NoOp: break; - case destroy: + case Destroy: delete ptr_; break; } @@ -385,9 +389,13 @@ SharedWeakUnion::SharedWeakUnion(SharedWeakUnion const& rhs) : tp_{rhs.tp_} return; if (rhs.isStrong()) + { p->addStrongRef(); + } else + { p->addWeakRef(); + } } template @@ -398,7 +406,7 @@ SharedWeakUnion::SharedWeakUnion(SharedIntrusive const& rhs) auto p = rhs.unsafeGetRawPtr(); if (p) p->addStrongRef(); - unsafeSetRawPtr(p, RefStrength::strong); + unsafeSetRawPtr(p, RefStrength::Strong); } template @@ -414,8 +422,8 @@ SharedWeakUnion::SharedWeakUnion(SharedIntrusive&& rhs) { auto p = rhs.unsafeGetRawPtr(); if (p) - unsafeSetRawPtr(p, RefStrength::strong); - rhs.unsafeSetRawPtr(nullptr); + unsafeSetRawPtr(p, RefStrength::Strong); + std::move(rhs).unsafeSetRawPtr(nullptr); } template @@ -431,12 +439,12 @@ SharedWeakUnion::operator=(SharedWeakUnion const& rhs) if (rhs.isStrong()) { p->addStrongRef(); - unsafeSetRawPtr(p, RefStrength::strong); + unsafeSetRawPtr(p, RefStrength::Strong); } else { p->addWeakRef(); - unsafeSetRawPtr(p, RefStrength::weak); + unsafeSetRawPtr(p, RefStrength::Weak); } } else @@ -456,7 +464,7 @@ SharedWeakUnion::operator=(SharedIntrusive const& rhs) auto p = rhs.unsafeGetRawPtr(); if (p) p->addStrongRef(); - unsafeSetRawPtr(p, RefStrength::strong); + unsafeSetRawPtr(p, RefStrength::Strong); return *this; } @@ -467,8 +475,8 @@ SharedWeakUnion& SharedWeakUnion::operator=(SharedIntrusive&& rhs) { unsafeReleaseNoStore(); - unsafeSetRawPtr(rhs.unsafeGetRawPtr(), RefStrength::strong); - rhs.unsafeSetRawPtr(nullptr); + unsafeSetRawPtr(rhs.unsafeGetRawPtr(), RefStrength::Strong); + std::move(rhs).unsafeSetRawPtr(nullptr); return *this; } @@ -517,10 +525,10 @@ SharedWeakUnion::get() const template std::size_t -SharedWeakUnion::use_count() const +SharedWeakUnion::useCount() const { if (auto p = get()) - return p->use_count(); + return p->useCount(); return 0; } @@ -559,14 +567,14 @@ template bool SharedWeakUnion::isStrong() const { - return !(tp_ & tagMask); + return (tp_ & kTagMask) == 0u; } template bool SharedWeakUnion::isWeak() const { - return tp_ & tagMask; + return (tp_ & kTagMask) != 0u; } template @@ -581,10 +589,10 @@ SharedWeakUnion::convertToStrong() { [[maybe_unused]] auto action = p->releaseWeakRef(); XRPL_ASSERT( - (action == ReleaseWeakRefAction::noop), + (action == ReleaseWeakRefAction::NoOp), "xrpl::SharedWeakUnion::convertToStrong : " "action is noop"); - unsafeSetRawPtr(p, RefStrength::strong); + unsafeSetRawPtr(p, RefStrength::Strong); return true; } return false; @@ -605,9 +613,9 @@ SharedWeakUnion::convertToWeak() auto action = p->addWeakReleaseStrongRef(); switch (action) { - case noop: + case NoOp: break; - case destroy: + case Destroy: // We just added a weak ref. How could we destroy? // LCOV_EXCL_START UNREACHABLE( @@ -617,7 +625,7 @@ SharedWeakUnion::convertToWeak() unsafeSetRawPtr(nullptr); return true; // Should never happen // LCOV_EXCL_STOP - case partialDestroy: + case PartialDestroy: // This is a weird case. We just converted the last strong // pointer to a weak pointer. p->partialDestructor(); @@ -625,7 +633,7 @@ SharedWeakUnion::convertToWeak() // p is null and may no longer be used break; } - unsafeSetRawPtr(p, RefStrength::weak); + unsafeSetRawPtr(p, RefStrength::Weak); return true; } @@ -633,7 +641,7 @@ template T* SharedWeakUnion::unsafeGetRawPtr() const { - return reinterpret_cast(tp_ & ptrMask); + return reinterpret_cast(tp_ & kPtrMask); } template @@ -641,8 +649,8 @@ void SharedWeakUnion::unsafeSetRawPtr(T* p, RefStrength rs) { tp_ = reinterpret_cast(p); - if (tp_ && rs == RefStrength::weak) - tp_ |= tagMask; + if (tp_ && rs == RefStrength::Weak) + tp_ |= kTagMask; } template @@ -666,12 +674,12 @@ SharedWeakUnion::unsafeReleaseNoStore() auto strongAction = p->releaseStrongRef(); switch (strongAction) { - case noop: + case NoOp: break; - case destroy: + case Destroy: delete p; break; - case partialDestroy: + case PartialDestroy: p->partialDestructor(); partialDestructorFinished(&p); // p is null and may no longer be used @@ -684,9 +692,9 @@ SharedWeakUnion::unsafeReleaseNoStore() auto weakAction = p->releaseWeakRef(); switch (weakAction) { - case noop: + case NoOp: break; - case destroy: + case Destroy: delete p; break; } diff --git a/include/xrpl/basics/IntrusiveRefCounts.h b/include/xrpl/basics/IntrusiveRefCounts.h index e7c31bde93..0b00f1d5b1 100644 --- a/include/xrpl/basics/IntrusiveRefCounts.h +++ b/include/xrpl/basics/IntrusiveRefCounts.h @@ -18,7 +18,7 @@ namespace xrpl { destroy: Run the destructor. This action will occur when either the strong count or weak count is decremented and the other count is also zero. */ -enum class ReleaseStrongRefAction { noop, partialDestroy, destroy }; +enum class ReleaseStrongRefAction { NoOp, PartialDestroy, Destroy }; /** Action to perform when releasing a weak pointer. @@ -28,7 +28,7 @@ enum class ReleaseStrongRefAction { noop, partialDestroy, destroy }; destroy: Run the destructor. This action will occur when either the strong count or weak count is decremented and the other count is also zero. */ -enum class ReleaseWeakRefAction { noop, destroy }; +enum class ReleaseWeakRefAction { NoOp, Destroy }; /** Implement the strong count, weak count, and bit flags for an intrusive pointer. @@ -71,7 +71,7 @@ struct IntrusiveRefCounts expired() const noexcept; std::size_t - use_count() const noexcept; + useCount() const noexcept; // This function MUST be called after a partial destructor finishes running. // Calling this function may cause other threads to delete the object @@ -98,11 +98,11 @@ private: // enough for strong pointers and 14 bit counts are enough for weak // pointers. Use type aliases to make it easy to switch types. using CountType = std::uint16_t; - static constexpr size_t StrongCountNumBits = sizeof(CountType) * 8; - static constexpr size_t WeakCountNumBits = StrongCountNumBits - 2; + static constexpr size_t kStrongCountNumBits = sizeof(CountType) * 8; + static constexpr size_t kWeakCountNumBits = kStrongCountNumBits - 2; using FieldType = std::uint32_t; - static constexpr size_t FieldTypeBits = sizeof(FieldType) * 8; - static constexpr FieldType one = 1; + static constexpr size_t kFieldTypeBits = sizeof(FieldType) * 8; + static constexpr FieldType kOne = 1; /** `refCounts` consists of four fields that are treated atomically: @@ -137,21 +137,21 @@ private: */ - mutable std::atomic refCounts{strongDelta}; + mutable std::atomic refCounts_{kStrongDelta}; /** Amount to change the strong count when adding or releasing a reference Note: The strong count is stored in the low `StrongCountNumBits` bits of refCounts */ - static constexpr FieldType strongDelta = 1; + static constexpr FieldType kStrongDelta = 1; /** Amount to change the weak count when adding or releasing a reference Note: The weak count is stored in the high `WeakCountNumBits` bits of refCounts */ - static constexpr FieldType weakDelta = (one << StrongCountNumBits); + static constexpr FieldType kWeakDelta = (kOne << kStrongCountNumBits); /** Flag that is set when the partialDestroy function has started running (or is about to start running). @@ -159,33 +159,33 @@ private: See description of the `refCounts` field for a fuller description of this field. */ - static constexpr FieldType partialDestroyStartedMask = (one << (FieldTypeBits - 1)); + static constexpr FieldType kPartialDestroyStartedMask = (kOne << (kFieldTypeBits - 1)); /** Flag that is set when the partialDestroy function has finished running See description of the `refCounts` field for a fuller description of this field. */ - static constexpr FieldType partialDestroyFinishedMask = (one << (FieldTypeBits - 2)); + static constexpr FieldType kPartialDestroyFinishedMask = (kOne << (kFieldTypeBits - 2)); /** Mask that will zero out all the `count` bits and leave the tag bits unchanged. */ - static constexpr FieldType tagMask = partialDestroyStartedMask | partialDestroyFinishedMask; + static constexpr FieldType kTagMask = kPartialDestroyStartedMask | kPartialDestroyFinishedMask; /** Mask that will zero out the `tag` bits and leave the count bits unchanged. */ - static constexpr FieldType valueMask = ~tagMask; + static constexpr FieldType kValueMask = ~kTagMask; /** Mask that will zero out everything except the strong count. */ - static constexpr FieldType strongMask = ((one << StrongCountNumBits) - 1) & valueMask; + static constexpr FieldType kStrongMask = ((kOne << kStrongCountNumBits) - 1) & kValueMask; /** Mask that will zero out everything except the weak count. */ - static constexpr FieldType weakMask = - (((one << WeakCountNumBits) - 1) << StrongCountNumBits) & valueMask; + static constexpr FieldType kWeakMask = + (((kOne << kWeakCountNumBits) - 1) << kStrongCountNumBits) & kValueMask; /** Unpack the count and tag fields from the packed atomic integer form. */ struct RefCountPair @@ -210,29 +210,29 @@ private: [[nodiscard]] FieldType combinedValue() const noexcept; - static constexpr CountType maxStrongValue = - static_cast((one << StrongCountNumBits) - 1); - static constexpr CountType maxWeakValue = - static_cast((one << WeakCountNumBits) - 1); + static constexpr CountType kMaxStrongValue = + static_cast((kOne << kStrongCountNumBits) - 1); + static constexpr CountType kMaxWeakValue = + static_cast((kOne << kWeakCountNumBits) - 1); /** Put an extra margin to detect when running up against limits. This is only used in debug code, and is useful if we reduce the number of bits in the strong and weak counts (to 16 and 14 bits). */ - static constexpr CountType checkStrongMaxValue = maxStrongValue - 32; - static constexpr CountType checkWeakMaxValue = maxWeakValue - 32; + static constexpr CountType kCheckStrongMaxValue = kMaxStrongValue - 32; + static constexpr CountType kCheckWeakMaxValue = kMaxWeakValue - 32; }; }; inline void IntrusiveRefCounts::addStrongRef() const noexcept { - refCounts.fetch_add(strongDelta, std::memory_order_acq_rel); + refCounts_.fetch_add(kStrongDelta, std::memory_order_acq_rel); } inline void IntrusiveRefCounts::addWeakRef() const noexcept { - refCounts.fetch_add(weakDelta, std::memory_order_acq_rel); + refCounts_.fetch_add(kWeakDelta, std::memory_order_acq_rel); } inline ReleaseStrongRefAction @@ -246,36 +246,36 @@ IntrusiveRefCounts::releaseStrongRef() const // conditional `fetch_or`. This loop will almost always run once. using enum ReleaseStrongRefAction; - auto prevIntVal = refCounts.load(std::memory_order_acquire); + auto prevIntVal = refCounts_.load(std::memory_order_acquire); while (true) { RefCountPair const prevVal{prevIntVal}; XRPL_ASSERT( - (prevVal.strong >= strongDelta), + (prevVal.strong >= kStrongDelta), "xrpl::IntrusiveRefCounts::releaseStrongRef : previous ref " "higher than new"); - auto nextIntVal = prevIntVal - strongDelta; - ReleaseStrongRefAction action = noop; + auto nextIntVal = prevIntVal - kStrongDelta; + ReleaseStrongRefAction action = NoOp; if (prevVal.strong == 1) { if (prevVal.weak == 0) { - action = destroy; + action = Destroy; } else { - nextIntVal |= partialDestroyStartedMask; - action = partialDestroy; + nextIntVal |= kPartialDestroyStartedMask; + action = PartialDestroy; } } - if (refCounts.compare_exchange_weak(prevIntVal, nextIntVal, std::memory_order_acq_rel)) + if (refCounts_.compare_exchange_weak(prevIntVal, nextIntVal, std::memory_order_acq_rel)) { // Can't be in partial destroy because only decrementing the strong // count to zero can start a partial destroy, and that can't happen // twice. XRPL_ASSERT( - (action == noop) || !(prevIntVal & partialDestroyStartedMask), + (action == NoOp) || !(prevIntVal & kPartialDestroyStartedMask), "xrpl::IntrusiveRefCounts::releaseStrongRef : not in partial " "destroy"); return action; @@ -288,9 +288,9 @@ IntrusiveRefCounts::addWeakReleaseStrongRef() const { using enum ReleaseStrongRefAction; - static_assert(weakDelta > strongDelta); - auto constexpr delta = weakDelta - strongDelta; - auto prevIntVal = refCounts.load(std::memory_order_acquire); + static_assert(kWeakDelta > kStrongDelta); + static constexpr auto kDelta = kWeakDelta - kStrongDelta; + auto prevIntVal = refCounts_.load(std::memory_order_acquire); // This loop will almost always run once. The loop is needed to atomically // change the counts and flags (the count could be atomically changed, but // the flags depend on the current value of the counts). @@ -311,24 +311,24 @@ IntrusiveRefCounts::addWeakReleaseStrongRef() const "xrpl::IntrusiveRefCounts::addWeakReleaseStrongRef : not in " "partial destroy"); - auto nextIntVal = prevIntVal + delta; - ReleaseStrongRefAction action = noop; + auto nextIntVal = prevIntVal + kDelta; + ReleaseStrongRefAction action = NoOp; if (prevVal.strong == 1) { if (prevVal.weak == 0) { - action = noop; + action = NoOp; } else { - nextIntVal |= partialDestroyStartedMask; - action = partialDestroy; + nextIntVal |= kPartialDestroyStartedMask; + action = PartialDestroy; } } - if (refCounts.compare_exchange_weak(prevIntVal, nextIntVal, std::memory_order_acq_rel)) + if (refCounts_.compare_exchange_weak(prevIntVal, nextIntVal, std::memory_order_acq_rel)) { XRPL_ASSERT( - (!(prevIntVal & partialDestroyStartedMask)), + (!(prevIntVal & kPartialDestroyStartedMask)), "xrpl::IntrusiveRefCounts::addWeakReleaseStrongRef : not " "started partial destroy"); return action; @@ -339,7 +339,7 @@ IntrusiveRefCounts::addWeakReleaseStrongRef() const inline ReleaseWeakRefAction IntrusiveRefCounts::releaseWeakRef() const { - auto prevIntVal = refCounts.fetch_sub(weakDelta, std::memory_order_acq_rel); + auto prevIntVal = refCounts_.fetch_sub(kWeakDelta, std::memory_order_acq_rel); RefCountPair prev = prevIntVal; if (prev.weak == 1 && prev.strong == 0) { @@ -348,19 +348,19 @@ IntrusiveRefCounts::releaseWeakRef() const // This case should only be hit if the partialDestroyStartedBit is // set non-atomically (and even then very rarely). The code is kept // in case we need to set the flag non-atomically for perf reasons. - refCounts.wait(prevIntVal, std::memory_order_acquire); - prevIntVal = refCounts.load(std::memory_order_acquire); + refCounts_.wait(prevIntVal, std::memory_order_acquire); + prevIntVal = refCounts_.load(std::memory_order_acquire); prev = RefCountPair{prevIntVal}; } if (prev.partialDestroyFinishedBit == 0u) { // partial destroy MUST finish before running a full destroy (when // using weak pointers) - refCounts.wait(prevIntVal - weakDelta, std::memory_order_acquire); + refCounts_.wait(prevIntVal - kWeakDelta, std::memory_order_acquire); } - return ReleaseWeakRefAction::destroy; + return ReleaseWeakRefAction::Destroy; } - return ReleaseWeakRefAction::noop; + return ReleaseWeakRefAction::NoOp; } inline bool @@ -369,13 +369,13 @@ IntrusiveRefCounts::checkoutStrongRefFromWeak() const noexcept auto curValue = RefCountPair{1, 1}.combinedValue(); auto desiredValue = RefCountPair{2, 1}.combinedValue(); - while (!refCounts.compare_exchange_weak(curValue, desiredValue, std::memory_order_acq_rel)) + while (!refCounts_.compare_exchange_weak(curValue, desiredValue, std::memory_order_acq_rel)) { RefCountPair const prev{curValue}; if (prev.strong == 0u) return false; - desiredValue = curValue + strongDelta; + desiredValue = curValue + kStrongDelta; } return true; } @@ -383,38 +383,38 @@ IntrusiveRefCounts::checkoutStrongRefFromWeak() const noexcept inline bool IntrusiveRefCounts::expired() const noexcept { - RefCountPair const val = refCounts.load(std::memory_order_acquire); + RefCountPair const val = refCounts_.load(std::memory_order_acquire); return val.strong == 0; } inline std::size_t -IntrusiveRefCounts::use_count() const noexcept +IntrusiveRefCounts::useCount() const noexcept { - RefCountPair const val = refCounts.load(std::memory_order_acquire); + RefCountPair const val = refCounts_.load(std::memory_order_acquire); return val.strong; } inline IntrusiveRefCounts::~IntrusiveRefCounts() noexcept { #ifndef NDEBUG - auto v = refCounts.load(std::memory_order_acquire); + auto v = refCounts_.load(std::memory_order_acquire); XRPL_ASSERT( - (!(v & valueMask)), "xrpl::IntrusiveRefCounts::~IntrusiveRefCounts : count must be zero"); - auto t = v & tagMask; - XRPL_ASSERT((!t || t == tagMask), "xrpl::IntrusiveRefCounts::~IntrusiveRefCounts : valid tag"); + (!(v & kValueMask)), "xrpl::IntrusiveRefCounts::~IntrusiveRefCounts : count must be zero"); + auto t = v & kTagMask; + XRPL_ASSERT((!t || t == kTagMask), "xrpl::IntrusiveRefCounts::~IntrusiveRefCounts : valid tag"); #endif } //------------------------------------------------------------------------------ inline IntrusiveRefCounts::RefCountPair::RefCountPair(IntrusiveRefCounts::FieldType v) noexcept - : strong{static_cast(v & strongMask)} - , weak{static_cast((v & weakMask) >> StrongCountNumBits)} - , partialDestroyStartedBit{v & partialDestroyStartedMask} - , partialDestroyFinishedBit{v & partialDestroyFinishedMask} + : strong{static_cast(v & kStrongMask)} + , weak{static_cast((v & kWeakMask) >> kStrongCountNumBits)} + , partialDestroyStartedBit{v & kPartialDestroyStartedMask} + , partialDestroyFinishedBit{v & kPartialDestroyFinishedMask} { XRPL_ASSERT( - (strong < checkStrongMaxValue && weak < checkWeakMaxValue), + (strong < kCheckStrongMaxValue && weak < kCheckWeakMaxValue), "xrpl::IntrusiveRefCounts::RefCountPair(FieldType) : inputs inside " "range"); } @@ -425,7 +425,7 @@ inline IntrusiveRefCounts::RefCountPair::RefCountPair( : strong{s}, weak{w} { XRPL_ASSERT( - (strong < checkStrongMaxValue && weak < checkWeakMaxValue), + (strong < kCheckStrongMaxValue && weak < kCheckWeakMaxValue), "xrpl::IntrusiveRefCounts::RefCountPair(CountType, CountType) : " "inputs inside range"); } @@ -434,11 +434,11 @@ inline IntrusiveRefCounts::FieldType IntrusiveRefCounts::RefCountPair::combinedValue() const noexcept { XRPL_ASSERT( - (strong < checkStrongMaxValue && weak < checkWeakMaxValue), + (strong < kCheckStrongMaxValue && weak < kCheckWeakMaxValue), "xrpl::IntrusiveRefCounts::RefCountPair::combinedValue : inputs " "inside range"); return (static_cast(weak) - << IntrusiveRefCounts::StrongCountNumBits) | + << IntrusiveRefCounts::kStrongCountNumBits) | static_cast(strong) | partialDestroyStartedBit | partialDestroyFinishedBit; } @@ -449,7 +449,7 @@ partialDestructorFinished(T** o) { T& self = **o; IntrusiveRefCounts::RefCountPair const p = - self.refCounts.fetch_or(IntrusiveRefCounts::partialDestroyFinishedMask); + self.refCounts_.fetch_or(IntrusiveRefCounts::kPartialDestroyFinishedMask); XRPL_ASSERT( (!p.partialDestroyFinishedBit && p.partialDestroyStartedBit && !p.strong), "xrpl::partialDestructorFinished : not a weak ref"); @@ -458,7 +458,7 @@ partialDestructorFinished(T** o) // There was a weak count before the partial destructor ran (or we would // have run the full destructor) and now there isn't a weak count. Some // thread is waiting to run the destructor. - self.refCounts.notify_one(); + self.refCounts_.notify_one(); } // Set the pointer to null to emphasize that the object shouldn't be used // after calling this function as it may be destroyed in another thread. diff --git a/include/xrpl/basics/LocalValue.h b/include/xrpl/basics/LocalValue.h index 421ea7af23..1c2a657a18 100644 --- a/include/xrpl/basics/LocalValue.h +++ b/include/xrpl/basics/LocalValue.h @@ -26,17 +26,17 @@ struct LocalValues template struct Value : BasicValue { - T t_; + T t; Value() = default; - explicit Value(T t) : t_(std::move(t)) + explicit Value(T t) : t(std::move(t)) { } void* get() override { - return &t_; + return &t; } }; @@ -55,8 +55,8 @@ template boost::thread_specific_ptr& getLocalValues() { - static boost::thread_specific_ptr tsp(&detail::LocalValues::cleanup); - return tsp; + static boost::thread_specific_ptr kTsp(&detail::LocalValues::cleanup); + return kTsp; } } // namespace detail diff --git a/include/xrpl/basics/Log.h b/include/xrpl/basics/Log.h index 58cca4f486..6bafbc7c54 100644 --- a/include/xrpl/basics/Log.h +++ b/include/xrpl/basics/Log.h @@ -10,23 +10,11 @@ #include #include #include +#include #include namespace xrpl { -// DEPRECATED use beast::severities::Severity instead -enum LogSeverity { - lsINVALID = -1, // used to indicate an invalid severity - lsTRACE = 0, // Very low-level progress information, details inside - // an operation - lsDEBUG = 1, // Function-level progress information, operations - lsINFO = 2, // Server-level progress information, major operations - lsWARNING = 3, // Conditions that warrant human attention, may indicate - // a problem - lsERROR = 4, // A condition that indicates a problem - lsFATAL = 5 // A severe condition that indicates a server problem -}; - /** Manages partitions for logging. */ class Logs { @@ -38,17 +26,17 @@ private: std::string partition_; public: - Sink(std::string partition, beast::severities::Severity thresh, Logs& logs); + Sink(std::string partition, beast::Severity thresh, Logs& logs); Sink(Sink const&) = delete; Sink& operator=(Sink const&) = delete; void - write(beast::severities::Severity level, std::string const& text) override; + write(beast::Severity level, std::string const& text) override; void - writeAlways(beast::severities::Severity level, std::string const& text) override; + writeAlways(beast::Severity level, std::string const& text) override; }; /** Manages a system file containing logged output. @@ -129,18 +117,18 @@ private: /** @} */ private: - std::unique_ptr m_stream; - boost::filesystem::path m_path; + std::unique_ptr stream_; + boost::filesystem::path path_; }; std::mutex mutable mutex_; std::map, boost::beast::iless> sinks_; - beast::severities::Severity thresh_; + beast::Severity thresh_; File file_; bool silent_ = false; public: - Logs(beast::severities::Severity level); + Logs(beast::Severity level); Logs(Logs const&) = delete; Logs& @@ -160,18 +148,18 @@ public: beast::Journal journal(std::string const& name); - beast::severities::Severity + beast::Severity threshold() const; void - threshold(beast::severities::Severity thresh); + threshold(beast::Severity thresh); std::vector> - partition_severities() const; + partitionSeverities() const; void write( - beast::severities::Severity level, + beast::Severity level, std::string const& partition, std::string const& text, bool console); @@ -191,34 +179,25 @@ public: } virtual std::unique_ptr - makeSink(std::string const& partition, beast::severities::Severity startingLevel); + makeSink(std::string const& partition, beast::Severity startingLevel); public: - static LogSeverity - fromSeverity(beast::severities::Severity level); - - static beast::severities::Severity - toSeverity(LogSeverity level); - static std::string - toString(LogSeverity s); + toString(beast::Severity s); - static LogSeverity + static std::optional fromString(std::string const& s); private: - enum { - // Maximum line length for log messages. - // If the message exceeds this length it will be truncated with - // ellipses. - maximumMessageCharacters = 12 * 1024 - }; + // Maximum line length for log messages. + // If the message exceeds this length it will be truncated with ellipses. + static constexpr auto kMaximumMessageCharacters = 12 * 1024; static void format( std::string& output, std::string const& message, - beast::severities::Severity severity, + beast::Severity severity, std::string const& partition); }; diff --git a/include/xrpl/basics/Mutex.hpp b/include/xrpl/basics/Mutex.hpp index 5855ee2017..4432e27b4b 100644 --- a/include/xrpl/basics/Mutex.hpp +++ b/include/xrpl/basics/Mutex.hpp @@ -131,7 +131,7 @@ public: * @tparam LockType The type of lock to use * @return A lock on the mutex and a reference to the protected data */ - template