Compare commits

...

66 Commits

Author SHA1 Message Date
Ayaz Salikhov
3c43f4614f release: Bump version to 3.2.0 2026-06-15 19:46:56 -04:00
dependabot[bot]
6b63f0ff61 ci: [DEPENDABOT] bump codecov/codecov-action from 6.0.1 to 7.0.0 (#7426)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-15 19:46:49 -04:00
Bart
0ac8e6cf1e release: Bump version to 3.2.0-rc6 2026-06-15 22:24:03 +01:00
Vito Tumas
ed5f13481a fix: Disable transaction invariants 2026-06-15 22:24:03 +01:00
Vito Tumas
781ef175c9 perf: Dispatch "hasInvalidAmount()" on type tag instead of dynamic_cast 2026-06-15 22:24:03 +01:00
Ed Hennis
e5785c4fcb fix: Fix Number comparison operator 2026-06-15 22:24:02 +01:00
Michael Legleux
96d0563ea6 fix: Adjust xrpld systemd service 2026-06-15 22:24:02 +01:00
Bart
61dae6f792 release: Bump version to 3.2.0-rc5 2026-06-15 22:24:02 +01:00
yinyiqian1
fded06652a fix: Add zero NFT Offer ID check for NFTokenCancelOffer 2026-06-15 22:24:02 +01:00
Valentin Balaschenko
e833e8884d refactor: Revert "Explicitly trim the heap after cache sweeps (#6022)" 2026-06-15 22:24:02 +01:00
Michael Legleux
8e3eabc398 refactor: Remove auto-update script and update RPM version
* refactor: Update RPM version scheme; remove auto-update script; service hardening

- **RPM version scheme**: pre-releases now use `~` in the `Version` field instead of the `0.<release>.<suffix>` `Release`-field hack. Matches Debian's `~` convention, so RPM and DEB version strings are symmetric. Requires rpm ≥ 4.10 (RHEL 9 ships 4.17).

  Before/after for a pre-release build:
  ```
  # before
  xrpld-3.2.0-0.1.rc3+202606011647.d4cb68d5.el9.x86_64.rpm

  # after (symmetric with DEB)
  xrpld-3.2.0~rc2+202606010139.7679a310-1.el9.x86_64.rpm
  xrpld_3.2.0~rc2+202606010139.7679a310-1_amd64.deb
  ```
- **Auto-update removed**: `update-xrpld`, `update-xrpld.service`, and `update-xrpld.timer` deleted. The `50-xrpld.preset` `disable` line for the timer is dropped too.
- **Service hardening** (two new `[Service]` directives in `xrpld.service`):
  - `CapabilityBoundingSet=CAP_NET_BIND_SERVICE` — drops every Linux capability except `CAP_NET_BIND_SERVICE`, capping the privilege ceiling to least-privilege while still letting operators bind ports <1024 (e.g. WS/HTTPS on 443).
  - `SystemCallArchitectures=native` — restricts the service to the native syscall ABI, blocking alternate-ABI (32-bit/x32) syscalls used to evade seccomp filtering.

- [ ] Build RPM from a pre-release version (e.g. `3.2.0-b1`) and confirm `rpm -qi` shows `Version: 3.2.0~b1`, `Release: 1`
- [ ] Confirm `3.2.0~b1` sorts before `3.2.0` via `rpmvercmp`
- [ ] Install package and confirm no `update-xrpld*` units appear in `systemctl list-unit-files`
- [ ] Confirm `systemctl show xrpld` reflects the new `CapabilityBoundingSet` and `SystemCallArchitectures`

* fix: Track tmpfiles-created directories in RPM %files as %ghost
2026-06-15 22:24:02 +01:00
Sergey Kuznetsov
47b06ecd17 refactor: Use rocksdb includes only when it is available 2026-06-15 22:23:54 +01:00
Bart
5a25c9188b release: Bump version to 3.2.0-rc4 2026-06-15 22:23:53 +01:00
Bart
82ee5b7556 refactor: Handle int and uint API versions separately 2026-06-15 22:23:38 +01:00
Pratik Mankawde
f98c251011 refactor: Improve tracking of book (un)subscriptions 2026-06-15 22:23:38 +01:00
Sergey Kuznetsov
e29dc474b3 refactor: Improve payment channel closing and returned error codes 2026-06-15 22:23:28 +01:00
Pratik Mankawde
2728e11809 fix: Set request size limits and differential pricing for get-object-by-hash calls 2026-06-15 22:23:28 +01:00
Jingchen
9650fe8a6e refactor: Use explicit types to help compiler 2026-06-15 22:22:53 +01:00
Bart
7e15621e7b release: Bump version to 3.2.0-rc3 (#7371)
Co-authored-by: Bart <11445373+bthomee@users.noreply.github.com>
2026-05-31 22:55:18 +00:00
Vito Tumas
99431d7833 fix: Pin overpayment principal reduction to exact on-grid value (#7360) 2026-05-31 22:54:23 +00:00
Ed Hennis
47365f4220 fix: Improve upward rounding edge cases for Number::operator/= (#7328)
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Vito Tumas <5780819+Tapanito@users.noreply.github.com>
2026-05-31 00:23:29 +00:00
Bart
1599c1a672 refactor: Revert "perf: Remove unnecessary caches (#5439)" (#7359)
Co-authored-by: Bart <11445373+bthomee@users.noreply.github.com>
2026-05-30 18:48:59 +00:00
yinyiqian1
763dd503be fix: Add zero domainID check for permissionedDomain (#7362) 2026-05-30 00:16:25 +00:00
Bart
2f3558c610 ci: Run PR title and description checks on staging and release branches (#7331)
Co-authored-by: Bart <11445373+bthomee@users.noreply.github.com>
2026-05-28 14:57:29 +00:00
Ayaz Salikhov
f9551ac5ca style: Run shfmt on workflows, actions and markdown bash code (#7333) 2026-05-27 19:24:18 +00:00
Bart
1acc42313c release: Bump version to 3.2.0-rc2 (#7348) 2026-05-27 15:11:38 -04:00
Bart
396d772a15 refactor: Enable support for fixCleanup3_2_0 amendment (#7347) 2026-05-27 19:10:33 +00:00
Ayaz Salikhov
1438bf1c67 release: Bump version to 3.2.0-rc1 (#7335) 2026-05-27 13:20:57 -04:00
Ed Hennis
7da643d864 fix: Fix a rounding error at the Number::maxRep cusp (#7051)
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Vito Tumas <5780819+Tapanito@users.noreply.github.com>
2026-05-27 15:19:20 +00:00
Ayaz Salikhov
1162371def ci: Only push docker images in XRPLF/rippled (#7330) 2026-05-26 20:03:04 +00:00
dependabot[bot]
2a0feca46b ci: [DEPENDABOT] bump docker/setup-buildx-action from 4.0.0 to 4.1.0 (#7322)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-26 19:36:32 +00:00
dependabot[bot]
108a4c8217 ci: [DEPENDABOT] bump codecov/codecov-action from 6.0.0 to 6.0.1 (#7321)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-26 19:36:21 +00:00
dependabot[bot]
4584b01bde ci: [DEPENDABOT] bump docker/build-push-action from 7.1.0 to 7.2.0 (#7320)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-26 19:36:13 +00:00
dependabot[bot]
7c59786565 ci: [DEPENDABOT] bump docker/metadata-action from 6.0.0 to 6.1.0 (#7319)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-26 19:36:00 +00:00
dependabot[bot]
9623e67b76 ci: [DEPENDABOT] bump docker/login-action from 4.1.0 to 4.2.0 (#7318)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-26 19:35:52 +00:00
Andrzej Budzanowski
85af406a0f fix: Update clang-tidy to include src/tests directory header check (#7307) 2026-05-26 19:35:32 +00:00
Ayaz Salikhov
ac33fb32a7 chore: Pin Python packages for codegen using uv (#7329) 2026-05-26 18:35:38 +00:00
Ayaz Salikhov
23d0812827 style: Use shfmt instead of bashate (#7326) 2026-05-26 18:28:23 +00:00
Vito Tumas
49567e7283 fix: Fix edge-case where vault-depositor may get stuck (#7139) 2026-05-26 18:18:40 +00:00
Vito Tumas
633ef4706f fix: Fix VaultInvariant and VaultDeposit precision bugs at IOU scale boundaries (#7272)
Co-authored-by: Bart <bthomee@users.noreply.github.com>
2026-05-26 16:32:44 +00:00
Ayaz Salikhov
49cb3f45a4 ci: Add clang to nix images (#7308)
Co-authored-by: semgrep-companion-app[bot] <218312740+semgrep-companion-app[bot]@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-05-26 15:45:33 +00:00
Vito Tumas
22a21b175e fix: Include management-fee delta in doOverpayment assertion (#7039) 2026-05-26 14:01:52 +00:00
Pratik Mankawde
e9d885bd9b fix: Fix clang-tidy pre-commit hook to locate compile_commands.json from repo root (#7325)
Signed-off-by: Pratik Mankawde <3397372+pratikmankawde@users.noreply.github.com>
2026-05-26 13:50:18 +00:00
Jingchen
a911f9089e fix: Use consistent scale for debtTotal (#7093)
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-05-24 20:44:29 +00:00
Peter Chen
e34c2667d7 fix: Skip deleted book directories and non-root modifications in ValidBookDirectory invariant (#7312) 2026-05-24 20:37:16 +00:00
Valentin Balaschenko
30de556224 fix: Address review feedback on FD/handle guarding (#5823 follow-up) (#7310) 2026-05-23 14:48:48 +00:00
Gregory Tsipenyuk
dcd2ff0b5f fix: Fix non-canonical MPT amount (#7117)
Co-authored-by: xrplf-ai-reviewer[bot] <266832837+xrplf-ai-reviewer[bot]@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-05-23 06:40:26 +00:00
Bart
dfb9b8ed9a release: Bump version to 3.2.0-b7 (#7316)
Co-authored-by: Bart <11445373+bthomee@users.noreply.github.com>
2026-05-22 19:32:12 +00:00
Jingchen
179e73594a fix: Check if the MPT first loss cover can be sent to the broker before deleting the broker (#7125)
Co-authored-by: xrplf-ai-reviewer[bot] <266832837+xrplf-ai-reviewer[bot]@users.noreply.github.com>
2026-05-22 11:58:48 +00:00
Michael Legleux
15dd653e4b fix: Fix RPM prerelease ordering and start xrpld on DEB install (#7313) 2026-05-22 11:30:45 +00:00
Michael Legleux
a37afe13ff ci: Re-enable full nproc for Linux (#7315) 2026-05-22 11:30:37 +00:00
Gregory Tsipenyuk
3547a9335f fix: Add assorted MPT/DEX fixes (#7040)
Co-authored-by: xrplf-ai-reviewer[bot] <266832837+xrplf-ai-reviewer[bot]@users.noreply.github.com>
Co-authored-by: Shawn Xie <35279399+shawnxie999@users.noreply.github.com>
2026-05-21 18:29:53 +00:00
Bart
1a98182e23 refactor: Remove dead fetchBatch code (#7309)
Co-authored-by: Bart <11445373+bthomee@users.noreply.github.com>
2026-05-21 17:52:41 +00:00
Bart
79308705c5 release: Bump version to 3.2.0-b6 (#7311)
Co-authored-by: Bart <11445373+bthomee@users.noreply.github.com>
2026-05-21 17:50:59 +00:00
Vito Tumas
e24de65f42 chore: Revert graceful peer disconnection and follow-up fix (#7296) 2026-05-21 16:13:41 +00:00
Vito Tumas
7fdaa0a5ef fix: Fix IOU precision issues in LoanBrokerCover transactions (#7274) 2026-05-21 14:51:58 +00:00
Vito Tumas
795dc5e364 fix: Avoid principal-zeroing in non-final loan payments at coarse scale (#7050)
Co-authored-by: Ed Hennis <ed@ripple.com>
2026-05-21 14:46:26 +00:00
Pratik Mankawde
f6fd5ddb0a fix: Add null check (#7305)
Signed-off-by: Pratik Mankawde <3397372+pratikmankawde@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-21 13:24:04 +00:00
Rithvik Reddygari
afcf6fbcdc docs: Add --parallel flag to cmake build commands in BUILD.md (#7302) 2026-05-21 06:33:19 +00:00
Shawn Xie
28cc20c816 fix: Fix wrong hybrid offer orderbook placement and update LedgerStateFix to amend ExchangeRate meta (#7087)
Co-authored-by: Peter Chen <ychen@ripple.com>
2026-05-21 06:19:04 +00:00
Alex Kremer
a830ab10ef style: More clang-tidy identifier renaming (#7290) 2026-05-20 21:31:15 +00:00
Shawn Xie
8c0080020f fix: Update pDEX invariant firing under a valid offer deletion (#7118)
Co-authored-by: Peter Chen <ychen@ripple.com>
2026-05-20 21:10:04 +00:00
yinyiqian1
9cb0740673 fix: Fix multisign and signfor to check for delegate (#7064) 2026-05-20 20:24:09 +00:00
Mayukha Vadari
242ce3e9e4 refactor: Fix sfGeneric and sfInvalid field names (#7300) 2026-05-20 19:47:59 +00:00
box4wangjing
a5d238e7d4 docs: Fix some comments to improve readability (#7122)
Signed-off-by: box4wangjing <box4wangjing@outlook.com>
Co-authored-by: Mayukha Vadari <mvadari@ripple.com>
2026-05-20 19:46:45 +00:00
Vito Tumas
9cb049276d feat: Propagate underlying MPT flags to vault shares (#7077)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Denis Angell <dangell@transia.co>
Co-authored-by: Fomo <508629+shortthefomo@users.noreply.github.com>
Co-authored-by: Bart <bthomee@users.noreply.github.com>
Co-authored-by: Ayaz Salikhov <mathbunnyru@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-20 19:44:09 +00:00
364 changed files with 14612 additions and 5329 deletions

View File

@@ -153,6 +153,7 @@ Checks: "-*,
readability-use-std-min-max readability-use-std-min-max
" "
# --- # ---
# bugprone-narrowing-conversions, # this will break a lot of code but we should enable it in the future because it can eliminate a lot of bugs
# readability-inconsistent-declaration-parameter-name, # in this codebase this check will break a lot of arg names # readability-inconsistent-declaration-parameter-name, # in this codebase this check will break a lot of arg names
# readability-static-accessed-through-instance, # this check is probably unnecessary. it makes the code less readable # readability-static-accessed-through-instance, # this check is probably unnecessary. it makes the code less readable
# --- # ---
@@ -191,11 +192,14 @@ CheckOptions:
readability-identifier-naming.ParameterCase: camelBack readability-identifier-naming.ParameterCase: camelBack
readability-identifier-naming.FunctionCase: camelBack readability-identifier-naming.FunctionCase: camelBack
readability-identifier-naming.MemberCase: camelBack readability-identifier-naming.MemberCase: camelBack
readability-identifier-naming.PrivateMemberCase: camelBack
readability-identifier-naming.PrivateMemberSuffix: _ readability-identifier-naming.PrivateMemberSuffix: _
readability-identifier-naming.ProtectedMemberCase: camelBack
readability-identifier-naming.ProtectedMemberSuffix: _ readability-identifier-naming.ProtectedMemberSuffix: _
readability-identifier-naming.PublicMemberCase: camelBack
readability-identifier-naming.PublicMemberSuffix: "" readability-identifier-naming.PublicMemberSuffix: ""
readability-identifier-naming.GlobalFunctionIgnoredRegexp: "^(to_string|hash_append|tuple_hash)$" readability-identifier-naming.GlobalFunctionIgnoredRegexp: "^(to_string|hash_append|tuple_hash)$"
HeaderFilterRegex: '^.*/(test|xrpl|xrpld)/.*\.(h|hpp|ipp)$' HeaderFilterRegex: '^.*/(tests?|xrpl|xrpld)/.*\.(h|hpp|ipp)$'
ExcludeHeaderFilterRegex: '^.*/protocol_autogen/.*\.(h|hpp)$' ExcludeHeaderFilterRegex: '^.*/protocol_autogen/.*\.(h|hpp)$'
WarningsAsErrors: "*" WarningsAsErrors: "*"

View File

@@ -37,12 +37,12 @@ runs:
run: | run: |
echo 'Installing dependencies.' echo 'Installing dependencies.'
conan install \ conan install \
--profile ci \ --profile ci \
--build="${BUILD_OPTION}" \ --build="${BUILD_OPTION}" \
--options:host='&:tests=True' \ --options:host='&:tests=True' \
--options:host='&:xrpld=True' \ --options:host='&:xrpld=True' \
--settings:all build_type="${BUILD_TYPE}" \ --settings:all build_type="${BUILD_TYPE}" \
--conf:all tools.build:jobs=${BUILD_NPROC} \ --conf:all tools.build:jobs=${BUILD_NPROC} \
--conf:all tools.build:verbosity="${LOG_VERBOSITY}" \ --conf:all tools.build:verbosity="${LOG_VERBOSITY}" \
--conf:all tools.compilation:verbosity="${LOG_VERBOSITY}" \ --conf:all tools.compilation:verbosity="${LOG_VERBOSITY}" \
. .

View File

@@ -15,7 +15,7 @@ runs:
shell: bash shell: bash
env: env:
VERSION: ${{ github.ref_name }} 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 # 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. # from the BuildInfo.cpp file and the shortened commit hash appended to it.
@@ -28,17 +28,17 @@ runs:
echo 'Extracting version from BuildInfo.cpp.' echo 'Extracting version from BuildInfo.cpp.'
VERSION="$(cat src/libxrpl/protocol/BuildInfo.cpp | grep "versionString =" | awk -F '"' '{print $2}')" VERSION="$(cat src/libxrpl/protocol/BuildInfo.cpp | grep "versionString =" | awk -F '"' '{print $2}')"
if [[ -z "${VERSION}" ]]; then if [[ -z "${VERSION}" ]]; then
echo 'Unable to extract version from BuildInfo.cpp.' echo 'Unable to extract version from BuildInfo.cpp.'
exit 1 exit 1
fi fi
echo 'Appending shortened commit hash to version.' echo 'Appending shortened commit hash to version.'
SHA='${{ github.sha }}' SHA='${{ github.sha }}'
VERSION="${VERSION}+${SHA:0:7}" VERSION="${VERSION}+${SHA:0:7}"
echo "VERSION=${VERSION}" >> "${GITHUB_ENV}" echo "VERSION=${VERSION}" >>"${GITHUB_ENV}"
- name: Output version - name: Output version
id: version id: version
shell: bash shell: bash
run: echo "version=${VERSION}" >> "${GITHUB_OUTPUT}" run: echo "version=${VERSION}" >>"${GITHUB_OUTPUT}"

403
.github/scripts/format-inline-bash.py vendored Executable file
View File

@@ -0,0 +1,403 @@
#!/usr/bin/env python3
"""
Format embedded shell snippets using the shfmt hook configured in
.pre-commit-config.yaml.
Two shapes are recognised:
* YAML workflow/action files: literal block-scalar runs (`run: |`) and
single-line runs (`run: some command`). A single-line run is upgraded to
a `run: |` block scalar if shfmt's output spans multiple lines.
* Markdown files: ``` ```bash ``` fenced code blocks.
Any block that shfmt cannot parse is skipped with a warning on stderr, so
the file is left untouched and surrounding blocks still get formatted.
For each occurrence the body is dedented, written to a temp .sh file,
formatted via `pre-commit run shfmt --files <temp>` (falling back to
`prek`), then re-indented and written back in place.
When invoked without arguments, every .yml/.yaml under .github/ plus every
.md file in the repo is scanned. When invoked with file arguments (the
pre-commit case), only those files are processed.
"""
from __future__ import annotations
import re
import shutil
import subprocess
import sys
import tempfile
from dataclasses import dataclass
from pathlib import Path
from typing import Union
REPO = Path(__file__).resolve().parents[2]
_HOOK_RUNNER = next((cmd for cmd in ("pre-commit", "prek") if shutil.which(cmd)), None)
if _HOOK_RUNNER is None:
sys.exit("error: neither `pre-commit` nor `prek` found on PATH")
RUN_BLOCK_RE = re.compile(r"^(?P<prefix>[ \t]*(?:- )?)run:[ \t]*\|[+-]?[ \t]*$")
RUN_INLINE_RE = re.compile(
r"^(?P<prefix>[ \t]*(?:- )?)run:[ \t]+" r"(?P<value>(?!\|[+-]?[ \t]*$)\S.*?)[ \t]*$"
)
MD_BASH_OPEN_RE = re.compile(r"^(?P<indent>[ ]{0,3})`{3}bash[ \t]*$")
MD_FENCE_CLOSE_RE = re.compile(r"^[ ]{0,3}`{3,}[ \t]*$")
@dataclass(frozen=True)
class BlockRun:
"""A `run: |` block scalar; `body_start:body_end` slices into `lines`."""
body_start: int
body_end: int
body_indent: int
@dataclass(frozen=True)
class InlineRun:
"""A single-line `run: value` at `line_idx`."""
line_idx: int
prefix: str
value: str
@dataclass(frozen=True)
class MdBashBlock:
"""A markdown ``` ```bash ``` fenced code block.
`body_start:body_end` slices into the file's lines; `open_line_idx`
points at the opening fence line.
"""
open_line_idx: int
body_start: int
body_end: int
body_indent: int
RunItem = Union[BlockRun, InlineRun]
def _scan_block_body(
lines: list[str], body_start: int, run_col: int
) -> tuple[int | None, int]:
"""Locate the body of a `run: |` block scalar starting at `body_start`.
Returns `(body_indent, scan_end)`. `scan_end` is the line index where the
outer scanner should resume. `body_indent` is `None` when no body is
present (the scalar is empty, or the next non-blank line has indent
`<= run_col`).
"""
body_indent: int | None = None
scan_end = len(lines)
for idx in range(body_start, len(lines)):
line = lines[idx]
if line.strip() == "":
continue
indent = len(line) - len(line.lstrip(" "))
if body_indent is None:
if indent > run_col:
body_indent = indent
else:
scan_end = idx
break
elif indent < body_indent:
scan_end = idx
break
if body_indent is not None:
while scan_end > body_start and lines[scan_end - 1].strip() == "":
scan_end -= 1
if scan_end <= body_start:
body_indent = None
return body_indent, scan_end
def find_run_blocks(lines: list[str]) -> list[RunItem]:
"""Return run items in document order."""
items: list[RunItem] = []
line_idx = 0
while line_idx < len(lines):
line = lines[line_idx]
if block_match := RUN_BLOCK_RE.match(line):
run_col = len(block_match.group("prefix"))
body_start = line_idx + 1
body_indent, scan_end = _scan_block_body(lines, body_start, run_col)
if body_indent is not None:
items.append(
BlockRun(
body_start=body_start,
body_end=scan_end,
body_indent=body_indent,
)
)
line_idx = scan_end
continue
if inline_match := RUN_INLINE_RE.match(line):
items.append(
InlineRun(
line_idx=line_idx,
prefix=inline_match.group("prefix"),
value=inline_match.group("value"),
)
)
line_idx += 1
return items
def find_md_bash_blocks(lines: list[str]) -> list[MdBashBlock]:
"""Return ``` ```bash ``` fenced code blocks in document order."""
blocks: list[MdBashBlock] = []
line_idx = 0
while line_idx < len(lines):
open_match = MD_BASH_OPEN_RE.match(lines[line_idx])
if not open_match:
line_idx += 1
continue
body_start = line_idx + 1
close_idx = next(
(
j
for j in range(body_start, len(lines))
if MD_FENCE_CLOSE_RE.match(lines[j])
),
None,
)
if close_idx is None:
line_idx = body_start
continue
body = lines[body_start:close_idx]
non_blank = [b for b in body if b.strip()]
body_indent = (
min(len(b) - len(b.lstrip(" ")) for b in non_blank)
if non_blank
else len(open_match.group("indent"))
)
blocks.append(
MdBashBlock(
open_line_idx=line_idx,
body_start=body_start,
body_end=close_idx,
body_indent=body_indent,
)
)
line_idx = close_idx + 1
return blocks
def dedent(lines: list[str], n: int) -> list[str]:
pad = " " * n
return [
(
""
if line.strip() == ""
else (line[n:] if line.startswith(pad) else line.lstrip(" "))
)
for line in lines
]
def reindent(lines: list[str], n: int) -> list[str]:
pad = " " * n
return [pad + line if line else "" for line in lines]
_SHFMT_ERR_RE = re.compile(r"\.sh:\d+:\d+:\s")
_GHA_EXPR_RE = re.compile(r"\$\{\{.*?\}\}", re.DOTALL)
_GHA_PLACEHOLDER_RE = re.compile(r"__GHA_EXPR_(\d+)__")
def _encode_gha_exprs(text: str) -> tuple[str, list[str]]:
"""Replace `${{ ... }}` expressions with bash-safe placeholder identifiers."""
exprs: list[str] = []
def repl(match: re.Match[str]) -> str:
exprs.append(match.group(0))
return f"__GHA_EXPR_{len(exprs) - 1}__"
return _GHA_EXPR_RE.sub(repl, text), exprs
def _decode_gha_exprs(text: str, exprs: list[str]) -> str:
"""Restore `${{ ... }}` expressions from placeholder identifiers."""
return _GHA_PLACEHOLDER_RE.sub(lambda m: exprs[int(m.group(1))], text)
def shfmt_via_hook(tmp_path: Path) -> tuple[bool, str]:
# `${{ ... }}` is not valid shell, so swap it for a placeholder identifier
# that shfmt can parse, then restore it after formatting.
encoded, exprs = _encode_gha_exprs(tmp_path.read_text())
if exprs:
tmp_path.write_text(encoded)
res = subprocess.run(
[_HOOK_RUNNER, "run", "shfmt", "--files", str(tmp_path)],
cwd=REPO,
capture_output=True,
text=True,
)
output = res.stdout + res.stderr
# shfmt emits parse errors as "<path>:<line>:<col>: <message>".
parse_err = bool(_SHFMT_ERR_RE.search(output))
# A non-zero exit that is neither a parse error nor pre-commit's "I had
# to modify files" signal means the hook itself failed to run (missing
# binary, install failure, bad config, ...). Surface that loudly rather
# than silently treating it as a no-op.
if (
res.returncode != 0
and not parse_err
and "files were modified by this hook" not in output
):
sys.exit(
f"error: `{_HOOK_RUNNER} run shfmt` failed with exit {res.returncode}:\n{output}"
)
if exprs and not parse_err:
tmp_path.write_text(_decode_gha_exprs(tmp_path.read_text(), exprs))
return not parse_err, output
def _skip(path: Path, where: int, kind: str, output: str) -> None:
print(
f" shfmt could not parse {kind} at {path}:{where + 1} — skipped",
file=sys.stderr,
)
print(f" {output.strip()}", file=sys.stderr)
def process_yaml_file(path: Path, tmp_path: Path) -> int:
text = path.read_text()
had_nl = text.endswith("\n")
lines = text.split("\n")
if had_nl:
lines = lines[:-1]
items = find_run_blocks(lines)
if not items:
return 0
changed = 0
# Process in reverse so earlier indices remain valid as we splice.
for item in reversed(items):
if isinstance(item, BlockRun):
body = lines[item.body_start : item.body_end]
tmp_path.write_text("\n".join(dedent(body, item.body_indent)) + "\n")
ok, output = shfmt_via_hook(tmp_path)
if not ok:
_skip(path, item.body_start, "block", output)
continue
formatted = tmp_path.read_text().rstrip("\n")
new_body = reindent(formatted.split("\n"), item.body_indent)
if new_body != body:
lines[item.body_start : item.body_end] = new_body
changed += 1
else:
tmp_path.write_text(item.value + "\n")
ok, output = shfmt_via_hook(tmp_path)
if not ok:
_skip(path, item.line_idx, "inline run", output)
continue
formatted = tmp_path.read_text().rstrip("\n")
if formatted == item.value:
continue
formatted_lines = formatted.split("\n")
if len(formatted_lines) == 1:
lines[item.line_idx] = f"{item.prefix}run: {formatted}"
else:
body_indent = len(item.prefix) + 2
lines[item.line_idx : item.line_idx + 1] = [
f"{item.prefix}run: |",
*reindent(formatted_lines, body_indent),
]
changed += 1
new_text = "\n".join(lines) + ("\n" if had_nl else "")
if new_text != text:
path.write_text(new_text)
return changed
def process_md_file(path: Path, tmp_path: Path) -> int:
text = path.read_text()
had_nl = text.endswith("\n")
lines = text.split("\n")
if had_nl:
lines = lines[:-1]
blocks = find_md_bash_blocks(lines)
if not blocks:
return 0
changed = 0
for block in reversed(blocks):
body = lines[block.body_start : block.body_end]
tmp_path.write_text("\n".join(dedent(body, block.body_indent)) + "\n")
ok, output = shfmt_via_hook(tmp_path)
if not ok:
_skip(path, block.open_line_idx, "```bash block", output)
continue
formatted = tmp_path.read_text().rstrip("\n")
formatted_lines = formatted.split("\n") if formatted else []
new_body = reindent(formatted_lines, block.body_indent)
if new_body != body:
lines[block.body_start : block.body_end] = new_body
changed += 1
new_text = "\n".join(lines) + ("\n" if had_nl else "")
if new_text != text:
path.write_text(new_text)
return changed
def process_file(path: Path, tmp_path: Path) -> int:
if path.suffix in (".yml", ".yaml"):
return process_yaml_file(path, tmp_path)
if path.suffix == ".md":
return process_md_file(path, tmp_path)
return 0
def gather_files(argv: list[str]) -> list[Path]:
"""Return YAML workflow/action files and markdown files that we should
process — either the paths in `argv` or, when `argv` is empty, every
such file in the repo (skipping `external/`)."""
if argv:
candidates: list[Path] = [
(REPO / a).resolve() if not Path(a).is_absolute() else Path(a) for a in argv
]
else:
gh = REPO / ".github"
candidates = [
*gh.rglob("*.yml"),
*gh.rglob("*.yaml"),
*(
p
for p in REPO.rglob("*.md")
if "external" not in p.relative_to(REPO).parts
),
]
return sorted(
p
for p in candidates
if p.exists()
and (
(p.suffix in (".yml", ".yaml") and ".github" in p.parts)
or p.suffix == ".md"
)
)
def main(argv: list[str]) -> int:
files = gather_files(argv)
if not files:
return 0
with tempfile.TemporaryDirectory(prefix="format-inline-bash-") as tmpdir:
tmp_path = Path(tmpdir) / "shfmt.sh"
total = 0
for f in files:
n = process_file(f, tmp_path)
if n:
print(f"{f.relative_to(REPO)}: reformatted {n} block(s)")
total += n
return 1 if total else 0
if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))

0
.github/scripts/levelization/generate.py vendored Normal file → Executable file
View File

View File

@@ -12,7 +12,6 @@ libxrpl.ledger > xrpl.json
libxrpl.ledger > xrpl.ledger libxrpl.ledger > xrpl.ledger
libxrpl.ledger > xrpl.nodestore libxrpl.ledger > xrpl.nodestore
libxrpl.ledger > xrpl.protocol libxrpl.ledger > xrpl.protocol
libxrpl.ledger > xrpl.server
libxrpl.ledger > xrpl.shamap libxrpl.ledger > xrpl.shamap
libxrpl.net > xrpl.basics libxrpl.net > xrpl.basics
libxrpl.net > xrpl.net libxrpl.net > xrpl.net
@@ -206,7 +205,6 @@ xrpl.core > xrpl.protocol
xrpl.json > xrpl.basics xrpl.json > xrpl.basics
xrpl.ledger > xrpl.basics xrpl.ledger > xrpl.basics
xrpl.ledger > xrpl.protocol xrpl.ledger > xrpl.protocol
xrpl.ledger > xrpl.server
xrpl.ledger > xrpl.shamap xrpl.ledger > xrpl.shamap
xrpl.net > xrpl.basics xrpl.net > xrpl.basics
xrpl.nodestore > xrpl.basics xrpl.nodestore > xrpl.basics

View File

@@ -6,7 +6,7 @@ set -e
# On MacOS, ensure that GNU sed is installed and available as `gsed`. # On MacOS, ensure that GNU sed is installed and available as `gsed`.
SED_COMMAND=sed SED_COMMAND=sed
if [[ "${OSTYPE}" == 'darwin'* ]]; then 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'." echo "Error: gsed is not installed. Please install it using 'brew install gnu-sed'."
exit 1 exit 1
fi fi

View File

@@ -8,12 +8,12 @@ set -e
SED_COMMAND=sed SED_COMMAND=sed
HEAD_COMMAND=head HEAD_COMMAND=head
if [[ "${OSTYPE}" == 'darwin'* ]]; then 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'." echo "Error: gsed is not installed. Please install it using 'brew install gnu-sed'."
exit 1 exit 1
fi fi
SED_COMMAND=gsed 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'." echo "Error: ghead is not installed. Please install it using 'brew install coreutils'."
exit 1 exit 1
fi 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. # The script has been rerun, so just restore the name of the binary.
${SED_COMMAND} -i 's/"xrpld"/"rippled"/' cmake/XrplCore.cmake ${SED_COMMAND} -i 's/"xrpld"/"rippled"/' cmake/XrplCore.cmake
elif ! grep -q '"rippled"' cmake/XrplCore.cmake; then elif ! grep -q '"rippled"' cmake/XrplCore.cmake; then
${HEAD_COMMAND} -n -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 ' # 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 echo ' set_target_properties(xrpld PROPERTIES OUTPUT_NAME "rippled")' >>cmake.tmp
tail -1 cmake/XrplCore.cmake >> cmake.tmp tail -1 cmake/XrplCore.cmake >>cmake.tmp
mv cmake.tmp cmake/XrplCore.cmake mv cmake.tmp cmake/XrplCore.cmake
fi fi

View File

@@ -6,7 +6,7 @@ set -e
# On MacOS, ensure that GNU sed is installed and available as `gsed`. # On MacOS, ensure that GNU sed is installed and available as `gsed`.
SED_COMMAND=sed SED_COMMAND=sed
if [[ "${OSTYPE}" == 'darwin'* ]]; then 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'." echo "Error: gsed is not installed. Please install it using 'brew install gnu-sed'."
exit 1 exit 1
fi fi

View File

@@ -6,7 +6,7 @@ set -e
# On MacOS, ensure that GNU sed is installed and available as `gsed`. # On MacOS, ensure that GNU sed is installed and available as `gsed`.
SED_COMMAND=sed SED_COMMAND=sed
if [[ "${OSTYPE}" == 'darwin'* ]]; then 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'." echo "Error: gsed is not installed. Please install it using 'brew install gnu-sed'."
exit 1 exit 1
fi fi
@@ -62,37 +62,37 @@ done
# restoring the verbiage that is already present in LICENSE.md. Ensure that if # restoring the verbiage that is already present in LICENSE.md. Ensure that if
# the script is run multiple times, duplicate notices are not added. # the script is run multiple times, duplicate notices are not added.
if ! grep -q 'Raw Material Software' include/xrpl/beast/core/CurrentThreadName.h; then 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 fi
if ! grep -q 'Dev Null' src/test/app/NetworkID_test.cpp; then 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 fi
if ! grep -q 'Dev Null' src/test/app/tx/apply_test.cpp; then 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 fi
if ! grep -q 'Dev Null' src/test/rpc/ManifestRPC_test.cpp; then 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 fi
if ! grep -q 'Dev Null' src/test/rpc/ValidatorInfo_test.cpp; then 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 fi
if ! grep -q 'Dev Null' src/xrpld/rpc/handlers/server_info/Manifest.cpp; then 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 fi
if ! grep -q 'Dev Null' src/xrpld/rpc/handlers/admin/status/ValidatorInfo.cpp; then 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 fi
if ! grep -q 'Bougalis' include/xrpl/basics/SlabAllocator.h; then if ! grep -q 'Bougalis' include/xrpl/basics/SlabAllocator.h; then
echo -e "// Copyright (c) 2022, Nikolaos D. Bougalis <nikb@bougalis.net>\n\n$(cat include/xrpl/basics/SlabAllocator.h)" > include/xrpl/basics/SlabAllocator.h # cspell: ignore Nikolaos Bougalis nikb echo -e "// Copyright (c) 2022, Nikolaos D. Bougalis <nikb@bougalis.net>\n\n$(cat include/xrpl/basics/SlabAllocator.h)" >include/xrpl/basics/SlabAllocator.h # cspell: ignore Nikolaos Bougalis nikb
fi fi
if ! grep -q 'Bougalis' include/xrpl/basics/spinlock.h; then if ! grep -q 'Bougalis' include/xrpl/basics/spinlock.h; then
echo -e "// Copyright (c) 2022, Nikolaos D. Bougalis <nikb@bougalis.net>\n\n$(cat include/xrpl/basics/spinlock.h)" > include/xrpl/basics/spinlock.h # cspell: ignore Nikolaos Bougalis nikb echo -e "// Copyright (c) 2022, Nikolaos D. Bougalis <nikb@bougalis.net>\n\n$(cat include/xrpl/basics/spinlock.h)" >include/xrpl/basics/spinlock.h # cspell: ignore Nikolaos Bougalis nikb
fi fi
if ! grep -q 'Bougalis' include/xrpl/basics/tagged_integer.h; then if ! grep -q 'Bougalis' include/xrpl/basics/tagged_integer.h; then
echo -e "// Copyright (c) 2014, Nikolaos D. Bougalis <nikb@bougalis.net>\n\n$(cat include/xrpl/basics/tagged_integer.h)" > include/xrpl/basics/tagged_integer.h # cspell: ignore Nikolaos Bougalis nikb echo -e "// Copyright (c) 2014, Nikolaos D. Bougalis <nikb@bougalis.net>\n\n$(cat include/xrpl/basics/tagged_integer.h)" >include/xrpl/basics/tagged_integer.h # cspell: ignore Nikolaos Bougalis nikb
fi fi
if ! grep -q 'Ritchford' include/xrpl/beast/utility/Zero.h; then if ! grep -q 'Ritchford' include/xrpl/beast/utility/Zero.h; then
echo -e "// Copyright (c) 2014, Tom Ritchford <tom@swirly.com>\n\n$(cat include/xrpl/beast/utility/Zero.h)" > include/xrpl/beast/utility/Zero.h # cspell: ignore Ritchford echo -e "// Copyright (c) 2014, Tom Ritchford <tom@swirly.com>\n\n$(cat include/xrpl/beast/utility/Zero.h)" >include/xrpl/beast/utility/Zero.h # cspell: ignore Ritchford
fi fi
# Restore newlines and tabs in string literals in the affected file. # Restore newlines and tabs in string literals in the affected file.

View File

@@ -6,7 +6,7 @@ set -e
# On MacOS, ensure that GNU sed is installed and available as `gsed`. # On MacOS, ensure that GNU sed is installed and available as `gsed`.
SED_COMMAND=sed SED_COMMAND=sed
if [[ "${OSTYPE}" == 'darwin'* ]]; then 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'." echo "Error: gsed is not installed. Please install it using 'brew install gnu-sed'."
exit 1 exit 1
fi fi

View File

@@ -6,7 +6,7 @@ set -e
# On MacOS, ensure that GNU sed is installed and available as `gsed`. # On MacOS, ensure that GNU sed is installed and available as `gsed`.
SED_COMMAND=sed SED_COMMAND=sed
if [[ "${OSTYPE}" == 'darwin'* ]]; then 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'." echo "Error: gsed is not installed. Please install it using 'brew install gnu-sed'."
exit 1 exit 1
fi fi

View File

@@ -6,7 +6,7 @@ set -e
# On MacOS, ensure that GNU sed is installed and available as `gsed`. # On MacOS, ensure that GNU sed is installed and available as `gsed`.
SED_COMMAND=sed SED_COMMAND=sed
if [[ "${OSTYPE}" == 'darwin'* ]]; then 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'." echo "Error: gsed is not installed. Please install it using 'brew install gnu-sed'."
exit 1 exit 1
fi fi

View File

@@ -6,14 +6,16 @@ on:
- develop - develop
paths: paths:
- ".github/workflows/build-nix-image.yml" - ".github/workflows/build-nix-image.yml"
- "docker/nix.Dockerfile" - ".github/workflows/reusable-build-docker-image.yml"
- "docker/**"
- "flake.nix" - "flake.nix"
- "flake.lock" - "flake.lock"
- "nix/**" - "nix/**"
pull_request: pull_request:
paths: paths:
- ".github/workflows/build-nix-image.yml" - ".github/workflows/build-nix-image.yml"
- "docker/nix.Dockerfile" - ".github/workflows/reusable-build-docker-image.yml"
- "docker/**"
- "flake.nix" - "flake.nix"
- "flake.lock" - "flake.lock"
- "nix/**" - "nix/**"
@@ -27,75 +29,81 @@ defaults:
run: run:
shell: bash shell: bash
env:
UBUNTU_VERSION: "20.04"
RHEL_VERSION: "9"
DEBIAN_VERSION: "bookworm"
jobs: jobs:
build: build:
name: Build and push Nix image (${{ matrix.distro }}) 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 runs-on: ubuntu-latest
permissions: permissions:
contents: read contents: read
packages: write packages: write
strategy: strategy:
fail-fast: false
matrix: matrix:
include: distro: [nixos, ubuntu, rhel, debian]
- distro: nixos env:
- distro: ubuntu IMAGE_NAME: ghcr.io/xrplf/xrpld/nix-${{ matrix.distro }}
- distro: rhel
- distro: debian
steps: steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Determine base image
id: vars
run: |
case "${{ matrix.distro }}" in
nixos)
echo "base_image=nixos/nix:latest" >> $GITHUB_OUTPUT
;;
ubuntu)
echo "base_image=ubuntu:${UBUNTU_VERSION}" >> $GITHUB_OUTPUT
;;
rhel)
echo "base_image=registry.access.redhat.com/ubi${RHEL_VERSION}/ubi:latest" >> $GITHUB_OUTPUT
;;
debian)
echo "base_image=debian:${DEBIAN_VERSION}" >> $GITHUB_OUTPUT
;;
esac
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 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 - name: Login to GitHub Container Registry
if: github.event_name == 'push' uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.repository_owner }} username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Docker metadata - name: Create multi-arch manifests
id: meta run: |
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0 for tag in $(jq -cr '.tags[]' <<<"$DOCKER_METADATA_OUTPUT_JSON"); do
with: docker buildx imagetools create -t "$tag" "${tag}-amd64" "${tag}-arm64"
images: ghcr.io/xrplf/ci/nix-${{ matrix.distro }} done
tags: |
type=sha,prefix=sha-,format=short
type=raw,value=latest
- name: Build and push - name: Inspect image
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0 run: |
with: docker buildx imagetools inspect "${IMAGE_NAME}:${{ steps.meta.outputs.version }}"
context: .
file: docker/nix.Dockerfile
platforms: linux/amd64
push: ${{ github.event_name == 'push' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
build-args: BASE_IMAGE=${{ steps.vars.outputs.base_image }}

View File

@@ -5,8 +5,17 @@ on:
types: types:
- checks_requested - checks_requested
pull_request: pull_request:
types: [opened, edited, reopened, synchronize, ready_for_review] types:
branches: [develop] - opened
- edited
- reopened
- synchronize
- ready_for_review
branches:
- develop
- "release-*"
- "release/*"
- "staging/*"
jobs: jobs:
check_description: check_description:
@@ -20,11 +29,11 @@ jobs:
env: env:
PR_BODY: ${{ github.event.pull_request.body }} PR_BODY: ${{ github.event.pull_request.body }}
if: ${{ github.event_name == 'pull_request' }} 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 - name: Check PR description differs from template
if: ${{ github.event_name == 'pull_request' }} if: ${{ github.event_name == 'pull_request' }}
run: > run: |
python .github/scripts/check-pr-description.py python .github/scripts/check-pr-description.py \
--template-file .github/pull_request_template.md --template-file .github/pull_request_template.md \
--pr-body-file pr_body.md --pr-body-file pr_body.md

View File

@@ -5,10 +5,19 @@ on:
types: types:
- checks_requested - checks_requested
pull_request: pull_request:
types: [opened, edited, reopened, synchronize, ready_for_review] types:
branches: [develop] - opened
- edited
- reopened
- synchronize
- ready_for_review
branches:
- develop
- "release-*"
- "release/*"
- "staging/*"
jobs: jobs:
check_title: check_title:
if: ${{ github.event.pull_request.draft != true }} if: ${{ github.event.pull_request.draft != true }}
uses: XRPLF/actions/.github/workflows/check-pr-title.yml@291206777251b4d493641b5afbdf7c23009d2988 uses: XRPLF/actions/.github/workflows/check-pr-title.yml@cba1f0891650baf1a9c88624dc2d72573be2eb81

View File

@@ -98,7 +98,7 @@ jobs:
READY: ${{ contains(github.event.pull_request.labels.*.name, 'Ready to merge') }} READY: ${{ contains(github.event.pull_request.labels.*.name, 'Ready to merge') }}
MERGE: ${{ github.event_name == 'merge_group' }} MERGE: ${{ github.event_name == 'merge_group' }}
run: | 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}" cat "${GITHUB_OUTPUT}"
outputs: outputs:
go: ${{ steps.go.outputs.go == 'true' }} go: ${{ steps.go.outputs.go == 'true' }}
@@ -168,9 +168,9 @@ jobs:
PR_URL: ${{ github.event.pull_request.html_url }} PR_URL: ${{ github.event.pull_request.html_url }}
run: | run: |
gh api --method POST -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" \ 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" \ /repos/xrplf/clio/dispatches -f "event_type=check_libxrpl" \
-F "client_payload[ref]=${{ needs.upload-recipe.outputs.recipe_ref }}" \ -F "client_payload[ref]=${{ needs.upload-recipe.outputs.recipe_ref }}" \
-F "client_payload[pr_url]=${PR_URL}" -F "client_payload[pr_url]=${PR_URL}"
passed: passed:
if: failure() || cancelled() if: failure() || cancelled()

View File

@@ -14,7 +14,7 @@ on:
jobs: jobs:
# Call the workflow in the XRPLF/actions repo that runs the pre-commit hooks. # Call the workflow in the XRPLF/actions repo that runs the pre-commit hooks.
run-hooks: run-hooks:
uses: XRPLF/actions/.github/workflows/pre-commit.yml@5e942d61bf32f7557a7c159cfac4712a687b3e3a uses: XRPLF/actions/.github/workflows/pre-commit.yml@cba1f0891650baf1a9c88624dc2d72573be2eb81
with: with:
runs_on: ubuntu-latest runs_on: ubuntu-latest
container: '{ "image": "ghcr.io/xrplf/ci/tools-rippled-pre-commit:sha-41ec7c1" }' container: '{ "image": "ghcr.io/xrplf/ci/tools-rippled-pre-commit:sha-41ec7c1" }'

View File

@@ -0,0 +1,89 @@
# Build a single-platform Docker image. On push, the image is pushed to
# GHCR with arch-suffixed tags (e.g. `:latest-amd64`, `:sha-abc-amd64`)
# so the calling workflow can stitch per-arch builds into a multi-arch
# manifest without needing to pass digests around.
name: Reusable build Docker image (single platform)
on:
workflow_call:
inputs:
image_name:
description: "Full image name without tag (e.g. 'ghcr.io/xrplf/xrpld/nix-ubuntu')"
required: true
type: string
dockerfile:
description: "Path to the Dockerfile, relative to the repository root"
required: true
type: string
base_image:
description: "Value passed to the Dockerfile as the BASE_IMAGE build arg"
required: true
type: string
platform:
description: "Docker platform string, e.g. linux/amd64"
required: true
type: string
runner:
description: "GitHub Actions runner label to build on"
required: true
type: string
push:
description: "Whether to push the image to GHCR"
required: true
type: boolean
defaults:
run:
shell: bash
jobs:
build:
name: Build (${{ inputs.platform }})
runs-on: ${{ inputs.runner }}
permissions:
contents: read
packages: write
steps:
- name: Checkout repository
uses: actions/checkout@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 }}

View File

@@ -113,7 +113,7 @@ jobs:
- name: Set ccache log file - name: Set ccache log file
if: ${{ inputs.ccache_enabled && runner.debug == '1' }} 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 - name: Print build environment
uses: XRPLF/actions/print-build-env@59dec886e4afb05a1724443af08baccbc045b574 uses: XRPLF/actions/print-build-env@59dec886e4afb05a1724443af08baccbc045b574
@@ -146,11 +146,11 @@ jobs:
CMAKE_ARGS: ${{ inputs.cmake_args }} CMAKE_ARGS: ${{ inputs.cmake_args }}
run: | run: |
cmake \ cmake \
-G '${{ runner.os == 'Windows' && 'Visual Studio 17 2022' || 'Ninja' }}' \ -G '${{ runner.os == 'Windows' && 'Visual Studio 17 2022' || 'Ninja' }}' \
-DCMAKE_TOOLCHAIN_FILE:FILEPATH=build/generators/conan_toolchain.cmake \ -DCMAKE_TOOLCHAIN_FILE:FILEPATH=build/generators/conan_toolchain.cmake \
-DCMAKE_BUILD_TYPE="${BUILD_TYPE}" \ -DCMAKE_BUILD_TYPE="${BUILD_TYPE}" \
${CMAKE_ARGS} \ ${CMAKE_ARGS} \
.. ..
- name: Check protocol autogen files are up-to-date - name: Check protocol autogen files are up-to-date
working-directory: ${{ env.BUILD_DIR }} working-directory: ${{ env.BUILD_DIR }}
@@ -172,32 +172,32 @@ jobs:
cmake --build . --target code_gen cmake --build . --target code_gen
DIFF=$(git -C .. status --porcelain -- include/xrpl/protocol_autogen src/tests/libxrpl/protocol_autogen) DIFF=$(git -C .. status --porcelain -- include/xrpl/protocol_autogen src/tests/libxrpl/protocol_autogen)
if [ -n "${DIFF}" ]; then if [ -n "${DIFF}" ]; then
echo "::error::Generated protocol files are out of date" echo "::error::Generated protocol files are out of date"
git -C .. diff -- include/xrpl/protocol_autogen src/tests/libxrpl/protocol_autogen git -C .. diff -- include/xrpl/protocol_autogen src/tests/libxrpl/protocol_autogen
echo "${MESSAGE}" echo "${MESSAGE}"
exit 1 exit 1
fi fi
- name: Build the binary - name: Build the binary
working-directory: ${{ env.BUILD_DIR }} working-directory: ${{ env.BUILD_DIR }}
env: env:
BUILD_NPROC: ${{ runner.os == 'Linux' && '16' || steps.nproc.outputs.nproc }} BUILD_NPROC: ${{ steps.nproc.outputs.nproc }}
BUILD_TYPE: ${{ inputs.build_type }} BUILD_TYPE: ${{ inputs.build_type }}
CMAKE_TARGET: ${{ inputs.cmake_target }} CMAKE_TARGET: ${{ inputs.cmake_target }}
run: | run: |
cmake \ cmake \
--build . \ --build . \
--config "${BUILD_TYPE}" \ --config "${BUILD_TYPE}" \
--parallel "${BUILD_NPROC}" \ --parallel "${BUILD_NPROC}" \
--target "${CMAKE_TARGET}" --target "${CMAKE_TARGET}"
- name: Show ccache statistics - name: Show ccache statistics
if: ${{ inputs.ccache_enabled }} if: ${{ inputs.ccache_enabled }}
run: | run: |
ccache --show-stats -vv ccache --show-stats -vv
if [ '${{ runner.debug }}' = '1' ]; then if [ '${{ runner.debug }}' = '1' ]; then
cat "${CCACHE_LOGFILE}" cat "${CCACHE_LOGFILE}"
curl ${CCACHE_REMOTE_STORAGE%|*}/status || true curl ${CCACHE_REMOTE_STORAGE%|*}/status || true
fi fi
- name: Upload the binary (Linux) - name: Upload the binary (Linux)
@@ -214,7 +214,7 @@ jobs:
working-directory: ${{ env.BUILD_DIR }} working-directory: ${{ env.BUILD_DIR }}
run: | run: |
set -o pipefail 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 - name: Upload server definitions
if: ${{ github.event.repository.visibility == 'public' && inputs.config_name == 'debian-bookworm-gcc-13-amd64-release' }} if: ${{ github.event.repository.visibility == 'public' && inputs.config_name == 'debian-bookworm-gcc-13-amd64-release' }}
@@ -231,10 +231,10 @@ jobs:
run: | run: |
ldd ./xrpld ldd ./xrpld
if [ "$(ldd ./xrpld | grep -E '(libstdc\+\+|libgcc)' | wc -l)" -eq 0 ]; then 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 else
echo 'The binary is dynamically linked.' echo 'The binary is dynamically linked.'
exit 1 exit 1
fi fi
- name: Verify presence of instrumentation (Linux) - name: Verify presence of instrumentation (Linux)
@@ -250,12 +250,12 @@ jobs:
run: | run: |
ASAN_OPTS="include=${GITHUB_WORKSPACE}/sanitizers/suppressions/runtime-asan-options.txt:suppressions=${GITHUB_WORKSPACE}/sanitizers/suppressions/asan.supp" ASAN_OPTS="include=${GITHUB_WORKSPACE}/sanitizers/suppressions/runtime-asan-options.txt:suppressions=${GITHUB_WORKSPACE}/sanitizers/suppressions/asan.supp"
if [[ "${CONFIG_NAME}" == *gcc* ]]; then if [[ "${CONFIG_NAME}" == *gcc* ]]; then
ASAN_OPTS="${ASAN_OPTS}:alloc_dealloc_mismatch=0" ASAN_OPTS="${ASAN_OPTS}:alloc_dealloc_mismatch=0"
fi fi
echo "ASAN_OPTIONS=${ASAN_OPTS}" >> ${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 "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 "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 "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 - name: Run the separate tests
if: ${{ !inputs.build_only }} if: ${{ !inputs.build_only }}
@@ -266,9 +266,9 @@ jobs:
PARALLELISM: ${{ runner.os == 'Windows' && '1' || steps.nproc.outputs.nproc }} PARALLELISM: ${{ runner.os == 'Windows' && '1' || steps.nproc.outputs.nproc }}
run: | run: |
ctest \ ctest \
--output-on-failure \ --output-on-failure \
-C "${BUILD_TYPE}" \ -C "${BUILD_TYPE}" \
-j "${PARALLELISM}" -j "${PARALLELISM}"
- name: Run the embedded tests - name: Run the embedded tests
if: ${{ !inputs.build_only }} if: ${{ !inputs.build_only }}
@@ -278,7 +278,7 @@ jobs:
run: | run: |
set -o pipefail set -o pipefail
# Coverage builds are slower due to instrumentation; use fewer parallel jobs to avoid flakiness # 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 ./xrpld --unittest --unittest-jobs "${BUILD_NPROC}" 2>&1 | tee unittest.log
- name: Show test failure summary - name: Show test failure summary
@@ -287,19 +287,19 @@ jobs:
WORKING_DIR: ${{ runner.os == 'Windows' && format('{0}\{1}', env.BUILD_DIR, inputs.build_type) || env.BUILD_DIR }} WORKING_DIR: ${{ runner.os == 'Windows' && format('{0}\{1}', env.BUILD_DIR, inputs.build_type) || env.BUILD_DIR }}
run: | run: |
if [ ! -d "${WORKING_DIR}" ]; then if [ ! -d "${WORKING_DIR}" ]; then
echo "Working directory '${WORKING_DIR}' does not exist." echo "Working directory '${WORKING_DIR}' does not exist."
exit 0 exit 0
fi fi
cd "${WORKING_DIR}" cd "${WORKING_DIR}"
if [ ! -f unittest.log ]; then if [ ! -f unittest.log ]; then
echo "unittest.log not found; embedded tests may not have run." echo "unittest.log not found; embedded tests may not have run."
exit 0 exit 0
fi fi
if ! grep -E "failed" unittest.log; then 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 fi
- name: Debug failure (Linux) - name: Debug failure (Linux)
if: ${{ failure() && runner.os == 'Linux' && !inputs.build_only }} if: ${{ failure() && runner.os == 'Linux' && !inputs.build_only }}
@@ -317,14 +317,14 @@ jobs:
BUILD_TYPE: ${{ inputs.build_type }} BUILD_TYPE: ${{ inputs.build_type }}
run: | run: |
cmake \ cmake \
--build . \ --build . \
--config "${BUILD_TYPE}" \ --config "${BUILD_TYPE}" \
--parallel "${BUILD_NPROC}" \ --parallel "${BUILD_NPROC}" \
--target coverage --target coverage
- name: Upload coverage report - name: Upload coverage report
if: ${{ github.repository == 'XRPLF/rippled' && !inputs.build_only && env.COVERAGE_ENABLED == 'true' }} if: ${{ github.repository == 'XRPLF/rippled' && !inputs.build_only && env.COVERAGE_ENABLED == 'true' }}
uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0 uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
with: with:
disable_search: true disable_search: true
disable_telem: true disable_telem: true

View File

@@ -38,9 +38,9 @@ jobs:
run: | run: |
DIFF=$(git status --porcelain) DIFF=$(git status --porcelain)
if [ -n "${DIFF}" ]; then if [ -n "${DIFF}" ]; then
# Print the differences to give the contributor a hint about what to # Print the differences to give the contributor a hint about what to
# expect when running levelization on their own machine. # expect when running levelization on their own machine.
git diff git diff
echo "${MESSAGE}" echo "${MESSAGE}"
exit 1 exit 1
fi fi

View File

@@ -48,9 +48,9 @@ jobs:
run: | run: |
DIFF=$(git status --porcelain) DIFF=$(git status --porcelain)
if [ -n "${DIFF}" ]; then if [ -n "${DIFF}" ]; then
# Print the differences to give the contributor a hint about what to # Print the differences to give the contributor a hint about what to
# expect when running the renaming scripts on their own machine. # expect when running the renaming scripts on their own machine.
git diff git diff
echo "${MESSAGE}" echo "${MESSAGE}"
exit 1 exit 1
fi fi

View File

@@ -70,13 +70,13 @@ jobs:
working-directory: ${{ env.BUILD_DIR }} working-directory: ${{ env.BUILD_DIR }}
run: | run: |
cmake \ cmake \
-G 'Ninja' \ -G 'Ninja' \
-DCMAKE_TOOLCHAIN_FILE:FILEPATH=build/generators/conan_toolchain.cmake \ -DCMAKE_TOOLCHAIN_FILE:FILEPATH=build/generators/conan_toolchain.cmake \
-DCMAKE_BUILD_TYPE="${BUILD_TYPE}" \ -DCMAKE_BUILD_TYPE="${BUILD_TYPE}" \
-Dtests=ON \ -Dtests=ON \
-Dwerr=ON \ -Dwerr=ON \
-Dxrpld=ON \ -Dxrpld=ON \
.. ..
# clang-tidy needs headers generated from proto files # clang-tidy needs headers generated from proto files
- name: Build libxrpl.libpb - name: Build libxrpl.libpb
@@ -133,7 +133,7 @@ jobs:
- name: Write issue header - name: Write issue header
if: ${{ steps.run_clang_tidy.outcome != 'success' }} if: ${{ steps.run_clang_tidy.outcome != 'success' }}
run: | run: |
cat > "${ISSUE_FILE}" <<EOF cat >"${ISSUE_FILE}" <<EOF
## Clang-tidy Check Failed ## Clang-tidy Check Failed
### Clang-tidy Output: ### Clang-tidy Output:
@@ -144,30 +144,30 @@ jobs:
if: ${{ steps.run_clang_tidy.outcome != 'success' }} if: ${{ steps.run_clang_tidy.outcome != 'success' }}
run: | run: |
if [ -f "${OUTPUT_FILE}" ]; then if [ -f "${OUTPUT_FILE}" ]; then
# Extract lines containing 'error:', 'warning:', or 'note:' # Extract lines containing 'error:', 'warning:', or 'note:'
grep -E '(error:|warning:|note:)' "${OUTPUT_FILE}" > filtered-output.txt || true grep -E '(error:|warning:|note:)' "${OUTPUT_FILE}" >filtered-output.txt || true
# If filtered output is empty, use original (might be a different error format) # If filtered output is empty, use original (might be a different error format)
if [ ! -s filtered-output.txt ]; then if [ ! -s filtered-output.txt ]; then
cp "${OUTPUT_FILE}" filtered-output.txt cp "${OUTPUT_FILE}" filtered-output.txt
fi fi
# Truncate if too large # Truncate if too large
head -c 60000 filtered-output.txt >> "${ISSUE_FILE}" head -c 60000 filtered-output.txt >>"${ISSUE_FILE}"
if [ "$(wc -c < filtered-output.txt)" -gt 60000 ]; then if [ "$(wc -c <filtered-output.txt)" -gt 60000 ]; then
echo "" >> "${ISSUE_FILE}" echo "" >>"${ISSUE_FILE}"
echo "... (output truncated, see artifacts for full output)" >> "${ISSUE_FILE}" echo "... (output truncated, see artifacts for full output)" >>"${ISSUE_FILE}"
fi fi
rm filtered-output.txt rm filtered-output.txt
else else
echo "No output file found" >> "${ISSUE_FILE}" echo "No output file found" >>"${ISSUE_FILE}"
fi fi
- name: Append issue footer - name: Append issue footer
if: ${{ steps.run_clang_tidy.outcome != 'success' }} if: ${{ steps.run_clang_tidy.outcome != 'success' }}
run: | run: |
cat >> "${ISSUE_FILE}" <<EOF cat >>"${ISSUE_FILE}" <<EOF
\`\`\` \`\`\`
--- ---
@@ -176,7 +176,7 @@ jobs:
- name: Create issue - name: Create issue
if: ${{ steps.run_clang_tidy.outcome != 'success' && inputs.create_issue_on_failure }} if: ${{ steps.run_clang_tidy.outcome != 'success' && inputs.create_issue_on_failure }}
uses: XRPLF/actions/create-issue@36d450d12d301e8410c1b7936e5de70c291cbe36 uses: XRPLF/actions/create-issue@2b8bc36af85b88bca0dd7bfac2e2dc05f94ad712
with: with:
title: "Clang-tidy check failed" title: "Clang-tidy check failed"
body_file: ${{ env.ISSUE_FILE }} body_file: ${{ env.ISSUE_FILE }}

View File

@@ -39,7 +39,7 @@ jobs:
id: generate id: generate
working-directory: .github/scripts/strategy-matrix working-directory: .github/scripts/strategy-matrix
run: | run: |
./generate.py --packaging --config=linux.json >> "${GITHUB_OUTPUT}" ./generate.py --packaging --config=linux.json >>"${GITHUB_OUTPUT}"
generate-version: generate-version:
runs-on: ubuntu-latest runs-on: ubuntu-latest

View File

@@ -42,4 +42,4 @@ jobs:
env: env:
GENERATE_CONFIG: ${{ inputs.os != '' && format('--config={0}.json', inputs.os) || '' }} GENERATE_CONFIG: ${{ inputs.os != '' && format('--config={0}.json', inputs.os) || '' }}
GENERATE_OPTION: ${{ inputs.strategy_matrix == 'all' && '--all' || '' }} 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}"

View File

@@ -37,37 +37,50 @@ repos:
exclude: ^include/xrpl/protocol_autogen/(transactions|ledger_entries)/ exclude: ^include/xrpl/protocol_autogen/(transactions|ledger_entries)/
- repo: https://github.com/pre-commit/mirrors-clang-format - repo: https://github.com/pre-commit/mirrors-clang-format
rev: cd481d7b0bfb5c7b3090c21846317f9a8262e891 # frozen: v22.1.0 rev: dd18dad857d6133e90bbe478f4f2f22ec0030269 # frozen: v22.1.5
hooks: hooks:
- id: clang-format - id: clang-format
args: [--style=file] args: [--style=file]
"types_or": [c++, c, proto] "types_or": [c++, c, proto]
exclude: ^include/xrpl/protocol_autogen/(transactions|ledger_entries)/ exclude: ^include/xrpl/protocol_autogen/(transactions|ledger_entries)/
- repo: https://github.com/BlankSpruce/gersemi - repo: https://github.com/BlankSpruce/gersemi-pre-commit
rev: 0.26.0 rev: faadd6a9d852369ca94f4d15b2404c967ba8cb01 # frozen: 0.27.6
hooks: hooks:
- id: gersemi - id: gersemi
- repo: https://github.com/rbubley/mirrors-prettier - repo: https://github.com/rbubley/mirrors-prettier
rev: c2bc67fe8f8f549cc489e00ba8b45aa18ee713b1 # frozen: v3.8.1 rev: 515f543f5718ebfd6ce22e16708bb32c68ff96e1 # frozen: v3.8.3
hooks: hooks:
- id: prettier - id: prettier
args: [--end-of-line=auto] args: [--end-of-line=auto]
- repo: https://github.com/psf/black-pre-commit-mirror - repo: https://github.com/psf/black-pre-commit-mirror
rev: ea488cebbfd88a5f50b8bd95d5c829d0bb76feb8 # frozen: 26.1.0 rev: 4160603246a6b365d4a2af661c6d71b0a0f50478 # frozen: 26.5.1
hooks: hooks:
- id: black - id: black
- repo: https://github.com/openstack/bashate - repo: https://github.com/scop/pre-commit-shfmt
rev: 5798d24d571676fc407e81df574c1ef57b520f23 # frozen: 2.1.1 rev: 05c1426671b9237fb5e1444dd63aa5731bec0dfb # frozen: v3.13.1-1
hooks: hooks:
- id: bashate - id: shfmt
args: ["--ignore=E006"] 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 - repo: https://github.com/streetsidesoftware/cspell-cli
rev: a42085ade523f591dca134379a595e7859986445 # frozen: v9.7.0 rev: 4643f154907327ee0a2c7038f0296e0dd77d9776 # frozen: v10.0.0
hooks: hooks:
- id: cspell # Spell check changed files - id: cspell # Spell check changed files
exclude: | exclude: |

View File

@@ -151,8 +151,8 @@ git init
git remote add origin git@github.com:XRPLF/conan-center-index.git git remote add origin git@github.com:XRPLF/conan-center-index.git
git sparse-checkout init git sparse-checkout init
for recipe in "${recipes[@]}"; do for recipe in "${recipes[@]}"; do
echo "Checking out recipe '${recipe}'..." echo "Checking out recipe '${recipe}'..."
git sparse-checkout add recipes/${recipe} git sparse-checkout add recipes/${recipe}
done done
git fetch origin master git fetch origin master
git checkout 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`: 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. 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', 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', '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: Single-config generators:
``` ```
cmake --build . cmake --build . --parallel N
``` ```
Multi-config generators: Multi-config generators:
``` ```
cmake --build . --config Release cmake --build . --config Release --parallel N
cmake --build . --config Debug 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. 5. Test xrpld.
Single-config generators: Single-config generators:

View File

@@ -1,8 +1,8 @@
#!/bin/bash #!/bin/bash
if [[ $# -ne 1 || "$1" == "--help" || "$1" == "-h" ]]; then if [[ $# -ne 1 || "$1" == "--help" || "$1" == "-h" ]]; then
name=$( basename $0 ) name=$(basename $0)
cat <<- USAGE cat <<-USAGE
Usage: $name <username> Usage: $name <username>
Where <username> is the Github username of the upstream repo. e.g. XRPLF Where <username> is the Github username of the upstream repo. e.g. XRPLF
@@ -14,7 +14,7 @@ fi
shift shift
user="$1" user="$1"
# Get the origin URL. Expect it be an SSH-style URL # 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 if [[ "${origin}" == "" ]]; then
echo Invalid origin remote >&2 echo Invalid origin remote >&2
exit 1 exit 1
@@ -22,11 +22,11 @@ fi
# echo "Origin: ${origin}" # echo "Origin: ${origin}"
# Parse the origin # Parse the origin
ifs_orig="${IFS}" ifs_orig="${IFS}"
IFS=':' read remote originpath <<< "${origin}" IFS=':' read remote originpath <<<"${origin}"
# echo "Remote: ${remote}, Originpath: ${originpath}" # echo "Remote: ${remote}, Originpath: ${originpath}"
IFS='@' read sshuser server <<< "${remote}" IFS='@' read sshuser server <<<"${remote}"
# echo "SSHUser: ${sshuser}, Server: ${server}" # echo "SSHUser: ${sshuser}, Server: ${server}"
IFS='/' read originuser repo <<< "${originpath}" IFS='/' read originuser repo <<<"${originpath}"
# echo "Originuser: ${originuser}, Repo: ${repo}" # echo "Originuser: ${originuser}, Repo: ${repo}"
if [[ "${sshuser}" == "" || "${server}" == "" || "${originuser}" == "" || "${repo}" == "" ]]; then if [[ "${sshuser}" == "" || "${server}" == "" || "${originuser}" == "" || "${repo}" == "" ]]; then
echo "Can't parse origin URL: ${origin}" >&2 echo "Can't parse origin URL: ${origin}" >&2
@@ -35,9 +35,9 @@ fi
upstream="https://${server}/${user}/${repo}" upstream="https://${server}/${user}/${repo}"
upstreampush="${remote}:${user}/${repo}" upstreampush="${remote}:${user}/${repo}"
upstreamgroup="upstream upstream-push" upstreamgroup="upstream upstream-push"
current=$( git remote get-url upstream 2>/dev/null ) current=$(git remote get-url upstream 2>/dev/null)
currentpush=$( git remote get-url upstream-push 2>/dev/null ) currentpush=$(git remote get-url upstream-push 2>/dev/null)
currentgroup=$( git config remotes.upstreams ) currentgroup=$(git config remotes.upstreams)
if [[ "${current}" == "${upstream}" ]]; then if [[ "${current}" == "${upstream}" ]]; then
echo "Upstream already set up correctly. Skip" echo "Upstream already set up correctly. Skip"
elif [[ -n "${current}" && "${current}" != "${upstream}" && "${current}" != "${upstreampush}" ]]; then elif [[ -n "${current}" && "${current}" != "${upstream}" && "${current}" != "${upstreampush}" ]]; then
@@ -45,9 +45,9 @@ elif [[ -n "${current}" && "${current}" != "${upstream}" && "${current}" != "${u
else else
if [[ "${current}" == "${upstreampush}" ]]; then if [[ "${current}" == "${upstreampush}" ]]; then
echo "Upstream set to dangerous push URL. Update." echo "Upstream set to dangerous push URL. Update."
_run git remote rename upstream upstream-push || \ _run git remote rename upstream upstream-push ||
_run git remote remove upstream _run git remote remove upstream
currentpush=$( git remote get-url upstream-push 2>/dev/null ) currentpush=$(git remote get-url upstream-push 2>/dev/null)
fi fi
_run git remote add upstream "${upstream}" _run git remote add upstream "${upstream}"
fi fi

View File

@@ -1,8 +1,8 @@
#!/bin/bash #!/bin/bash
if [[ $# -lt 3 || "$1" == "--help" || "$1" = "-h" ]]; then if [[ $# -lt 3 || "$1" == "--help" || "$1" = "-h" ]]; then
name=$( basename $0 ) name=$(basename $0)
cat <<- USAGE cat <<-USAGE
Usage: $name workbranch base/branch user/branch [user/branch [...]] Usage: $name workbranch base/branch user/branch [user/branch [...]]
* workbranch will be created locally from base/branch * workbranch will be created locally from base/branch
@@ -16,7 +16,7 @@ fi
work="$1" work="$1"
shift shift
branches=( $( echo "${@}" | sed "s/:/\//" ) ) branches=($(echo "${@}" | sed "s/:/\//"))
base="${branches[0]}" base="${branches[0]}"
unset branches[0] unset branches[0]
@@ -24,10 +24,10 @@ set -e
users=() users=()
for b in "${branches[@]}"; do for b in "${branches[@]}"; do
users+=( $( echo $b | cut -d/ -f1 ) ) users+=($(echo $b | cut -d/ -f1))
done done
users=( $( printf '%s\n' "${users[@]}" | sort -u ) ) users=($(printf '%s\n' "${users[@]}" | sort -u))
git fetch --multiple upstreams "${users[@]}" git fetch --multiple upstreams "${users[@]}"
git checkout -B "$work" --no-track "$base" git checkout -B "$work" --no-track "$base"
@@ -40,7 +40,7 @@ done
# Make sure the commits look right # Make sure the commits look right
git log --show-signature "$base..HEAD" git log --show-signature "$base..HEAD"
parts=( $( echo $base | sed "s/\// /" ) ) parts=($(echo $base | sed "s/\// /"))
repo="${parts[0]}" repo="${parts[0]}"
b="${parts[1]}" b="${parts[1]}"
push=$repo push=$repo
@@ -50,7 +50,7 @@ fi
if [[ "$repo" == "upstream" ]]; then if [[ "$repo" == "upstream" ]]; then
repo="upstreams" repo="upstreams"
fi fi
cat << PUSH cat <<PUSH
------------------------------------------------------------------- -------------------------------------------------------------------
This script will not push. Verify everything is correct, then push This script will not push. Verify everything is correct, then push

View File

@@ -1,8 +1,8 @@
#!/bin/bash #!/bin/bash
if [[ $# -ne 3 || "$1" == "--help" || "$1" = "-h" ]]; then if [[ $# -ne 3 || "$1" == "--help" || "$1" = "-h" ]]; then
name=$( basename $0 ) name=$(basename $0)
cat <<- USAGE cat <<-USAGE
Usage: $name workbranch base/branch version Usage: $name workbranch base/branch version
* workbranch will be created locally from base/branch. If it exists, * workbranch will be created locally from base/branch. If it exists,
@@ -16,7 +16,7 @@ fi
work="$1" work="$1"
shift shift
base=$( echo "$1" | sed "s/:/\//" ) base=$(echo "$1" | sed "s/:/\//")
shift shift
version=$1 version=$1
@@ -28,16 +28,16 @@ git fetch upstreams
git checkout -B "${work}" --no-track "${base}" git checkout -B "${work}" --no-track "${base}"
push=$( git rev-parse --abbrev-ref --symbolic-full-name '@{push}' \ push=$(git rev-parse --abbrev-ref --symbolic-full-name '@{push}' \
2>/dev/null ) || true 2>/dev/null) || true
if [[ "${push}" != "" ]]; then if [[ "${push}" != "" ]]; then
echo "Warning: ${push} may already exist." echo "Warning: ${push} may already exist."
fi fi
build=$( find -name BuildInfo.cpp ) build=$(find -name BuildInfo.cpp)
sed 's/\(^.*versionString =\).*$/\1 "'${version}'"/' ${build} > version.cpp && \ sed 's/\(^.*versionString =\).*$/\1 "'${version}'"/' ${build} >version.cpp &&
diff "${build}" version.cpp && exit 1 || \ diff "${build}" version.cpp && exit 1 ||
mv -vi version.cpp ${build} mv -vi version.cpp ${build}
git diff git diff
@@ -47,7 +47,7 @@ git commit -S -m "Set version to ${version}"
git log --oneline --first-parent ${base}^.. git log --oneline --first-parent ${base}^..
cat << PUSH cat <<PUSH
------------------------------------------------------------------- -------------------------------------------------------------------
This script will not push. Verify everything is correct, then push This script will not push. Verify everything is correct, then push

View File

@@ -168,7 +168,13 @@ def main():
if not os.environ.get("TIDY"): if not os.environ.get("TIDY"):
return 0 return 0
repo_root = Path(__file__).parent.parent repo_root = Path(
subprocess.check_output(
["git", "rev-parse", "--show-toplevel"],
cwd=Path(__file__).parent,
text=True,
).strip()
)
files = staged_files(repo_root) files = staged_files(repo_root)
if not files: if not files:
return 0 return 0

View File

@@ -953,6 +953,21 @@
# #
# Optional keys for NuDB and RocksDB: # Optional keys for NuDB and RocksDB:
# #
# cache_size Size of cache for database records. Default is 16384.
# Setting this value to 0 will use the default value.
#
# cache_age Length of time in minutes to keep database records
# cached. Default is 5 minutes. Setting this value to
# 0 will use the default value.
#
# Note: if cache_size or cache_age is not specified,
# default values will be used for the unspecified
# parameter.
#
# Note: the cache will not be created if online_delete
# is specified, because the rotating NodeStore does
# not use this cache).
#
# fast_load Boolean. If set, load the last persisted ledger # fast_load Boolean. If set, load the last persisted ledger
# from disk upon process start before syncing to # from disk upon process start before syncing to
# the network. This is likely to improve performance # the network. This is likely to improve performance

View File

@@ -0,0 +1,13 @@
# Python dependencies for XRP Ledger code generation scripts
#
# These packages are required to run the code generation scripts that
# parse macro files and generate C++ wrapper classes.
# C preprocessor for Python - used to preprocess macro files
pcpp>=1.30
# Parser combinator library - used to parse the macro DSL
pyparsing>=3.0.0
# Template engine - used to generate C++ code from templates
Mako>=1.2.2

View File

@@ -1,13 +1,105 @@
# Python dependencies for XRP Ledger code generation scripts # This file was autogenerated by uv via the following command:
# # uv pip compile requirements.in --generate-hashes --output-file requirements.txt
# These packages are required to run the code generation scripts that mako==1.3.12 \
# parse macro files and generate C++ wrapper classes. --hash=sha256:8f61569480282dbf557145ce441e4ba888be453c30989f879f0d652e39f53ea9 \
--hash=sha256:9f778e93289bd410bb35daadeb4fc66d95a746f0b75777b942088b7fd7af550a
# C preprocessor for Python - used to preprocess macro files # via -r requirements.in
pcpp>=1.30 markupsafe==3.0.3 \
--hash=sha256:0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f \
# Parser combinator library - used to parse the macro DSL --hash=sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a \
pyparsing>=3.0.0 --hash=sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf \
--hash=sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19 \
# Template engine - used to generate C++ code from templates --hash=sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf \
Mako>=1.2.2 --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

View File

@@ -93,6 +93,7 @@ words:
- daria - daria
- dcmake - dcmake
- dearmor - dearmor
- dedented
- deleteme - deleteme
- demultiplexer - demultiplexer
- deserializaton - deserializaton
@@ -199,11 +200,13 @@ words:
- nonxrp - nonxrp
- noreplace - noreplace
- noripple - noripple
- nostdinc
- notifempty - notifempty
- nudb - nudb
- nullptr - nullptr
- nunl - nunl
- Nyffenegger - Nyffenegger
- onlatest
- ostr - ostr
- pargs - pargs
- partitioner - partitioner
@@ -254,6 +257,7 @@ words:
- sfields - sfields
- shamap - shamap
- shamapitem - shamapitem
- shfmt
- shlibs - shlibs
- sidechain - sidechain
- SIGGOOD - SIGGOOD
@@ -298,6 +302,7 @@ words:
- unauthorizing - unauthorizing
- unergonomic - unergonomic
- unfetched - unfetched
- unfindable
- unflatten - unflatten
- unfund - unfund
- unimpair - unimpair

48
docker/check-sanitizers.sh Executable file
View File

@@ -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 <cpp_files_dir>}"
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

28
docker/cpp_files/asan.cpp Normal file
View File

@@ -0,0 +1,28 @@
#include <atomic>
#include <cstddef>
#include <iostream>
#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;
}

26
docker/cpp_files/tsan.cpp Normal file
View File

@@ -0,0 +1,26 @@
#include <iostream>
#include <thread>
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;
}

View File

@@ -0,0 +1,13 @@
#include <iostream>
#include <limits>
int
main()
{
int maxInt = std::numeric_limits<int>::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;
}

View File

@@ -45,8 +45,30 @@ COPY --from=builder /tmp/build/result /nix/ci-env
ENV PATH="/nix/ci-env/bin:$PATH" 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 <<EOF
case "$(uname -m)" in
x86_64) target=/lib64/ld-linux-x86-64.so.2 ;;
aarch64) target=/lib/ld-linux-aarch64.so.1 ;;
*) echo "Unsupported arch: $(uname -m)" >&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 <<EOF RUN <<EOF
ccache --version ccache --version
clang --version
clang++ --version
clang-format --version clang-format --version
cmake --version cmake --version
conan --version conan --version
@@ -64,3 +86,10 @@ python3 --version
run-clang-tidy --help run-clang-tidy --help
vim --version vim --version
EOF EOF
# Sanity-check that the sanitizer runtimes shipped with g++/clang++ work
# end-to-end against the system loader.
COPY docker/cpp_files/ /tmp/cpp_files/
COPY docker/check-sanitizers.sh /tmp/check-sanitizers.sh
RUN grep -qi ubuntu /etc/os-release 2>/dev/null && /tmp/check-sanitizers.sh /tmp/cpp_files || true

4
flake.lock generated
View File

@@ -15,7 +15,7 @@
"type": "indirect" "type": "indirect"
} }
}, },
"nixpkgs-glibc231": { "nixpkgs-custom-glibc": {
"flake": false, "flake": false,
"locked": { "locked": {
"lastModified": 1593520194, "lastModified": 1593520194,
@@ -35,7 +35,7 @@
"root": { "root": {
"inputs": { "inputs": {
"nixpkgs": "nixpkgs", "nixpkgs": "nixpkgs",
"nixpkgs-glibc231": "nixpkgs-glibc231" "nixpkgs-custom-glibc": "nixpkgs-custom-glibc"
} }
} }
}, },

View File

@@ -6,16 +6,16 @@
# version — matches the system libc on Ubuntu 20.04 LTS. Imported # version — matches the system libc on Ubuntu 20.04 LTS. Imported
# manually (flake = false) because this revision predates nixpkgs' # manually (flake = false) because this revision predates nixpkgs'
# own flake.nix. # own flake.nix.
nixpkgs-glibc231 = { nixpkgs-custom-glibc = {
url = "github:NixOS/nixpkgs/9cd98386a38891d1074fc18036b842dc4416f562"; url = "github:NixOS/nixpkgs/9cd98386a38891d1074fc18036b842dc4416f562";
flake = false; flake = false;
}; };
}; };
outputs = outputs =
{ nixpkgs, nixpkgs-glibc231, ... }: { nixpkgs, nixpkgs-custom-glibc, ... }:
let let
forEachSystem = import ./nix/utils.nix { inherit nixpkgs nixpkgs-glibc231; }; forEachSystem = import ./nix/utils.nix { inherit nixpkgs nixpkgs-custom-glibc; };
in in
{ {
devShells = forEachSystem (import ./nix/devshell.nix); devShells = forEachSystem (import ./nix/devshell.nix);

View File

@@ -2,12 +2,16 @@
#include <xrpl/beast/utility/instrumentation.h> #include <xrpl/beast/utility/instrumentation.h>
#include <array>
#include <cstdint> #include <cstdint>
#include <functional> #include <functional>
#include <limits> #include <limits>
#include <optional> #include <optional>
#include <ostream> #include <ostream>
#include <set>
#include <stdexcept>
#include <string> #include <string>
#include <unordered_map>
namespace xrpl { namespace xrpl {
@@ -38,17 +42,58 @@ isPowerOfTen(T value)
return logTen(value).has_value(); return logTen(value).has_value();
} }
namespace detail {
/** Builds a table of the powers of 10
*
* This function is marked consteval, so it can only be run in
* a constexpr context. This assures that it is and can only be run at
* compile time. Doing it at runtime would be pretty wasteful and
* inefficient.
*/
constexpr std::size_t kInt64Digits = 20;
consteval std::array<std::uint64_t, kInt64Digits>
buildPowersOfTen()
{
std::array<std::uint64_t, kInt64Digits> result{};
std::uint64_t power = 1;
std::size_t exponent = 0;
// end the loop early so it doesn't overflow;
for (; exponent < result.size() - 1; ++exponent, power *= 10)
{
result[exponent] = power;
if (power > std::numeric_limits<std::uint64_t>::max() / 10)
throw std::logic_error("Power of 10 table is too big");
}
result[exponent] = power;
if (power < std::numeric_limits<std::uint64_t>::max() / 10)
throw std::logic_error("Power of 10 table is not big enough for the uint64_t type");
return result;
}
} // namespace detail
constexpr std::array<std::uint64_t, detail::kInt64Digits> kPowerOfTen = detail::buildPowersOfTen();
static_assert(kPowerOfTen[0] == 1);
static_assert(kPowerOfTen[1] == 10);
static_assert(kPowerOfTen[10] == 10'000'000'000);
static_assert(
isPowerOfTen(kPowerOfTen.back()) && *logTen(kPowerOfTen.back()) == detail::kInt64Digits - 1);
/** MantissaRange defines a range for the mantissa of a normalized Number. /** MantissaRange defines a range for the mantissa of a normalized Number.
* *
* The mantissa is in the range [min, max], where * The mantissa is in the range [min, max], where
* * min is a power of 10, and * * min is a power of 10, and
* * max = min * 10 - 1. * * max = min * 10 - 1.
* *
* The mantissa_scale enum indicates whether the range is "small" or "large". * The MantissaScale enum indicates properties of the range: size, and some behavioral
* This intentionally restricts the number of MantissaRanges that can be * options. This intentionally restricts the number of unique MantissaRanges that can
* instantiated to two: one for each scale. * be instantiated: one for each scale.
* *
* The "small" scale is based on the behavior of STAmount for IOUs. It has a min * The "Small" scale is based on the behavior of STAmount for IOUs. It has a min
* value of 10^15, and a max value of 10^16-1. This was sufficient for * value of 10^15, and a max value of 10^16-1. This was sufficient for
* uses before Lending Protocol was implemented, mostly related to AMM. * uses before Lending Protocol was implemented, mostly related to AMM.
* *
@@ -59,46 +104,100 @@ isPowerOfTen(T value)
* STNumber field type, and for internal calculations. That necessitated the * STNumber field type, and for internal calculations. That necessitated the
* "large" scale. * "large" scale.
* *
* The "large" scale is intended to represent all values that can be represented * The "Large" scales are intended to represent all values that can be represented
* by an STAmount - IOUs, XRP, and MPTs. It has a min value of 10^18, and a max * by an STAmount - IOUs, XRP, and MPTs. It has a min value of 10^18, and a max
* value of 10^19-1. * value of 10^19-1. "LargeLegacy" is like "Large", but preserves
* a rounding error when a computation results in a mantissa of
* Number::kMaxRep that needs to be rounded up, but rounds down
* instead. It will maintain consistent behavior until the fixCleanup3_2_0
* amendment is enabled.
* *
* Note that if the mentioned amendments are eventually retired, this class * Note that if the mentioned amendments are eventually retired, this class
* should be left in place, but the "small" scale option should be removed. This * should be left in place, but the "Small" scale option should be removed. This
* will allow for future expansion beyond 64-bits if it is ever needed. * will allow for future expansion beyond 64-bits if it is ever needed.
*/ */
struct MantissaRange struct MantissaRange final
{ {
using rep = std::uint64_t; using rep = std::uint64_t;
enum class MantissaScale { Small, Large };
explicit constexpr MantissaRange(MantissaScale scale) enum class MantissaScale {
: min(getMin(scale)), log(logTen(min).value_or(-1)), scale(scale) Small,
// LargeLegacy can be removed when fixCleanup3_2_0 is retired
LargeLegacy,
Large,
};
// This entire enum can be removed when fixCleanup3_2_0 is retired
enum class CuspRoundingFix : bool {
Disabled = false,
Enabled = true,
};
explicit constexpr MantissaRange(MantissaScale sc) : scale(sc)
{ {
} }
rep min; MantissaScale const scale;
rep max{(min * 10) - 1}; int const log{getExponent(scale)};
int log; rep const min{getMin(scale, log)};
MantissaScale scale; rep const max{(min * 10) - 1};
CuspRoundingFix const cuspRoundingFixEnabled{isCuspFixEnabled(scale)};
static MantissaRange const&
getMantissaRange(MantissaScale scale);
static std::set<MantissaScale> const&
getAllScales();
private: private:
static constexpr rep static constexpr int
getMin(MantissaScale scale) getExponent(MantissaScale scale)
{ {
switch (scale) switch (scale)
{ {
case MantissaScale::Small: case MantissaScale::Small:
return 1'000'000'000'000'000ULL; return 15;
case MantissaScale::LargeLegacy:
case MantissaScale::Large: case MantissaScale::Large:
return 1'000'000'000'000'000'000ULL; return 18;
// LCOV_EXCL_START
default: default:
// Since this can never be called outside a non-constexpr // If called in a constexpr context, this throw assures that the build fails if an
// context, this throw assures that the build fails if an
// invalid scale is used. // invalid scale is used.
throw std::runtime_error("Unknown mantissa scale"); throw std::runtime_error("Unknown mantissa scale");
// LCOV_EXCL_STOP
} }
} }
// Keep this function for future use with different ways to compute
// the ranges.
static constexpr rep
getMin(MantissaScale scale, int exponent)
{
if (exponent < 0 || exponent >= kPowerOfTen.size())
throw std::runtime_error("Invalid exponent"); // LCOV_EXCL_LINE
return kPowerOfTen[exponent];
}
static constexpr CuspRoundingFix
isCuspFixEnabled(MantissaScale scale)
{
switch (scale)
{
case MantissaScale::Small:
case MantissaScale::LargeLegacy:
return CuspRoundingFix::Disabled;
case MantissaScale::Large:
return CuspRoundingFix::Enabled;
default:
// If called in a constexpr context, this throw assures that the build fails if an
// invalid scale is used.
throw std::runtime_error("Unknown mantissa scale"); // LCOV_EXCL_LINE
}
}
static std::unordered_map<MantissaScale, MantissaRange> const&
getRanges();
}; };
// Like std::integral, but only 64-bit integral types. // Like std::integral, but only 64-bit integral types.
@@ -203,7 +302,7 @@ concept Integral64 = std::is_same_v<T, std::int64_t> || std::is_same_v<T, std::u
* amendments are enabled to determine which result to expect. * amendments are enabled to determine which result to expect.
* *
*/ */
class Number class Number final
{ {
using rep = std::int64_t; using rep = std::int64_t;
using internalrep = MantissaRange::rep; using internalrep = MantissaRange::rep;
@@ -309,33 +408,40 @@ public:
} }
friend constexpr bool friend constexpr bool
operator<(Number const& x, Number const& y) noexcept operator<(Number const& l, Number const& r) noexcept
{ {
bool const lneg = l.negative_;
bool const rneg = r.negative_;
// If the two amounts have different signs (zero is treated as positive) // If the two amounts have different signs (zero is treated as positive)
// then the comparison is true iff the left is negative. // then the comparison is true iff the left is negative.
bool const lneg = x.negative_;
bool const rneg = y.negative_;
if (lneg != rneg) if (lneg != rneg)
return lneg; return lneg;
// Both have same sign and the left is zero: the right must be // Both have same sign and the left is zero: both must be non-negative.
// greater than 0. // If the right is greater than 0, then it is larger, so the comparison is true.
if (x.mantissa_ == 0) if (l.mantissa_ == 0)
return y.mantissa_ > 0; return r.mantissa_ > 0;
// Both have same sign, the right is zero and the left is non-zero. // Both have same sign, the right is zero and the left is non-zero, so the left must be
if (y.mantissa_ == 0) // positive, and thus is larger, so the comparison is false.
if (r.mantissa_ == 0)
return false; return false;
// Both have the same sign, compare by exponents: // Both have the same sign, compare by exponents:
if (x.exponent_ > y.exponent_) if (l.exponent_ > r.exponent_)
return lneg; return lneg;
if (x.exponent_ < y.exponent_) if (l.exponent_ < r.exponent_)
return !lneg; return !lneg;
// If equal exponents, compare mantissas // If equal signs and exponents, compare mantissas.
return x.mantissa_ < y.mantissa_; if (lneg)
{
// If negative, the operator is reversed.
return l.mantissa_ > r.mantissa_;
}
return l.mantissa_ < r.mantissa_;
} }
/** Return the sign of the amount */ /** Return the sign of the amount */
@@ -424,49 +530,28 @@ public:
return kRange.get().log; return kRange.get().log;
} }
/// oneSmall is needed because the ranges are private
static constexpr Number
oneSmall();
/// oneLarge is needed because the ranges are private
static constexpr Number
oneLarge();
// And one is needed because it needs to choose between oneSmall and
// oneLarge based on the current range
static Number static Number
one(); one();
template <Integral64 T> template <
auto MinMantissa,
auto MaxMantissa,
Integral64 T = std::decay_t<decltype(MinMantissa)>>
[[nodiscard]] [[nodiscard]]
std::pair<T, int> std::pair<T, int>
normalizeToRange(T minMantissa, T maxMantissa) const; normalizeToRange() const;
private: private:
static thread_local RoundingMode mode; static thread_local RoundingMode mode;
// The available ranges for mantissa // The available ranges for mantissa
static constexpr MantissaRange kSmallRange{MantissaRange::MantissaScale::Small};
static_assert(isPowerOfTen(kSmallRange.min));
static_assert(kSmallRange.min == 1'000'000'000'000'000LL);
static_assert(kSmallRange.max == 9'999'999'999'999'999LL);
static_assert(kSmallRange.log == 15);
static_assert(kSmallRange.min < kMaxRep);
static_assert(kSmallRange.max < kMaxRep);
static constexpr MantissaRange kLargeRange{MantissaRange::MantissaScale::Large};
static_assert(isPowerOfTen(kLargeRange.min));
static_assert(kLargeRange.min == 1'000'000'000'000'000'000ULL);
static_assert(kLargeRange.max == internalrep(9'999'999'999'999'999'999ULL));
static_assert(kLargeRange.log == 18);
static_assert(kLargeRange.min < kMaxRep);
static_assert(kLargeRange.max > kMaxRep);
// The range for the mantissa when normalized. // The range for the mantissa when normalized.
// Use reference_wrapper to avoid making copies, and prevent accidentally // Use reference_wrapper to avoid making copies, and prevent accidentally
// changing the values inside the range. // changing the values inside the range.
static thread_local std::reference_wrapper<MantissaRange const> kRange; static thread_local std::reference_wrapper<MantissaRange const> kRange;
void void
normalize(); normalize(MantissaRange const& range);
/** Normalize Number components to an arbitrary range. /** Normalize Number components to an arbitrary range.
* *
@@ -481,7 +566,8 @@ private:
T& mantissa, T& mantissa,
int& exponent, int& exponent,
internalrep const& minMantissa, internalrep const& minMantissa,
internalrep const& maxMantissa); internalrep const& maxMantissa,
MantissaRange::CuspRoundingFix cuspRoundingFixEnabled);
template <class T> template <class T>
friend void friend void
@@ -490,7 +576,9 @@ private:
T& mantissa, T& mantissa,
int& exponent, int& exponent,
MantissaRange::rep const& minMantissa, MantissaRange::rep const& minMantissa,
MantissaRange::rep const& maxMantissa); MantissaRange::rep const& maxMantissa,
MantissaRange::CuspRoundingFix cuspRoundingFixEnabled,
bool dropped);
[[nodiscard]] bool [[nodiscard]] bool
isnormal() const noexcept; isnormal() const noexcept;
@@ -526,7 +614,7 @@ static constexpr Number kNumZero{};
inline Number::Number(bool negative, internalrep mantissa, int exponent, Normalized) inline Number::Number(bool negative, internalrep mantissa, int exponent, Normalized)
: Number(negative, mantissa, exponent, Unchecked{}) : Number(negative, mantissa, exponent, Unchecked{})
{ {
normalize(); normalize(kRange);
} }
inline Number::Number(internalrep mantissa, int exponent, Normalized) inline Number::Number(internalrep mantissa, int exponent, Normalized)
@@ -696,10 +784,21 @@ Number::isnormal() const noexcept
kMinExponent <= exponent_ && exponent_ <= kMaxExponent); kMinExponent <= exponent_ && exponent_ <= kMaxExponent);
} }
template <Integral64 T> template <auto MinMantissa, auto MaxMantissa, Integral64 T>
std::pair<T, int> std::pair<T, int>
Number::normalizeToRange(T minMantissa, T maxMantissa) const Number::normalizeToRange() const
{ {
static_assert(std::is_same_v<T, std::uint64_t> || std::is_same_v<T, std::int64_t>);
static_assert(std::is_same_v<T, std::decay_t<decltype(MinMantissa)>>);
static_assert(std::is_same_v<T, std::decay_t<decltype(MaxMantissa)>>);
auto constexpr kMIN = static_cast<T>(MinMantissa);
auto constexpr kMAX = static_cast<T>(MaxMantissa);
static_assert(kMIN > 0);
static_assert(kMIN % 10 == 0);
static_assert(isPowerOfTen(kMIN));
static_assert(kMAX % 10 == 9);
static_assert((kMAX + 1) / 10 == kMIN);
bool negative = negative_; bool negative = negative_;
internalrep mantissa = mantissa_; internalrep mantissa = mantissa_;
int exponent = exponent_; int exponent = exponent_;
@@ -711,7 +810,10 @@ Number::normalizeToRange(T minMantissa, T maxMantissa) const
"xrpl::Number::normalizeToRange", "xrpl::Number::normalizeToRange",
"Number is non-negative for unsigned range."); "Number is non-negative for unsigned range.");
} }
Number::normalize(negative, mantissa, exponent, minMantissa, maxMantissa); // Don't need to worry about the cuspRounding fix because rounding up will never take the
// mantissa over maxMantissa with a ones digit value other than 0. 0 can safely be truncated.
Number::normalize(
negative, mantissa, exponent, kMIN, kMAX, MantissaRange::CuspRoundingFix::Disabled);
auto const sign = negative ? -1 : 1; auto const sign = negative ? -1 : 1;
return std::make_pair(static_cast<T>(sign * mantissa), exponent); return std::make_pair(static_cast<T>(sign * mantissa), exponent);
@@ -763,6 +865,8 @@ to_string(MantissaRange::MantissaScale const& scale)
{ {
case MantissaRange::MantissaScale::Small: case MantissaRange::MantissaScale::Small:
return "small"; return "small";
case MantissaRange::MantissaScale::LargeLegacy:
return "largeLegacy";
case MantissaRange::MantissaScale::Large: case MantissaRange::MantissaScale::Large:
return "large"; return "large";
default: default:

View File

@@ -181,14 +181,14 @@ private:
beast::insight::Collector::ptr const& collector) beast::insight::Collector::ptr const& collector)
: hook(collector->makeHook(handler)) : hook(collector->makeHook(handler))
, size(collector->makeGauge(prefix, "size")) , size(collector->makeGauge(prefix, "size"))
, hit_rate(collector->makeGauge(prefix, "hit_rate")) , hitRate(collector->makeGauge(prefix, "hit_rate"))
{ {
} }
beast::insight::Hook hook; beast::insight::Hook hook;
beast::insight::Gauge size; beast::insight::Gauge size;
beast::insight::Gauge hit_rate; beast::insight::Gauge hitRate;
std::size_t hits{0}; std::size_t hits{0};
std::size_t misses{0}; std::size_t misses{0};
@@ -197,16 +197,16 @@ private:
class KeyOnlyEntry class KeyOnlyEntry
{ {
public: public:
clock_type::time_point last_access; clock_type::time_point lastAccess;
explicit KeyOnlyEntry(clock_type::time_point const& lastAccess) : last_access(lastAccess) explicit KeyOnlyEntry(clock_type::time_point const& lastAccess) : lastAccess(lastAccess)
{ {
} }
void void
touch(clock_type::time_point const& now) touch(clock_type::time_point const& now)
{ {
last_access = now; lastAccess = now;
} }
}; };
@@ -214,10 +214,10 @@ private:
{ {
public: public:
shared_weak_combo_pointer_type ptr; shared_weak_combo_pointer_type ptr;
clock_type::time_point last_access; clock_type::time_point lastAccess;
ValueEntry(clock_type::time_point const& lastAccess, shared_pointer_type const& ptr) ValueEntry(clock_type::time_point const& lastAccess, shared_pointer_type const& ptr)
: ptr(ptr), last_access(lastAccess) : ptr(ptr), lastAccess(lastAccess)
{ {
} }
@@ -246,7 +246,7 @@ private:
void void
touch(clock_type::time_point const& now) touch(clock_type::time_point const& now)
{ {
last_access = now; lastAccess = now;
} }
}; };
@@ -286,13 +286,13 @@ private:
std::string name_; std::string name_;
// Desired number of cache entries (0 = ignore) // Desired number of cache entries (0 = ignore)
int const target_size_; int const targetSize_;
// Desired maximum cache age // Desired maximum cache age
clock_type::duration const target_age_; clock_type::duration const targetAge_;
// Number of items cached // Number of items cached
int cache_count_{0}; int cacheCount_{0};
cache_type cache_; // Hold strong reference to recent objects cache_type cache_; // Hold strong reference to recent objects
std::uint64_t hits_{0}; std::uint64_t hits_{0};
std::uint64_t misses_{0}; std::uint64_t misses_{0};

View File

@@ -34,8 +34,8 @@ inline TaggedCache<
, clock_(clock) , clock_(clock)
, stats_(name, std::bind(&TaggedCache::collectMetrics, this), collector) , stats_(name, std::bind(&TaggedCache::collectMetrics, this), collector)
, name_(name) , name_(name)
, target_size_(size) , targetSize_(size)
, target_age_(expiration) , targetAge_(expiration)
{ {
} }
@@ -86,7 +86,7 @@ TaggedCache<Key, T, IsKeyCache, SharedWeakUnionPointer, SharedPointerType, Hash,
getCacheSize() const getCacheSize() const
{ {
std::scoped_lock const lock(mutex_); std::scoped_lock const lock(mutex_);
return cache_count_; return cacheCount_;
} }
template < template <
@@ -139,7 +139,7 @@ TaggedCache<Key, T, IsKeyCache, SharedWeakUnionPointer, SharedPointerType, Hash,
{ {
std::scoped_lock const lock(mutex_); std::scoped_lock const lock(mutex_);
cache_.clear(); cache_.clear();
cache_count_ = 0; cacheCount_ = 0;
} }
template < template <
@@ -157,7 +157,7 @@ TaggedCache<Key, T, IsKeyCache, SharedWeakUnionPointer, SharedPointerType, Hash,
{ {
std::scoped_lock const lock(mutex_); std::scoped_lock const lock(mutex_);
cache_.clear(); cache_.clear();
cache_count_ = 0; cacheCount_ = 0;
hits_ = 0; hits_ = 0;
misses_ = 0; misses_ = 0;
} }
@@ -213,21 +213,21 @@ TaggedCache<Key, T, IsKeyCache, SharedWeakUnionPointer, SharedPointerType, Hash,
{ {
std::scoped_lock const lock(mutex_); std::scoped_lock const lock(mutex_);
if (target_size_ == 0 || (static_cast<int>(cache_.size()) <= target_size_)) if (targetSize_ == 0 || (static_cast<int>(cache_.size()) <= targetSize_))
{ {
whenExpire = now - target_age_; whenExpire = now - targetAge_;
} }
else else
{ {
whenExpire = now - (target_age_ * target_size_ / cache_.size()); whenExpire = now - (targetAge_ * targetSize_ / cache_.size());
clock_type::duration const minimumAge(std::chrono::seconds(1)); clock_type::duration const minimumAge(std::chrono::seconds(1));
if (whenExpire > (now - minimumAge)) if (whenExpire > (now - minimumAge))
whenExpire = now - minimumAge; whenExpire = now - minimumAge;
JLOG(journal_.trace()) JLOG(journal_.trace())
<< name_ << " is growing fast " << cache_.size() << " of " << target_size_ << name_ << " is growing fast " << cache_.size() << " of " << targetSize_
<< " aging at " << (now - whenExpire).count() << " of " << target_age_.count(); << " aging at " << (now - whenExpire).count() << " of " << targetAge_.count();
} }
std::vector<std::thread> workers; std::vector<std::thread> workers;
@@ -242,7 +242,7 @@ TaggedCache<Key, T, IsKeyCache, SharedWeakUnionPointer, SharedPointerType, Hash,
for (std::thread& worker : workers) for (std::thread& worker : workers)
worker.join(); worker.join();
cache_count_ -= allRemovals; cacheCount_ -= allRemovals;
} }
// At this point allStuffToSweep will go out of scope outside the lock // At this point allStuffToSweep will go out of scope outside the lock
// and decrement the reference count on each strong pointer. // and decrement the reference count on each strong pointer.
@@ -280,7 +280,7 @@ TaggedCache<Key, T, IsKeyCache, SharedWeakUnionPointer, SharedPointerType, Hash,
if (entry.isCached()) if (entry.isCached())
{ {
--cache_count_; --cacheCount_;
entry.ptr.convertToWeak(); entry.ptr.convertToWeak();
ret = true; ret = true;
} }
@@ -317,7 +317,7 @@ TaggedCache<Key, T, IsKeyCache, SharedWeakUnionPointer, SharedPointerType, Hash,
std::piecewise_construct, std::piecewise_construct,
std::forward_as_tuple(key), std::forward_as_tuple(key),
std::forward_as_tuple(clock_.now(), data)); std::forward_as_tuple(clock_.now(), data));
++cache_count_; ++cacheCount_;
return false; return false;
} }
@@ -366,12 +366,12 @@ TaggedCache<Key, T, IsKeyCache, SharedWeakUnionPointer, SharedPointerType, Hash,
data = cachedData; data = cachedData;
} }
++cache_count_; ++cacheCount_;
return true; return true;
} }
entry.ptr = data; entry.ptr = data;
++cache_count_; ++cacheCount_;
return false; return false;
} }
@@ -477,7 +477,7 @@ TaggedCache<Key, T, IsKeyCache, SharedWeakUnionPointer, SharedPointerType, Hash,
auto [it, inserted] = cache_.emplace( auto [it, inserted] = cache_.emplace(
std::piecewise_construct, std::forward_as_tuple(key), std::forward_as_tuple(now)); std::piecewise_construct, std::forward_as_tuple(key), std::forward_as_tuple(now));
if (!inserted) if (!inserted)
it->second.last_access = now; it->second.lastAccess = now;
return inserted; return inserted;
} }
@@ -626,7 +626,7 @@ TaggedCache<Key, T, IsKeyCache, SharedWeakUnionPointer, SharedPointerType, Hash,
if (entry.isCached()) if (entry.isCached())
{ {
// independent of cache size, so not counted as a hit // independent of cache size, so not counted as a hit
++cache_count_; ++cacheCount_;
entry.touch(clock_.now()); entry.touch(clock_.now());
return entry.ptr.getStrong(); return entry.ptr.getStrong();
} }
@@ -658,7 +658,7 @@ TaggedCache<Key, T, IsKeyCache, SharedWeakUnionPointer, SharedPointerType, Hash,
if (total != 0) if (total != 0)
hitRate = (hits_ * 100) / total; hitRate = (hits_ * 100) / total;
} }
stats_.hit_rate.set(hitRate); stats_.hitRate.set(hitRate);
} }
} }
@@ -706,7 +706,7 @@ TaggedCache<Key, T, IsKeyCache, SharedWeakUnionPointer, SharedPointerType, Hash,
++cit; ++cit;
} }
} }
else if (cit->second.last_access <= whenExpire) else if (cit->second.lastAccess <= whenExpire)
{ {
// strong, expired // strong, expired
++cacheRemovals; ++cacheRemovals;
@@ -773,12 +773,12 @@ TaggedCache<Key, T, IsKeyCache, SharedWeakUnionPointer, SharedPointerType, Hash,
auto cit = partition.begin(); auto cit = partition.begin();
while (cit != partition.end()) while (cit != partition.end())
{ {
if (cit->second.last_access > now) if (cit->second.lastAccess > now)
{ {
cit->second.last_access = now; cit->second.lastAccess = now;
++cit; ++cit;
} }
else if (cit->second.last_access <= whenExpire) else if (cit->second.lastAccess <= whenExpire)
{ {
cit = partition.erase(cit); cit = partition.erase(cit);
} }

View File

@@ -1,29 +0,0 @@
#pragma once
#if XRPL_ROCKSDB_AVAILABLE
// #include <rocksdb2/port/port_posix.h>
#include <rocksdb/cache.h>
#include <rocksdb/compaction_filter.h>
#include <rocksdb/comparator.h>
#include <rocksdb/convenience.h>
#include <rocksdb/db.h>
#include <rocksdb/env.h>
#include <rocksdb/filter_policy.h>
#include <rocksdb/flush_block_policy.h>
#include <rocksdb/iterator.h>
#include <rocksdb/memtablerep.h>
#include <rocksdb/merge_operator.h>
#include <rocksdb/options.h>
#include <rocksdb/perf_context.h>
#include <rocksdb/slice.h>
#include <rocksdb/slice_transform.h>
#include <rocksdb/statistics.h>
#include <rocksdb/status.h>
#include <rocksdb/table.h>
#include <rocksdb/table_properties.h>
#include <rocksdb/transaction_log.h>
#include <rocksdb/types.h>
#include <rocksdb/universal_compaction.h>
#include <rocksdb/write_batch.h>
#endif

View File

@@ -24,20 +24,20 @@ namespace xrpl {
template <class EF> template <class EF>
class ScopeExit class ScopeExit
{ {
EF exit_function_; EF exitFunction_;
bool execute_on_destruction_{true}; bool executeOnDestruction_{true};
public: public:
~ScopeExit() ~ScopeExit()
{ {
if (execute_on_destruction_) if (executeOnDestruction_)
exit_function_(); exitFunction_();
} }
ScopeExit(ScopeExit&& rhs) noexcept( ScopeExit(ScopeExit&& rhs) noexcept(
std::is_nothrow_move_constructible_v<EF> || std::is_nothrow_copy_constructible_v<EF>) std::is_nothrow_move_constructible_v<EF> || std::is_nothrow_copy_constructible_v<EF>)
: exit_function_{std::forward<EF>(rhs.exit_function_)} : exitFunction_{std::forward<EF>(rhs.exitFunction_)}
, execute_on_destruction_{rhs.execute_on_destruction_} , executeOnDestruction_{rhs.executeOnDestruction_}
{ {
rhs.release(); rhs.release();
} }
@@ -51,7 +51,7 @@ public:
std::enable_if_t< std::enable_if_t<
!std::is_same_v<std::remove_cv_t<EFP>, ScopeExit> && !std::is_same_v<std::remove_cv_t<EFP>, ScopeExit> &&
std::is_constructible_v<EF, EFP>>* = 0) noexcept std::is_constructible_v<EF, EFP>>* = 0) noexcept
: exit_function_{std::forward<EFP>(f)} : exitFunction_{std::forward<EFP>(f)}
{ {
static_assert(std::is_nothrow_constructible_v<EF, decltype(std::forward<EFP>(f))>); static_assert(std::is_nothrow_constructible_v<EF, decltype(std::forward<EFP>(f))>);
} }
@@ -59,7 +59,7 @@ public:
void void
release() noexcept release() noexcept
{ {
execute_on_destruction_ = false; executeOnDestruction_ = false;
} }
}; };
@@ -69,22 +69,22 @@ ScopeExit(EF) -> ScopeExit<EF>;
template <class EF> template <class EF>
class ScopeFail class ScopeFail
{ {
EF exit_function_; EF exitFunction_;
bool execute_on_destruction_{true}; bool executeOnDestruction_{true};
int uncaught_on_creation_{std::uncaught_exceptions()}; int uncaughtOnCreation_{std::uncaught_exceptions()};
public: public:
~ScopeFail() ~ScopeFail()
{ {
if (execute_on_destruction_ && std::uncaught_exceptions() > uncaught_on_creation_) if (executeOnDestruction_ && std::uncaught_exceptions() > uncaughtOnCreation_)
exit_function_(); exitFunction_();
} }
ScopeFail(ScopeFail&& rhs) noexcept( ScopeFail(ScopeFail&& rhs) noexcept(
std::is_nothrow_move_constructible_v<EF> || std::is_nothrow_copy_constructible_v<EF>) std::is_nothrow_move_constructible_v<EF> || std::is_nothrow_copy_constructible_v<EF>)
: exit_function_{std::forward<EF>(rhs.exit_function_)} : exitFunction_{std::forward<EF>(rhs.exitFunction_)}
, execute_on_destruction_{rhs.execute_on_destruction_} , executeOnDestruction_{rhs.executeOnDestruction_}
, uncaught_on_creation_{rhs.uncaught_on_creation_} , uncaughtOnCreation_{rhs.uncaughtOnCreation_}
{ {
rhs.release(); rhs.release();
} }
@@ -98,7 +98,7 @@ public:
std::enable_if_t< std::enable_if_t<
!std::is_same_v<std::remove_cv_t<EFP>, ScopeFail> && !std::is_same_v<std::remove_cv_t<EFP>, ScopeFail> &&
std::is_constructible_v<EF, EFP>>* = 0) noexcept std::is_constructible_v<EF, EFP>>* = 0) noexcept
: exit_function_{std::forward<EFP>(f)} : exitFunction_{std::forward<EFP>(f)}
{ {
static_assert(std::is_nothrow_constructible_v<EF, decltype(std::forward<EFP>(f))>); static_assert(std::is_nothrow_constructible_v<EF, decltype(std::forward<EFP>(f))>);
} }
@@ -106,7 +106,7 @@ public:
void void
release() noexcept release() noexcept
{ {
execute_on_destruction_ = false; executeOnDestruction_ = false;
} }
}; };
@@ -116,22 +116,22 @@ ScopeFail(EF) -> ScopeFail<EF>;
template <class EF> template <class EF>
class ScopeSuccess class ScopeSuccess
{ {
EF exit_function_; EF exitFunction_;
bool execute_on_destruction_{true}; bool executeOnDestruction_{true};
int uncaught_on_creation_{std::uncaught_exceptions()}; int uncaughtOnCreation_{std::uncaught_exceptions()};
public: public:
~ScopeSuccess() noexcept(noexcept(exit_function_())) ~ScopeSuccess() noexcept(noexcept(exitFunction_()))
{ {
if (execute_on_destruction_ && std::uncaught_exceptions() <= uncaught_on_creation_) if (executeOnDestruction_ && std::uncaught_exceptions() <= uncaughtOnCreation_)
exit_function_(); exitFunction_();
} }
ScopeSuccess(ScopeSuccess&& rhs) noexcept( ScopeSuccess(ScopeSuccess&& rhs) noexcept(
std::is_nothrow_move_constructible_v<EF> || std::is_nothrow_copy_constructible_v<EF>) std::is_nothrow_move_constructible_v<EF> || std::is_nothrow_copy_constructible_v<EF>)
: exit_function_{std::forward<EF>(rhs.exit_function_)} : exitFunction_{std::forward<EF>(rhs.exitFunction_)}
, execute_on_destruction_{rhs.execute_on_destruction_} , executeOnDestruction_{rhs.executeOnDestruction_}
, uncaught_on_creation_{rhs.uncaught_on_creation_} , uncaughtOnCreation_{rhs.uncaughtOnCreation_}
{ {
rhs.release(); rhs.release();
} }
@@ -146,14 +146,14 @@ public:
!std::is_same_v<std::remove_cv_t<EFP>, ScopeSuccess> && !std::is_same_v<std::remove_cv_t<EFP>, ScopeSuccess> &&
std::is_constructible_v<EF, EFP>>* = std::is_constructible_v<EF, EFP>>* =
0) noexcept(std::is_nothrow_constructible_v<EF, EFP> || std::is_nothrow_constructible_v<EF, EFP&>) 0) noexcept(std::is_nothrow_constructible_v<EF, EFP> || std::is_nothrow_constructible_v<EF, EFP&>)
: exit_function_{std::forward<EFP>(f)} : exitFunction_{std::forward<EFP>(f)}
{ {
} }
void void
release() noexcept release() noexcept
{ {
execute_on_destruction_ = false; executeOnDestruction_ = false;
} }
}; };

View File

@@ -77,8 +77,8 @@ private:
std::ostream& os_; std::ostream& os_;
Results results_; Results results_;
SuiteResults suite_results_; SuiteResults suiteResults_;
CaseResults case_results_; CaseResults caseResults_;
public: public:
Reporter(Reporter const&) = delete; Reporter(Reporter const&) = delete;
@@ -196,22 +196,22 @@ template <class Unused>
void void
Reporter<Unused>::onSuiteBegin(SuiteInfo const& info) Reporter<Unused>::onSuiteBegin(SuiteInfo const& info)
{ {
suite_results_ = SuiteResults{info.fullName()}; suiteResults_ = SuiteResults{info.fullName()};
} }
template <class Unused> template <class Unused>
void void
Reporter<Unused>::onSuiteEnd() Reporter<Unused>::onSuiteEnd()
{ {
results_.add(suite_results_); results_.add(suiteResults_);
} }
template <class Unused> template <class Unused>
void void
Reporter<Unused>::onCaseBegin(std::string const& name) Reporter<Unused>::onCaseBegin(std::string const& name)
{ {
case_results_ = CaseResults(name); caseResults_ = CaseResults(name);
os_ << suite_results_.name << (case_results_.name.empty() ? "" : (" " + case_results_.name)) os_ << suiteResults_.name << (caseResults_.name.empty() ? "" : (" " + caseResults_.name))
<< std::endl; << std::endl;
} }
@@ -219,23 +219,23 @@ template <class Unused>
void void
Reporter<Unused>::onCaseEnd() Reporter<Unused>::onCaseEnd()
{ {
suite_results_.add(case_results_); suiteResults_.add(caseResults_);
} }
template <class Unused> template <class Unused>
void void
Reporter<Unused>::onPass() Reporter<Unused>::onPass()
{ {
++case_results_.total; ++caseResults_.total;
} }
template <class Unused> template <class Unused>
void void
Reporter<Unused>::onFail(std::string const& reason) Reporter<Unused>::onFail(std::string const& reason)
{ {
++case_results_.failed; ++caseResults_.failed;
++case_results_.total; ++caseResults_.total;
os_ << "#" << case_results_.total << " failed" << (reason.empty() ? "" : ": ") << reason os_ << "#" << caseResults_.total << " failed" << (reason.empty() ? "" : ": ") << reason
<< std::endl; << std::endl;
} }

View File

@@ -47,7 +47,7 @@ inline bool
JobQueue::Coro::post() JobQueue::Coro::post()
{ {
{ {
std::scoped_lock const lk(mutex_run_); std::scoped_lock const lk(mutexRun_);
running_ = true; running_ = true;
} }
@@ -58,7 +58,7 @@ JobQueue::Coro::post()
} }
// The coroutine will not run. Clean up running_. // The coroutine will not run. Clean up running_.
std::scoped_lock const lk(mutex_run_); std::scoped_lock const lk(mutexRun_);
running_ = false; running_ = false;
cv_.notify_all(); cv_.notify_all();
return false; return false;
@@ -68,7 +68,7 @@ inline void
JobQueue::Coro::resume() JobQueue::Coro::resume()
{ {
{ {
std::scoped_lock const lk(mutex_run_); std::scoped_lock const lk(mutexRun_);
running_ = true; running_ = true;
} }
{ {
@@ -92,7 +92,7 @@ JobQueue::Coro::resume()
} }
detail::getLocalValues().release(); detail::getLocalValues().release();
detail::getLocalValues().reset(saved); detail::getLocalValues().reset(saved);
std::scoped_lock const lk(mutex_run_); std::scoped_lock const lk(mutexRun_);
running_ = false; running_ = false;
cv_.notify_all(); cv_.notify_all();
} }
@@ -127,7 +127,7 @@ JobQueue::Coro::expectEarlyExit()
inline void inline void
JobQueue::Coro::join() JobQueue::Coro::join()
{ {
std::unique_lock<std::mutex> lk(mutex_run_); std::unique_lock<std::mutex> lk(mutexRun_);
cv_.wait(lk, [this]() { return !running_; }); cv_.wait(lk, [this]() { return !running_; });
} }

View File

@@ -127,7 +127,7 @@ private:
std::function<void()> job_; std::function<void()> job_;
std::shared_ptr<LoadEvent> loadEvent_; std::shared_ptr<LoadEvent> loadEvent_;
std::string name_; std::string name_;
clock_type::time_point queue_time_; clock_type::time_point queueTime_;
}; };
using JobCounter = ClosureCounter<void>; using JobCounter = ClosureCounter<void>;

View File

@@ -52,7 +52,7 @@ public:
std::string name_; std::string name_;
bool running_{false}; bool running_{false};
std::mutex mutex_; std::mutex mutex_;
std::mutex mutex_run_; std::mutex mutexRun_;
std::condition_variable cv_; std::condition_variable cv_;
boost::coroutines2::coroutine<void>::push_type* yield_{}; boost::coroutines2::coroutine<void>::push_type* yield_{};
boost::coroutines2::coroutine<void>::pull_type coro_; boost::coroutines2::coroutine<void>::pull_type coro_;
@@ -246,7 +246,7 @@ private:
// Statistics tracking // Statistics tracking
perf::PerfLog& perfLog_; perf::PerfLog& perfLog_;
beast::insight::Collector::ptr collector_; beast::insight::Collector::ptr collector_;
beast::insight::Gauge job_count_; beast::insight::Gauge jobCount_;
beast::insight::Hook hook_; beast::insight::Hook hook_;
std::condition_variable cv_; std::condition_variable cv_;

View File

@@ -161,7 +161,7 @@ public:
* While the JSON spec doesn't explicitly disallow this, you should avoid * While the JSON spec doesn't explicitly disallow this, you should avoid
* calling this method twice with the same tag for the same object. * calling this method twice with the same tag for the same object.
* *
* If CHECK_JSON_WRITER is defined, this function throws an exception if if * If CHECK_JSON_WRITER is defined, this function throws an exception if
* the tag you use has already been used in this object. * the tag you use has already been used in this object.
*/ */
template <typename Type> template <typename Type>

View File

@@ -9,7 +9,7 @@ class BookDirs
private: private:
ReadView const* view_ = nullptr; ReadView const* view_ = nullptr;
uint256 const root_; uint256 const root_;
uint256 const next_quality_; uint256 const nextQuality_;
uint256 const key_; uint256 const key_;
std::shared_ptr<SLE const> sle_ = nullptr; std::shared_ptr<SLE const> sle_ = nullptr;
unsigned int entry_ = 0; unsigned int entry_ = 0;
@@ -67,15 +67,15 @@ private:
friend class BookDirs; friend class BookDirs;
const_iterator(ReadView const& view, uint256 const& root, uint256 const& dirKey) const_iterator(ReadView const& view, uint256 const& root, uint256 const& dirKey)
: view_(&view), root_(root), key_(dirKey), cur_key_(dirKey) : view_(&view), root_(root), key_(dirKey), curKey_(dirKey)
{ {
} }
ReadView const* view_ = nullptr; ReadView const* view_ = nullptr;
uint256 root_; uint256 root_;
uint256 next_quality_; uint256 nextQuality_;
uint256 key_; uint256 key_;
uint256 cur_key_; uint256 curKey_;
std::shared_ptr<SLE const> sle_; std::shared_ptr<SLE const> sle_;
unsigned int entry_ = 0; unsigned int entry_ = 0;
uint256 index_; uint256 index_;

View File

@@ -1,49 +0,0 @@
#pragma once
#include <xrpl/protocol/MultiApiJson.h>
#include <xrpl/server/InfoSub.h>
#include <memory>
#include <mutex>
namespace xrpl {
/** Listen to public/subscribe messages from a book. */
class BookListeners
{
public:
using pointer = std::shared_ptr<BookListeners>;
BookListeners() = default;
/** Add a new subscription for this book
*/
void
addSubscriber(InfoSub::ref sub);
/** Stop publishing to a subscriber
*/
void
removeSubscriber(std::uint64_t sub);
/** Publish a transaction to subscribers
Publish a transaction to clients subscribed to changes on this book.
Uses havePublished to prevent sending duplicate transactions to clients
that have subscribed to multiple books.
@param jvObj JSON transaction data to publish
@param havePublished InfoSub sequence numbers that have already
published this transaction.
*/
void
publish(MultiApiJson const& jvObj, hash_set<std::uint64_t>& havePublished);
private:
std::recursive_mutex lock_;
hash_map<std::uint64_t, InfoSub::wptr> listeners_;
};
} // namespace xrpl

View File

@@ -76,7 +76,7 @@ private:
// monotonic_resource_ must outlive `items_`. Make a pointer so it may be // monotonic_resource_ must outlive `items_`. Make a pointer so it may be
// easily moved. // easily moved.
std::unique_ptr<boost::container::pmr::monotonic_buffer_resource> monotonic_resource_; std::unique_ptr<boost::container::pmr::monotonic_buffer_resource> monotonicResource_;
txs_map txs_; txs_map txs_;
Rules rules_; Rules rules_;
LedgerHeader header_; LedgerHeader header_;

View File

@@ -1,11 +1,11 @@
#pragma once #pragma once
#include <xrpl/basics/UnorderedContainers.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/AcceptedLedgerTx.h> #include <xrpl/ledger/AcceptedLedgerTx.h>
#include <xrpl/ledger/BookListeners.h>
#include <xrpl/ledger/ReadView.h> #include <xrpl/ledger/ReadView.h>
#include <xrpl/protocol/Asset.h> #include <xrpl/protocol/Asset.h>
#include <xrpl/protocol/Book.h> #include <xrpl/protocol/Book.h>
#include <xrpl/protocol/MultiApiJson.h>
#include <xrpl/protocol/UintTypes.h> #include <xrpl/protocol/UintTypes.h>
#include <memory> #include <memory>
@@ -77,34 +77,24 @@ public:
*/ */
virtual bool virtual bool
isBookToXRP(Asset const& asset, std::optional<Domain> const& domain = std::nullopt) = 0; isBookToXRP(Asset const& asset, std::optional<Domain> const& domain = std::nullopt) = 0;
/**
* Process a transaction for order book tracking.
* @param ledger The ledger the transaction was applied to
* @param alTx The transaction to process
* @param jvObj The JSON object of the transaction
*/
virtual void
processTxn(
std::shared_ptr<ReadView const> const& ledger,
AcceptedLedgerTx const& alTx,
MultiApiJson const& jvObj) = 0;
/**
* Get the book listeners for a book.
* @param book The book to get the listeners for
* @return The book listeners for the book
*/
virtual BookListeners::pointer
getBookListeners(Book const&) = 0;
/**
* Create a new book listeners for a book.
* @param book The book to create the listeners for
* @return The new book listeners for the book
*/
virtual BookListeners::pointer
makeBookListeners(Book const&) = 0;
}; };
/** Extract the set of books affected by a transaction.
*
* Walks the transaction's metadata nodes and collects every order book
* whose offers were created, modified, or deleted. Used by NetworkOPs to
* fan transaction notifications out to book subscribers.
*
* @param alTx The accepted ledger transaction to inspect.
* @param j Journal used to log per-node parsing failures. Inspecting an
* offer node can throw if a required field is missing; in that
* case the bad node is skipped and a warn-level message is
* emitted via @p j. Other affected books in the same transaction
* are still returned.
* @return The set of books whose offers were created, modified, or
* deleted. May be empty for non-offer transactions.
*/
hash_set<Book>
affectedBooks(AcceptedLedgerTx const& alTx, beast::Journal const& j);
} // namespace xrpl } // namespace xrpl

View File

@@ -57,7 +57,7 @@ isVaultPseudoAccountFrozen(
ReadView const& view, ReadView const& view,
AccountID const& account, AccountID const& account,
MPTIssue const& mptShare, MPTIssue const& mptShare,
int depth); std::uint8_t depth);
[[nodiscard]] bool [[nodiscard]] bool
isLPTokenFrozen( isLPTokenFrozen(

View File

@@ -22,14 +22,14 @@ public:
static constexpr size_t kInitialBufferSize = kilobytes(256); static constexpr size_t kInitialBufferSize = kilobytes(256);
RawStateTable() RawStateTable()
: monotonic_resource_{std::make_unique<boost::container::pmr::monotonic_buffer_resource>( : monotonicResource_{std::make_unique<boost::container::pmr::monotonic_buffer_resource>(
kInitialBufferSize)} kInitialBufferSize)}
, items_{monotonic_resource_.get()} {}; , items_{monotonicResource_.get()} {};
RawStateTable(RawStateTable const& rhs) RawStateTable(RawStateTable const& rhs)
: monotonic_resource_{std::make_unique<boost::container::pmr::monotonic_buffer_resource>( : monotonicResource_{std::make_unique<boost::container::pmr::monotonic_buffer_resource>(
kInitialBufferSize)} kInitialBufferSize)}
, items_{rhs.items_, monotonic_resource_.get()} , items_{rhs.items_, monotonicResource_.get()}
, dropsDestroyed_{rhs.dropsDestroyed_} {}; , dropsDestroyed_{rhs.dropsDestroyed_} {};
RawStateTable(RawStateTable&&) = default; RawStateTable(RawStateTable&&) = default;
@@ -101,7 +101,7 @@ private:
boost::container::pmr::polymorphic_allocator<std::pair<key_type const, SleAction>>>; boost::container::pmr::polymorphic_allocator<std::pair<key_type const, SleAction>>>;
// monotonic_resource_ must outlive `items_`. Make a pointer so it may be // monotonic_resource_ must outlive `items_`. Make a pointer so it may be
// easily moved. // easily moved.
std::unique_ptr<boost::container::pmr::monotonic_buffer_resource> monotonic_resource_; std::unique_ptr<boost::container::pmr::monotonic_buffer_resource> monotonicResource_;
items_t items_; items_t items_;
XRPAmount dropsDestroyed_{0}; XRPAmount dropsDestroyed_{0};

View File

@@ -4,8 +4,38 @@
#include <xrpl/protocol/Rules.h> #include <xrpl/protocol/Rules.h>
#include <xrpl/protocol/st.h> #include <xrpl/protocol/st.h>
#include <string_view>
namespace xrpl { namespace xrpl {
/**
* Broker cover preclaim precision guard (fixCleanup3_2_0).
*
* Prevents a "silent sub-ULP no-op" where a deposit, withdrawal, or clawback
* amount is so small that it rounds to zero at `sfCoverAvailable`'s scale.
* Without this guard, both the pseudo trust-line and `sfCoverAvailable` would
* identically absorb the rounded zero, resulting in a successful transaction
* (tesSUCCESS) where no funds actually moved.
*
* @param view Read view (rules used for amendment gating).
* @param sleBroker The loan broker SLE (read-only).
* @param vaultAsset The underlying vault asset (the broker's cover asset).
* @param amount The effective subtraction/addition amount.
* @param j Journal for logging.
* @param logPrefix Transactor name for log diagnostics.
*
* @return `tecPRECISION_LOSS` if the request rounds to zero at cover scale.
* `tesSUCCESS` if the amendment is disabled or the request is safely supra-ULP.
*/
[[nodiscard]] TER
canApplyToBrokerCover(
ReadView const& view,
SLE::const_ref sleBroker,
Asset const& vaultAsset,
STAmount const& amount,
beast::Journal j,
std::string_view logPrefix);
// Lending protocol has dependencies, so capture them here. // Lending protocol has dependencies, so capture them here.
bool bool
checkLendingProtocolDependencies(Rules const& rules, STTx const& tx); checkLendingProtocolDependencies(Rules const& rules, STTx const& tx);
@@ -173,6 +203,21 @@ getAssetsTotalScale(SLE::const_ref vaultSle)
return scale(vaultSle->at(sfAssetsTotal), vaultSle->at(sfAsset)); return scale(vaultSle->at(sfAssetsTotal), vaultSle->at(sfAsset));
} }
// Compute the minimum required broker cover, rounded consistently.
// DebtTotal is a broker-level aggregate maintained at vault scale, so the
// rounding must also use vault scale — never an individual loan's scale.
inline Number
minimumBrokerCover(Number const& debtTotal, TenthBips32 coverRateMinimum, SLE::const_ref vaultSle)
{
XRPL_ASSERT(
vaultSle && vaultSle->getType() == ltVAULT, "xrpl::minimumBrokerCover : valid Vault sle");
NumberRoundModeGuard const mg(Number::RoundingMode::Upward);
return roundToAsset(
vaultSle->at(sfAsset),
tenthBipsOfValue(debtTotal, coverRateMinimum),
getAssetsTotalScale(vaultSle));
}
TER TER
checkLoanGuards( checkLoanGuards(
Asset const& vaultAsset, Asset const& vaultAsset,
@@ -416,6 +461,7 @@ loanAccruedInterest(
ExtendedPaymentComponents ExtendedPaymentComponents
computeOverpaymentComponents( computeOverpaymentComponents(
Rules const& rules,
Asset const& asset, Asset const& asset,
int32_t const loanScale, int32_t const loanScale,
Number const& overpayment, Number const& overpayment,

View File

@@ -27,14 +27,18 @@ isGlobalFrozen(ReadView const& view, MPTIssue const& mptIssue);
isIndividualFrozen(ReadView const& view, AccountID const& account, MPTIssue const& mptIssue); isIndividualFrozen(ReadView const& view, AccountID const& account, MPTIssue const& mptIssue);
[[nodiscard]] bool [[nodiscard]] bool
isFrozen(ReadView const& view, AccountID const& account, MPTIssue const& mptIssue, int depth = 0); isFrozen(
ReadView const& view,
AccountID const& account,
MPTIssue const& mptIssue,
std::uint8_t depth = 0);
[[nodiscard]] bool [[nodiscard]] bool
isAnyFrozen( isAnyFrozen(
ReadView const& view, ReadView const& view,
std::initializer_list<AccountID> const& accounts, std::initializer_list<AccountID> const& accounts,
MPTIssue const& mptIssue, MPTIssue const& mptIssue,
int depth = 0); std::uint8_t depth = 0);
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// //
@@ -88,7 +92,7 @@ requireAuth(
MPTIssue const& mptIssue, MPTIssue const& mptIssue,
AccountID const& account, AccountID const& account,
AuthType authType = AuthType::Legacy, AuthType authType = AuthType::Legacy,
int depth = 0); std::uint8_t depth = 0);
/** Enforce account has MPToken to match its authorization. /** Enforce account has MPToken to match its authorization.
* *
@@ -104,22 +108,77 @@ enforceMPTokenAuthorization(
XRPAmount const& priorBalance, XRPAmount const& priorBalance,
beast::Journal j); beast::Journal j);
/** Check if the destination account is allowed /** Resolve the underlying asset of a vault share.
* to receive MPT. Return tecNO_AUTH if it doesn't *
* and tesSUCCESS otherwise. * Reads sfReferenceHolding from @p sleShareIssuance to determine which
* asset the vault wraps. @p sleHolding must be the SLE that
* sfReferenceHolding points to — either an ltMPTOKEN (returns its
* MPTIssue) or an ltRIPPLE_STATE (returns its low/high Issue).
*
* @pre Both SLEs must exist and @p sleHolding must be of type ltMPTOKEN
* or ltRIPPLE_STATE. Passing any other type is undefined behaviour.
* @param sleShareIssuance MPTokenIssuance SLE for the vault share token.
* @param sleHolding SLE referenced by sfReferenceHolding.
* @return The underlying Asset (MPTIssue or Issue).
*/
[[nodiscard]] Asset
assetOfHolding(SLE const& sleShareIssuance, SLE const& sleHolding);
/** Check whether @p to may receive the given MPT from @p from.
*
* The check passes when any of the following is true:
* - @p waive is WaiveMPTCanTransfer::Yes (recovery-path exemption), or
* - @p from or @p to is the issuer, or
* - lsfMPTCanTransfer is set on the MPTokenIssuance.
*
* For vault shares (MPTokenIssuances that carry sfReferenceHolding) the
* check recurses into the underlying asset's transferability. This
* recursion is defensive; vault-of-vault-shares is rejected at vault
* creation, so in practice depth never exceeds 1.
*
* @param view Ledger state to read from.
* @param mptIssue The MPT issuance being transferred.
* @param from Sending account.
* @param to Receiving account.
* @param waive WaiveMPTCanTransfer::Yes skips the lsfMPTCanTransfer
* check. Use for recovery paths (e.g. unwinding SAV or
* Lending Protocol positions after an issuer revokes
* transferability).
* @param depth Recursion depth; bounded at kMaxAssetCheckDepth.
* @return tesSUCCESS if the transfer is allowed, tecNO_AUTH otherwise.
*/ */
[[nodiscard]] TER [[nodiscard]] TER
canTransfer( canTransfer(
ReadView const& view, ReadView const& view,
MPTIssue const& mptIssue, MPTIssue const& mptIssue,
AccountID const& from, AccountID const& from,
AccountID const& to); AccountID const& to,
WaiveMPTCanTransfer waive = WaiveMPTCanTransfer::No,
std::uint8_t depth = 0);
/** Check if Asset can be traded on DEX. return tecNO_PERMISSION /** Check whether @p asset may be traded on the DEX.
* if it doesn't and tesSUCCESS otherwise. *
* For IOU assets the check delegates to the existing offer/AMM freeze
* logic. For MPT assets it checks lsfMPTCanTrade on the MPTokenIssuance.
* Vault shares recurse into the underlying asset's tradability via
* sfReferenceHolding; depth is bounded at kMaxAssetCheckDepth.
*
* @param view Ledger state to read from.
* @param asset The asset to check.
* @param depth Recursion depth; bounded at kMaxAssetCheckDepth.
* @return tesSUCCESS if trading is allowed, tecNO_PERMISSION otherwise.
*/ */
[[nodiscard]] TER [[nodiscard]] TER
canTrade(ReadView const& view, Asset const& asset); canTrade(ReadView const& view, Asset const& asset, std::uint8_t depth = 0);
/** Convenience to combine canTrade/Transfer. Returns tesSUCCESS if Asset is Issue.
*/
[[nodiscard]] TER
canMPTTradeAndTransfer(
ReadView const& v,
Asset const& asset,
AccountID const& from,
AccountID const& to);
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// //
@@ -227,17 +286,4 @@ issuerFundsToSelfIssue(ReadView const& view, MPTIssue const& issue);
void void
issuerSelfDebitHookMPT(ApplyView& view, MPTIssue const& issue, std::uint64_t amount); issuerSelfDebitHookMPT(ApplyView& view, MPTIssue const& issue, std::uint64_t amount);
//------------------------------------------------------------------------------
//
// MPT DEX
//
//------------------------------------------------------------------------------
/* Return true if a transaction is allowed for the specified MPT/account. The
* function checks MPTokenIssuance and MPToken objects flags to determine if the
* transaction is allowed.
*/
TER
checkMPTTxAllowed(ReadView const& v, TxType tx, Asset const& asset, AccountID const& accountID);
} // namespace xrpl } // namespace xrpl

View File

@@ -5,8 +5,21 @@
#include <xrpl/protocol/TER.h> #include <xrpl/protocol/TER.h>
#include <xrpl/protocol/UintTypes.h> #include <xrpl/protocol/UintTypes.h>
#include <cstdint>
#include <memory>
#include <optional>
namespace xrpl { namespace xrpl {
/** Close a payment channel and return its remaining funds to the channel owner.
*
* @param slep The SLE for the PayChannel object to close.
* @param view The apply view in which ledger state modifications are made.
* @param key The ledger key identifying the PayChannel entry.
* @param j Journal used for fatal-level diagnostic messages.
* @return tesSUCCESS on success; tefBAD_LEDGER if a directory removal
* fails; tefINTERNAL if the source account SLE cannot be found.
*/
TER TER
closeChannel( closeChannel(
std::shared_ptr<SLE> const& slep, std::shared_ptr<SLE> const& slep,
@@ -14,4 +27,27 @@ closeChannel(
uint256 const& key, uint256 const& key,
beast::Journal j); beast::Journal j);
/** Add two uint32_t values with saturation at UINT32_MAX.
*
* @param rules The current ledger rules used to check amendment status.
* @param lhs Left-hand operand.
* @param rhs Right-hand operand.
* @return @p lhs + @p rhs, saturated at UINT32_MAX when the amendment
* is active.
*/
uint32_t
saturatingAdd(Rules const& rules, uint32_t const lhs, uint32_t const rhs);
/** Determine whether a payment channel time field represents an expired time.
*
* @param view The apply view providing the parent close time and rules.
* @param timeField The optional expiry timestamp (seconds since the XRP
* Ledger epoch). If empty, the function returns false.
* @return @c true if @p timeField is set and the indicated time is
* in the past relative to the view's parent close time;
* @c false otherwise.
*/
bool
isChannelExpired(ApplyView const& view, std::optional<std::uint32_t> timeField);
} // namespace xrpl } // namespace xrpl

View File

@@ -93,7 +93,7 @@ isFrozen(ReadView const& view, AccountID const& account, Issue const& issue)
// Overload with depth parameter for uniformity with MPTIssue version. // Overload with depth parameter for uniformity with MPTIssue version.
// The depth parameter is ignored for IOUs since they don't have vault recursion. // The depth parameter is ignored for IOUs since they don't have vault recursion.
[[nodiscard]] inline bool [[nodiscard]] inline bool
isFrozen(ReadView const& view, AccountID const& account, Issue const& issue, int /*depth*/) isFrozen(ReadView const& view, AccountID const& account, Issue const& issue, std::uint8_t /*depth*/)
{ {
return isFrozen(view, account, issue); return isFrozen(view, account, issue);
} }
@@ -110,7 +110,7 @@ isDeepFrozen(
ReadView const& view, ReadView const& view,
AccountID const& account, AccountID const& account,
Issue const& issue, Issue const& issue,
int = 0 /*ignored*/) std::uint8_t = 0 /*ignored*/)
{ {
return isDeepFrozen(view, account, issue.currency, issue.account); return isDeepFrozen(view, account, issue.currency, issue.account);
} }

View File

@@ -34,6 +34,15 @@ enum class WaiveTransferFee : bool { No = false, Yes };
/** Controls whether accountSend is allowed to overflow OutstandingAmount **/ /** Controls whether accountSend is allowed to overflow OutstandingAmount **/
enum class AllowMPTOverflow : bool { No = false, Yes }; enum class AllowMPTOverflow : bool { No = false, Yes };
/** Controls whether canTransfer enforces lsfMPTCanTransfer on MPTs.
*
* Default is No (enforce). Use Yes at call sites that must remain available
* even when an MPT issuer has cleared lsfMPTCanTransfer - for example,
* unwinding existing positions in SAV or the Lending Protocol. Has no
* effect on the IOU branch of canTransfer.
*/
enum class WaiveMPTCanTransfer : bool { No = false, Yes };
/* Check if MPToken (for MPT) or trust line (for IOU) exists: /* Check if MPToken (for MPT) or trust line (for IOU) exists:
* - StrongAuth - before checking if authorization is required * - StrongAuth - before checking if authorization is required
* - WeakAuth * - WeakAuth
@@ -54,16 +63,26 @@ enum class AuthType { StrongAuth, WeakAuth, Legacy };
[[nodiscard]] bool [[nodiscard]] bool
isGlobalFrozen(ReadView const& view, Asset const& asset); isGlobalFrozen(ReadView const& view, Asset const& asset);
[[nodiscard]] TER
checkGlobalFrozen(ReadView const& view, Asset const& asset);
[[nodiscard]] bool [[nodiscard]] bool
isIndividualFrozen(ReadView const& view, AccountID const& account, Asset const& asset); isIndividualFrozen(ReadView const& view, AccountID const& account, Asset const& asset);
[[nodiscard]] TER
checkIndividualFrozen(ReadView const& view, AccountID const& account, Asset const& asset);
/** /**
* isFrozen check is recursive for MPT shares in a vault, descending to * isFrozen check is recursive for MPT shares in a vault, descending to
* assets in the vault, up to maxAssetCheckDepth recursion depth. This is * assets in the vault, up to maxAssetCheckDepth recursion depth. This is
* purely defensive, as we currently do not allow such vaults to be created. * purely defensive, as we currently do not allow such vaults to be created.
*/ */
[[nodiscard]] bool [[nodiscard]] bool
isFrozen(ReadView const& view, AccountID const& account, Asset const& asset, int depth = 0); isFrozen(
ReadView const& view,
AccountID const& account,
Asset const& asset,
std::uint8_t depth = 0);
[[nodiscard]] TER [[nodiscard]] TER
checkFrozen(ReadView const& view, AccountID const& account, Issue const& issue); checkFrozen(ReadView const& view, AccountID const& account, Issue const& issue);
@@ -85,14 +104,14 @@ isAnyFrozen(
ReadView const& view, ReadView const& view,
std::initializer_list<AccountID> const& accounts, std::initializer_list<AccountID> const& accounts,
Asset const& asset, Asset const& asset,
int depth = 0); std::uint8_t depth = 0);
[[nodiscard]] bool [[nodiscard]] bool
isDeepFrozen( isDeepFrozen(
ReadView const& view, ReadView const& view,
AccountID const& account, AccountID const& account,
MPTIssue const& mptIssue, MPTIssue const& mptIssue,
int depth = 0); std::uint8_t depth = 0);
/** /**
* isFrozen check is recursive for MPT shares in a vault, descending to * isFrozen check is recursive for MPT shares in a vault, descending to
@@ -100,7 +119,11 @@ isDeepFrozen(
* purely defensive, as we currently do not allow such vaults to be created. * purely defensive, as we currently do not allow such vaults to be created.
*/ */
[[nodiscard]] bool [[nodiscard]] bool
isDeepFrozen(ReadView const& view, AccountID const& account, Asset const& asset, int depth = 0); isDeepFrozen(
ReadView const& view,
AccountID const& account,
Asset const& asset,
std::uint8_t depth = 0);
[[nodiscard]] TER [[nodiscard]] TER
checkDeepFrozen(ReadView const& view, AccountID const& account, MPTIssue const& mptIssue); checkDeepFrozen(ReadView const& view, AccountID const& account, MPTIssue const& mptIssue);
@@ -234,7 +257,13 @@ requireAuth(
AuthType authType = AuthType::Legacy); AuthType authType = AuthType::Legacy);
[[nodiscard]] TER [[nodiscard]] TER
canTransfer(ReadView const& view, Asset const& asset, AccountID const& from, AccountID const& to); canTransfer(
ReadView const& view,
Asset const& asset,
AccountID const& from,
AccountID const& to,
WaiveMPTCanTransfer waive = WaiveMPTCanTransfer::No,
std::uint8_t depth = 0);
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// //

View File

@@ -1,5 +1,7 @@
#pragma once #pragma once
#include <xrpl/ledger/ReadView.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/STAmount.h> #include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/STLedgerEntry.h> #include <xrpl/protocol/STLedgerEntry.h>
@@ -43,6 +45,14 @@ sharesToAssetsDeposit(
/** Controls whether to truncate shares instead of rounding. */ /** Controls whether to truncate shares instead of rounding. */
enum class TruncateShares : bool { No = false, Yes = true }; enum class TruncateShares : bool { No = false, Yes = true };
/** Controls whether the withdraw conversion helpers
(assetsToSharesWithdraw and sharesToAssetsWithdraw) subtract
sfLossUnrealized from sfAssetsTotal before computing the exchange rate.
The default (No) applies the standard discounted rate; Yes is used when
the redeemer is the sole remaining shareholder.
*/
enum class WaiveUnrealizedLoss : bool { No = false, Yes = true };
/** From the perspective of a vault, return the number of shares to demand from /** From the perspective of a vault, return the number of shares to demand from
the depositor when they ask to withdraw a fixed amount of assets. Since the depositor when they ask to withdraw a fixed amount of assets. Since
shares are MPT this number is integral, and it will be rounded to nearest shares are MPT this number is integral, and it will be rounded to nearest
@@ -52,6 +62,8 @@ enum class TruncateShares : bool { No = false, Yes = true };
@param issuance The MPTokenIssuance SLE for the vault's shares. @param issuance The MPTokenIssuance SLE for the vault's shares.
@param assets The amount of assets to convert. @param assets The amount of assets to convert.
@param truncate Whether to truncate instead of rounding. @param truncate Whether to truncate instead of rounding.
@param waive Whether to waive the unrealized-loss discount when computing
the exchange rate.
@return The number of shares, or nullopt on error. @return The number of shares, or nullopt on error.
*/ */
@@ -60,7 +72,8 @@ assetsToSharesWithdraw(
std::shared_ptr<SLE const> const& vault, std::shared_ptr<SLE const> const& vault,
std::shared_ptr<SLE const> const& issuance, std::shared_ptr<SLE const> const& issuance,
STAmount const& assets, STAmount const& assets,
TruncateShares truncate = TruncateShares::No); TruncateShares truncate = TruncateShares::No,
WaiveUnrealizedLoss waive = WaiveUnrealizedLoss::No);
/** From the perspective of a vault, return the number of assets to give the /** From the perspective of a vault, return the number of assets to give the
depositor when they redeem a fixed amount of shares. Note, since shares are depositor when they redeem a fixed amount of shares. Note, since shares are
@@ -69,6 +82,8 @@ assetsToSharesWithdraw(
@param vault The vault SLE. @param vault The vault SLE.
@param issuance The MPTokenIssuance SLE for the vault's shares. @param issuance The MPTokenIssuance SLE for the vault's shares.
@param shares The amount of shares to convert. @param shares The amount of shares to convert.
@param waive Whether to waive (i.e. not subtract) the vault's unrealized
loss when computing the exchange rate.
@return The number of assets, or nullopt on error. @return The number of assets, or nullopt on error.
*/ */
@@ -76,6 +91,22 @@ assetsToSharesWithdraw(
sharesToAssetsWithdraw( sharesToAssetsWithdraw(
std::shared_ptr<SLE const> const& vault, std::shared_ptr<SLE const> const& vault,
std::shared_ptr<SLE const> const& issuance, std::shared_ptr<SLE const> const& issuance,
STAmount const& shares); STAmount const& shares,
WaiveUnrealizedLoss waive = WaiveUnrealizedLoss::No);
/** Returns true iff `account` holds all of the vault's outstanding shares —
i.e. is the sole remaining shareholder. Returns false if the account
holds no shares or fewer than the total outstanding.
@param view The ledger view.
@param account The candidate sole shareholder.
@param issuance The MPTokenIssuance SLE for the vault's shares; provides
both the share MPTID and the outstanding-amount total.
*/
[[nodiscard]] bool
isSoleShareholder(
ReadView const& view,
AccountID const& account,
std::shared_ptr<SLE const> const& issuance);
} // namespace xrpl } // namespace xrpl

View File

@@ -21,13 +21,13 @@ public:
bool sslVerify, bool sslVerify,
beast::Journal j, beast::Journal j,
boost::asio::ssl::context_base::method method = boost::asio::ssl::context::sslv23) boost::asio::ssl::context_base::method method = boost::asio::ssl::context::sslv23)
: ssl_context_{method}, j_(j), verify_{sslVerify} : sslContext_{method}, j_(j), verify_{sslVerify}
{ {
boost::system::error_code ec; boost::system::error_code ec;
if (sslVerifyFile.empty()) if (sslVerifyFile.empty())
{ {
registerSSLCerts(ssl_context_, ec, j_); registerSSLCerts(sslContext_, ec, j_);
if (ec && sslVerifyDir.empty()) if (ec && sslVerifyDir.empty())
{ {
@@ -37,12 +37,12 @@ public:
} }
else else
{ {
ssl_context_.load_verify_file(sslVerifyFile); sslContext_.load_verify_file(sslVerifyFile);
} }
if (!sslVerifyDir.empty()) if (!sslVerifyDir.empty())
{ {
ssl_context_.add_verify_path(sslVerifyDir, ec); sslContext_.add_verify_path(sslVerifyDir, ec);
if (ec) if (ec)
{ {
@@ -55,7 +55,7 @@ public:
boost::asio::ssl::context& boost::asio::ssl::context&
context() context()
{ {
return ssl_context_; return sslContext_;
} }
[[nodiscard]] bool [[nodiscard]] bool
@@ -153,7 +153,7 @@ public:
} }
private: private:
boost::asio::ssl::context ssl_context_; boost::asio::ssl::context sslContext_;
beast::Journal const j_; beast::Journal const j_;
bool const verify_; bool const verify_;
}; };

View File

@@ -83,10 +83,6 @@ public:
virtual Status virtual Status
fetch(uint256 const& hash, std::shared_ptr<NodeObject>* pObject) = 0; fetch(uint256 const& hash, std::shared_ptr<NodeObject>* pObject) = 0;
/** Fetch a batch synchronously. */
virtual std::pair<std::vector<std::shared_ptr<NodeObject>>, Status>
fetchBatch(std::vector<uint256> const& hashes) = 0;
/** Store a single object. /** Store a single object.
Depending on the implementation this may happen immediately Depending on the implementation this may happen immediately
or deferred using a scheduled task. or deferred using a scheduled task.

View File

@@ -131,6 +131,10 @@ public:
std::uint32_t ledgerSeq, std::uint32_t ledgerSeq,
std::function<void(std::shared_ptr<NodeObject> const&)>&& callback); std::function<void(std::shared_ptr<NodeObject> const&)>&& callback);
/** Remove expired entries from the positive and negative caches. */
virtual void
sweep() = 0;
/** Gather statistics pertaining to read and write activities. /** Gather statistics pertaining to read and write activities.
* *
* @param obj Json object reference into which to place counters. * @param obj Json object reference into which to place counters.

View File

@@ -22,6 +22,32 @@ public:
beast::Journal j) beast::Journal j)
: Database(scheduler, readThreads, config, j), backend_(std::move(backend)) : Database(scheduler, readThreads, config, j), backend_(std::move(backend))
{ {
std::optional<int> cacheSize, cacheAge;
if (config.exists("cache_size"))
{
cacheSize = get<int>(config, "cache_size");
if (cacheSize.value() < 0)
Throw<std::runtime_error>("Specified negative value for cache_size");
}
if (config.exists("cache_age"))
{
cacheAge = get<int>(config, "cache_age");
if (cacheAge.value() < 0)
Throw<std::runtime_error>("Specified negative value for cache_age");
}
if (cacheSize.has_value() || cacheAge.has_value())
{
cache_ = std::make_shared<TaggedCache<uint256, NodeObject>>(
"DatabaseNodeImp",
cacheSize.value_or(0),
std::chrono::minutes(cacheAge.value_or(0)),
stopwatch(),
j);
}
XRPL_ASSERT( XRPL_ASSERT(
backend_, backend_,
"xrpl::NodeStore::DatabaseNodeImp::DatabaseNodeImp : non-null " "xrpl::NodeStore::DatabaseNodeImp::DatabaseNodeImp : non-null "
@@ -67,16 +93,19 @@ public:
backend_->sync(); backend_->sync();
} }
std::vector<std::shared_ptr<NodeObject>>
fetchBatch(std::vector<uint256> const& hashes);
void void
asyncFetch( asyncFetch(
uint256 const& hash, uint256 const& hash,
std::uint32_t ledgerSeq, std::uint32_t ledgerSeq,
std::function<void(std::shared_ptr<NodeObject> const&)>&& callback) override; std::function<void(std::shared_ptr<NodeObject> const&)>&& callback) override;
void
sweep() override;
private: private:
// Cache for database objects. This cache is not always initialized. Check
// for null before using.
std::shared_ptr<TaggedCache<uint256, NodeObject>> cache_;
// Persistent key/value storage // Persistent key/value storage
std::shared_ptr<Backend> backend_; std::shared_ptr<Backend> backend_;

View File

@@ -55,6 +55,9 @@ public:
void void
sync() override; sync() override;
void
sweep() override;
private: private:
std::shared_ptr<Backend> writableBackend_; std::shared_ptr<Backend> writableBackend_;
std::shared_ptr<Backend> archiveBackend_; std::shared_ptr<Backend> archiveBackend_;

View File

@@ -102,25 +102,32 @@ getAPIVersionNumber(json::Value const& jv, bool betaEnabled)
json::Value const maxVersion( json::Value const maxVersion(
betaEnabled ? RPC::kApiBetaVersion : RPC::kApiMaximumSupportedVersion); betaEnabled ? RPC::kApiBetaVersion : RPC::kApiMaximumSupportedVersion);
if (jv.isObject()) if (!jv.isObject() || !jv.isMember(jss::api_version))
return RPC::kApiVersionIfUnspecified;
try
{ {
if (jv.isMember(jss::api_version)) auto const& rawVersion = jv[jss::api_version];
switch (rawVersion.type())
{ {
auto const specifiedVersion = jv[jss::api_version]; case json::ValueType::Int:
if (!specifiedVersion.isInt() && !specifiedVersion.isUInt()) if (rawVersion.asInt() < 0)
{ return RPC::kApiInvalidVersion;
return RPC::kApiInvalidVersion; [[fallthrough]];
case json::ValueType::UInt: {
auto const apiVersion = rawVersion.asUInt();
if (apiVersion < kMinVersion || apiVersion > maxVersion)
return RPC::kApiInvalidVersion;
return apiVersion;
} }
auto const specifiedVersionInt = specifiedVersion.asInt(); default:
if (specifiedVersionInt < kMinVersion || specifiedVersionInt > maxVersion)
{
return RPC::kApiInvalidVersion; return RPC::kApiInvalidVersion;
}
return specifiedVersionInt;
} }
} }
catch (...)
return RPC::kApiVersionIfUnspecified; {
return RPC::kApiInvalidVersion;
}
} }
} // namespace RPC } // namespace RPC

View File

@@ -140,8 +140,8 @@ private:
using issue_hasher = std::hash<xrpl::Issue>; using issue_hasher = std::hash<xrpl::Issue>;
using mptissue_hasher = std::hash<xrpl::MPTIssue>; using mptissue_hasher = std::hash<xrpl::MPTIssue>;
issue_hasher m_issue_hasher_; issue_hasher mIssueHasher_;
mptissue_hasher m_mptissue_hasher_; mptissue_hasher mMptissueHasher_;
public: public:
explicit hash() = default; explicit hash() = default;
@@ -151,11 +151,11 @@ public:
{ {
return asset.visit( return asset.visit(
[&](xrpl::Issue const& issue) { [&](xrpl::Issue const& issue) {
value_type const result(m_issue_hasher_(issue)); value_type const result(mIssueHasher_(issue));
return result; return result;
}, },
[&](xrpl::MPTIssue const& issue) { [&](xrpl::MPTIssue const& issue) {
value_type const result(m_mptissue_hasher_(issue)); value_type const result(mMptissueHasher_(issue));
return result; return result;
}); });
} }
@@ -170,8 +170,8 @@ private:
using asset_hasher = std::hash<xrpl::Asset>; using asset_hasher = std::hash<xrpl::Asset>;
using uint256_hasher = xrpl::uint256::hasher; using uint256_hasher = xrpl::uint256::hasher;
asset_hasher issue_hasher_; asset_hasher issueHasher_;
uint256_hasher uint256_hasher_; uint256_hasher uint256Hasher_;
public: public:
hash() = default; hash() = default;
@@ -182,11 +182,11 @@ public:
value_type value_type
operator()(argument_type const& value) const operator()(argument_type const& value) const
{ {
value_type result(issue_hasher_(value.in)); value_type result(issueHasher_(value.in));
boost::hash_combine(result, issue_hasher_(value.out)); boost::hash_combine(result, issueHasher_(value.out));
if (value.domain) if (value.domain)
boost::hash_combine(result, uint256_hasher_(*value.domain)); boost::hash_combine(result, uint256Hasher_(*value.domain));
return result; return result;
} }

View File

@@ -172,24 +172,24 @@ struct ErrorInfo
{ {
// Default ctor needed to produce an empty std::array during constexpr eval. // Default ctor needed to produce an empty std::array during constexpr eval.
constexpr ErrorInfo() constexpr ErrorInfo()
: code(RpcUnknown), token("unknown"), message("An unknown error code."), http_status(200) : code(RpcUnknown), token("unknown"), message("An unknown error code."), httpStatus(200)
{ {
} }
constexpr ErrorInfo(ErrorCodeI code, char const* token, char const* message) constexpr ErrorInfo(ErrorCodeI code, char const* token, char const* message)
: code(code), token(token), message(message), http_status(200) : code(code), token(token), message(message), httpStatus(200)
{ {
} }
constexpr ErrorInfo(ErrorCodeI code, char const* token, char const* message, int httpStatus) constexpr ErrorInfo(ErrorCodeI code, char const* token, char const* message, int httpStatus)
: code(code), token(token), message(message), http_status(httpStatus) : code(code), token(token), message(message), httpStatus(httpStatus)
{ {
} }
ErrorCodeI code; ErrorCodeI code;
json::StaticString token; json::StaticString token;
json::StaticString message; json::StaticString message;
int http_status; int httpStatus;
}; };
/** Returns an ErrorInfo that reflects the error code. */ /** Returns an ErrorInfo that reflects the error code. */

View File

@@ -122,4 +122,17 @@ private:
std::optional<Rules> saved_; std::optional<Rules> saved_;
}; };
class NumberSO;
class NumberMantissaScaleGuard;
bool
useRulesGuards(Rules const& rules);
void
createGuards(
Rules const& rules,
std::optional<NumberSO>& stNumberSO,
std::optional<CurrentTransactionRulesGuard>& rulesGuard,
std::optional<NumberMantissaScaleGuard>& mantissaScaleGuard);
} // namespace xrpl } // namespace xrpl

View File

@@ -365,8 +365,8 @@ using SF_XCHAIN_BRIDGE = TypedField<STXChainBridge>;
#define UNTYPED_SFIELD(sfName, stiSuffix, fieldValue, ...) extern SField const sfName; #define UNTYPED_SFIELD(sfName, stiSuffix, fieldValue, ...) extern SField const sfName;
#define TYPED_SFIELD(sfName, stiSuffix, fieldValue, ...) extern SF_##stiSuffix const sfName; #define TYPED_SFIELD(sfName, stiSuffix, fieldValue, ...) extern SF_##stiSuffix const sfName;
extern SField const kSfInvalid; extern SField const sfInvalid; // NOLINT(readability-identifier-naming)
extern SField const kSfGeneric; extern SField const sfGeneric; // NOLINT(readability-identifier-naming)
#include <xrpl/protocol/detail/sfields.macro> #include <xrpl/protocol/detail/sfields.macro>

View File

@@ -3,11 +3,13 @@
#include <xrpl/basics/CountedObject.h> #include <xrpl/basics/CountedObject.h>
#include <xrpl/basics/LocalValue.h> #include <xrpl/basics/LocalValue.h>
#include <xrpl/basics/Number.h> #include <xrpl/basics/Number.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/beast/utility/instrumentation.h> #include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/protocol/Asset.h> #include <xrpl/protocol/Asset.h>
#include <xrpl/protocol/IOUAmount.h> #include <xrpl/protocol/IOUAmount.h>
#include <xrpl/protocol/Issue.h> #include <xrpl/protocol/Issue.h>
#include <xrpl/protocol/MPTAmount.h> #include <xrpl/protocol/MPTAmount.h>
#include <xrpl/protocol/Protocol.h>
#include <xrpl/protocol/SField.h> #include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STBase.h> #include <xrpl/protocol/STBase.h>
#include <xrpl/protocol/Serializer.h> #include <xrpl/protocol/Serializer.h>
@@ -184,6 +186,23 @@ public:
[[nodiscard]] STAmount const& [[nodiscard]] STAmount const&
value() const noexcept; value() const noexcept;
/**
* Checks if this amount evaluates to zero when constrained to a specific
* accounting scale.
* For XRP and MPT `roundToScale` is a no-op, returns true only when the amount itself is zero.
* The `scale` argument is ignored in that case.
* For IOU, the amount is rounded to the given scale using Number::RoundingMode::ToNearest mode
* and the result is checked for zero; if `scale <= exponent()`, `roundToScale` short-circuits
* and returns the value unchanged, so this returns false for any non-zero amount.
*
* @param scale The target accounting scale to evaluate against.
* @return `true` if this amount rounds to zero at the given scale, `false` otherwise.
*
* @see roundToScale
*/
[[nodiscard]] bool
isZeroAtScale(int scale) const;
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
// //
// Operators // Operators
@@ -540,7 +559,7 @@ STAmount::fromNumber(A const& a, Number const& number)
return STAmount{asset, intValue, 0, negative}; return STAmount{asset, intValue, 0, negative};
} }
auto const [mantissa, exponent] = working.normalizeToRange(kMinValue, kMaxValue); auto const [mantissa, exponent] = working.normalizeToRange<kMinValue, kMaxValue>();
return STAmount{asset, mantissa, exponent, negative}; return STAmount{asset, mantissa, exponent, negative};
} }
@@ -575,12 +594,25 @@ STAmount::value() const noexcept
return *this; return *this;
} }
inline bool [[nodiscard]] inline bool
isLegalNet(STAmount const& value) isLegalNet(STAmount const& value)
{ {
return !value.native() || (value.mantissa() <= STAmount::kMaxNativeN); return !value.native() || (value.mantissa() <= STAmount::kMaxNativeN);
} }
[[nodiscard]] inline bool
isLegalMPT(STAmount const& value)
{
return !value.holds<MPTIssue>() ||
(!value.negative() && value.exponent() == 0 && value.mantissa() <= kMaxMpTokenAmount);
}
/* Check recursively if an object has invalid MPTAmount or XRPAmount in STAmount field.
* Calls isLegalNet() and isLegalMPT().
*/
[[nodiscard]] bool
hasInvalidAmount(STBase const& field, beast::Journal j);
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// //
// Operators // Operators

View File

@@ -24,7 +24,7 @@ public:
STBlob(SField const& f, void const* data, std::size_t size); STBlob(SField const& f, void const* data, std::size_t size);
STBlob(SField const& f, Buffer&& b); STBlob(SField const& f, Buffer&& b);
STBlob(SField const& n); STBlob(SField const& n);
STBlob(SerialIter&, SField const& name = kSfGeneric); STBlob(SerialIter&, SField const& name = sfGeneric);
[[nodiscard]] std::size_t [[nodiscard]] std::size_t
size() const; size() const;

View File

@@ -21,8 +21,8 @@ class STPathElement final : public CountedObject<STPathElement>
PathAsset assetID_; PathAsset assetID_;
AccountID issuerID_; AccountID issuerID_;
bool is_offer_; bool isOffer_;
std::size_t hash_value_; std::size_t hashValue_;
public: public:
// Bitwise values (typeCurrency | typeMPT) // Bitwise values (typeCurrency | typeMPT)
@@ -235,9 +235,9 @@ private:
// ------------ STPathElement ------------ // ------------ STPathElement ------------
inline STPathElement::STPathElement() : type_(TypeNone), is_offer_(true) inline STPathElement::STPathElement() : type_(TypeNone), isOffer_(true)
{ {
hash_value_ = getHash(*this); hashValue_ = getHash(*this);
} }
inline STPathElement::STPathElement( inline STPathElement::STPathElement(
@@ -248,11 +248,11 @@ inline STPathElement::STPathElement(
{ {
if (!account) if (!account)
{ {
is_offer_ = true; isOffer_ = true;
} }
else else
{ {
is_offer_ = false; isOffer_ = false;
accountID_ = *account; accountID_ = *account;
type_ |= TypeAccount; type_ |= TypeAccount;
XRPL_ASSERT( XRPL_ASSERT(
@@ -272,7 +272,7 @@ inline STPathElement::STPathElement(
XRPL_ASSERT(issuerID_ != noAccount(), "xrpl::STPathElement::STPathElement : issuer is set"); XRPL_ASSERT(issuerID_ != noAccount(), "xrpl::STPathElement::STPathElement : issuer is set");
} }
hash_value_ = getHash(*this); hashValue_ = getHash(*this);
} }
inline STPathElement::STPathElement( inline STPathElement::STPathElement(
@@ -284,9 +284,9 @@ inline STPathElement::STPathElement(
, accountID_(account) , accountID_(account)
, assetID_(asset) , assetID_(asset)
, issuerID_(issuer) , issuerID_(issuer)
, is_offer_(isXRP(accountID_)) , isOffer_(isXRP(accountID_))
{ {
if (!is_offer_) if (!isOffer_)
type_ |= TypeAccount; type_ |= TypeAccount;
if (forceAsset || !isXRP(assetID_)) if (forceAsset || !isXRP(assetID_))
@@ -295,7 +295,7 @@ inline STPathElement::STPathElement(
if (!isXRP(issuer)) if (!isXRP(issuer))
type_ |= TypeIssuer; type_ |= TypeIssuer;
hash_value_ = getHash(*this); hashValue_ = getHash(*this);
} }
inline STPathElement::STPathElement( inline STPathElement::STPathElement(
@@ -307,12 +307,12 @@ inline STPathElement::STPathElement(
, accountID_(account) , accountID_(account)
, assetID_(asset) , assetID_(asset)
, issuerID_(issuer) , issuerID_(issuer)
, is_offer_(isXRP(accountID_)) , isOffer_(isXRP(accountID_))
{ {
assetID_.visit( assetID_.visit(
[&](Currency const&) { type_ = type_ & (~Type::TypeMpt); }, [&](Currency const&) { type_ = type_ & (~Type::TypeMpt); },
[&](MPTID const&) { type_ = type_ & (~Type::TypeCurrency); }); [&](MPTID const&) { type_ = type_ & (~Type::TypeCurrency); });
hash_value_ = getHash(*this); hashValue_ = getHash(*this);
} }
inline auto inline auto
@@ -324,7 +324,7 @@ STPathElement::getNodeType() const
inline bool inline bool
STPathElement::isOffer() const STPathElement::isOffer() const
{ {
return is_offer_; return isOffer_;
} }
inline bool inline bool
@@ -404,7 +404,7 @@ STPathElement::getIssuerID() const
inline bool inline bool
STPathElement::operator==(STPathElement const& t) const STPathElement::operator==(STPathElement const& t) const
{ {
return (type_ & TypeAccount) == (t.type_ & TypeAccount) && hash_value_ == t.hash_value_ && return (type_ & TypeAccount) == (t.type_ & TypeAccount) && hashValue_ == t.hashValue_ &&
accountID_ == t.accountID_ && assetID_ == t.assetID_ && issuerID_ == t.issuerID_; accountID_ == t.accountID_ && assetID_ == t.assetID_ && issuerID_ == t.issuerID_;
} }

View File

@@ -27,7 +27,7 @@ enum class TxnSql : char {
class STTx final : public STObject, public CountedObject<STTx> class STTx final : public STObject, public CountedObject<STTx>
{ {
uint256 tid_; uint256 tid_;
TxType tx_type_; TxType txType_;
public: public:
static constexpr std::size_t kMinMultiSigners = 1; static constexpr std::size_t kMinMultiSigners = 1;
@@ -187,7 +187,7 @@ inline STTx::STTx(SerialIter&& sit) // NOLINT(cppcoreguidelines-rvalue-referenc
inline TxType inline TxType
STTx::getTxnType() const STTx::getTxnType() const
{ {
return tx_type_; return txType_;
} }
inline Blob inline Blob

View File

@@ -15,7 +15,7 @@
// Add new amendments to the top of this list. // Add new amendments to the top of this list.
// Keep it sorted in reverse chronological order. // Keep it sorted in reverse chronological order.
XRPL_FIX (Cleanup3_2_0, Supported::No, VoteBehavior::DefaultNo) XRPL_FIX (Cleanup3_2_0, Supported::Yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(MPTokensV2, Supported::No, VoteBehavior::DefaultNo) XRPL_FEATURE(MPTokensV2, Supported::No, VoteBehavior::DefaultNo)
XRPL_FIX (Cleanup3_1_3, Supported::Yes, VoteBehavior::DefaultYes) XRPL_FIX (Cleanup3_1_3, Supported::Yes, VoteBehavior::DefaultYes)
XRPL_FIX (BatchInnerSigs, Supported::No, VoteBehavior::DefaultNo) XRPL_FIX (BatchInnerSigs, Supported::No, VoteBehavior::DefaultNo)

View File

@@ -400,6 +400,7 @@ LEDGER_ENTRY(ltMPTOKEN_ISSUANCE, 0x007e, MPTokenIssuance, mpt_issuance, ({
{sfPreviousTxnLgrSeq, SoeRequired}, {sfPreviousTxnLgrSeq, SoeRequired},
{sfDomainID, SoeOptional}, {sfDomainID, SoeOptional},
{sfMutableFlags, SoeDefault}, {sfMutableFlags, SoeDefault},
{sfReferenceHolding, SoeOptional},
})) }))
/** A ledger object which tracks MPToken /** A ledger object which tracks MPToken
@@ -591,7 +592,7 @@ LEDGER_ENTRY(ltLOAN, 0x0089, Loan, loan, ({
// LoanBroker.ManagementFeeRate // LoanBroker.ManagementFeeRate
// The unrounded true total fee still owed to the broker. // The unrounded true total fee still owed to the broker.
// //
// Note the the "True" values may differ significantly from the tracked // Note the "True" values may differ significantly from the tracked
// rounded values. // rounded values.
{sfPaymentRemaining, SoeDefault}, {sfPaymentRemaining, SoeDefault},
{sfPeriodicPayment, SoeRequired}, {sfPeriodicPayment, SoeRequired},

View File

@@ -205,6 +205,7 @@ TYPED_SFIELD(sfParentBatchID, UINT256, 36)
TYPED_SFIELD(sfLoanBrokerID, UINT256, 37, TYPED_SFIELD(sfLoanBrokerID, UINT256, 37,
SField::kSmdPseudoAccount | SField::kSmdDefault) SField::kSmdPseudoAccount | SField::kSmdDefault)
TYPED_SFIELD(sfLoanID, UINT256, 38) TYPED_SFIELD(sfLoanID, UINT256, 38)
TYPED_SFIELD(sfReferenceHolding, UINT256, 39)
// number (common) // number (common)
TYPED_SFIELD(sfNumber, NUMBER, 1) TYPED_SFIELD(sfNumber, NUMBER, 1)

View File

@@ -688,6 +688,7 @@ TRANSACTION(ttLEDGER_STATE_FIX, 53, LedgerStateFix,
({ ({
{sfLedgerFixType, SoeRequired}, {sfLedgerFixType, SoeRequired},
{sfOwner, SoeOptional}, {sfOwner, SoeOptional},
{sfBookDirectory, SoeOptional},
})) }))
/** This transaction type creates a MPTokensIssuance instance */ /** This transaction type creates a MPTokensIssuance instance */

View File

@@ -15,8 +15,8 @@ Generation requires a one-time setup step to create a virtual environment
and install Python dependencies, followed by running the generation target: and install Python dependencies, followed by running the generation target:
```bash ```bash
cmake --build . --target setup_code_gen # create venv and install dependencies (once) cmake --build . --target setup_code_gen # create venv and install dependencies (once)
cmake --build . --target code_gen # generate code cmake --build . --target code_gen # generate code
``` ```
By default, `CODEGEN_VENV_DIR` points to `.venv` in the project root. The By default, `CODEGEN_VENV_DIR` points to `.venv` in the project root. The

View File

@@ -278,6 +278,30 @@ public:
{ {
return this->sle_->isFieldPresent(sfMutableFlags); return this->sle_->isFieldPresent(sfMutableFlags);
} }
/**
* @brief Get sfReferenceHolding (SoeOptional)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_UINT256::type::value_type>
getReferenceHolding() const
{
if (hasReferenceHolding())
return this->sle_->at(sfReferenceHolding);
return std::nullopt;
}
/**
* @brief Check if sfReferenceHolding is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasReferenceHolding() const
{
return this->sle_->isFieldPresent(sfReferenceHolding);
}
}; };
/** /**
@@ -469,6 +493,17 @@ public:
return *this; return *this;
} }
/**
* @brief Set sfReferenceHolding (SoeOptional)
* @return Reference to this builder for method chaining.
*/
MPTokenIssuanceBuilder&
setReferenceHolding(std::decay_t<typename SF_UINT256::type::value_type> const& value)
{
object_[sfReferenceHolding] = value;
return *this;
}
/** /**
* @brief Build and return the completed MPTokenIssuance wrapper. * @brief Build and return the completed MPTokenIssuance wrapper.
* @param index The ledger entry index. * @param index The ledger entry index.

View File

@@ -83,6 +83,32 @@ public:
{ {
return this->tx_->isFieldPresent(sfOwner); return this->tx_->isFieldPresent(sfOwner);
} }
/**
* @brief Get sfBookDirectory (SoeOptional)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_UINT256::type::value_type>
getBookDirectory() const
{
if (hasBookDirectory())
{
return this->tx_->at(sfBookDirectory);
}
return std::nullopt;
}
/**
* @brief Check if sfBookDirectory is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasBookDirectory() const
{
return this->tx_->isFieldPresent(sfBookDirectory);
}
}; };
/** /**
@@ -149,6 +175,17 @@ public:
return *this; return *this;
} }
/**
* @brief Set sfBookDirectory (SoeOptional)
* @return Reference to this builder for method chaining.
*/
LedgerStateFixBuilder&
setBookDirectory(std::decay_t<typename SF_UINT256::type::value_type> const& value)
{
object_[sfBookDirectory] = value;
return *this;
}
/** /**
* @brief Build and return the LedgerStateFix wrapper. * @brief Build and return the LedgerStateFix wrapper.
* @param publicKey The public key for signing. * @param publicKey The public key for signing.

View File

@@ -21,7 +21,7 @@ struct Entry : public beast::List<Entry>::Node
@param now Construction time of Entry. @param now Construction time of Entry.
*/ */
explicit Entry(clock_type::time_point const now) explicit Entry(clock_type::time_point const now)
: refcount(0), local_balance(now), remote_balance(0) : refcount(0), localBalance(now), remoteBalance(0)
{ {
} }
@@ -46,7 +46,7 @@ struct Entry : public beast::List<Entry>::Node
int int
balance(clock_type::time_point const now) balance(clock_type::time_point const now)
{ {
return local_balance.value(now) + remote_balance; return localBalance.value(now) + remoteBalance;
} }
// Add a charge and return normalized balance // Add a charge and return normalized balance
@@ -54,7 +54,7 @@ struct Entry : public beast::List<Entry>::Node
int int
add(int charge, clock_type::time_point const now) add(int charge, clock_type::time_point const now)
{ {
return local_balance.add(charge, now) + remote_balance; return localBalance.add(charge, now) + remoteBalance;
} }
// The public key of the peer // The public key of the peer
@@ -67,10 +67,10 @@ struct Entry : public beast::List<Entry>::Node
int refcount; int refcount;
// Exponentially decaying balance of resource consumption // Exponentially decaying balance of resource consumption
DecayingSample<kDecayWindowSeconds, clock_type> local_balance; DecayingSample<kDecayWindowSeconds, clock_type> localBalance;
// Normalized balance contribution from imports // Normalized balance contribution from imports
int remote_balance; int remoteBalance;
// Time of the last warning // Time of the last warning
clock_type::time_point lastWarningTime; clock_type::time_point lastWarningTime;

View File

@@ -25,11 +25,11 @@ struct Key
std::size_t std::size_t
operator()(Key const& v) const operator()(Key const& v) const
{ {
return addr_hash_(v.address); return addrHash_(v.address);
} }
private: private:
beast::Uhash<> addr_hash_; beast::Uhash<> addrHash_;
}; };
struct KeyEqual struct KeyEqual

View File

@@ -194,34 +194,34 @@ public:
for (auto& inboundEntry : inbound_) for (auto& inboundEntry : inbound_)
{ {
int const localBalance = inboundEntry.local_balance.value(now); int const localBalance = inboundEntry.localBalance.value(now);
if ((localBalance + inboundEntry.remote_balance) >= threshold) if ((localBalance + inboundEntry.remoteBalance) >= threshold)
{ {
json::Value& entry = (ret[inboundEntry.toString()] = json::ValueType::Object); json::Value& entry = (ret[inboundEntry.toString()] = json::ValueType::Object);
entry[jss::local] = localBalance; entry[jss::local] = localBalance;
entry[jss::remote] = inboundEntry.remote_balance; entry[jss::remote] = inboundEntry.remoteBalance;
entry[jss::type] = "inbound"; entry[jss::type] = "inbound";
} }
} }
for (auto& outboundEntry : outbound_) for (auto& outboundEntry : outbound_)
{ {
int const localBalance = outboundEntry.local_balance.value(now); int const localBalance = outboundEntry.localBalance.value(now);
if ((localBalance + outboundEntry.remote_balance) >= threshold) if ((localBalance + outboundEntry.remoteBalance) >= threshold)
{ {
json::Value& entry = (ret[outboundEntry.toString()] = json::ValueType::Object); json::Value& entry = (ret[outboundEntry.toString()] = json::ValueType::Object);
entry[jss::local] = localBalance; entry[jss::local] = localBalance;
entry[jss::remote] = outboundEntry.remote_balance; entry[jss::remote] = outboundEntry.remoteBalance;
entry[jss::type] = "outbound"; entry[jss::type] = "outbound";
} }
} }
for (auto& adminEntry : admin_) for (auto& adminEntry : admin_)
{ {
int const localBalance = adminEntry.local_balance.value(now); int const localBalance = adminEntry.localBalance.value(now);
if ((localBalance + adminEntry.remote_balance) >= threshold) if ((localBalance + adminEntry.remoteBalance) >= threshold)
{ {
json::Value& entry = (ret[adminEntry.toString()] = json::ValueType::Object); json::Value& entry = (ret[adminEntry.toString()] = json::ValueType::Object);
entry[jss::local] = localBalance; entry[jss::local] = localBalance;
entry[jss::remote] = adminEntry.remote_balance; entry[jss::remote] = adminEntry.remoteBalance;
entry[jss::type] = "admin"; entry[jss::type] = "admin";
} }
} }
@@ -242,7 +242,7 @@ public:
for (auto& inboundEntry : inbound_) for (auto& inboundEntry : inbound_)
{ {
Gossip::Item item; Gossip::Item item;
item.balance = inboundEntry.local_balance.value(now); item.balance = inboundEntry.localBalance.value(now);
if (item.balance >= kMinimumGossipBalance) if (item.balance >= kMinimumGossipBalance)
{ {
item.address = inboundEntry.key->address; item.address = inboundEntry.key->address;
@@ -278,7 +278,7 @@ public:
Import::Item item; Import::Item item;
item.balance = gossipItem.balance; item.balance = gossipItem.balance;
item.consumer = newInboundEndpoint(gossipItem.address); item.consumer = newInboundEndpoint(gossipItem.address);
item.consumer.entry().remote_balance += item.balance; item.consumer.entry().remoteBalance += item.balance;
next.items.push_back(item); next.items.push_back(item);
} }
} }
@@ -295,14 +295,14 @@ public:
Import::Item item; Import::Item item;
item.balance = gossipItem.balance; item.balance = gossipItem.balance;
item.consumer = newInboundEndpoint(gossipItem.address); item.consumer = newInboundEndpoint(gossipItem.address);
item.consumer.entry().remote_balance += item.balance; item.consumer.entry().remoteBalance += item.balance;
next.items.push_back(item); next.items.push_back(item);
} }
Import& prev(resultIt->second); Import& prev(resultIt->second);
for (auto& item : prev.items) for (auto& item : prev.items)
{ {
item.consumer.entry().remote_balance -= item.balance; item.consumer.entry().remoteBalance -= item.balance;
} }
std::swap(next, prev); std::swap(next, prev);
@@ -345,7 +345,7 @@ public:
for (auto itemIter(import.items.begin()); itemIter != import.items.end(); for (auto itemIter(import.items.begin()); itemIter != import.items.end();
++itemIter) ++itemIter)
{ {
itemIter->consumer.entry().remote_balance -= itemIter->balance; itemIter->consumer.entry().remoteBalance -= itemIter->balance;
} }
iter = importTable_.erase(iter); iter = importTable_.erase(iter);
@@ -520,8 +520,8 @@ public:
item["count"] = entry.refcount; item["count"] = entry.refcount;
item["name"] = entry.toString(); item["name"] = entry.toString();
item["balance"] = entry.balance(now); item["balance"] = entry.balance(now);
if (entry.remote_balance != 0) if (entry.remoteBalance != 0)
item["remote_balance"] = entry.remote_balance; item["remote_balance"] = entry.remoteBalance;
} }
} }

View File

@@ -21,7 +21,7 @@ struct Handoff
bool moved = false; bool moved = false;
// If response is set, this determines the keep alive // If response is set, this determines the keep alive
bool keep_alive = false; bool keepAlive = false;
// When set, this will be sent back // When set, this will be sent back
std::shared_ptr<Writer> response; std::shared_ptr<Writer> response;

View File

@@ -1,6 +1,7 @@
#pragma once #pragma once
#include <xrpl/basics/CountedObject.h> #include <xrpl/basics/CountedObject.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/json/json_value.h> #include <xrpl/json/json_value.h>
#include <xrpl/protocol/Book.h> #include <xrpl/protocol/Book.h>
#include <xrpl/protocol/ErrorCodes.h> #include <xrpl/protocol/ErrorCodes.h>
@@ -26,6 +27,19 @@ public:
}; };
/** Manages a client's subscription to data feeds. /** Manages a client's subscription to data feeds.
*
* An InfoSub holds a non-owning reference to its `Source` (typically the
* process-wide `NetworkOPsImp`). The destructor reaches back into the
* `Source` to remove this subscriber from every server-side subscription
* map.
*
* @note Lifetime contract: every `InfoSub` instance MUST be destroyed
* before the backing `Source`. NetworkOPsImp shutdown drops all
* subscriber strong refs before its own teardown to satisfy this.
* @note Thread-safety: per-instance state is guarded by `lock_`. The
* destructor reads tracking sets without taking `lock_` because
* the strong-pointer ref-count is zero at destruction time, so
* no other thread can be calling the public mutators.
*/ */
class InfoSub : public CountedObject<InfoSub> class InfoSub : public CountedObject<InfoSub>
{ {
@@ -117,8 +131,43 @@ public:
virtual bool virtual bool
subBook(ref ispListener, Book const&) = 0; subBook(ref ispListener, Book const&) = 0;
/**
* Remove a book subscription for a live subscriber.
*
* Clears the book from the subscriber's own tracking set
* (InfoSub::bookSubscriptions_) and then removes the server-side
* entry from subBook_. Call this from RPC unsubscribe handlers.
*
* @param ispListener The subscriber requesting removal.
* @param book The order book to unsubscribe from.
* @return true if the entry was present and removed, false if the
* subscriber was not subscribed to @p book.
*
* @note Thread-safety: acquires subLock_ internally.
* @note Do NOT call from ~InfoSub(). Use unsubBookInternal instead
* to avoid a redundant write-back to bookSubscriptions_ on a
* partially-destroyed object.
*/
virtual bool virtual bool
unsubBook(std::uint64_t uListener, Book const&) = 0; unsubBook(ref ispListener, Book const&) = 0;
/**
* Remove a book subscription during InfoSub teardown.
*
* Removes only the server-side entry from subBook_. Does NOT touch
* InfoSub::bookSubscriptions_ because the InfoSub is being destroyed.
* Called by ~InfoSub() for each book in bookSubscriptions_.
*
* @param uListener The sequence number of the subscriber being torn down.
* @param book The order book entry to remove.
* @return true if the entry was present and removed, false otherwise
* (e.g., already removed by a concurrent RPC unsubscribe).
*
* @note Thread-safety: acquires subLock_ internally.
*/
virtual bool
unsubBookInternal(std::uint64_t uListener, Book const&) = 0;
virtual bool virtual bool
subTransactions(ref ispListener) = 0; subTransactions(ref ispListener) = 0;
@@ -158,6 +207,13 @@ public:
addRpcSub(std::string const& strUrl, ref rspEntry) = 0; addRpcSub(std::string const& strUrl, ref rspEntry) = 0;
virtual bool virtual bool
tryRemoveRpcSub(std::string const& strUrl) = 0; tryRemoveRpcSub(std::string const& strUrl) = 0;
/** Journal used by InfoSub for diagnostics that occur after the
* owning subsystem (e.g. application-level Logs) is the only
* surviving sink — primarily destructor-time cleanup failures.
*/
[[nodiscard]] virtual beast::Journal const&
journal() const = 0;
}; };
public: public:
@@ -184,6 +240,31 @@ public:
void void
deleteSubAccountInfo(AccountID const& account, bool rt); deleteSubAccountInfo(AccountID const& account, bool rt);
/** Record that this subscriber is following @p book.
*
* Called by NetworkOPsImp::subBook so that ~InfoSub() can issue a
* matching unsubBook for every book this subscriber is tracking,
* keeping per-subscriber state symmetric with the server-side map.
*
* @param book The order book this subscriber has just subscribed to.
* @note Idempotent: re-inserting an already-tracked book is a no-op.
* @note Thread-safe: takes InfoSub::lock_.
*/
void
insertBookSubscription(Book const& book);
/** Stop tracking @p book for this subscriber.
*
* Called by the unsubscribe RPC handler so that the book is not
* re-unsubscribed by ~InfoSub(). Pairs with insertBookSubscription.
*
* @param book The order book to forget.
* @note No-op if @p book was not previously inserted.
* @note Thread-safe: takes InfoSub::lock_.
*/
void
deleteBookSubscription(Book const& book);
// return false if already subscribed to this account // return false if already subscribed to this account
bool bool
insertSubAccountHistory(AccountID const& account); insertSubAccountHistory(AccountID const& account);
@@ -217,6 +298,7 @@ private:
std::shared_ptr<InfoSubRequest> request_; std::shared_ptr<InfoSubRequest> request_;
std::uint64_t seq_; std::uint64_t seq_;
hash_set<AccountID> accountHistorySubscriptions_; hash_set<AccountID> accountHistorySubscriptions_;
hash_set<Book> bookSubscriptions_;
unsigned int apiVersion_ = 0; unsigned int apiVersion_ = 0;
static int static int

View File

@@ -249,6 +249,19 @@ public:
virtual void virtual void
stateAccounting(json::Value& obj) = 0; stateAccounting(json::Value& obj) = 0;
/** Total number of (book, subscriber) entries currently tracked.
*
* Counts every weak_ptr stored across every book in subBook_, NOT the
* number of distinct subscribers and NOT the number of distinct
* books: a single subscriber following N books contributes N entries.
*
* @note Diagnostic accessor; intended for tests and operator visibility
* into per-book subscription state. The returned value is a
* snapshot under the subscription lock.
*/
virtual std::size_t
getBookSubscribersCount() = 0;
}; };
} // namespace xrpl } // namespace xrpl

View File

@@ -30,19 +30,19 @@ struct Port
boost::asio::ip::address ip; boost::asio::ip::address ip;
std::uint16_t port = 0; std::uint16_t port = 0;
std::set<std::string, boost::beast::iless> protocol; std::set<std::string, boost::beast::iless> protocol;
std::vector<boost::asio::ip::network_v4> admin_nets_v4; std::vector<boost::asio::ip::network_v4> adminNetsV4;
std::vector<boost::asio::ip::network_v6> admin_nets_v6; std::vector<boost::asio::ip::network_v6> adminNetsV6;
std::vector<boost::asio::ip::network_v4> secure_gateway_nets_v4; std::vector<boost::asio::ip::network_v4> secureGatewayNetsV4;
std::vector<boost::asio::ip::network_v6> secure_gateway_nets_v6; std::vector<boost::asio::ip::network_v6> secureGatewayNetsV6;
std::string user; std::string user;
std::string password; std::string password;
std::string admin_user; std::string adminUser;
std::string admin_password; std::string adminPassword;
std::string ssl_key; std::string sslKey;
std::string ssl_cert; std::string sslCert;
std::string ssl_chain; std::string sslChain;
std::string ssl_ciphers; std::string sslCiphers;
boost::beast::websocket::permessage_deflate pmd_options; boost::beast::websocket::permessage_deflate pmdOptions;
std::shared_ptr<boost::asio::ssl::context> context; std::shared_ptr<boost::asio::ssl::context> context;
// How many incoming connections are allowed on this // How many incoming connections are allowed on this
@@ -50,7 +50,7 @@ struct Port
int limit = 0; int limit = 0;
// Websocket disconnects if send queue exceeds this limit // Websocket disconnects if send queue exceeds this limit
std::uint16_t ws_queue_limit{}; std::uint16_t wsQueueLimit{};
// Returns `true` if any websocket protocols are specified // Returns `true` if any websocket protocols are specified
[[nodiscard]] bool [[nodiscard]] bool
@@ -78,22 +78,22 @@ struct ParsedPort
std::set<std::string, boost::beast::iless> protocol; std::set<std::string, boost::beast::iless> protocol;
std::string user; std::string user;
std::string password; std::string password;
std::string admin_user; std::string adminUser;
std::string admin_password; std::string adminPassword;
std::string ssl_key; std::string sslKey;
std::string ssl_cert; std::string sslCert;
std::string ssl_chain; std::string sslChain;
std::string ssl_ciphers; std::string sslCiphers;
boost::beast::websocket::permessage_deflate pmd_options; boost::beast::websocket::permessage_deflate pmdOptions;
int limit = 0; int limit = 0;
std::uint16_t ws_queue_limit{}; std::uint16_t wsQueueLimit{};
std::optional<boost::asio::ip::address> ip; std::optional<boost::asio::ip::address> ip;
std::optional<std::uint16_t> port; std::optional<std::uint16_t> port;
std::vector<boost::asio::ip::network_v4> admin_nets_v4; std::vector<boost::asio::ip::network_v4> adminNetsV4;
std::vector<boost::asio::ip::network_v6> admin_nets_v6; std::vector<boost::asio::ip::network_v6> adminNetsV6;
std::vector<boost::asio::ip::network_v4> secure_gateway_nets_v4; std::vector<boost::asio::ip::network_v4> secureGatewayNetsV4;
std::vector<boost::asio::ip::network_v6> secure_gateway_nets_v6; std::vector<boost::asio::ip::network_v6> secureGatewayNetsV6;
}; };
void void

View File

@@ -58,13 +58,13 @@ protected:
Handler& handler_; Handler& handler_;
boost::asio::executor_work_guard<boost::asio::executor> work_; boost::asio::executor_work_guard<boost::asio::executor> work_;
boost::asio::strand<boost::asio::executor> strand_; boost::asio::strand<boost::asio::executor> strand_;
endpoint_type remote_address_; endpoint_type remoteAddress_;
beast::Journal const journal_; beast::Journal const journal_;
std::string id_; std::string id_;
std::size_t nid_; std::size_t nid_;
boost::asio::streambuf read_buf_; boost::asio::streambuf readBuf_;
http_request_type message_; http_request_type message_;
std::vector<Buffer> wq_; std::vector<Buffer> wq_;
std::vector<Buffer> wq2_; std::vector<Buffer> wq2_;
@@ -73,9 +73,9 @@ protected:
bool complete_ = false; bool complete_ = false;
boost::system::error_code ec_; boost::system::error_code ec_;
int request_count_ = 0; int requestCount_ = 0;
std::size_t bytes_in_ = 0; std::size_t bytesIn_ = 0;
std::size_t bytes_out_ = 0; std::size_t bytesOut_ = 0;
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
@@ -151,7 +151,7 @@ protected:
beast::IP::Endpoint beast::IP::Endpoint
remoteAddress() override remoteAddress() override
{ {
return beast::IPAddressConversion::fromAsio(remote_address_); return beast::IPAddressConversion::fromAsio(remoteAddress_);
} }
http_request_type& http_request_type&
@@ -191,23 +191,23 @@ BaseHTTPPeer<Handler, Impl>::BaseHTTPPeer(
, handler_(handler) , handler_(handler)
, work_(boost::asio::make_work_guard(executor)) , work_(boost::asio::make_work_guard(executor))
, strand_(boost::asio::make_strand(executor)) , strand_(boost::asio::make_strand(executor))
, remote_address_(std::move(remoteAddress)) , remoteAddress_(std::move(remoteAddress))
, journal_(journal) , journal_(journal)
{ {
read_buf_.commit( readBuf_.commit(
boost::asio::buffer_copy(read_buf_.prepare(boost::asio::buffer_size(buffers)), buffers)); boost::asio::buffer_copy(readBuf_.prepare(boost::asio::buffer_size(buffers)), buffers));
static std::atomic<int> kSid; static std::atomic<int> kSid;
nid_ = ++kSid; nid_ = ++kSid;
id_ = std::string("#") + std::to_string(nid_) + " "; id_ = std::string("#") + std::to_string(nid_) + " ";
JLOG(journal_.trace()) << id_ << "accept: " << remote_address_.address(); JLOG(journal_.trace()) << id_ << "accept: " << remoteAddress_.address();
} }
template <class Handler, class Impl> template <class Handler, class Impl>
BaseHTTPPeer<Handler, Impl>::~BaseHTTPPeer() BaseHTTPPeer<Handler, Impl>::~BaseHTTPPeer()
{ {
handler_.onClose(session(), ec_); handler_.onClose(session(), ec_);
JLOG(journal_.trace()) << id_ << "destroyed: " << request_count_ JLOG(journal_.trace()) << id_ << "destroyed: " << requestCount_
<< ((request_count_ == 1) ? " request" : " requests"); << ((requestCount_ == 1) ? " request" : " requests");
} }
template <class Handler, class Impl> template <class Handler, class Impl>
@@ -245,7 +245,7 @@ BaseHTTPPeer<Handler, Impl>::startTimer()
boost::beast::get_lowest_layer(impl().stream_) boost::beast::get_lowest_layer(impl().stream_)
.expires_after( .expires_after(
std::chrono::seconds( std::chrono::seconds(
remote_address_.address().is_loopback() ? kTimeoutSecondsLocal : kTimeoutSeconds)); remoteAddress_.address().is_loopback() ? kTimeoutSecondsLocal : kTimeoutSeconds));
} }
// Convenience for discarding the error code // Convenience for discarding the error code
@@ -274,7 +274,7 @@ BaseHTTPPeer<Handler, Impl>::doRead(yield_context doYield)
complete_ = false; complete_ = false;
error_code ec; error_code ec;
startTimer(); startTimer();
boost::beast::http::async_read(impl().stream_, read_buf_, message_, doYield[ec]); boost::beast::http::async_read(impl().stream_, readBuf_, message_, doYield[ec]);
cancelTimer(); cancelTimer();
if (ec == boost::beast::http::error::end_of_stream) if (ec == boost::beast::http::error::end_of_stream)
return doClose(); return doClose();
@@ -296,7 +296,7 @@ BaseHTTPPeer<Handler, Impl>::onWrite(error_code const& ec, std::size_t bytesTran
return onTimer(); return onTimer();
if (ec) if (ec)
return fail(ec, "write"); return fail(ec, "write");
bytes_out_ += bytesTransferred; bytesOut_ += bytesTransferred;
{ {
std::scoped_lock const lock(mutex_); std::scoped_lock const lock(mutex_);
wq2_.clear(); wq2_.clear();

View File

@@ -27,7 +27,7 @@ protected:
Port const& port_; Port const& port_;
Handler& handler_; Handler& handler_;
endpoint_type remote_address_; endpoint_type remoteAddress_;
beast::WrappedSink sink_; beast::WrappedSink sink_;
beast::Journal const j_; beast::Journal const j_;
@@ -65,7 +65,7 @@ BasePeer<Handler, Impl>::BasePeer(
beast::Journal journal) beast::Journal journal)
: port_(port) : port_(port)
, handler_(handler) , handler_(handler)
, remote_address_(std::move(remoteAddress)) , remoteAddress_(std::move(remoteAddress))
, sink_( , sink_(
journal.sink(), journal.sink(),
[] { [] {

View File

@@ -42,15 +42,15 @@ private:
/// The socket has been closed, or will close after the next write /// The socket has been closed, or will close after the next write
/// finishes. Do not do any more writes, and don't try to close /// finishes. Do not do any more writes, and don't try to close
/// again. /// again.
bool do_close_ = false; bool doClose_ = false;
boost::beast::websocket::close_reason cr_; boost::beast::websocket::close_reason cr_;
waitable_timer timer_; waitable_timer timer_;
bool close_on_timer_ = false; bool closeOnTimer_ = false;
bool ping_active_ = false; bool pingActive_ = false;
boost::beast::websocket::ping_data payload_; boost::beast::websocket::ping_data payload_;
error_code ec_; error_code ec_;
std::function<void(boost::beast::websocket::frame_type, boost::beast::string_view)> std::function<void(boost::beast::websocket::frame_type, boost::beast::string_view)>
control_callback_; controlCallback_;
public: public:
template <class Body, class Headers> template <class Body, class Headers>
@@ -85,7 +85,7 @@ public:
[[nodiscard]] boost::asio::ip::tcp::endpoint const& [[nodiscard]] boost::asio::ip::tcp::endpoint const&
remoteEndpoint() const override remoteEndpoint() const override
{ {
return this->remote_address_; return this->remoteAddress_;
} }
void void
@@ -173,14 +173,14 @@ BaseWSPeer<Handler, Impl>::run()
{ {
if (!strand_.running_in_this_thread()) if (!strand_.running_in_this_thread())
return post(strand_, std::bind(&BaseWSPeer::run, impl().shared_from_this())); return post(strand_, std::bind(&BaseWSPeer::run, impl().shared_from_this()));
impl().ws_.set_option(port().pmd_options); impl().ws_.set_option(port().pmdOptions);
// Must manage the control callback memory outside of the `control_callback` // Must manage the control callback memory outside of the `control_callback`
// function // function
control_callback_ = controlCallback_ =
std::bind(&BaseWSPeer::onPingPong, this, std::placeholders::_1, std::placeholders::_2); std::bind(&BaseWSPeer::onPingPong, this, std::placeholders::_1, std::placeholders::_2);
impl().ws_.control_callback(control_callback_); impl().ws_.control_callback(controlCallback_);
startTimer(); startTimer();
close_on_timer_ = true; closeOnTimer_ = true;
impl().ws_.set_option(boost::beast::websocket::stream_base::decorator([](auto& res) { impl().ws_.set_option(boost::beast::websocket::stream_base::decorator([](auto& res) {
res.set(boost::beast::http::field::server, BuildInfo::getFullVersionString()); res.set(boost::beast::http::field::server, BuildInfo::getFullVersionString());
})); }));
@@ -198,9 +198,9 @@ BaseWSPeer<Handler, Impl>::send(std::shared_ptr<WSMsg> w)
{ {
if (!strand_.running_in_this_thread()) if (!strand_.running_in_this_thread())
return post(strand_, std::bind(&BaseWSPeer::send, impl().shared_from_this(), std::move(w))); return post(strand_, std::bind(&BaseWSPeer::send, impl().shared_from_this(), std::move(w)));
if (do_close_) if (doClose_)
return; return;
if (wq_.size() > port().ws_queue_limit) if (wq_.size() > port().wsQueueLimit)
{ {
cr_.code = safeCast<decltype(cr_.code)>(boost::beast::websocket::close_code::policy_error); cr_.code = safeCast<decltype(cr_.code)>(boost::beast::websocket::close_code::policy_error);
cr_.reason = "Policy error: client is too slow."; cr_.reason = "Policy error: client is too slow.";
@@ -227,9 +227,9 @@ BaseWSPeer<Handler, Impl>::close(boost::beast::websocket::close_reason const& re
{ {
if (!strand_.running_in_this_thread()) if (!strand_.running_in_this_thread())
return post(strand_, [self = impl().shared_from_this(), reason] { self->close(reason); }); return post(strand_, [self = impl().shared_from_this(), reason] { self->close(reason); });
if (do_close_) if (doClose_)
return; return;
do_close_ = true; doClose_ = true;
if (wq_.empty()) if (wq_.empty())
{ {
impl().ws_.async_close( impl().ws_.async_close(
@@ -260,7 +260,7 @@ BaseWSPeer<Handler, Impl>::onWsHandshake(error_code const& ec)
{ {
if (ec) if (ec)
return fail(ec, "on_ws_handshake"); return fail(ec, "on_ws_handshake");
close_on_timer_ = false; closeOnTimer_ = false;
doRead(); doRead();
} }
@@ -313,7 +313,7 @@ BaseWSPeer<Handler, Impl>::onWriteFin(error_code const& ec)
if (ec) if (ec)
return fail(ec, "write_fin"); return fail(ec, "write_fin");
wq_.pop_front(); wq_.pop_front();
if (do_close_) if (doClose_)
{ {
impl().ws_.async_close( impl().ws_.async_close(
cr_, cr_,
@@ -409,7 +409,7 @@ BaseWSPeer<Handler, Impl>::onPing(error_code const& ec)
{ {
if (ec == boost::asio::error::operation_aborted) if (ec == boost::asio::error::operation_aborted)
return; return;
ping_active_ = false; pingActive_ = false;
if (!ec) if (!ec)
return; return;
fail(ec, "on_ping"); fail(ec, "on_ping");
@@ -426,7 +426,7 @@ BaseWSPeer<Handler, Impl>::onPingPong(
boost::beast::string_view const p(payload_.begin()); boost::beast::string_view const p(payload_.begin());
if (payload == p) if (payload == p)
{ {
close_on_timer_ = false; closeOnTimer_ = false;
JLOG(this->j_.trace()) << "got matching pong"; JLOG(this->j_.trace()) << "got matching pong";
} }
else else
@@ -444,11 +444,11 @@ BaseWSPeer<Handler, Impl>::onTimer(error_code ec)
return; return;
if (!ec) if (!ec)
{ {
if (!close_on_timer_ || !ping_active_) if (!closeOnTimer_ || !pingActive_)
{ {
startTimer(); startTimer();
close_on_timer_ = true; closeOnTimer_ = true;
ping_active_ = true; pingActive_ = true;
// cryptographic is probably overkill.. // cryptographic is probably overkill..
beast::rngfill(payload_.begin(), payload_.size(), cryptoPrng()); beast::rngfill(payload_.begin(), payload_.size(), cryptoPrng());
impl().ws_.async_ping( impl().ws_.async_ping(

View File

@@ -23,7 +23,6 @@
#include <sys/resource.h> #include <sys/resource.h>
#include <dirent.h> #include <dirent.h>
#include <unistd.h>
#endif #endif
#include <algorithm> #include <algorithm>
@@ -61,7 +60,7 @@ private:
boost::asio::io_context& ioc_; boost::asio::io_context& ioc_;
stream_type stream_; stream_type stream_;
socket_type& socket_; socket_type& socket_;
endpoint_type remote_address_; endpoint_type remoteAddress_;
boost::asio::strand<boost::asio::io_context::executor_type> strand_; boost::asio::strand<boost::asio::io_context::executor_type> strand_;
beast::Journal const j_; beast::Journal const j_;
@@ -90,16 +89,19 @@ private:
acceptor_type acceptor_; acceptor_type acceptor_;
boost::asio::strand<boost::asio::io_context::executor_type> strand_; boost::asio::strand<boost::asio::io_context::executor_type> strand_;
bool ssl_{ bool ssl_{
port_.protocol.count("https") > 0 || port_.protocol.count("wss") > 0 || port_.protocol.contains("https") || port_.protocol.contains("wss") ||
port_.protocol.count("wss2") > 0 || port_.protocol.count("peer") > 0}; port_.protocol.contains("wss2") || port_.protocol.contains("peer")};
bool plain_{ bool plain_{
port_.protocol.count("http") > 0 || port_.protocol.count("ws") > 0 || port_.protocol.contains("http") || port_.protocol.contains("ws") ||
(port_.protocol.count("ws2") != 0u)}; (port_.protocol.contains("ws2"))};
static constexpr std::chrono::milliseconds kInitialAcceptDelay{50}; static constexpr std::chrono::milliseconds kInitialAcceptDelay{50};
static constexpr std::chrono::milliseconds kMaxAcceptDelay{2000}; static constexpr std::chrono::milliseconds kMaxAcceptDelay{2000};
std::chrono::milliseconds accept_delay_{kInitialAcceptDelay}; std::chrono::milliseconds acceptDelay_{kInitialAcceptDelay};
boost::asio::steady_timer backoff_timer_; boost::asio::steady_timer backoffTimer_;
static constexpr double kFreeFdThreshold = 0.70; static constexpr std::uint64_t kMaxUsedFdPercent = 70;
static constexpr std::chrono::milliseconds kFdSampleInterval{250};
clock_type::time_point fdSampleAt_;
bool cachedThrottle_{false};
struct FDStats struct FDStats
{ {
@@ -164,7 +166,7 @@ Door<Handler>::Detector::Detector(
, ioc_(ioc) , ioc_(ioc)
, stream_(std::move(stream)) , stream_(std::move(stream))
, socket_(stream_.socket()) , socket_(stream_.socket())
, remote_address_(std::move(remoteAddress)) , remoteAddress_(std::move(remoteAddress))
, strand_(boost::asio::make_strand(ioc_)) , strand_(boost::asio::make_strand(ioc_))
, j_(j) , j_(j)
{ {
@@ -199,18 +201,18 @@ Door<Handler>::Detector::doDetect(boost::asio::yield_context doYield)
if (ssl) if (ssl)
{ {
if (auto sp = ios().template emplace<SSLHTTPPeer<Handler>>( if (auto sp = ios().template emplace<SSLHTTPPeer<Handler>>(
port_, handler_, ioc_, j_, remote_address_, buf.data(), std::move(stream_))) port_, handler_, ioc_, j_, remoteAddress_, buf.data(), std::move(stream_)))
sp->run(); sp->run();
return; return;
} }
if (auto sp = ios().template emplace<PlainHTTPPeer<Handler>>( if (auto sp = ios().template emplace<PlainHTTPPeer<Handler>>(
port_, handler_, ioc_, j_, remote_address_, buf.data(), std::move(stream_))) port_, handler_, ioc_, j_, remoteAddress_, buf.data(), std::move(stream_)))
sp->run(); sp->run();
return; return;
} }
if (ec != boost::asio::error::operation_aborted) if (ec != boost::asio::error::operation_aborted)
{ {
JLOG(j_.trace()) << "Error detecting ssl: " << ec.message() << " from " << remote_address_; JLOG(j_.trace()) << "Error detecting ssl: " << ec.message() << " from " << remoteAddress_;
} }
} }
@@ -279,7 +281,8 @@ Door<Handler>::Door(
, ioc_(ioContext) , ioc_(ioContext)
, acceptor_(ioContext) , acceptor_(ioContext)
, strand_(boost::asio::make_strand(ioContext)) , strand_(boost::asio::make_strand(ioContext))
, backoff_timer_(ioContext) , backoffTimer_(ioContext)
, fdSampleAt_(clock_type::now() - kFdSampleInterval)
{ {
reOpen(); reOpen();
} }
@@ -302,7 +305,7 @@ Door<Handler>::close()
return boost::asio::post( return boost::asio::post(
strand_, std::bind(&Door<Handler>::close, this->shared_from_this())); strand_, std::bind(&Door<Handler>::close, this->shared_from_this()));
} }
backoff_timer_.cancel(); backoffTimer_.cancel();
error_code ec; error_code ec;
acceptor_.close(ec); acceptor_.close(ec);
} }
@@ -338,11 +341,11 @@ Door<Handler>::doAccept(boost::asio::yield_context doYield)
{ {
if (shouldThrottleForFds()) if (shouldThrottleForFds())
{ {
backoff_timer_.expires_after(accept_delay_); JLOG(j_.warn()) << "Throttling do_accept for " << acceptDelay_.count() << "ms.";
backoffTimer_.expires_after(acceptDelay_);
boost::system::error_code tec; boost::system::error_code tec;
backoff_timer_.async_wait(doYield[tec]); backoffTimer_.async_wait(doYield[tec]);
accept_delay_ = std::min(accept_delay_ * 2, kMaxAcceptDelay); acceptDelay_ = std::min(acceptDelay_ * 2, kMaxAcceptDelay);
JLOG(j_.warn()) << "Throttling do_accept for " << accept_delay_.count() << "ms.";
continue; continue;
} }
@@ -359,14 +362,17 @@ Door<Handler>::doAccept(boost::asio::yield_context doYield)
if (ec == boost::asio::error::no_descriptors || if (ec == boost::asio::error::no_descriptors ||
ec == boost::asio::error::no_buffer_space) ec == boost::asio::error::no_buffer_space)
{ {
JLOG(j_.warn()) << "accept: Too many open files. Pausing for " char const* const cause = (ec == boost::asio::error::no_descriptors)
<< accept_delay_.count() << "ms."; ? "too many open files"
: "kernel buffer space exhausted";
JLOG(j_.warn()) << "accept: " << cause << ". Pausing for " << acceptDelay_.count()
<< "ms.";
backoff_timer_.expires_after(accept_delay_); backoffTimer_.expires_after(acceptDelay_);
boost::system::error_code tec; boost::system::error_code tec;
backoff_timer_.async_wait(doYield[tec]); backoffTimer_.async_wait(doYield[tec]);
accept_delay_ = std::min(accept_delay_ * 2, kMaxAcceptDelay); acceptDelay_ = std::min(acceptDelay_ * 2, kMaxAcceptDelay);
} }
else else
{ {
@@ -375,7 +381,7 @@ Door<Handler>::doAccept(boost::asio::yield_context doYield)
continue; continue;
} }
accept_delay_ = kInitialAcceptDelay; acceptDelay_ = kInitialAcceptDelay;
if (ssl_ && plain_) if (ssl_ && plain_)
{ {
@@ -428,14 +434,15 @@ Door<Handler>::shouldThrottleForFds()
#if BOOST_OS_WINDOWS #if BOOST_OS_WINDOWS
return false; return false;
#else #else
auto const stats = queryFdStats(); auto const now = clock_type::now();
if (!stats || stats->limit == 0) if (now - fdSampleAt_ < kFdSampleInterval)
return false; return cachedThrottle_;
auto const& s = *stats; fdSampleAt_ = now;
auto const free = (s.limit > s.used) ? (s.limit - s.used) : 0ull; auto const stats = queryFdStats();
double const freeRatio = static_cast<double>(free) / static_cast<double>(s.limit); cachedThrottle_ =
return freeRatio < kFreeFdThreshold; stats && stats->limit > 0 && stats->used * 100 > stats->limit * kMaxUsedFdPercent;
return cachedThrottle_;
#endif #endif
} }

View File

@@ -82,7 +82,7 @@ template <class Handler>
void void
PlainHTTPPeer<Handler>::run() PlainHTTPPeer<Handler>::run()
{ {
if (!this->handler_.onAccept(this->session(), this->remote_address_)) if (!this->handler_.onAccept(this->session(), this->remoteAddress_))
{ {
util::spawn(this->strand_, std::bind(&PlainHTTPPeer::doClose, this->shared_from_this())); util::spawn(this->strand_, std::bind(&PlainHTTPPeer::doClose, this->shared_from_this()));
return; return;
@@ -103,7 +103,7 @@ PlainHTTPPeer<Handler>::websocketUpgrade()
auto ws = this->ios().template emplace<PlainWSPeer<Handler>>( auto ws = this->ios().template emplace<PlainWSPeer<Handler>>(
this->port_, this->port_,
this->handler_, this->handler_,
this->remote_address_, this->remoteAddress_,
std::move(this->message_), std::move(this->message_),
std::move(stream_), std::move(stream_),
this->journal_); this->journal_);
@@ -114,20 +114,20 @@ template <class Handler>
void void
PlainHTTPPeer<Handler>::doRequest() PlainHTTPPeer<Handler>::doRequest()
{ {
++this->request_count_; ++this->requestCount_;
auto const what = auto const what =
this->handler_.onHandoff(this->session(), std::move(this->message_), this->remote_address_); this->handler_.onHandoff(this->session(), std::move(this->message_), this->remoteAddress_);
if (what.moved) if (what.moved)
return; return;
boost::system::error_code ec; boost::system::error_code ec;
if (what.response) if (what.response)
{ {
// half-close on Connection: close // half-close on Connection: close
if (!what.keep_alive) if (!what.keepAlive)
socket_.shutdown(socket_type::shutdown_receive, ec); socket_.shutdown(socket_type::shutdown_receive, ec);
if (ec) if (ec)
return this->fail(ec, "request"); return this->fail(ec, "request");
return this->write(what.response, what.keep_alive); return this->write(what.response, what.keepAlive);
} }
// Perform half-close when Connection: close and not SSL // Perform half-close when Connection: close and not SSL

View File

@@ -26,7 +26,7 @@ private:
using yield_context = boost::asio::yield_context; using yield_context = boost::asio::yield_context;
using error_code = boost::system::error_code; using error_code = boost::system::error_code;
std::unique_ptr<stream_type> stream_ptr_; std::unique_ptr<stream_type> streamPtr_;
stream_type& stream_; stream_type& stream_;
socket_type& socket_; socket_type& socket_;
@@ -80,8 +80,8 @@ SSLHTTPPeer<Handler>::SSLHTTPPeer(
journal, journal,
remoteAddress, remoteAddress,
buffers) buffers)
, stream_ptr_(std::make_unique<stream_type>(middle_type(std::move(stream)), *port.context)) , streamPtr_(std::make_unique<stream_type>(middle_type(std::move(stream)), *port.context))
, stream_(*stream_ptr_) , stream_(*streamPtr_)
, socket_(stream_.next_layer().socket()) , socket_(stream_.next_layer().socket())
{ {
} }
@@ -91,7 +91,7 @@ template <class Handler>
void void
SSLHTTPPeer<Handler>::run() SSLHTTPPeer<Handler>::run()
{ {
if (!this->handler_.onAccept(this->session(), this->remote_address_)) if (!this->handler_.onAccept(this->session(), this->remoteAddress_))
{ {
util::spawn(this->strand_, std::bind(&SSLHTTPPeer::doClose, this->shared_from_this())); util::spawn(this->strand_, std::bind(&SSLHTTPPeer::doClose, this->shared_from_this()));
return; return;
@@ -110,9 +110,9 @@ SSLHTTPPeer<Handler>::websocketUpgrade()
auto ws = this->ios().template emplace<SSLWSPeer<Handler>>( auto ws = this->ios().template emplace<SSLWSPeer<Handler>>(
this->port_, this->port_,
this->handler_, this->handler_,
this->remote_address_, this->remoteAddress_,
std::move(this->message_), std::move(this->message_),
std::move(this->stream_ptr_), std::move(this->streamPtr_),
this->journal_); this->journal_);
return ws; return ws;
} }
@@ -124,8 +124,8 @@ SSLHTTPPeer<Handler>::doHandshake(yield_context doYield)
boost::system::error_code ec; boost::system::error_code ec;
stream_.set_verify_mode(boost::asio::ssl::verify_none); stream_.set_verify_mode(boost::asio::ssl::verify_none);
this->startTimer(); this->startTimer();
this->read_buf_.consume( this->readBuf_.consume(
stream_.async_handshake(stream_type::server, this->read_buf_.data(), doYield[ec])); stream_.async_handshake(stream_type::server, this->readBuf_.data(), doYield[ec]));
this->cancelTimer(); this->cancelTimer();
if (ec == boost::beast::error::timeout) if (ec == boost::beast::error::timeout)
return this->onTimer(); return this->onTimer();
@@ -148,13 +148,13 @@ template <class Handler>
void void
SSLHTTPPeer<Handler>::doRequest() SSLHTTPPeer<Handler>::doRequest()
{ {
++this->request_count_; ++this->requestCount_;
auto const what = this->handler_.onHandoff( auto const what = this->handler_.onHandoff(
this->session(), std::move(stream_ptr_), std::move(this->message_), this->remote_address_); this->session(), std::move(streamPtr_), std::move(this->message_), this->remoteAddress_);
if (what.moved) if (what.moved)
return; return;
if (what.response) if (what.response)
return this->write(what.response, what.keep_alive); return this->write(what.response, what.keepAlive);
// legacy // legacy
this->handler_.onRequest(this->session()); this->handler_.onRequest(this->session());
} }

View File

@@ -28,7 +28,7 @@ class SSLWSPeer : public BaseWSPeer<Handler, SSLWSPeer<Handler>>,
using stream_type = boost::beast::ssl_stream<socket_type>; using stream_type = boost::beast::ssl_stream<socket_type>;
using waitable_timer = boost::asio::basic_waitable_timer<clock_type>; using waitable_timer = boost::asio::basic_waitable_timer<clock_type>;
std::unique_ptr<stream_type> stream_ptr_; std::unique_ptr<stream_type> streamPtr_;
boost::beast::websocket::stream<stream_type&> ws_; boost::beast::websocket::stream<stream_type&> ws_;
public: public:
@@ -61,8 +61,8 @@ SSLWSPeer<Handler>::SSLWSPeer(
remoteEndpoint, remoteEndpoint,
std::move(request), std::move(request),
journal) journal)
, stream_ptr_(std::move(streamPtr)) , streamPtr_(std::move(streamPtr))
, ws_(*stream_ptr_) , ws_(*streamPtr_)
{ {
} }

View File

@@ -66,7 +66,7 @@ private:
Handler& handler_; Handler& handler_;
beast::Journal const j_; beast::Journal const j_;
boost::asio::io_context& io_context_; boost::asio::io_context& ioContext_;
boost::asio::strand<boost::asio::io_context::executor_type> strand_; boost::asio::strand<boost::asio::io_context::executor_type> strand_;
std::optional<boost::asio::executor_work_guard<boost::asio::io_context::executor_type>> work_; std::optional<boost::asio::executor_work_guard<boost::asio::io_context::executor_type>> work_;
@@ -104,7 +104,7 @@ public:
boost::asio::io_context& boost::asio::io_context&
getIoContext() getIoContext()
{ {
return io_context_; return ioContext_;
} }
bool bool
@@ -122,9 +122,9 @@ ServerImpl<Handler>::ServerImpl(
beast::Journal journal) beast::Journal journal)
: handler_(handler) : handler_(handler)
, j_(journal) , j_(journal)
, io_context_(ioContext) , ioContext_(ioContext)
, strand_(boost::asio::make_strand(io_context_)) , strand_(boost::asio::make_strand(ioContext_))
, work_(std::in_place, boost::asio::make_work_guard(io_context_)) , work_(std::in_place, boost::asio::make_work_guard(ioContext_))
{ {
} }
@@ -150,7 +150,7 @@ ServerImpl<Handler>::ports(std::vector<Port> const& ports)
{ {
ports_.push_back(port); ports_.push_back(port);
auto& internalPort = ports_.back(); auto& internalPort = ports_.back();
if (auto sp = ios_.emplace<Door<Handler>>(handler_, io_context_, internalPort, j_)) if (auto sp = ios_.emplace<Door<Handler>>(handler_, ioContext_, internalPort, j_))
{ {
list_.push_back(sp); list_.push_back(sp);

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