Compare commits

...

35 Commits

Author SHA1 Message Date
Alex Kremer
5a5ad8673a style: Precommit hook for gtest naming (#8026)
Co-authored-by: Bart <bthomee@users.noreply.github.com>
2026-09-22 22:39:23 +00:00
Copilot
8f4e9c25d8 fix: Add CTID to ledger command expanded transactions (#6401)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Mayukha Vadari <mvadari@gmail.com>
Co-authored-by: Mayukha Vadari <mvadari@ripple.com>
Co-authored-by: xrplf-ai-reviewer[bot] <266832837+xrplf-ai-reviewer[bot]@users.noreply.github.com>
Co-authored-by: Timur Yalymov <36795566+tyalymov@users.noreply.github.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Vito Tumas <5780819+Tapanito@users.noreply.github.com>
Co-authored-by: Ayaz Salikhov <mathbunnyru@users.noreply.github.com>
2026-09-22 20:19:05 +00:00
Bart
d8870162eb test: Share peer test doubles across the overlay and app suites (#8246)
Co-authored-by: Bart <11445373+bthomee@users.noreply.github.com>
2026-09-22 20:03:55 +00:00
yinyiqian1
8b1a2282c3 feat: Support ConfidentialMPTMirrorUpdate (#8192) 2026-09-22 17:56:52 +00:00
yinyiqian1
0219c01b33 fix: Check zero object id in SponsorshipTransfer preflight (#8254)
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-09-22 17:02:59 +00:00
Bart
00606bec1a fix: Derive traversal node IDs from the branch actually descended (#7942)
Co-authored-by: Bart <11445373+bthomee@users.noreply.github.com>
2026-09-22 14:02:11 +00:00
Mayukha Vadari
2bc17c3e73 refactor: Add initial wrapper classes for all SLEs (#7886) 2026-09-21 19:47:23 +00:00
dependabot[bot]
0229c294a9 ci: [DEPENDABOT] bump codecov/codecov-action from 7.0.0 to 7.1.1 in the github-actions group across 1 directory (#8251)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-21 12:09:57 +00:00
Bart
184fe173fb refactor: Remove a dead unwrap from the WebSocket success path (#8252)
Co-authored-by: Bart <11445373+bthomee@users.noreply.github.com>
2026-09-21 11:23:31 +00:00
Bart
16f7b263fd docs: Correct three comments about null-terminated views (#8253)
Co-authored-by: Bart <11445373+bthomee@users.noreply.github.com>
2026-09-21 11:12:09 +00:00
Bart
f6b51f0b8b ci: Say when a package publish is a dry run (#8247)
Co-authored-by: Bart <11445373+bthomee@users.noreply.github.com>
2026-09-18 18:31:07 +00:00
Denis Angell
07b36871c7 ci: Publish packages through packages-upload.xrplf.org (#8241) 2026-09-17 18:05:51 +00:00
Bart
638ae08eeb chore: Bump version to 3.5.0-b0 2026-09-17 13:33:08 +02:00
yinyiqian1
24be48ee84 fix: Reject PaymentBurn payments that cross zero balance 2026-09-17 13:33:08 +02:00
Pratik Mankawde
e20f448a71 fix: Reject variable-length prefixes the encoder cannot write 2026-09-17 13:33:07 +02:00
Vito Tumas
f6c80fef68 fix: Relax MPT authorize cap for LoanSet and VaultWithdraw 2026-09-17 13:33:07 +02:00
Gregory Tsipenyuk
a0c12420b5 fix: Skip CheckCash limit waiver for the issuer 2026-09-17 13:33:06 +02:00
Ayaz Salikhov
1245254cac build: Add missing script to conan package 2026-09-17 13:33:06 +02:00
Jingchen
b60636169a fix: Make calculateBaseFee exception-safe 2026-09-17 13:33:06 +02:00
Ayaz Salikhov
d6022fbc4d build: Fix test installation on debian:11 due to EOL 2026-09-17 13:33:06 +02:00
Ayaz Salikhov
295b74da1c build: Add assert-enabled builds and packages 2026-09-17 13:33:05 +02:00
Timothy Banks
faa2bf583f fix: Cap TMTransactions list size and charge fee for undeserializable transactions 2026-09-17 13:33:04 +02:00
Vito Tumas
da260fa42b fix: Relax Loan Invariants to allow zero-principal LoanPay transaction 2026-09-17 13:33:04 +02:00
Timothy Banks
53788b193d test: Add ProtocolMessage harness for testing TMPing 2026-09-17 13:33:03 +02:00
Timothy Banks
227f1b4d9c fix: Unbounded Database Seek via TMGetLedger 2026-09-17 13:33:03 +02:00
Ed Hennis
26b66957ec fix: Trim unknown fields when parsing incoming peer protobuf messages 2026-09-17 13:33:02 +02:00
Mayukha Vadari
54e62a621f fix: Prevent simulate from updating the orderbook db 2026-09-17 13:33:02 +02:00
Timothy Banks
53628b70c0 fix: Use a hardened hash on the STPathElement 2026-09-17 13:33:01 +02:00
Vito Tumas
6fec2c11bf refactor: Rename vault accrual accounting to instant interest recognition (#8237) 2026-09-17 10:27:44 +00:00
Bart
04108a030c refactor: Build the RPC dispatch and command-line tables at compile time (#8006)
Co-authored-by: Bart <11445373+bthomee@users.noreply.github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 10:01:28 +00:00
Bart
551a19b10d refactor: Add Cluster::isMember, a membership query that copies nothing (#8221)
Co-authored-by: Bart <11445373+bthomee@users.noreply.github.com>
2026-09-15 20:40:29 +00:00
Peter Chen
b0a940a383 refactor: Extract common tx-building helpers for ConfidentialMPT in MPTTester (#8135) 2026-09-15 17:13:44 +00:00
Bart
e302e4eeed fix: Set the peer limit total when per-direction limits are configured (#8220)
Co-authored-by: Bart <11445373+bthomee@users.noreply.github.com>
Co-authored-by: Vito Tumas <5780819+Tapanito@users.noreply.github.com>
2026-09-15 13:14:12 +00:00
Mayukha Vadari
1a4a40ebb8 fix: Update noripple_check to exclude transactions field on error responses (#6303)
Co-authored-by: Timur Yalymov <36795566+tyalymov@users.noreply.github.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Vito Tumas <5780819+Tapanito@users.noreply.github.com>
Co-authored-by: Ayaz Salikhov <mathbunnyru@users.noreply.github.com>
2026-09-15 00:21:57 +00:00
yinyiqian1
7f55dd390c feat: Support mirror key epochs in confidential MPT transactions for Key Rotation amendment (#8210) 2026-09-14 18:23:38 +00:00
231 changed files with 13624 additions and 2884 deletions

View File

@@ -64,6 +64,7 @@ words:
- blindings
- bookdir
- Bougalis
- bthomee
- Britto
- Btrfs
- Buildx
@@ -141,6 +142,7 @@ words:
- hwrap
- ifndef
- inequation
- Injectivity
- insuf
- insuff
- invasively

View File

@@ -82,7 +82,6 @@ test.app > xrpl.tx
test.basics > test.jtx
test.basics > xrpl.basics
test.basics > xrpl.core
test.basics > xrpld.rpc
test.basics > xrpl.json
test.basics > xrpl.protocol
test.beast > xrpl.basics
@@ -113,6 +112,7 @@ test.jtx > xrpl.config
test.jtx > xrpl.core
test.jtx > xrpld.app
test.jtx > xrpld.core
test.jtx > xrpld.overlay
test.jtx > xrpld.rpc
test.jtx > xrpl.json
test.jtx > xrpl.ledger
@@ -286,10 +286,10 @@ xrpld.perflog > xrpl.basics
xrpld.perflog > xrpl.config
xrpld.perflog > xrpl.core
xrpld.perflog > xrpld.app
xrpld.perflog > xrpld.rpc
xrpld.perflog > xrpl.json
xrpld.perflog > xrpl.nodestore
xrpld.perflog > xrpl.protocol
xrpld.perflog > xrpl.server
xrpld.rpc > xrpl.basics
xrpld.rpc > xrpl.config
xrpld.rpc > xrpl.core

View File

@@ -15,6 +15,14 @@ _BASE_CMAKE_ARGS = [
"-Drust=ON",
]
# The package formats a config can be packaged as, each with its own
# install-test job in reusable-package.yml.
PACKAGE_TYPES = ("deb", "rpm")
# The package name a variant suffixes, as build_pkg.py's BASE_NAME spells it:
# the two have to agree, or the artifact globs miss what was built.
BASE_NAME = "xrpld"
# Maps sanitizer names (as used in cmake) to short config-name suffixes.
_SANITIZER_SUFFIX: dict[str, str] = {
"address": "asan",
@@ -62,10 +70,20 @@ def get_cmake_args(build_type: str, extra_args: str) -> str:
class PackageConfig:
"""The 'package' map of a config whose binaries are also packaged."""
type: str # "deb" or "rpm"; has to match what the image provides
type: str # has to match what the image provides
# The packaging container image: a vanilla distro image, not the nix image
# the config itself builds in.
image: str
# A flavour of the package, named xrpld-<variant>, for a config whose
# binaries are not the plain release build. A variant needs no counterpart
# in the other format.
variant: str = ""
def __post_init__(self) -> None:
assert self.type in PACKAGE_TYPES, (
f"unsupported package type {self.type!r}: "
f"use one of {', '.join(PACKAGE_TYPES)}."
)
@dataclasses.dataclass
@@ -178,6 +196,8 @@ class PackagingEntry:
validator_keys_artifact_name: str
image: str
package_type: str # "deb" or "rpm"; drives the format-specific steps
package_variant: str # passed to build_pkg.py --variant; empty for xrpld
package_name: str # the name it builds under, which the artifact globs use
# ---------------------------------------------------------------------------
@@ -267,12 +287,32 @@ def expand_linux_packaging(linux: LinuxFile) -> list[PackagingEntry]:
validator_keys_artifact_name=f"validator-keys-{name}",
image=cfg.package.image,
package_type=cfg.package.type,
package_variant=cfg.package.variant,
package_name=(
f"{BASE_NAME}-{cfg.package.variant}"
if cfg.package.variant
else BASE_NAME
),
)
)
return entries
def package_names_by_type(entries: list[PackagingEntry]) -> dict[str, list[str]]:
"""The names of the packages in 'entries', keyed by format.
Derived from the packaging matrix rather than listed again, so the packages
the install-test jobs look for are the packages that were built.
"""
return {
package_type: sorted(
{e.package_name for e in entries if e.package_type == package_type}
)
for package_type in PACKAGE_TYPES
}
def expand_platform_matrix(pf: PlatformFile, minimal: bool) -> list[MatrixEntry]:
"""Expand a PlatformFile (macOS or Windows) into matrix entries.
@@ -341,6 +381,10 @@ if __name__ == "__main__":
if args.packaging:
matrix = expand_linux_packaging(LinuxFile.load(THIS_DIR / "linux.json"))
# One list per format, so each install-test job installs the packages its
# own format produced.
for package_type, names in package_names_by_type(matrix).items():
print(f"{package_type}_package_names={json.dumps(names)}")
else:
if args.config in ("linux", None):
matrix += expand_linux_matrix(

View File

@@ -76,6 +76,19 @@
"type": "deb",
"image": "ghcr.io/xrplf/xrpld/packaging-debian:sha-49cdc10"
}
},
{
"compiler": ["gcc"],
"build_type": ["Release"],
"arch": ["amd64"],
"minimal": false,
"suffix": "assert",
"extra_cmake_args": "-Dvalidator_keys=ON -Dassert=ON",
"package": {
"type": "deb",
"image": "ghcr.io/xrplf/xrpld/packaging-debian:sha-49cdc10",
"variant": "assert"
}
}
],

View File

@@ -85,6 +85,7 @@ jobs:
.github/workflows/reusable-build-test.yml
.github/workflows/reusable-check-autogen.yml
.github/workflows/reusable-clang-tidy.yml
.github/workflows/reusable-package-test-install.yml
.github/workflows/reusable-package.yml
.github/workflows/reusable-rust.yml
.github/workflows/reusable-strategy-matrix.yml
@@ -189,6 +190,12 @@ jobs:
# matrix (i.e. not yet labeled "Ready to merge" or "Full CI build").
if: ${{ needs.should-run.outputs.go == 'true' && (github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'Ready to merge') || contains(github.event.pull_request.labels.*.name, 'Full CI build')) }}
uses: ./.github/workflows/reusable-package.yml
with:
# A pull request builds packages to prove they still build, and publishes
# nothing. Stated rather than left to the input's default, so that changing
# that default cannot start publishing from pull requests. No secrets are
# passed either, which is the second reason a publish here cannot succeed.
publish: false
upload-recipe:
needs:

View File

@@ -23,6 +23,7 @@ on:
- ".github/workflows/reusable-build-test.yml"
- ".github/workflows/reusable-check-autogen.yml"
- ".github/workflows/reusable-clang-tidy.yml"
- ".github/workflows/reusable-package-test-install.yml"
- ".github/workflows/reusable-package.yml"
- ".github/workflows/reusable-rust.yml"
- ".github/workflows/reusable-strategy-matrix.yml"

View File

@@ -439,7 +439,7 @@ jobs:
- name: Upload coverage report
if: ${{ github.repository_owner == 'XRPLF' && !inputs.build_only && env.COVERAGE_ENABLED == 'true' }}
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
uses: codecov/codecov-action@303a32d7a59b442fa8d48b6a1cc6825c09c847a5 # v7.1.1
with:
disable_search: true
disable_telem: true

View File

@@ -0,0 +1,120 @@
# Install one package format on every distro family it targets, one job per
# package name and image, and run the binaries there. Called once per format by
# reusable-package.yml, which owns the names and the image lists.
name: Install packages
on:
workflow_call:
inputs:
package_type:
description: 'The package format to install ("deb" or "rpm").'
required: true
type: string
package_names:
description: "JSON array of package names built for this format."
required: true
type: string
images:
description: "JSON array of container images to install in."
required: true
type: string
defaults:
run:
shell: bash
env:
PACKAGE_DIR: packages
jobs:
install:
strategy:
fail-fast: false
matrix:
package_name: ${{ fromJson(inputs.package_names) }}
image: ${{ fromJson(inputs.images) }}
name: "${{ matrix.package_name }} on ${{ matrix.image }}"
permissions:
contents: read
runs-on: ubuntu-latest
container: ${{ matrix.image }}
timeout-minutes: 5
steps:
# Every package lands in one directory; the step below picks its own,
# which keeps this independent of the artifact names.
- name: Download package artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
pattern: "*-pkg"
merge-multiple: true
path: ${{ env.PACKAGE_DIR }}
- name: Find the package
id: find
env:
PACKAGE_NAME: ${{ matrix.package_name }}
PACKAGE_TYPE: ${{ inputs.package_type }}
run: |
# The version follows the name, separated by '_' in a DEB and '-' in an
# RPM. Requiring a digit after it is what keeps 'xrpld' from picking up
# another package, such as 'xrpld-assert'.
pattern="${PACKAGE_NAME}[_-][0-9]*.${PACKAGE_TYPE}"
package="$(find "${PACKAGE_DIR}" -type f -name "${pattern}" -print -quit)"
test -n "${package}" || {
echo "no ${pattern} found in ${PACKAGE_DIR}" >&2
exit 1
}
echo "package=${package}" >>"${GITHUB_OUTPUT}"
# Debian 11 went end-of-life on 2026-08-31
# (https://www.debian.org/News/2026/20260831) and its packages are
# already partly gone from deb.debian.org, so switch to the
# snapshot.debian.org entries the image ships commented out in its
# sources.list: they are pinned to the snapshot the image was built
# from, so they serve every version it needs and never go away.
# Snapshots keep their original, long-passed Valid-Until, hence the
# disabled check; the retries absorb snapshot.debian.org's throttling.
- name: Switch Debian 11 to snapshot.debian.org
if: ${{ matrix.image == 'debian:11' }}
run: |
sed -i 's|^deb |# deb |; s|^# deb http://snapshot|deb http://snapshot|' /etc/apt/sources.list
printf '%s\n' \
'Acquire::Check-Valid-Until "false";' \
'Acquire::Retries "3";' \
>/etc/apt/apt.conf.d/99snapshot
- name: Install the DEB
if: ${{ inputs.package_type == 'deb' }}
env:
DEBIAN_FRONTEND: noninteractive
PACKAGE: ${{ steps.find.outputs.package }}
run: |
# Stock Debian and Ubuntu images carry no package lists, so apt has
# nothing to resolve the systemd dependency from until it fetches them.
apt-get update -qq
apt-get install -y "./${PACKAGE}"
- name: Install the RPM
if: ${{ inputs.package_type == 'rpm' }}
env:
PACKAGE: ${{ steps.find.outputs.package }}
run: dnf install -y "./${PACKAGE}"
- name: Run xrpld
run: xrpld --version
- name: Run validator-keys
run: validator-keys --version
- name: Run rippled, the legacy compatibility symlink
run: rippled --version
- name: Check the service account
run: id xrpld
- name: Check the state directory
run: test -d /var/lib/xrpld
- name: Check the log directory
run: test -d /var/log/xrpld

View File

@@ -3,8 +3,10 @@
#
# - 'package' builds and signs one format per config that carries a "package"
# map in linux.json; that map names the container image and the format
# - 'test-install' installs what was built on a range of distros and runs the
# binaries there, so a package that cannot be installed never reaches Nexus
# - 'test-install-deb' and 'test-install-rpm' call
# reusable-package-test-install.yml to install what was built on a range of
# distros and run the binaries there, so a package that cannot be installed
# never reaches Nexus
# - 'publish' uploads with the image's publish_pkg.py, doing a --dry-run
# unless 'publish: true'
#
@@ -23,7 +25,7 @@ on:
description: "The base URL of the Nexus instance hosting the deb and rpm repositories."
required: false
type: string
default: https://packages.xrplf.org
default: https://packages-upload.xrplf.org
secrets:
remote_username:
@@ -49,6 +51,8 @@ jobs:
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.generate.outputs.matrix }}
deb_package_names: ${{ steps.generate.outputs.deb_package_names }}
rpm_package_names: ${{ steps.generate.outputs.rpm_package_names }}
steps:
- name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
@@ -107,6 +111,7 @@ jobs:
- name: Build package
env:
PACKAGE_TYPE: ${{ matrix.package_type }}
PACKAGE_VARIANT: ${{ matrix.package_variant }}
PKG_RELEASE: ${{ steps.release_info.outputs.pkg_release }}
CHANNEL: ${{ steps.release_info.outputs.channel }}
run: |
@@ -114,6 +119,7 @@ jobs:
--package-type "${PACKAGE_TYPE}" \
--build-dir "${BUILD_DIR}" \
--pkg-release "${PKG_RELEASE}" \
--variant "${PACKAGE_VARIANT}" \
--channel "${CHANNEL}"
# Before the upload, so the artifact, the tested package and the published
@@ -125,14 +131,17 @@ jobs:
run: ./package/sign_rpm.py --package-dir "${BUILD_DIR}"
# Split from the debug symbols, which are an order of magnitude larger, so
# that test-install downloads only what it installs.
# that test-install downloads only what it installs. In the globs below the
# version follows the name, separated by '_' in a DEB and '-' in an RPM. A
# version starts with a digit and a longer name does not, so that one digit
# is what tells 'xrpld-3.4.1-...' from 'xrpld-assert-3.4.1-...'.
- name: Upload package artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: ${{ matrix.xrpld_artifact_name }}-pkg
path: |
${{ env.BUILD_DIR }}/debbuild/xrpld_*.deb
${{ env.BUILD_DIR }}/rpmbuild/RPMS/**/xrpld-[0-9]*.rpm
${{ env.BUILD_DIR }}/debbuild/${{ matrix.package_name }}_[0-9]*.deb
${{ env.BUILD_DIR }}/rpmbuild/RPMS/**/${{ matrix.package_name }}-[0-9]*.rpm
if-no-files-found: error
- name: Upload debug symbol artifact
@@ -140,133 +149,59 @@ jobs:
with:
name: ${{ matrix.xrpld_artifact_name }}-pkg-debug
path: |
${{ env.BUILD_DIR }}/debbuild/xrpld-dbgsym_*.deb
${{ env.BUILD_DIR }}/debbuild/xrpld-dbgsym_*.ddeb
${{ env.BUILD_DIR }}/rpmbuild/RPMS/**/xrpld-debuginfo-*.rpm
${{ env.BUILD_DIR }}/debbuild/${{ matrix.package_name }}-dbgsym_[0-9]*.deb
${{ env.BUILD_DIR }}/debbuild/${{ matrix.package_name }}-dbgsym_[0-9]*.ddeb
${{ env.BUILD_DIR }}/rpmbuild/RPMS/**/${{ matrix.package_name }}-debuginfo-[0-9]*.rpm
if-no-files-found: error
# Every distro family the packages target, oldest release first, so both ends
# of the dependency range they declare are exercised.
test-install:
needs: [package]
strategy:
fail-fast: false
matrix:
include:
- package_type: deb
image: debian:11
- package_type: deb
image: debian:12
- package_type: deb
image: debian:13
- package_type: deb
image: ubuntu:20.04
- package_type: deb
image: ubuntu:22.04
- package_type: deb
image: ubuntu:24.04
- package_type: deb
image: ubuntu:26.04
# One call per format, so a variant packaged for one format is installed for
# that format alone. The images are every distro family that format targets,
# oldest release first, so both ends of the dependency range the packages
# declare are exercised.
test-install-deb:
needs: [generate-matrix, package]
name: install deb
uses: ./.github/workflows/reusable-package-test-install.yml
with:
package_type: deb
package_names: ${{ needs.generate-matrix.outputs.deb_package_names }}
images: |
[
"debian:11",
"debian:12",
"debian:13",
"ubuntu:20.04",
"ubuntu:22.04",
"ubuntu:24.04",
"ubuntu:26.04"
]
- package_type: rpm
image: almalinux:9
- package_type: rpm
image: almalinux:10
- package_type: rpm
image: rockylinux/rockylinux:9
- package_type: rpm
image: rockylinux/rockylinux:10
- package_type: rpm
image: registry.access.redhat.com/ubi9/ubi
- package_type: rpm
image: registry.access.redhat.com/ubi10/ubi
name: "install ${{ matrix.package_type }} on ${{ matrix.image }}"
permissions:
contents: read
runs-on: ubuntu-latest
container: ${{ matrix.image }}
timeout-minutes: 5
steps:
# Both formats land in one directory; the step below picks its own by
# extension, so this stays independent of the artifact names.
- name: Download package artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
pattern: "*-pkg"
merge-multiple: true
path: ${{ env.PACKAGE_DIR }}
- name: Find the package
id: find
env:
PACKAGE_TYPE: ${{ matrix.package_type }}
run: |
package="$(find "${PACKAGE_DIR}" -type f -name "*.${PACKAGE_TYPE}" -print -quit)"
test -n "${package}" || {
echo "no .${PACKAGE_TYPE} found in ${PACKAGE_DIR}" >&2
exit 1
}
echo "package=${package}" >>"${GITHUB_OUTPUT}"
# Debian 11 went end-of-life on 2026-08-31
# (https://www.debian.org/News/2026/20260831) and its packages are
# already partly gone from deb.debian.org, so switch to the
# snapshot.debian.org entries the image ships commented out in its
# sources.list: they are pinned to the snapshot the image was built
# from, so they serve every version it needs and never go away.
# Snapshots keep their original, long-passed Valid-Until, hence the
# disabled check; the retries absorb snapshot.debian.org's throttling.
- name: Switch Debian 11 to snapshot.debian.org
if: ${{ matrix.image == 'debian:11' }}
run: |
sed -i 's|^deb |# deb |; s|^# deb http://snapshot|deb http://snapshot|' /etc/apt/sources.list
printf '%s\n' \
'Acquire::Check-Valid-Until "false";' \
'Acquire::Retries "3";' \
>/etc/apt/apt.conf.d/99snapshot
- name: Install the DEB
if: ${{ matrix.package_type == 'deb' }}
env:
DEBIAN_FRONTEND: noninteractive
PACKAGE: ${{ steps.find.outputs.package }}
run: |
# Stock Debian and Ubuntu images carry no package lists, so apt has
# nothing to resolve the systemd dependency from until it fetches them.
apt-get update -qq
apt-get install -y "./${PACKAGE}"
- name: Install the RPM
if: ${{ matrix.package_type == 'rpm' }}
env:
PACKAGE: ${{ steps.find.outputs.package }}
run: dnf install -y "./${PACKAGE}"
- name: Run xrpld
run: xrpld --version
- name: Run validator-keys
run: validator-keys --version
- name: Run rippled, the legacy compatibility symlink
run: rippled --version
- name: Check the service account
run: id xrpld
- name: Check the state directory
run: test -d /var/lib/xrpld
- name: Check the log directory
run: test -d /var/log/xrpld
test-install-rpm:
needs: [generate-matrix, package]
name: install rpm
uses: ./.github/workflows/reusable-package-test-install.yml
with:
package_type: rpm
package_names: ${{ needs.generate-matrix.outputs.rpm_package_names }}
images: |
[
"almalinux:9",
"almalinux:10",
"rockylinux/rockylinux:9",
"rockylinux/rockylinux:10",
"registry.access.redhat.com/ubi9/ubi",
"registry.access.redhat.com/ubi10/ubi"
]
publish:
needs: [generate-matrix, package, test-install]
needs: [generate-matrix, package, test-install-deb, test-install-rpm]
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.generate-matrix.outputs.matrix) }}
name: "publish ${{ matrix.xrpld_artifact_name }}"
# The name says which of the two this is, because the job runs either way:
# with publish false it passes --dry-run and uploads nothing, and a job
# called "publish ..." succeeding on a pull request reads like a release.
name: "publish ${{ matrix.xrpld_artifact_name }}${{ !inputs.publish && ' (dry run)' || '' }}"
permissions:
contents: read
runs-on: ["self-hosted", "Linux", "X64", "heavy"]

View File

@@ -57,7 +57,7 @@ jobs:
- name: Upload coverage report
if: ${{ github.repository == 'XRPLF/rippled' }}
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
uses: codecov/codecov-action@303a32d7a59b442fa8d48b6a1cc6825c09c847a5 # v7.1.1
with:
disable_search: true
disable_telem: true

View File

@@ -53,6 +53,11 @@ repos:
entry: ./bin/pre-commit/check_doxygen_style.py
language: python
types_or: [c++, c]
- id: fix-gtest-names
name: "fix gtest names: CamelCase suite, snake_case test case"
entry: ./bin/pre-commit/fix_gtest_names.py
language: python
types_or: [c++, c]
- repo: https://github.com/pre-commit/mirrors-clang-format
rev: f4d7745e17a28aad7eed2f4874ca8d1568c11c4c # frozen: v22.1.8

View File

@@ -41,6 +41,7 @@ Version 3.4.0 is not yet released. These changes are available in the 3.4.0 beta
- `gateway_balances`: The `account` and `ident` fields now return an `invalidParams` error if the value is not a string, instead of an `internal` error. [#7655](https://github.com/XRPLF/rippled/pull/7655)
- `account_lines`: The `peer` field now returns an error if the value is not a string. [#7728](https://github.com/XRPLF/rippled/pull/7728)
- `ledger`: `delivered_amount` is now included in the metadata of successful `AccountDelete` transactions when transactions are expanded (`expand`, or admin-only `full`). Previously it was only added for `Payment` and `CheckCash`, which made `ledger` inconsistent with `tx` and `account_tx`. [#5706](https://github.com/XRPLF/rippled/pull/5706)
- `noripple_check`: The `transactions` field is no longer included in error responses; it is still returned (possibly as an empty array) whenever `transactions` is `true` and the request succeeds. A malformed `account` is now rejected before the ledger is looked up, so that error response no longer carries the `ledger_hash`, `ledger_index`, and `validated` fields ([#6303](https://github.com/XRPLF/rippled/pull/6303)).
## XRP Ledger server version 3.3.0

144
bin/pre-commit/fix_gtest_names.py Executable file
View File

@@ -0,0 +1,144 @@
#!/usr/bin/env python3
"""
Rewrites gtest names to the required style in this project: the suite name is
CamelCase, the test-case name is snake_case.
TEST(SuiteName, test_case_name)
The gtest `DISABLED_` prefix is kept verbatim on either name.
Both conversions fold acronyms the way a reader expects:
`SetAndResetAccountTxnID` -> `set_and_reset_account_txn_id`, not
`set_and_reset_account_txn_i_d`.
The first argument of `TEST_F`, `TEST_P`, `TYPED_TEST` and `TYPED_TEST_P` is a
fixture class rather than a free identifier, so rewriting it here would leave
the class it names behind. Those are reported for a human to rename (clang-tidy
checks the class declaration itself, via readability-identifier-naming).
Usage: ./bin/pre-commit/fix_gtest_names.py <file1> <file2> ...
"""
import re
import sys
from collections import Counter
from pathlib import Path
# A test-case definition, `MACRO(SuiteOrFixture, TestName)`, anchored at the
# start of a line so that commented-out definitions and project macros that
# merely look similar (`TEST_EXPECT(...)`) are left alone. The `\s*` between
# arguments allows for a definition clang-format wrapped over several lines.
PATTERN = re.compile(
r"(?P<head>^[ \t]*(?P<macro>TYPED_TEST_P|TYPED_TEST|TEST_F|TEST_P|TEST)\s*\(\s*)"
r"(?P<suite>\w+)(?P<mid>\s*,\s*)(?P<name>\w+)(?P<tail>\s*\))",
re.MULTILINE,
)
# The macros whose first argument names a fixture class, not a free identifier.
FIXTURE_MACROS = ("TEST_F", "TEST_P", "TYPED_TEST", "TYPED_TEST_P")
DISABLED = "DISABLED_"
ACRONYM_BOUNDARY = re.compile(r"([A-Z]+)([A-Z][a-z])")
WORD_BOUNDARY = re.compile(r"([a-z\d])([A-Z])")
def _split_disabled(name: str) -> tuple[str, str]:
"""Splits off gtest's `DISABLED_` prefix, which is kept verbatim."""
if name.startswith(DISABLED):
return DISABLED, name[len(DISABLED) :]
return "", name
def snake_case(name: str) -> str:
"""Returns the name in snake_case, leaving acronyms whole.
`SetAndResetAccountTxnID` -> `set_and_reset_account_txn_id`,
`parseStatRSSkB` -> `parse_stat_rs_sk_b`.
"""
prefix, core = _split_disabled(name)
core = ACRONYM_BOUNDARY.sub(r"\1_\2", core)
return prefix + WORD_BOUNDARY.sub(r"\1_\2", core).lower()
def camel_case(name: str) -> str:
"""Returns the name in CamelCase, capitalizing each underscored word.
Only the letters that have to change are touched, so acronyms survive: a
conversion that went via snake_case would turn `SHAMapTest` into
`ShaMapTest`, whereas here it is already CamelCase and stays put.
`json_value` -> `JsonValue`, `parseStatRSSkB` -> `ParseStatRSSkB`.
"""
prefix, core = _split_disabled(name)
return prefix + "".join(w[:1].upper() + w[1:] for w in core.split("_") if w)
def _corrected(match: re.Match) -> tuple[str, str]:
"""Returns the suite and test-case names this definition should end up with."""
suite = match["suite"]
return (
suite if match["macro"] in FIXTURE_MACROS else camel_case(suite),
snake_case(match["name"]),
)
def fix_source(text: str) -> tuple[str, list[str]]:
"""Returns the corrected text and one `line: message` report per bad name."""
# gtest joins the suite and test names into one class name, so two test
# cases whose joined names agree cannot coexist: `TEST(a, b_c)` and
# `TEST(a_b, c)` both define `a_b_c_Test`. A rename that would introduce
# such a clash is reported for a human instead of applied.
joined = Counter("_".join(_corrected(m)) for m in PATTERN.finditer(text))
reports = []
def rewrite(match: re.Match) -> str:
suite, name = match["suite"], match["name"]
new_suite, new_name = _corrected(match)
line = text.count("\n", 0, match.start()) + 1
if match["macro"] in FIXTURE_MACROS and camel_case(suite) != suite:
reports.append(
f"{line}: fixture '{suite}' is not CamelCase: rename the class "
f"to '{camel_case(suite)}' by hand"
)
if (new_suite, new_name) == (suite, name):
return match[0]
if joined[f"{new_suite}_{new_name}"] > 1:
reports.append(
f"{line}: cannot rename '{suite}, {name}' to '{new_suite}, "
f"{new_name}': another test case already generates that name"
)
return match[0]
if new_suite != suite:
reports.append(f"{line}: renamed suite '{suite}' to '{new_suite}'")
if new_name != name:
reports.append(f"{line}: renamed test case '{name}' to '{new_name}'")
return match["head"] + new_suite + match["mid"] + new_name + match["tail"]
return PATTERN.sub(rewrite, text), reports
def fix_names(path: Path) -> bool:
"""Corrects one file's gtest names, reporting each on stdout."""
original = path.read_text(encoding="utf-8")
fixed, reports = fix_source(original)
for report in reports:
print(f"{path}:{report}")
if fixed != original:
path.write_text(fixed, encoding="utf-8")
return not reports
def main() -> int:
files = [Path(f) for f in sys.argv[1:]]
success = True
for path in files:
success &= fix_names(path)
return 0 if success else 1
if __name__ == "__main__":
sys.exit(main())

View File

@@ -0,0 +1,259 @@
#!/usr/bin/env python3
"""
Tests for fix_gtest_names.py.
Run directly (no test framework needed):
./bin/pre-commit/test_fix_gtest_names.py
or under pytest:
pytest bin/pre-commit/test_fix_gtest_names.py
"""
import sys
import textwrap
from fix_gtest_names import camel_case, fix_source, snake_case
def dedent(text: str) -> str:
"""Removes a fixture's common indentation and its leading newline.
Lets fixtures be written as indented triple-quoted here-docs while keeping
honest 1-based line numbers.
"""
return textwrap.dedent(text).lstrip("\n")
def fixed(text: str) -> str:
return fix_source(dedent(text))[0]
def reports(text: str) -> list[str]:
return fix_source(dedent(text))[1]
# --- conversion --------------------------------------------------------------
def test_snake_case_conversion() -> None:
assert snake_case("BadInputs") == "bad_inputs"
assert snake_case("mulDiv") == "mul_div"
assert snake_case("already_snake") == "already_snake"
assert snake_case("base64") == "base64"
def test_snake_case_keeps_acronyms_whole() -> None:
assert snake_case("SetAndResetAccountTxnID") == "set_and_reset_account_txn_id"
assert snake_case("XRPToIOU") == "xrp_to_iou"
assert snake_case("STAmountMath") == "st_amount_math"
def test_camel_case_conversion() -> None:
assert camel_case("json_value") == "JsonValue"
assert camel_case("mulDiv") == "MulDiv"
assert camel_case("scope") == "Scope"
assert camel_case("base64") == "Base64"
def test_camel_case_leaves_acronyms_alone() -> None:
# A snake_case round-trip would give `ShaMapTest` / `ParseStatmRsSkB` here.
assert camel_case("SHAMapTest") == "SHAMapTest"
assert camel_case("parseStatmRSSkB") == "ParseStatmRSSkB"
assert camel_case("XRPAmount") == "XRPAmount"
assert camel_case("CSPRNG") == "CSPRNG"
def test_disabled_prefix_preserved() -> None:
assert snake_case("DISABLED_FooBar") == "DISABLED_foo_bar"
assert snake_case("DISABLED_foo_bar") == "DISABLED_foo_bar"
assert snake_case("DISABLED_") == "DISABLED_"
assert camel_case("DISABLED_foo_bar") == "DISABLED_FooBar"
assert camel_case("DISABLED_") == "DISABLED_"
# --- what counts as a test definition ---------------------------------------
def test_all_macros_recognized() -> None:
code = """
TEST(Suite, oneName)
TEST_F(Fixture, twoName)
TEST_P(Fixture, threeName)
TYPED_TEST(Fixture, fourName)
TYPED_TEST_P(Fixture, fiveName)
"""
assert fixed(code) == dedent("""
TEST(Suite, one_name)
TEST_F(Fixture, two_name)
TEST_P(Fixture, three_name)
TYPED_TEST(Fixture, four_name)
TYPED_TEST_P(Fixture, five_name)
""")
def test_conforming_definitions_untouched() -> None:
code = """
TEST(AccountSet, bad_inputs)
TEST_F(MutexMakeTest, default_constructor)
TEST(SHAMap, DISABLED_slow_path)
"""
assert reports(code) == []
assert fixed(code) == dedent(code)
def test_lookalikes_ignored() -> None:
code = """
// TEST(Suite, notATest)
TEST_EXPECT(someCall())
TEST_EXPECTS(amount == value, amount.getText())
INSTANTIATE_TEST_SUITE_P(Prefix, Fixture, testValues());
auto x = TEST(Suite, notATest);
TYPED_TEST_SUITE(Fixture, MyTypes);
"""
assert reports(code) == []
assert fixed(code) == dedent(code)
def test_indented_and_wrapped_definitions() -> None:
code = """
namespace ripple {
TEST(Suite, indentedName)
}
TEST_F(
SomeVeryLongFixtureName,
wrappedName)
"""
assert fixed(code) == dedent("""
namespace ripple {
TEST(Suite, indented_name)
}
TEST_F(
SomeVeryLongFixtureName,
wrapped_name)
""")
# --- rewriting --------------------------------------------------------------
def test_only_the_two_names_are_rewritten() -> None:
code = """
TEST(mulDiv, mulDiv)
{
auto const mulDiv = 1; // mulDiv stays
}
"""
assert fixed(code) == dedent("""
TEST(MulDiv, mul_div)
{
auto const mulDiv = 1; // mulDiv stays
}
""")
def test_suite_name_camel_cased() -> None:
code = """
TEST(json_value, limits)
TEST(scope, ScopeExit)
"""
assert reports(code) == [
"1: renamed suite 'json_value' to 'JsonValue'",
"2: renamed suite 'scope' to 'Scope'",
"2: renamed test case 'ScopeExit' to 'scope_exit'",
]
assert fixed(code) == dedent("""
TEST(JsonValue, limits)
TEST(Scope, scope_exit)
""")
def test_fixture_reported_but_not_renamed() -> None:
# The first argument names a class, so only a human (or clang-tidy) can
# rename it; the test-case name is still fixed.
code = """
TEST_F(my_fixture, someTest)
"""
assert reports(code) == [
"1: fixture 'my_fixture' is not CamelCase: rename the class to "
"'MyFixture' by hand",
"1: renamed test case 'someTest' to 'some_test'",
]
assert fixed(code) == dedent("""
TEST_F(my_fixture, some_test)
""")
def test_reports_carry_line_numbers() -> None:
code = """
#include <foo.h>
TEST(Suite, firstName)
TEST(Suite, secondName)
"""
assert reports(code) == [
"3: renamed test case 'firstName' to 'first_name'",
"5: renamed test case 'secondName' to 'second_name'",
]
# --- collisions -------------------------------------------------------------
def test_collision_reported_and_not_applied() -> None:
# Both would define `Suite_mul_div_Test`.
code = """
TEST(Suite, mulDiv)
TEST(Suite, mul_div)
"""
assert reports(code) == [
"1: cannot rename 'Suite, mulDiv' to 'Suite, mul_div': another test "
"case already generates that name"
]
assert fixed(code) == dedent(code)
def test_collision_between_converging_suites() -> None:
# Both suites camel-case to `SuiteA`, so both would define
# `SuiteA_one_test_Test`.
code = """
TEST(SuiteA, oneTest)
TEST(Suite_a, one_test)
"""
assert [r.split(":")[1].strip() for r in reports(code)] == [
"cannot rename 'SuiteA, oneTest' to 'SuiteA, one_test'",
"cannot rename 'Suite_a, one_test' to 'SuiteA, one_test'",
]
assert fixed(code) == dedent(code)
def test_same_name_in_different_suites_is_not_a_collision() -> None:
code = """
TEST(SuiteOne, mulDiv)
TEST(SuiteTwo, mulDiv)
"""
assert fixed(code) == dedent("""
TEST(SuiteOne, mul_div)
TEST(SuiteTwo, mul_div)
""")
def main() -> int:
tests = sorted(
(name, fn)
for name, fn in globals().items()
if name.startswith("test_") and callable(fn)
)
failed = 0
for name, fn in tests:
try:
fn()
print(f"PASS {name}")
except AssertionError as exc:
failed += 1
print(f"FAIL {name}: {exc!r}")
print(f"\n{len(tests) - failed}/{len(tests)} passed")
return 1 if failed else 0
if __name__ == "__main__":
sys.exit(main())

View File

@@ -44,12 +44,18 @@ else()
set(pkg_type rpm)
endif()
# Unquoted below, so an empty value adds no argument at all.
set(pkg_variant_option "")
if(assert)
set(pkg_variant_option --variant=assert)
endif()
add_custom_target(
package
COMMAND
${CMAKE_SOURCE_DIR}/package/build_pkg.py --package-type=${pkg_type}
--build-dir=${CMAKE_BINARY_DIR} --pkg-release=${pkg_release}
--channel=UNRELEASED
${pkg_variant_option} --channel=UNRELEASED
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
DEPENDS xrpld validator-keys
COMMENT "Building Linux ${pkg_type} package"

View File

@@ -149,6 +149,7 @@ class Xrpl(ConanFile):
self.requires("xxhash/0.8.3", transitive_headers=True)
exports_sources = (
"bin/default-loader-path.sh",
"CMakeLists.txt",
"cfg/*",
"cmake/*",

View File

@@ -6,7 +6,8 @@
`xrpld` is published as DEB and RPM packages for 64-bit x86 Linux.
Use APT on Debian-based distributions such as Debian and Ubuntu,
and YUM on Red Hat-based distributions such as RHEL, AlmaLinux, and Rocky Linux.
and DNF on Red Hat-based distributions such as RHEL, AlmaLinux, and Rocky Linux,
where `yum` is a symlink to `dnf`.
To build from source instead, see [BUILD.md](../BUILD.md).
## Release channels
@@ -81,7 +82,7 @@ wherever it appears in the repository configuration.
sudo apt -y install xrpld
```
### With the YUM package manager
### With the DNF package manager
1. Add the XRPL Foundation package-signing key:
@@ -109,9 +110,23 @@ wherever it appears in the repository configuration.
3. Install the `xrpld` package:
```bash
sudo yum install -y xrpld
sudo dnf install -y xrpld
```
### Optional: the assert-enabled build
Every channel also carries `xrpld-assert` as a DEB, the same build with assertions
enabled, for diagnosing a problem on a non-production server.
It installs the same files as `xrpld` and replaces it, so install one or the other:
```bash
sudo apt -y install xrpld-assert # APT removes xrpld itself
```
Switching stops the service, since it is a removal and an installation rather than an upgrade,
and APT starts it again.
Install `xrpld` the same way to switch back.
## The xrpld service
Both package managers install a systemd unit and enable it, so `xrpld` starts on boot.
@@ -121,7 +136,7 @@ Check whether it is already running:
systemctl status xrpld.service
```
The APT packages start it immediately as well; the YUM packages do not, so start it yourself:
The DEB packages start it immediately as well; the RPM packages do not, so start it yourself:
```bash
sudo systemctl start xrpld.service

View File

@@ -162,4 +162,89 @@ toUInt64(std::string const& s);
bool
isProperlyFormedTomlDomain(std::string_view domain);
/**
* Whether a view can be passed on as a C string.
*
* A reader given only data() stops at the first null, so the view must reach the
* terminating null. The test rebuilds the view from data() and compares: a view
* that stops earlier rebuilds longer, and so compares unequal.
*
* consteval because reading the byte after the view is only defined when @p str
* points into storage holding a null at or after its end, such as a string
* literal. An unterminated view is then a compile error, not an out-of-bounds
* read.
*
* @param str The view to test.
* @return Whether @p str is null-terminated. A view with no data is not.
*/
consteval bool
isNullTerminated(std::string_view str)
{
if (str.data() == nullptr)
return false;
// Reading past the view is the point, so the usual data() warning does not
// apply.
// NOLINTNEXTLINE(bugprone-suspicious-stringview-data-usage)
return std::string_view{str.data()} == str;
}
/**
* A string that is known to reach its terminating null.
*
* Converts to std::string_view, so it compares and hashes as one. Unlike a
* view, asCString() may be handed to a reader that expects a C string, such
* as json::StaticString.
*
* The only constructor is consteval and rejects a view that stops before the
* null, so the property holds by construction and no caller asserts it.
*/
class NullTerminatedView
{
public:
/**
* Build a view from one that reaches its terminating null.
*
* Explicit, so that a plain view cannot become a proof of termination by
* accident. The conversion the other way stays implicit.
*
* @param view The string to hold. Rejected at compile time if it stops
* before its terminating null, or has no data.
*/
explicit consteval NullTerminatedView(std::string_view view)
: data_(view.data()), size_(view.size())
{
if (!isNullTerminated(view))
throw "xrpl::NullTerminatedView : view does not reach a null";
}
constexpr
operator std::string_view() const noexcept
{
return view();
}
/**
* @return The string as a view.
*/
[[nodiscard]] constexpr std::string_view
view() const noexcept
{
return {data_, size_};
}
/**
* @return The string as a C string. Never null.
*/
[[nodiscard]] constexpr char const*
asCString() const noexcept
{
return data_;
}
private:
char const* data_;
std::size_t size_;
};
} // namespace xrpl

View File

@@ -518,7 +518,7 @@ public:
* The input must be precisely `2 * bytes` hexadecimal characters
* long, with one exception: the value '0'.
*
* @param sv A null-terminated string of hexadecimal characters
* @param sv A string of hexadecimal characters
* @return true if the input was parsed properly; false otherwise.
*/
[[nodiscard]] constexpr bool

View File

@@ -1,6 +1,7 @@
#pragma once
#include <xrpl/basics/Log.h>
#include <xrpl/basics/StringUtilities.h>
#include <xrpl/core/Job.h>
#include <xrpl/json/json_value.h>
@@ -9,7 +10,8 @@
#include <filesystem>
#include <functional>
#include <memory>
#include <string>
#include <span>
#include <string_view>
namespace beast {
class Journal;
@@ -67,7 +69,7 @@ public:
* @param requestId Unique identifier to track command
*/
virtual void
rpcStart(std::string const& method, std::uint64_t requestId) = 0;
rpcStart(std::string_view method, std::uint64_t requestId) = 0;
/**
* Log successful finish of RPC call
@@ -76,7 +78,7 @@ public:
* @param requestId Unique identifier to track command
*/
virtual void
rpcFinish(std::string const& method, std::uint64_t requestId) = 0;
rpcFinish(std::string_view method, std::uint64_t requestId) = 0;
/**
* Log errored RPC call
@@ -85,7 +87,7 @@ public:
* @param requestId Unique identifier to track command
*/
virtual void
rpcError(std::string const& method, std::uint64_t requestId) = 0;
rpcError(std::string_view method, std::uint64_t requestId) = 0;
/**
* Log queued job
@@ -150,10 +152,20 @@ public:
PerfLog::Setup
setupPerfLog(Section const& section, std::filesystem::path const& configDir);
/**
* @param methodNames The RPC methods to count, one counter per name. Reported
* as JSON keys that borrow each name and read it as a C string, which is
* why the parameter type requires one that reaches its terminating null.
* The names must outlive the returned object, which holds views of them.
* The range itself need not: it is copied.
* Passed in rather than looked up here, so that this layer needs no
* knowledge of the dispatch table.
*/
std::unique_ptr<PerfLog>
makePerfLog(
PerfLog::Setup const& setup,
Application& app,
std::span<NullTerminatedView const> methodNames,
beast::Journal journal,
std::function<void()>&& signalStop);
@@ -161,7 +173,7 @@ template <typename Func, class Rep, class Period>
auto
measureDurationAndLog(
Func&& func,
std::string const& actionDescription,
std::string_view actionDescription,
std::chrono::duration<Rep, Period> maxDelay,
beast::Journal const& journal)
{

View File

@@ -0,0 +1,45 @@
#pragma once
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/SLEBase.h>
#include <xrpl/protocol/Asset.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
namespace xrpl {
template <typename ViewT>
class AMMEntry : public SLEBase<ViewT, ltAMM>
{
public:
using Base = SLEBase<ViewT, ltAMM>;
// Inherit base constructors: adopt an existing SLE, or resolve one from a
// Keylet against the view.
using Base::Base;
explicit AMMEntry(
Asset const& issue1,
Asset const& issue2,
Base::ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: Base(keylet::amm(issue1, issue2), view, j)
{
}
explicit AMMEntry(
uint256 const& ammID,
Base::ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: Base(keylet::amm(ammID), view, j)
{
}
};
using AMMEntryR = AMMEntry<ReadView>;
using AMMEntryW = AMMEntry<ApplyView>;
} // namespace xrpl

View File

@@ -0,0 +1,35 @@
#pragma once
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/SLEBase.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
namespace xrpl {
template <typename ViewT>
class AccountRootEntry : public SLEBase<ViewT, ltACCOUNT_ROOT>
{
public:
using Base = SLEBase<ViewT, ltACCOUNT_ROOT>;
// Inherit base constructors: adopt an existing SLE, or resolve one from a
// Keylet against the view.
using Base::Base;
explicit AccountRootEntry(
AccountID const& id,
Base::ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: Base(keylet::account(id), view, j)
{
}
};
using AccountRootEntryR = AccountRootEntry<ReadView>;
using AccountRootEntryW = AccountRootEntry<ApplyView>;
} // namespace xrpl

View File

@@ -0,0 +1,33 @@
#pragma once
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/SLEBase.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
namespace xrpl {
template <typename ViewT>
class AmendmentsEntry : public SLEBase<ViewT, ltAMENDMENTS>
{
public:
using Base = SLEBase<ViewT, ltAMENDMENTS>;
// Inherit base constructors: adopt an existing SLE, or resolve one from a
// Keylet against the view.
using Base::Base;
explicit AmendmentsEntry(
Base::ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: Base(keylet::amendments(), view, j)
{
}
};
using AmendmentsEntryR = AmendmentsEntry<ReadView>;
using AmendmentsEntryW = AmendmentsEntry<ApplyView>;
} // namespace xrpl

View File

@@ -0,0 +1,36 @@
#pragma once
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/SLEBase.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/STXChainBridge.h>
namespace xrpl {
template <typename ViewT>
class BridgeEntry : public SLEBase<ViewT, ltBRIDGE>
{
public:
using Base = SLEBase<ViewT, ltBRIDGE>;
// Inherit base constructors: adopt an existing SLE, or resolve one from a
// Keylet against the view.
using Base::Base;
explicit BridgeEntry(
STXChainBridge const& bridge,
STXChainBridge::ChainType chainType,
Base::ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: Base(keylet::bridge(bridge, chainType), view, j)
{
}
};
using BridgeEntryR = BridgeEntry<ReadView>;
using BridgeEntryW = BridgeEntry<ApplyView>;
} // namespace xrpl

View File

@@ -0,0 +1,46 @@
#pragma once
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/SLEBase.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/SeqProxy.h>
namespace xrpl {
template <typename ViewT>
class CheckEntry : public SLEBase<ViewT, ltCHECK>
{
public:
using Base = SLEBase<ViewT, ltCHECK>;
// Inherit base constructors: adopt an existing SLE, or resolve one from a
// Keylet against the view.
using Base::Base;
explicit CheckEntry(
AccountID const& id,
SeqProxy const& seq,
Base::ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: Base(keylet::check(id, seq), view, j)
{
}
explicit CheckEntry(
uint256 const& checkID,
Base::ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: Base(keylet::check(checkID), view, j)
{
}
};
using CheckEntryR = CheckEntry<ReadView>;
using CheckEntryW = CheckEntry<ApplyView>;
} // namespace xrpl

View File

@@ -0,0 +1,47 @@
#pragma once
#include <xrpl/basics/Slice.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/SLEBase.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
namespace xrpl {
template <typename ViewT>
class CredentialEntry : public SLEBase<ViewT, ltCREDENTIAL>
{
public:
using Base = SLEBase<ViewT, ltCREDENTIAL>;
// Inherit base constructors: adopt an existing SLE, or resolve one from a
// Keylet against the view.
using Base::Base;
explicit CredentialEntry(
AccountID const& subject,
AccountID const& issuer,
Slice const& credType,
Base::ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: Base(keylet::credential(subject, issuer, credType), view, j)
{
}
explicit CredentialEntry(
uint256 const& credentialID,
Base::ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: Base(keylet::credential(credentialID), view, j)
{
}
};
using CredentialEntryR = CredentialEntry<ReadView>;
using CredentialEntryW = CredentialEntry<ApplyView>;
} // namespace xrpl

View File

@@ -0,0 +1,35 @@
#pragma once
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/SLEBase.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
namespace xrpl {
template <typename ViewT>
class DIDEntry : public SLEBase<ViewT, ltDID>
{
public:
using Base = SLEBase<ViewT, ltDID>;
// Inherit base constructors: adopt an existing SLE, or resolve one from a
// Keylet against the view.
using Base::Base;
explicit DIDEntry(
AccountID const& account,
Base::ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: Base(keylet::did(account), view, j)
{
}
};
using DIDEntryR = DIDEntry<ReadView>;
using DIDEntryW = DIDEntry<ApplyView>;
} // namespace xrpl

View File

@@ -0,0 +1,36 @@
#pragma once
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/SLEBase.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
namespace xrpl {
template <typename ViewT>
class DelegateEntry : public SLEBase<ViewT, ltDELEGATE>
{
public:
using Base = SLEBase<ViewT, ltDELEGATE>;
// Inherit base constructors: adopt an existing SLE, or resolve one from a
// Keylet against the view.
using Base::Base;
explicit DelegateEntry(
AccountID const& account,
AccountID const& authorizedAccount,
Base::ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: Base(keylet::delegate(account, authorizedAccount), view, j)
{
}
};
using DelegateEntryR = DelegateEntry<ReadView>;
using DelegateEntryW = DelegateEntry<ApplyView>;
} // namespace xrpl

View File

@@ -0,0 +1,58 @@
#pragma once
#include <xrpl/basics/Slice.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/SLEBase.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <set>
#include <utility>
namespace xrpl {
template <typename ViewT>
class DepositPreauthEntry : public SLEBase<ViewT, ltDEPOSIT_PREAUTH>
{
public:
using Base = SLEBase<ViewT, ltDEPOSIT_PREAUTH>;
// Inherit base constructors: adopt an existing SLE, or resolve one from a
// Keylet against the view.
using Base::Base;
explicit DepositPreauthEntry(
AccountID const& owner,
AccountID const& preauthorized,
Base::ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: Base(keylet::depositPreauth(owner, preauthorized), view, j)
{
}
explicit DepositPreauthEntry(
AccountID const& owner,
std::set<std::pair<AccountID, Slice>> const& authCreds,
Base::ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: Base(keylet::depositPreauth(owner, authCreds), view, j)
{
}
explicit DepositPreauthEntry(
uint256 const& preauthID,
Base::ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: Base(keylet::depositPreauth(preauthID), view, j)
{
}
};
using DepositPreauthEntryR = DepositPreauthEntry<ReadView>;
using DepositPreauthEntryW = DepositPreauthEntry<ApplyView>;
} // namespace xrpl

View File

@@ -0,0 +1,50 @@
#pragma once
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/SLEBase.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <cstdint>
namespace xrpl {
template <typename ViewT>
class DirectoryNodeEntry : public SLEBase<ViewT, ltDIR_NODE>
{
public:
using Base = SLEBase<ViewT, ltDIR_NODE>;
// Inherit base constructors: adopt an existing SLE, or resolve one from a
// Keylet against the view.
using Base::Base;
explicit DirectoryNodeEntry(
AccountID const& id,
Base::ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: Base(keylet::ownerDir(id), view, j)
{
}
/**
* Resolve a specific page of the directory rooted at @p root.
*/
explicit DirectoryNodeEntry(
uint256 const& root,
std::uint64_t index,
Base::ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: Base(keylet::page(root, index), view, j)
{
}
};
using DirectoryNodeEntryR = DirectoryNodeEntry<ReadView>;
using DirectoryNodeEntryW = DirectoryNodeEntry<ApplyView>;
} // namespace xrpl

View File

@@ -0,0 +1,37 @@
#pragma once
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/SLEBase.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/SeqProxy.h>
namespace xrpl {
template <typename ViewT>
class EscrowEntry : public SLEBase<ViewT, ltESCROW>
{
public:
using Base = SLEBase<ViewT, ltESCROW>;
// Inherit base constructors: adopt an existing SLE, or resolve one from a
// Keylet against the view.
using Base::Base;
explicit EscrowEntry(
AccountID const& src,
SeqProxy const& seq,
Base::ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: Base(keylet::escrow(src, seq), view, j)
{
}
};
using EscrowEntryR = EscrowEntry<ReadView>;
using EscrowEntryW = EscrowEntry<ApplyView>;
} // namespace xrpl

View File

@@ -0,0 +1,33 @@
#pragma once
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/SLEBase.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
namespace xrpl {
template <typename ViewT>
class FeeSettingsEntry : public SLEBase<ViewT, ltFEE_SETTINGS>
{
public:
using Base = SLEBase<ViewT, ltFEE_SETTINGS>;
// Inherit base constructors: adopt an existing SLE, or resolve one from a
// Keylet against the view.
using Base::Base;
explicit FeeSettingsEntry(
Base::ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: Base(keylet::feeSettings(), view, j)
{
}
};
using FeeSettingsEntryR = FeeSettingsEntry<ReadView>;
using FeeSettingsEntryW = FeeSettingsEntry<ApplyView>;
} // namespace xrpl

View File

@@ -0,0 +1,33 @@
#pragma once
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/SLEBase.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
namespace xrpl {
template <typename ViewT>
class LedgerHashesEntry : public SLEBase<ViewT, ltLEDGER_HASHES>
{
public:
using Base = SLEBase<ViewT, ltLEDGER_HASHES>;
// Inherit base constructors: adopt an existing SLE, or resolve one from a
// Keylet against the view.
using Base::Base;
explicit LedgerHashesEntry(
Base::ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: Base(keylet::skip(), view, j)
{
}
};
using LedgerHashesEntryR = LedgerHashesEntry<ReadView>;
using LedgerHashesEntryW = LedgerHashesEntry<ApplyView>;
} // namespace xrpl

View File

@@ -0,0 +1,46 @@
#pragma once
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/SLEBase.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/SeqProxy.h>
namespace xrpl {
template <typename ViewT>
class LoanBrokerEntry : public SLEBase<ViewT, ltLOAN_BROKER>
{
public:
using Base = SLEBase<ViewT, ltLOAN_BROKER>;
// Inherit base constructors: adopt an existing SLE, or resolve one from a
// Keylet against the view.
using Base::Base;
explicit LoanBrokerEntry(
AccountID const& owner,
SeqProxy const& seq,
Base::ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: Base(keylet::loanBroker(owner, seq), view, j)
{
}
explicit LoanBrokerEntry(
uint256 const& loanBrokerID,
Base::ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: Base(keylet::loanBroker(loanBrokerID), view, j)
{
}
};
using LoanBrokerEntryR = LoanBrokerEntry<ReadView>;
using LoanBrokerEntryW = LoanBrokerEntry<ApplyView>;
} // namespace xrpl

View File

@@ -0,0 +1,45 @@
#pragma once
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/SLEBase.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/SeqProxy.h>
namespace xrpl {
template <typename ViewT>
class LoanEntry : public SLEBase<ViewT, ltLOAN>
{
public:
using Base = SLEBase<ViewT, ltLOAN>;
// Inherit base constructors: adopt an existing SLE, or resolve one from a
// Keylet against the view.
using Base::Base;
explicit LoanEntry(
uint256 const& loanBrokerID,
SeqProxy const& loanSeq,
Base::ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: Base(keylet::loan(loanBrokerID, loanSeq), view, j)
{
}
explicit LoanEntry(
uint256 const& loanID,
Base::ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: Base(keylet::loan(loanID), view, j)
{
}
};
using LoanEntryR = LoanEntry<ReadView>;
using LoanEntryW = LoanEntry<ApplyView>;
} // namespace xrpl

View File

@@ -0,0 +1,55 @@
#pragma once
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/SLEBase.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/UintTypes.h>
namespace xrpl {
template <typename ViewT>
class MPTokenEntry : public SLEBase<ViewT, ltMPTOKEN>
{
public:
using Base = SLEBase<ViewT, ltMPTOKEN>;
// Inherit base constructors: adopt an existing SLE, or resolve one from a
// Keylet against the view.
using Base::Base;
explicit MPTokenEntry(
MPTID const& issuanceID,
AccountID const& holder,
Base::ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: Base(keylet::mptoken(issuanceID, holder), view, j)
{
}
explicit MPTokenEntry(
uint256 const& issuanceKey,
AccountID const& holder,
Base::ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: Base(keylet::mptoken(issuanceKey, holder), view, j)
{
}
explicit MPTokenEntry(
uint256 const& mptokenKey,
Base::ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: Base(keylet::mptoken(mptokenKey), view, j)
{
}
};
using MPTokenEntryR = MPTokenEntry<ReadView>;
using MPTokenEntryW = MPTokenEntry<ApplyView>;
} // namespace xrpl

View File

@@ -0,0 +1,56 @@
#pragma once
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/SLEBase.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/UintTypes.h>
#include <cstdint>
namespace xrpl {
template <typename ViewT>
class MPTokenIssuanceEntry : public SLEBase<ViewT, ltMPTOKEN_ISSUANCE>
{
public:
using Base = SLEBase<ViewT, ltMPTOKEN_ISSUANCE>;
// Inherit base constructors: adopt an existing SLE, or resolve one from a
// Keylet against the view.
using Base::Base;
explicit MPTokenIssuanceEntry(
std::uint32_t seq,
AccountID const& issuer,
Base::ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: Base(keylet::mptokenIssuance(makeMptID(seq, issuer)), view, j)
{
}
explicit MPTokenIssuanceEntry(
MPTID const& issuanceID,
Base::ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: Base(keylet::mptokenIssuance(issuanceID), view, j)
{
}
explicit MPTokenIssuanceEntry(
uint256 const& issuanceKey,
Base::ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: Base(keylet::mptokenIssuance(issuanceKey), view, j)
{
}
};
using MPTokenIssuanceEntryR = MPTokenIssuanceEntry<ReadView>;
using MPTokenIssuanceEntryW = MPTokenIssuanceEntry<ApplyView>;
} // namespace xrpl

View File

@@ -0,0 +1,46 @@
#pragma once
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/SLEBase.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/SeqProxy.h>
namespace xrpl {
template <typename ViewT>
class NFTokenOfferEntry : public SLEBase<ViewT, ltNFTOKEN_OFFER>
{
public:
using Base = SLEBase<ViewT, ltNFTOKEN_OFFER>;
// Inherit base constructors: adopt an existing SLE, or resolve one from a
// Keylet against the view.
using Base::Base;
explicit NFTokenOfferEntry(
AccountID const& owner,
SeqProxy const& seq,
Base::ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: Base(keylet::nftokenOffer(owner, seq), view, j)
{
}
explicit NFTokenOfferEntry(
uint256 const& offerID,
Base::ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: Base(keylet::nftokenOffer(offerID), view, j)
{
}
};
using NFTokenOfferEntryR = NFTokenOfferEntry<ReadView>;
using NFTokenOfferEntryW = NFTokenOfferEntry<ApplyView>;
} // namespace xrpl

View File

@@ -0,0 +1,37 @@
#pragma once
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/SLEBase.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Keylet.h>
#include <xrpl/protocol/LedgerFormats.h>
namespace xrpl {
template <typename ViewT>
class NFTokenPageEntry : public SLEBase<ViewT, ltNFTOKEN_PAGE>
{
public:
using Base = SLEBase<ViewT, ltNFTOKEN_PAGE>;
// Inherit base constructors: adopt an existing SLE, or resolve one from a
// Keylet against the view.
using Base::Base;
explicit NFTokenPageEntry(
Keylet const& page,
uint256 const& token,
Base::ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: Base(keylet::nftokenPage(page, token), view, j)
{
}
};
using NFTokenPageEntryR = NFTokenPageEntry<ReadView>;
using NFTokenPageEntryW = NFTokenPageEntry<ApplyView>;
} // namespace xrpl

View File

@@ -0,0 +1,33 @@
#pragma once
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/SLEBase.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
namespace xrpl {
template <typename ViewT>
class NegativeUNLEntry : public SLEBase<ViewT, ltNEGATIVE_UNL>
{
public:
using Base = SLEBase<ViewT, ltNEGATIVE_UNL>;
// Inherit base constructors: adopt an existing SLE, or resolve one from a
// Keylet against the view.
using Base::Base;
explicit NegativeUNLEntry(
Base::ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: Base(keylet::negativeUNL(), view, j)
{
}
};
using NegativeUNLEntryR = NegativeUNLEntry<ReadView>;
using NegativeUNLEntryW = NegativeUNLEntry<ApplyView>;
} // namespace xrpl

View File

@@ -0,0 +1,46 @@
#pragma once
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/SLEBase.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/SeqProxy.h>
namespace xrpl {
template <typename ViewT>
class OfferEntry : public SLEBase<ViewT, ltOFFER>
{
public:
using Base = SLEBase<ViewT, ltOFFER>;
// Inherit base constructors: adopt an existing SLE, or resolve one from a
// Keylet against the view.
using Base::Base;
explicit OfferEntry(
AccountID const& id,
SeqProxy const& seq,
Base::ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: Base(keylet::offer(id, seq), view, j)
{
}
explicit OfferEntry(
uint256 const& offerID,
Base::ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: Base(keylet::offer(offerID), view, j)
{
}
};
using OfferEntryR = OfferEntry<ReadView>;
using OfferEntryW = OfferEntry<ApplyView>;
} // namespace xrpl

View File

@@ -0,0 +1,38 @@
#pragma once
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/SLEBase.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <cstdint>
namespace xrpl {
template <typename ViewT>
class OracleEntry : public SLEBase<ViewT, ltORACLE>
{
public:
using Base = SLEBase<ViewT, ltORACLE>;
// Inherit base constructors: adopt an existing SLE, or resolve one from a
// Keylet against the view.
using Base::Base;
explicit OracleEntry(
AccountID const& account,
std::uint32_t documentID,
Base::ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: Base(keylet::oracle(account, documentID), view, j)
{
}
};
using OracleEntryR = OracleEntry<ReadView>;
using OracleEntryW = OracleEntry<ApplyView>;
} // namespace xrpl

View File

@@ -0,0 +1,38 @@
#pragma once
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/SLEBase.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/SeqProxy.h>
namespace xrpl {
template <typename ViewT>
class PayChannelEntry : public SLEBase<ViewT, ltPAYCHAN>
{
public:
using Base = SLEBase<ViewT, ltPAYCHAN>;
// Inherit base constructors: adopt an existing SLE, or resolve one from a
// Keylet against the view.
using Base::Base;
explicit PayChannelEntry(
AccountID const& src,
AccountID const& dst,
SeqProxy const& seq,
Base::ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: Base(keylet::payChannel(src, dst, seq), view, j)
{
}
};
using PayChannelEntryR = PayChannelEntry<ReadView>;
using PayChannelEntryW = PayChannelEntry<ApplyView>;
} // namespace xrpl

View File

@@ -0,0 +1,46 @@
#pragma once
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/SLEBase.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/SeqProxy.h>
namespace xrpl {
template <typename ViewT>
class PermissionedDomainEntry : public SLEBase<ViewT, ltPERMISSIONED_DOMAIN>
{
public:
using Base = SLEBase<ViewT, ltPERMISSIONED_DOMAIN>;
// Inherit base constructors: adopt an existing SLE, or resolve one from a
// Keylet against the view.
using Base::Base;
explicit PermissionedDomainEntry(
AccountID const& account,
SeqProxy const& seq,
Base::ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: Base(keylet::permissionedDomain(account, seq), view, j)
{
}
explicit PermissionedDomainEntry(
uint256 const& domainID,
Base::ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: Base(keylet::permissionedDomain(domainID), view, j)
{
}
};
using PermissionedDomainEntryR = PermissionedDomainEntry<ReadView>;
using PermissionedDomainEntryW = PermissionedDomainEntry<ApplyView>;
} // namespace xrpl

View File

@@ -0,0 +1,48 @@
#pragma once
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/SLEBase.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Issue.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/UintTypes.h>
namespace xrpl {
template <typename ViewT>
class RippleStateEntry : public SLEBase<ViewT, ltRIPPLE_STATE>
{
public:
using Base = SLEBase<ViewT, ltRIPPLE_STATE>;
// Inherit base constructors: adopt an existing SLE, or resolve one from a
// Keylet against the view.
using Base::Base;
explicit RippleStateEntry(
AccountID const& id0,
AccountID const& id1,
Currency const& currency,
Base::ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: Base(keylet::trustLine(id0, id1, currency), view, j)
{
}
explicit RippleStateEntry(
AccountID const& id,
Issue const& issue,
Base::ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: Base(keylet::trustLine(id, issue), view, j)
{
}
};
using RippleStateEntryR = RippleStateEntry<ReadView>;
using RippleStateEntryW = RippleStateEntry<ApplyView>;
} // namespace xrpl

View File

@@ -0,0 +1,503 @@
#pragma once
#include <xrpl/basics/base_uint.h>
#include <xrpl/basics/contract.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/protocol/Keylet.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <concepts>
#include <memory>
#include <stdexcept>
#include <type_traits>
#include <utility>
namespace xrpl {
// Concept to distinguish read-only vs writable view types
template <typename V>
concept IsWritableView = std::derived_from<V, ApplyView>;
namespace detail {
/**
* Resolves a keylet for a read-only entry.
*
* ReadView::read() on an ApplyView returns the underlying ledger's entry
* whenever the view is not already tracking one, while peek() installs the
* view's own copy and returns that. A read-only entry built with read()
* would therefore hold an SLE that goes stale the moment anything peeks the
* same key and modifies it. Resolve through peek() whenever the view really is
* an ApplyView, so every entry over that view shares one SLE.
*
* @note The const_cast is what makes reaching ApplyView::peek() possible, and
* it is defined behavior only when the view really is a non-const
* object that the caller merely observes through a const reference.
* That holds for every production view today, but it is not a
* guarantee the codebase makes: the unit tests already build
* genuinely const ApplyView-derived objects (`Sandbox const` in
* Directory_test.cpp and View_test.cpp, `PaymentSandbox const` in
* TheoreticalQuality_test.cpp and View_test.cpp). Constructing a
* read-only entry over one of those would be undefined behavior, so
* do not, until #8069 removes the cast -- by giving ApplyView a
* const-qualified peek(), which needs no amendment because
* Action::Cache is invisible to apply(), visit() and metadata.
*
* @note Consequently a "read-only" entry over an ApplyView is not free of
* side effects: peek() installs an Action::Cache entry in the apply
* state table. That is benign for transaction metadata -- Cache entries
* are skipped in ApplyStateTable::apply(), ::visit() and in metadata
* generation -- but it does cost one deep SLE copy on first touch.
*/
inline SLE::const_pointer
resolveEntry(ReadView const& view, Keylet const& key)
{
// Safe only for a view that is not itself a const object -- see the
// note above. The entry holds a const reference because it does not
// modify the view, not because the view is const.
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast)
if (auto const applyView = dynamic_cast<ApplyView*>(const_cast<ReadView*>(&view)))
return applyView->peek(key);
return view.read(key);
}
} // namespace detail
/**
* View-parameterized base class for all ledger entries.
*
* SLEBase<ReadView> — read-only: holds shared_ptr<SLE const> + ReadView const&
* SLEBase<ApplyView> — writable: holds shared_ptr<SLE> + ApplyView& + Keylet,
* plus insert/update/erase operations
*
* Write-only members are gated by `requires` clauses, providing compile-time
* guarantees that read-only entries cannot mutate state.
*
* @tparam EntryType the ledger entry type this entry is statically bound to.
* Derived per-type entries pass their own type (e.g. ltACCOUNT_ROOT); the
* generic ReadOnlySLE / WritableSLE aliases leave it at ltANY, which opts out
* of the static type check. Binding the type here is what keeps an entry for
* one entry type from being constructed or converted from another -- see the
* converting constructor below.
*
* Derived classes should provide domain-specific accessors that hide
* implementation details of the underlying ledger entry format.
*/
template <typename ViewT, LedgerEntryType EntryType = ltANY>
class SLEBase
{
public:
static constexpr bool kIsWritable = IsWritableView<ViewT>;
// The ledger entry type this entry is bound to, and whether that binding
// is meaningful (ltANY means "any type", i.e. no static check).
static constexpr LedgerEntryType kEntryType = EntryType;
static constexpr bool kIsTyped = (EntryType != ltANY);
// SLE pointer type: mutable for writable views, const for read-only
using SlePtrType = std::conditional_t<kIsWritable, SLE::pointer, SLE::const_pointer>;
// View reference type: ApplyView& for writable, ReadView const& for
// read-only
using ViewRefType = std::conditional_t<kIsWritable, ApplyView&, ReadView const&>;
// Non-virtual by design: these entries are parameterized on the view and
// entry type, never used polymorphically through a base pointer. A vptr
// would be 8 bytes of pure overhead on a type meant to be as cheap as the
// shared_ptr it wraps. See the static_assert below the class.
//
// The destructor is public because the ReadOnlySLE / WritableSLE aliases
// name this class directly and are used as value types. Since it is not
// virtual, never delete a derived entry through an SLEBase*.
~SLEBase() = default;
SLEBase(SLEBase const&)
requires(!kIsWritable)
= default;
SLEBase(SLEBase&&) = default;
SLEBase&
operator=(SLEBase const&) = delete;
SLEBase&
operator=(SLEBase&&) = delete;
SLEBase() = delete;
// --- Constructors that adopt/resolve an SLE (public so the ReadOnlySLE /
// WritableSLE aliases and the per-type entries can be built directly
// from a keylet, or -- read-only only -- from an already-fetched
// SLE). ---
/**
* Constructor for read-only context (adopt an already-fetched SLE).
*
* There is deliberately no writable equivalent: a writable entry needs
* a Keylet so that newSLE() can still build an entry when none exists,
* and that cannot be recovered from a null SLE.
*/
explicit SLEBase(
SLE::const_pointer sle,
ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
requires(!kIsWritable)
: view_(view), sle_(std::move(sle)), j_(j)
{
XRPL_ASSERT(
!kIsTyped || !sle_ || sle_->getType() == kEntryType,
"xrpl::SLEBase::SLEBase : adopted SLE matches bound entry type");
}
/**
* Constructor for read-only context (read from view by keylet)
*/
explicit SLEBase(
Keylet const& key,
ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
requires(!kIsWritable)
: view_(view), sle_(detail::resolveEntry(view, key)), j_(j)
{
XRPL_ASSERT(
!kIsTyped || key.type == kEntryType,
"xrpl::SLEBase::SLEBase : keylet matches bound entry type");
}
/**
* Converting constructor: writable → read-only.
*
* Enables implicit conversion from SLEBase<ApplyView> to
* SLEBase<ReadView>, so functions taking ReadOnlySLE const& can accept
* WritableSLE.
*
* Constrained to the same entry type (or to a ltANY target, i.e. widening
* a typed entry to a generic ReadOnlySLE). The constraint is load-bearing:
* this constructor is inherited into every per-type entry, and unconstrained
* it would bind any writable entry that slices to SLEBase, so an OfferEntryW
* would convert to an AccountRootEntryR with no cast at the call site.
*/
template <typename OtherViewT, LedgerEntryType OtherType>
SLEBase(SLEBase<OtherViewT, OtherType> const& other)
requires(!kIsWritable && IsWritableView<OtherViewT> &&
(OtherType == EntryType || EntryType == ltANY))
: view_(other.readView()), sle_(other.rawSle()), j_(other.journal())
{
}
/**
* Constructor for writable context (peek from view by keylet)
*/
explicit SLEBase(
Keylet const& key,
ApplyView& view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
requires kIsWritable
: view_(view), key_(key), sle_(view_.peek(key)), j_(j)
{
XRPL_ASSERT(
!kIsTyped || key.type == kEntryType,
"xrpl::SLEBase::SLEBase : keylet matches bound entry type");
}
/**
* Constructor for writable context, for call sites that hold an
* ApplyViewContext (peek from ctx.view by keylet).
*
* ctx.tx is not retained: this exists purely so transactors can pass the
* context they already have instead of spelling out ctx.view. If an entry
* ever needs the applying transaction, store it here rather than adding
* another overload.
*/
explicit SLEBase(
Keylet const& key,
ApplyViewContext const& ctx,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
requires kIsWritable
: SLEBase(key, ctx.view, j)
{
}
// --- Common interface (always available) ---
/**
* Returns true if the ledger entry exists
*/
[[nodiscard]] bool
exists() const
{
return sle_ != nullptr;
}
/**
* Explicit conversion to bool for convenient existence checking
*/
explicit
operator bool() const
{
return exists();
}
/**
* Returns the underlying SLE for read access.
*
* Prefer operator-> / operator* for field access; this is for the call
* sites that need the shared_ptr itself.
*/
[[nodiscard]] SLE::const_pointer
rawSle() const
{
return sle_;
}
/**
* Returns the ledger entry type of this entry.
*
* For a per-type entry this is kEntryType, known at compile time and
* valid whether or not the entry exists. Only the generic ReadOnlySLE /
* WritableSLE aliases have to read it back out of the SLE.
*
* @throws std::logic_error for a generic (ltANY) entry if exists() is
* false.
*/
[[nodiscard]] LedgerEntryType
type() const
{
if constexpr (kIsTyped)
{
return kEntryType;
}
else
{
if (!exists())
Throw<std::logic_error>("xrpl::SLEBase::type : entry does not exist");
return sle_->getType();
}
}
/**
* Returns the keylet identifying this entry.
*
* Writable entries keep the keylet they were built from, so it is valid
* even before newSLE(). Read-only entries derive it from the SLE, which
* must therefore exist.
*
* @throws std::logic_error for a read-only entry if exists() is false.
*/
[[nodiscard]] Keylet
keylet() const
{
if constexpr (kIsWritable)
{
return key_;
}
else
{
if (!exists())
Throw<std::logic_error>("xrpl::SLEBase::keylet : entry does not exist");
// Take the type from the SLE, not from kEntryType: the adopt-SLE
// constructor's type check is assert-only, so a Release build can
// be holding an SLE whose type disagrees with the binding, and the
// SLE is the one telling the truth.
return Keylet(sle_->getType(), sle_->key());
}
}
/**
* Returns the ledger key of this entry.
*
* @throws std::logic_error same as keylet(): for read-only entries,
* if exists() is false.
*/
[[nodiscard]] uint256
key() const
{
return keylet().key;
}
/**
* Returns the read view (always available; ApplyView inherits ReadView)
*/
[[nodiscard]] ReadView const&
readView() const
{
return view_;
}
/**
* Const dereference operators (always available)
*
* @throws std::logic_error if exists() is false.
*/
STLedgerEntry const*
operator->() const
{
if (!exists())
Throw<std::logic_error>("xrpl::SLEBase::operator-> : entry does not exist");
return sle_.get();
}
STLedgerEntry const&
operator*() const
{
if (!exists())
Throw<std::logic_error>("xrpl::SLEBase::operator* : entry does not exist");
return *sle_;
}
// --- Writable interface (compile-time gated) ---
//
// Everything that hands out mutable access (or mutates) is non-const, so
// that a `FooEntryW const&` is as inert as a `FooEntryR`. Use readView()
// when a const entry only needs to inspect the view.
/**
* Returns the underlying SLE for write access.
*
* Prefer operator-> / operator* for field access; this is for the call
* sites that need the shared_ptr itself.
*/
[[nodiscard]] SlePtrType const&
mutableRawSle()
requires kIsWritable
{
return sle_;
}
/**
* Returns the apply view for write operations
*/
[[nodiscard]] ApplyView&
applyView()
requires kIsWritable
{
return view_;
}
/**
* Mutable dereference operators
*
* @throws std::logic_error if exists() is false.
*/
STLedgerEntry*
operator->()
requires kIsWritable
{
if (!exists())
Throw<std::logic_error>("xrpl::SLEBase::operator-> : entry does not exist");
return sle_.get();
}
STLedgerEntry&
operator*()
requires kIsWritable
{
if (!exists())
Throw<std::logic_error>("xrpl::SLEBase::operator* : entry does not exist");
return *sle_;
}
/**
* Inserts the entry into the view.
*
* @throws std::logic_error if exists() is false.
*/
void
insert()
requires kIsWritable
{
if (!exists())
Throw<std::logic_error>("xrpl::SLEBase::insert : entry does not exist");
view_.insert(sle_);
}
/**
* Erases the entry from the view.
*
* Drops the SLE afterwards, so the entry reports !exists() and any
* further use throws here rather than either throwing from deep inside
* ApplyStateTable or -- worse -- silently succeeding. For an
* entry that already existed, ApplyStateTable::erase keeps holding this
* exact SLE and builds the DeletedNode's FinalFields from it, so a write
* through the entry after erase() would land in transaction metadata
* with no diagnostic at all.
*
* @throws std::logic_error if exists() is false.
*/
void
erase()
requires kIsWritable
{
if (!exists())
Throw<std::logic_error>("xrpl::SLEBase::erase : entry does not exist");
view_.erase(sle_);
sle_ = nullptr;
}
/**
* @throws std::logic_error if exists() is false.
*/
void
update()
requires kIsWritable
{
if (!exists())
Throw<std::logic_error>("xrpl::SLEBase::update : entry does not exist");
view_.update(sle_);
}
/**
* @throws std::logic_error if exists() is true: newSLE() would otherwise
* silently discard the SLE already held.
*/
void
newSLE()
requires kIsWritable
{
if (exists())
Throw<std::logic_error>("xrpl::SLEBase::newSLE : entry already exists");
sle_ = std::make_shared<SLE>(key_);
}
[[nodiscard]] beast::Journal
journal() const
{
return j_;
}
protected:
ViewRefType view_;
// Keylet is only meaningful for writable views, which need it to build an
// SLE that does not exist yet; read-only entries derive it from the SLE.
struct Empty
{
};
// No default member initializer: Keylet is not default-constructible, so
// every writable constructor must initialize key_ explicitly.
[[no_unique_address]]
std::conditional_t<kIsWritable, Keylet, Empty> key_;
SlePtrType sle_{};
beast::Journal j_;
};
/**
* Generic (any-entry-type) SLE entries.
*
* Use these when the concrete ledger entry type is not known at a given site;
* otherwise prefer the per-type entries (e.g. AccountRootEntry.h), which
* additionally enforce the entry type at compile time.
*
* SLE::const_pointer / SLE::const_ref -> ReadOnlySLE
* SLE::pointer / SLE::ref -> WritableSLE
*/
using ReadOnlySLE = SLEBase<ReadView>;
using WritableSLE = SLEBase<ApplyView>;
static_assert(
!std::is_polymorphic_v<ReadOnlySLE> && !std::is_polymorphic_v<WritableSLE>,
"SLEBase must stay a thin value type; it must not acquire a vtable");
} // namespace xrpl

View File

@@ -0,0 +1,35 @@
#pragma once
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/SLEBase.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
namespace xrpl {
template <typename ViewT>
class SignerListEntry : public SLEBase<ViewT, ltSIGNER_LIST>
{
public:
using Base = SLEBase<ViewT, ltSIGNER_LIST>;
// Inherit base constructors: adopt an existing SLE, or resolve one from a
// Keylet against the view.
using Base::Base;
explicit SignerListEntry(
AccountID const& account,
Base::ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: Base(keylet::signerList(account), view, j)
{
}
};
using SignerListEntryR = SignerListEntry<ReadView>;
using SignerListEntryW = SignerListEntry<ApplyView>;
} // namespace xrpl

View File

@@ -0,0 +1,36 @@
#pragma once
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/SLEBase.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
namespace xrpl {
template <typename ViewT>
class SponsorshipEntry : public SLEBase<ViewT, ltSPONSORSHIP>
{
public:
using Base = SLEBase<ViewT, ltSPONSORSHIP>;
// Inherit base constructors: adopt an existing SLE, or resolve one from a
// Keylet against the view.
using Base::Base;
explicit SponsorshipEntry(
AccountID const& sponsor,
AccountID const& sponsee,
Base::ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: Base(keylet::sponsorship(sponsor, sponsee), view, j)
{
}
};
using SponsorshipEntryR = SponsorshipEntry<ReadView>;
using SponsorshipEntryW = SponsorshipEntry<ApplyView>;
} // namespace xrpl

View File

@@ -0,0 +1,46 @@
#pragma once
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/SLEBase.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/SeqProxy.h>
namespace xrpl {
template <typename ViewT>
class TicketEntry : public SLEBase<ViewT, ltTICKET>
{
public:
using Base = SLEBase<ViewT, ltTICKET>;
// Inherit base constructors: adopt an existing SLE, or resolve one from a
// Keylet against the view.
using Base::Base;
explicit TicketEntry(
AccountID const& id,
SeqProxy const& ticketSeq,
Base::ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: Base(keylet::ticket(id, ticketSeq), view, j)
{
}
explicit TicketEntry(
uint256 const& ticketID,
Base::ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: Base(keylet::ticket(ticketID), view, j)
{
}
};
using TicketEntryR = TicketEntry<ReadView>;
using TicketEntryW = TicketEntry<ApplyView>;
} // namespace xrpl

View File

@@ -0,0 +1,46 @@
#pragma once
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/SLEBase.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/SeqProxy.h>
namespace xrpl {
template <typename ViewT>
class VaultEntry : public SLEBase<ViewT, ltVAULT>
{
public:
using Base = SLEBase<ViewT, ltVAULT>;
// Inherit base constructors: adopt an existing SLE, or resolve one from a
// Keylet against the view.
using Base::Base;
explicit VaultEntry(
AccountID const& owner,
SeqProxy const& seq,
Base::ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: Base(keylet::vault(owner, seq), view, j)
{
}
explicit VaultEntry(
uint256 const& vaultID,
Base::ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: Base(keylet::vault(vaultID), view, j)
{
}
};
using VaultEntryR = VaultEntry<ReadView>;
using VaultEntryW = VaultEntry<ApplyView>;
} // namespace xrpl

View File

@@ -0,0 +1,38 @@
#pragma once
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/SLEBase.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/STXChainBridge.h>
#include <cstdint>
namespace xrpl {
template <typename ViewT>
class XChainOwnedClaimIDEntry : public SLEBase<ViewT, ltXCHAIN_OWNED_CLAIM_ID>
{
public:
using Base = SLEBase<ViewT, ltXCHAIN_OWNED_CLAIM_ID>;
// Inherit base constructors: adopt an existing SLE, or resolve one from a
// Keylet against the view.
using Base::Base;
explicit XChainOwnedClaimIDEntry(
STXChainBridge const& bridge,
std::uint64_t seq,
Base::ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: Base(keylet::xChainClaimID(bridge, seq), view, j)
{
}
};
using XChainOwnedClaimIDEntryR = XChainOwnedClaimIDEntry<ReadView>;
using XChainOwnedClaimIDEntryW = XChainOwnedClaimIDEntry<ApplyView>;
} // namespace xrpl

View File

@@ -0,0 +1,39 @@
#pragma once
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/SLEBase.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/STXChainBridge.h>
#include <cstdint>
namespace xrpl {
template <typename ViewT>
class XChainOwnedCreateAccountClaimIDEntry
: public SLEBase<ViewT, ltXCHAIN_OWNED_CREATE_ACCOUNT_CLAIM_ID>
{
public:
using Base = SLEBase<ViewT, ltXCHAIN_OWNED_CREATE_ACCOUNT_CLAIM_ID>;
// Inherit base constructors: adopt an existing SLE, or resolve one from a
// Keylet against the view.
using Base::Base;
explicit XChainOwnedCreateAccountClaimIDEntry(
STXChainBridge const& bridge,
std::uint64_t seq,
Base::ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: Base(keylet::xChainCreateAccountClaimID(bridge, seq), view, j)
{
}
};
using XChainOwnedCreateAccountClaimIDEntryR = XChainOwnedCreateAccountClaimIDEntry<ReadView>;
using XChainOwnedCreateAccountClaimIDEntryW = XChainOwnedCreateAccountClaimIDEntry<ApplyView>;
} // namespace xrpl

View File

@@ -338,9 +338,9 @@ struct AccountingDeltas
Number debtTotalDelta;
};
// Whole-life (pre-LendingProtocolV1_1) recognition model: interest is
// recognized into AssetsTotal/DebtTotal up front, at origination.
namespace accrual {
// Instant interest recognition (pre-LendingProtocolV1_1): interest is
// recognized into AssetsTotal/DebtTotal immediately, at origination.
namespace instant_recognition {
// LoanSet origination: what's added to Vault.AssetsTotal and LoanBroker.DebtTotal
AccountingDeltas
@@ -362,7 +362,7 @@ loanVaultExposure(SLE::const_ref loanSle);
AccountingDeltas
loanPaymentDeltas(LoanPaymentParts const& parts);
} // namespace accrual
} // namespace instant_recognition
// Cash-basis (LendingProtocolV1_1) recognition model: AssetsTotal/DebtTotal
// are principal-only, interest is recognized only as it's actually paid.
@@ -381,7 +381,7 @@ loanPaymentDeltas(LoanPaymentParts const& parts);
// Public dispatchers: pick cash_basis:: if featureLendingProtocolV1_1 is
// enabled AND the Vault's LEVersion (VaultHelpers::getVaultVersion) is
// VaultVersion::CashBasis, else accrual::. These are the only entry points
// VaultVersion::CashBasis, else instant_recognition::. These are the only entry points
// transactors call.
AccountingDeltas
loanOriginationDeltas(

View File

@@ -172,8 +172,8 @@ isSoleShareholder(ReadView const& view, AccountID const& account, SLE::const_ref
/**
* Resolves a Vault's LEVersion, the single point every accounting touch
* point should call to determine which recognition model (accrual vs.
* cash-basis) a Vault uses. Vaults created before featureLendingProtocolV1_1
* point should call to determine which recognition model (instant interest
* recognition vs. cash-basis) a Vault uses. Vaults created before featureLendingProtocolV1_1
* activated never have sfLEVersion set, which resolves here to
* VaultVersion::Legacy.
*

View File

@@ -92,7 +92,7 @@ public:
void
importDatabase(Database& source) override
{
importInternal(*backend_.get(), source);
importInternal(*backend_, source);
}
void

View File

@@ -26,7 +26,8 @@ struct Config
/**
* The largest number of public peer slots to allow.
* This includes both inbound and outbound, but does not include
* fixed peers.
* fixed peers. A configuration built by `makeConfig` always holds
* `maxPeers == inPeers + outPeers`.
*/
std::size_t maxPeers{tuning::kDefaultMaxPeers};

View File

@@ -6,6 +6,7 @@
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STInteger.h> // IWYU pragma: keep
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/STObject.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/UintTypes.h>
@@ -301,6 +302,87 @@ verifySchnorrProof(Slice const& pubKeySlice, Slice const& proofSlice, uint256 co
NotTEC
checkEncryptedAmountFormat(STObject const& object);
/**
* @brief Checks whether a holder's issuer mirror is encrypted under the
* issuance's currently registered issuer key.
*
* Verifies that the holder's issuer mirror epoch matches the active issuer key
* epoch on the issuance. An absent mirror epoch defaults to epoch 0. A holder without an issuer
* mirror is considered stale, as there is no key anchor for future re-encryptions.
*
* @param issuance The MPTokenIssuance ledger object.
* @param mptoken The holder's MPToken ledger object.
* @return true if the MPToken's issuer mirror is current. false if stale.
*/
[[nodiscard]] bool
isIssuerMirrorCurrent(SLE const& issuance, SLE const& mptoken);
/**
* @brief Checks whether a holder's auditor mirror is encrypted under the
* issuance's currently registered auditor key.
*
* Verifies that the holder's auditor mirror epoch matches the active auditor key
* epoch on the issuance. An absent mirror epoch defaults to epoch 0. An issuance
* without an auditor key requires no auditor mirror and is considered current.
*
* @param issuance The MPTokenIssuance ledger object.
* @param mptoken The holder's MPToken ledger object.
* @return true if the auditor mirror is current or not required.
*/
[[nodiscard]] bool
isAuditorMirrorCurrent(SLE const& issuance, SLE const& mptoken);
/**
* @brief Checks whether each mirror a holder is required to have is encrypted
* under the issuance's currently registered ElGamal keys.
*
* Verifies that both the issuer mirror and the auditor mirror (if required)
* are current. This serves as a combined check, ensuring all necessary
* holder mirror epochs match the active key epochs on the issuance.
*
* @param issuance The MPTokenIssuance ledger object.
* @param mptoken The holder's MPToken ledger object.
* @return true if the required mirrors are current.
*/
[[nodiscard]] bool
areMirrorsCurrent(SLE const& issuance, SLE const& mptoken);
/**
* @brief Set the holder's issuer mirror epoch to match the issuance's current issuer key epoch.
*
* Call this after writing the issuer mirror ciphertext under the issuance's
* currently registered issuer key, so that the mirror reads as current afterwards.
*
* @param issuance The MPTokenIssuance ledger object.
* @param mptoken The holder's MPToken ledger entry to update.
*/
void
setIssuerMirrorEpoch(SLE const& issuance, SLE& mptoken);
/**
* @brief Set the holder's auditor mirror epoch to match the issuance's current auditor key epoch.
*
* Call this after writing the auditor mirror ciphertext under the issuance's
* currently registered auditor key. Does nothing when the holder has no auditor mirror.
*
* @param issuance The MPTokenIssuance ledger object.
* @param mptoken The holder's MPToken ledger entry to update.
*/
void
setAuditorMirrorEpoch(SLE const& issuance, SLE& mptoken);
/**
* @brief Set the holder's MPToken mirror epochs to match the issuance's current key epochs.
*
* Call this after writing mirror ciphertexts under the issuance's currently
* registered keys, so that the mirrors read as current afterwards.
*
* @param issuance The MPTokenIssuance ledger object.
* @param mptoken The holder's MPToken ledger entry to update.
*/
void
setMirrorEpochs(SLE const& issuance, SLE& mptoken);
/**
* @brief Verifies revealed amount encryptions for all recipients.
*

View File

@@ -5,9 +5,11 @@
#include <xrpl/protocol/Concepts.h>
#include <xrpl/protocol/UintTypes.h>
#include <cstdint>
#include <ostream>
#include <stdexcept>
#include <string>
#include <type_traits>
#include <variant>
namespace xrpl {
@@ -121,9 +123,32 @@ operator==(PathAsset const& lhs, PathAsset const& rhs)
template <typename Hasher>
void
hash_append(Hasher& h, PathAsset const& pathAsset)
hash_append(Hasher& h, PathAsset const& pathAsset) noexcept
{
std::visit([&]<ValidPathAsset T>(T const& e) { hash_append(h, e); }, pathAsset.value());
using beast::hash_append;
using Variant = std::remove_cvref_t<decltype(pathAsset.value())>;
static_assert(
std::variant_size_v<Variant> < 0xFFu,
"PathAsset's discriminant must fit in a byte, leaving 0xFF reserved.");
// std::visit is not noexcept: it throws bad_variant_access when the variant
// is valueless_by_exception.
if (pathAsset.value().valueless_by_exception()) [[unlikely]]
{
hash_append(h, static_cast<std::uint8_t>(0xFFu));
return;
}
hash_append(h, static_cast<std::uint8_t>(pathAsset.value().index()));
std::visit(
[&]<ValidPathAsset T>(T const& e) noexcept {
static_assert(
noexcept(hash_append(h, e)),
"Every PathAsset alternative must be nothrow-hashable.");
hash_append(h, e);
},
pathAsset.value());
}
inline bool

View File

@@ -322,7 +322,7 @@ constexpr std::uint8_t kVaultMaximumIouScale = 18;
* Vault ledger-entry schema versions. Assigned to newly created
* Vaults once featureLendingProtocolV1_1 is enabled. Vaults created before
* activation are left without LEVersion (implicit legacy version 0,
* accrual-basis accounting).
* instant interest recognition).
*/
enum class VaultVersion : uint8_t {
Legacy = 0,
@@ -540,6 +540,11 @@ constexpr std::size_t kEcConvertBackProofLength =
*/
constexpr std::size_t kEcClawbackProofLength = SECP256K1_COMPACT_CLAWBACK_PROOF_SIZE;
/**
* Length of compact equality proof.
*/
constexpr std::size_t kEcEqualityProofLength = 128;
/**
* Extra base fee multiplier charged to confidential MPT transactions.
*/

View File

@@ -12,6 +12,8 @@
#include <xrpl/protocol/UintTypes.h>
#include <cstddef>
#include <cstdint>
#include <memory>
#include <optional>
#include <utility>
#include <vector>
@@ -65,7 +67,7 @@ public:
PathAsset const& asset,
AccountID const& issuer);
[[nodiscard]] auto
[[nodiscard]] std::uint32_t
getNodeType() const;
[[nodiscard]] bool
@@ -109,9 +111,6 @@ public:
[[nodiscard]] bool
isType(Type const& pe) const;
[[nodiscard]] size_t
getHash() const;
bool
operator==(STPathElement const& t) const;
@@ -120,6 +119,17 @@ private:
getHash(STPathElement const& element);
};
template <class Hasher>
void
hash_append(Hasher& h, STPathElement const& e) noexcept
{
using beast::hash_append;
hash_append(h, (e.getNodeType() & STPathElement::TypeAccount) != 0u);
hash_append(h, e.getAccountID());
hash_append(h, e.getPathAsset());
hash_append(h, e.getIssuerID());
}
class STPath final : public CountedObject<STPath>
{
std::vector<STPathElement> path_;
@@ -176,9 +186,10 @@ template <class Hasher>
void
hash_append(Hasher& h, STPath const& p) noexcept
{
using beast::hash_append;
for (auto const& e : p)
{
beast::hash_append(h, e.getHash());
hash_append(h, e);
}
}
@@ -188,13 +199,39 @@ hash_append(Hasher& h, STPath const& p) noexcept
class STPathSet final : public STBase, public CountedObject<STPathSet>
{
std::vector<STPath> value_;
xrpl::hardened_hash_set<STPath> seenHashes_;
/**
* Deduplication index over `value_`, for pathfinding.
* The use of a std::unique_ptr is intentional as it
* only requires 8 additional bytes of storage for the pointer
* as opposed to 64 bytes with an optional. This keeps the size
* of the STPathSet to within the `STVar::kMaxSize` limit of 72 bytes.
*/
std::unique_ptr<hardened_hash_set<STPath>> seen_;
public:
struct DeduplicationTag
{
};
STPathSet() = default;
/**
* Deduplication tagged constructor.
* Use when you want to ensure that the STPathSet does not contain duplicate paths.
*/
explicit STPathSet(DeduplicationTag);
STPathSet(SField const& n);
STPathSet(SerialIter& sit, SField const& name);
STPathSet(STPathSet const& other);
STPathSet(STPathSet&&) = default;
STPathSet&
operator=(STPathSet const& other);
STPathSet&
operator=(STPathSet&&) = default;
~STPathSet() override = default;
void
add(Serializer& s) const override;
@@ -204,6 +241,16 @@ public:
[[nodiscard]] SerializedTypeID
getSType() const override;
/**
* @brief assembleAdd adds a path to the set by combining a base path and a tail element.
*
* @param base The base path.
* @param tail The tail element.
* @return true if the path was added, false if it was a duplicate and not added.
* @remarks Requires the STPathSet to be constructed with the DeduplicationTag. The return value
* indicates whether the combined path was inserted (true) or rejected as a duplicate (false).
* It is fine for callers to ignore the return value.
*/
bool
assembleAdd(STPath const& base, STPathElement const& tail);
@@ -229,22 +276,61 @@ public:
[[nodiscard]] bool
empty() const;
void
/**
* @brief pushBack adds a path to the set.
*
* @param e The path to add.
* @return true if the path was added, false if it was a duplicate and not added.
* @remarks If the STPathSet was constructed with the DeduplicationTag, then this method will
* check for duplicates and only add the path if it is not already present in the
* set. If the STPathSet was constructed without the DeduplicationTag,
* then this method will always add the path to the set, regardless of duplicates.
* It is fine for callers to ignore the return value.
*/
bool
pushBack(STPath const& e);
/**
* @brief emplaceBack adds a path to the set.
*
* @param args The arguments to construct the path with.
* @return true if the path was added, false if it was a duplicate and not added.
* @remarks If the STPathSet was constructed with the DeduplicationTag, then this method will
* check for duplicates and only add the path if it is not already present in the
* set. If the STPathSet was constructed without the DeduplicationTag,
* then this method will always add the path to the set, regardless of duplicates.
* It is fine for callers to ignore the return value.
* @note The path is constructed before the duplicate check, so on a false
* return the constructed path is discarded and any argument
* forwarded as an rvalue is left in a moved-from state. Use
* pushBack when the caller needs to keep its path on rejection.
*/
template <typename... Args>
void
bool
emplaceBack(Args&&... args);
[[nodiscard]] bool
contains(STPath const& path) const;
private:
STBase*
copy(std::size_t n, void* buf) const override;
STBase*
move(std::size_t n, void* buf) override;
/**
* @brief Append a path via `append`, then register it in the deduplication index.
*
* @param append Invoked with `value_`; must append exactly one path to it.
* @return true if the path was kept, false if it was a duplicate and was rolled back.
* @remarks Appends to the vector before touching the index, so that a failed allocation
* there leaves both containers untouched rather than leaving the index holding
* a path the vector does not. If the index insert reports a duplicate, or
* throws, the append is rolled back so the two containers stay consistent; in
* the throwing case the exception propagates. With no index (constructed
* without the DeduplicationTag) the append is unconditional.
*/
template <typename Append>
bool
appendUnique(Append&& append);
friend class detail::STVar;
};
@@ -336,7 +422,7 @@ inline STPathElement::STPathElement(
hashValue_ = getHash(*this);
}
inline auto
inline std::uint32_t
STPathElement::getNodeType() const
{
return type_;
@@ -545,25 +631,50 @@ STPathSet::empty() const
return value_.empty();
}
inline void
STPathSet::pushBack(STPath const& e)
template <typename Append>
inline bool
STPathSet::appendUnique(Append&& append)
{
value_.push_back(e);
seenHashes_.emplace(value_.back());
}
// Append to the vector first, so that a failed allocation there leaves both
// containers untouched rather than leaving the index holding a path the
// vector does not.
append(value_);
template <typename... Args>
inline void
STPathSet::emplaceBack(Args&&... args)
{
value_.emplace_back(std::forward<Args>(args)...);
seenHashes_.emplace(value_.back());
if (seen_ == nullptr)
{
return true;
}
try
{
if (!seen_->insert(value_.back()).second)
{
// Already present: roll back the append.
value_.pop_back();
return false;
}
}
catch (...)
{
// The index insert failed, so roll back the append to keep the vector
// and the index consistent.
value_.pop_back();
throw;
}
return true;
}
inline bool
STPathSet::contains(STPath const& path) const
STPathSet::pushBack(STPath const& e)
{
return seenHashes_.contains(path);
return appendUnique([&](auto& value) { value.push_back(e); });
}
template <typename... Args>
inline bool
STPathSet::emplaceBack(Args&&... args)
{
return appendUnique([&](auto& value) { value.emplace_back(std::forward<Args>(args)...); });
}
} // namespace xrpl

View File

@@ -124,6 +124,13 @@ public:
[[nodiscard]] NodeID const&
getNodeID() const noexcept;
/**
* Whether this validation carries a good signature.
*
* Reports false if the signature cannot be checked at all, so a caller
* cannot tell that apart from a bad signature. Either way the validation is
* unusable, and the reason is logged. Only a computed answer is remembered.
*/
[[nodiscard]] bool
isValid() const noexcept;

View File

@@ -10,6 +10,7 @@
#include <xrpl/protocol/HashPrefix.h>
#include <xrpl/protocol/SField.h>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <stdexcept>
@@ -25,6 +26,101 @@ private:
Blob data_;
public:
/**
* A header is never longer than this. The encoder fills a buffer of this
* size and writes only the bytes it used.
*/
static constexpr int kMaxNumberOfBytesInHeader = 3;
// A field whose size varies is stored as a header holding its length, then
// the field data. The header is 1, 2 or 3 bytes long. Nothing outside it says
// which, so the decoder reads the first byte and its value says how long the
// header is:
//
// 0 ... 192 kMin/kMaxValueOfFirstByteFor1ByteHeader
// 193 ... 240 kMin/kMaxValueOfFirstByteFor2ByteHeader
// 241 ... 254 kMin/kMaxValueOfFirstByteFor3ByteHeader
// 255 belongs to no header
//
// Each range starts one past the end of the range before it.
static constexpr int kMinValueOfFirstByteFor1ByteHeader = 0;
static constexpr int kMaxValueOfFirstByteFor1ByteHeader = 192;
static constexpr int kMinValueOfFirstByteFor2ByteHeader =
kMaxValueOfFirstByteFor1ByteHeader + 1;
static constexpr int kMaxValueOfFirstByteFor2ByteHeader = 240;
static constexpr int kMinValueOfFirstByteFor3ByteHeader =
kMaxValueOfFirstByteFor2ByteHeader + 1;
static constexpr int kMaxValueOfFirstByteFor3ByteHeader = 254;
// A length x too big for one byte is split across the header. For 2 bytes:
//
// first byte = 193 + (x - 193) / 256
// second byte = (x - 193) % 256
//
// so 300 is stored as 193, 107. For 3 bytes it is the same, from 241, with
// the remainder split across two bytes: 20,000 is stored as 241, 29, 95.
static constexpr int kNumberOfValuesInOneByte = 256;
static constexpr int kNumberOfValuesInTwoBytes =
kNumberOfValuesInOneByte * kNumberOfValuesInOneByte;
// Each header length therefore covers a range of field lengths:
//
// 0 ... 192 kMin/kMaxValueOfLengthFor1ByteHeader
// 193 ... 12,480 kMin/kMaxValueOfLengthFor2ByteHeader
// 12,481 ... 918,744 kMin/kMaxValueOfLengthFor3ByteHeader
//
// The encoder always uses the shortest header that fits.
/**
* A 1 byte header holds the length in the byte itself, so both ends of
* this range are the same numbers as the first byte's own range.
*/
static constexpr int kMinValueOfLengthFor1ByteHeader = kMinValueOfFirstByteFor1ByteHeader;
static constexpr int kMaxValueOfLengthFor1ByteHeader = kMaxValueOfFirstByteFor1ByteHeader;
static constexpr int kMinValueOfLengthFor2ByteHeader = kMaxValueOfLengthFor1ByteHeader + 1;
/**
* 48 values of the first byte mean a 2 byte header, and each of them covers
* 256 lengths. The 48 is worked out from the two range ends above, so it
* stays right if either of them changes.
*/
static constexpr int kMaxValueOfLengthFor2ByteHeader = kMinValueOfLengthFor2ByteHeader +
((kMaxValueOfFirstByteFor2ByteHeader - kMaxValueOfFirstByteFor1ByteHeader) *
kNumberOfValuesInOneByte) -
1;
static constexpr int kMinValueOfLengthFor3ByteHeader = kMaxValueOfLengthFor2ByteHeader + 1;
/**
* 14 values of the first byte mean a 3 byte header, and each of them covers
* 65,536 lengths. Counted the same way, that gives the largest length any
* header can state.
*
* Nothing is accepted or rejected against this. The assertion below uses it
* to check that every length the encoder writes is one a header can state.
*/
static constexpr int kMaxRepresentableLength = kMinValueOfLengthFor3ByteHeader +
((kMaxValueOfFirstByteFor3ByteHeader - kMaxValueOfFirstByteFor2ByteHeader) *
kNumberOfValuesInTwoBytes) -
1;
/**
* The largest length the encoder will write. This is the one number here
* that is picked rather than worked out. The decoder accepts nothing above
* it, so both sides agree on the same set of lengths.
*/
static constexpr int kMaxValueOfLengthFor3ByteHeader = 918744;
static_assert(
kMaxValueOfLengthFor3ByteHeader <= kMaxRepresentableLength,
"a length the encoder writes must be one a header can state");
explicit Serializer(int n = 256)
{
data_.reserve(n);
@@ -61,7 +157,7 @@ public:
// assemble functions
int
add8(unsigned char i);
add8(unsigned char byteValue);
int
add16(std::uint16_t i);
@@ -270,18 +366,90 @@ public:
return v.data_ == data_;
}
/**
* Works out how long a header is, from its first byte.
*
* Each overload of decodeVLLength below reads one header length, so call
* this first to learn which of them to call.
*
* @param firstByte First byte of the header, as read from the stream.
* @return How many bytes the whole header takes, counting firstByte: 1, 2
* or 3.
* @throws std::overflow_error if firstByte is the one value that starts no
* header.
*/
static int
decodeLengthLength(int b1);
decodeLengthLength(std::byte firstByte);
/**
* Reads the field length out of a 1 byte header.
*
* @param firstByte The single header byte, which is the length itself.
* @return Field length in bytes, from kMinValueOfLengthFor1ByteHeader to
* kMaxValueOfLengthFor1ByteHeader.
* @throws std::overflow_error if firstByte is big enough to mean a longer
* header, in which case it is not a length by itself.
*/
static int
decodeVLLength(int b1);
decodeVLLength(std::byte firstByte);
/**
* Reads the field length out of a 2 byte header.
*
* @param firstByte First header byte. Its value means a 2 byte header, and
* how far it sits into that range gives the top part of the length.
* @param secondByte Second header byte, holding the rest of the length.
* @return Field length in bytes, from kMinValueOfLengthFor2ByteHeader to
* kMaxValueOfLengthFor2ByteHeader.
* @throws std::overflow_error if firstByte is outside the range that means
* a 2 byte header.
*/
static int
decodeVLLength(int b1, int b2);
decodeVLLength(std::byte firstByte, std::byte secondByte);
/**
* Reads the field length out of a 3 byte header.
*
* @param firstByte First header byte. Its value means a 3 byte header, and
* how far it sits into that range gives the top part of the length.
* @param secondByte Second header byte, holding the middle part of the
* length.
* @param thirdByte Third header byte, holding the low part.
* @return Field length in bytes, from kMinValueOfLengthFor3ByteHeader to
* kMaxValueOfLengthFor3ByteHeader.
* @throws std::overflow_error if firstByte is outside the range that means
* a 3 byte header, or if the three bytes together state a length above
* kMaxValueOfLengthFor3ByteHeader, which the encoder would not write back.
*/
static int
decodeVLLength(int b1, int b2, int b3);
decodeVLLength(std::byte firstByte, std::byte secondByte, std::byte thirdByte);
private:
/**
* Works out how many bytes the header needs for the given length.
*
* This deliberately repeats the width choice addEncoded makes, so that
* addVL's assertion can compare the two. It has no other caller; do not
* reach for it as a utility.
*
* @param length Field length in bytes.
* @return How many header bytes it needs: 1, 2 or 3.
* @throws std::overflow_error if length is negative, or above
* kMaxValueOfLengthFor3ByteHeader.
*/
static int
encodeLengthLength(int length); // length to encode length
encodeLengthLength(int length);
/**
* Appends the length header for a field of the given length.
*
* The field's own data is not written; the caller appends it next.
*
* @param length Field length in bytes.
* @return Offset within this Serializer at which the header was written.
* @throws std::overflow_error if length is negative, or above
* kMaxValueOfLengthFor3ByteHeader.
*/
int
addEncoded(int length);
};
@@ -390,9 +558,15 @@ public:
void
getFieldID(int& type, int& name);
// Returns the size of the VL if the
// next object is a VL. Advances the iterator
// to the beginning of the VL.
/**
* Reads the length header at the read position and steps past it.
*
* @return Field length in bytes. The iterator is left on the first byte of
* the field data.
* @throws std::overflow_error if the header states a length the encoder could
* not have written.
* @throws std::runtime_error if the data runs out before the header does.
*/
int
getVLDataLength();

View File

@@ -34,10 +34,11 @@ concept ValidConstructSTArgs =
// and includes a small-object allocation optimization.
class STVar
{
private:
public:
// The largest "small object" we can accommodate
static constexpr std::size_t kMaxSize = 72;
private:
alignas(std::max_align_t) std::byte d_[kMaxSize] = {};
STBase* p_ = nullptr;

View File

@@ -436,6 +436,8 @@ LEDGER_ENTRY(ltMPTOKEN, 0x007f, MPToken, mptoken, ({
{sfConfidentialBalanceVersion, SoeDefault},
{sfIssuerEncryptedBalance, SoeOptional},
{sfAuditorEncryptedBalance, SoeOptional},
{sfIssuerKeyMirrorEpoch, SoeOptional},
{sfAuditorKeyMirrorEpoch, SoeOptional},
{sfHolderEncryptionKey, SoeOptional},
}))

View File

@@ -1134,6 +1134,19 @@ TRANSACTION(ttSPONSORSHIP_SET, 91, SponsorshipSet,
{sfRemainingOwnerCountDelta, SoeOptional},
}))
#if TRANSACTION_INCLUDE
# include <xrpl/tx/transactors/token/ConfidentialMPTMirrorUpdate.h>
#endif
TRANSACTION(ttCONFIDENTIAL_MPT_MIRROR_UPDATE, 92, ConfidentialMPTMirrorUpdate,
({.delegable = Delegation::Delegable, .amendment = featureConfidentialMPTKeyRotation}),
({
{sfMPTokenIssuanceID, SoeRequired},
{sfHolder, SoeOptional},
{sfIssuerEncryptedAmount, SoeOptional},
{sfAuditorEncryptedAmount, SoeOptional},
{sfZKProof, SoeRequired},
}))
/** This system-generated transaction type is used to update the status of the various amendments.
For details, see: https://xrpl.org/amendments.html

View File

@@ -278,6 +278,7 @@ JSS(frozen_balances); // out: GatewayBalances
JSS(full); // in: LedgerClearer, handlers/Ledger
JSS(full_reply); // out: PathFind
JSS(fullbelow_size); // out: GetCounts
JSS(gateway); // in: noripple_check
JSS(git); // out: server_info
JSS(good); // out: RPCVersion
JSS(hash); // out: NetworkOPs, InboundLedger, LedgerToJson, STTx; field
@@ -481,6 +482,7 @@ JSS(ports); // out: NetworkOPs
JSS(previous); // out: Reservations
JSS(previous_ledger); // out: LedgerPropose
JSS(price); // out: amm_info, AuctionSlot
JSS(problems); // out: noripple_check
JSS(proof); // in: BookOffers
JSS(propose_seq); // out: LedgerPropose
JSS(proposers); // out: NetworkOPs, LedgerConsensus
@@ -660,6 +662,7 @@ JSS(url); // in/out: Subscribe, Unsubscribe
JSS(url_password); // in: Subscribe
JSS(url_username); // in: Subscribe
JSS(urlgravatar); //
JSS(user); // in: noripple_check
JSS(username); // in: Subscribe
JSS(validated); // out: NetworkOPs, RPCHelpers, AccountTx*, Tx
JSS(validator_list_expires); // out: NetworkOps, ValidatorList

View File

@@ -268,6 +268,54 @@ public:
return this->sle_->isFieldPresent(sfAuditorEncryptedBalance);
}
/**
* @brief Get sfIssuerKeyMirrorEpoch (SoeOptional)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_UINT32::type::value_type>
getIssuerKeyMirrorEpoch() const
{
if (hasIssuerKeyMirrorEpoch())
return this->sle_->at(sfIssuerKeyMirrorEpoch);
return std::nullopt;
}
/**
* @brief Check if sfIssuerKeyMirrorEpoch is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasIssuerKeyMirrorEpoch() const
{
return this->sle_->isFieldPresent(sfIssuerKeyMirrorEpoch);
}
/**
* @brief Get sfAuditorKeyMirrorEpoch (SoeOptional)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_UINT32::type::value_type>
getAuditorKeyMirrorEpoch() const
{
if (hasAuditorKeyMirrorEpoch())
return this->sle_->at(sfAuditorKeyMirrorEpoch);
return std::nullopt;
}
/**
* @brief Check if sfAuditorKeyMirrorEpoch is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasAuditorKeyMirrorEpoch() const
{
return this->sle_->isFieldPresent(sfAuditorKeyMirrorEpoch);
}
/**
* @brief Get sfHolderEncryptionKey (SoeOptional)
* @return The field value, or std::nullopt if not present.
@@ -471,6 +519,28 @@ public:
return *this;
}
/**
* @brief Set sfIssuerKeyMirrorEpoch (SoeOptional)
* @return Reference to this builder for method chaining.
*/
MPTokenBuilder&
setIssuerKeyMirrorEpoch(std::decay_t<typename SF_UINT32::type::value_type> const& value)
{
object_[sfIssuerKeyMirrorEpoch] = value;
return *this;
}
/**
* @brief Set sfAuditorKeyMirrorEpoch (SoeOptional)
* @return Reference to this builder for method chaining.
*/
MPTokenBuilder&
setAuditorKeyMirrorEpoch(std::decay_t<typename SF_UINT32::type::value_type> const& value)
{
object_[sfAuditorKeyMirrorEpoch] = value;
return *this;
}
/**
* @brief Set sfHolderEncryptionKey (SoeOptional)
* @return Reference to this builder for method chaining.

View File

@@ -0,0 +1,266 @@
// This file is auto-generated. Do not edit.
#pragma once
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/STParsedJSON.h>
#include <xrpl/protocol/jss.h>
#include <xrpl/protocol_autogen/TransactionBase.h>
#include <xrpl/protocol_autogen/TransactionBuilderBase.h>
#include <xrpl/json/json_value.h>
#include <stdexcept>
#include <optional>
namespace xrpl::transactions {
class ConfidentialMPTMirrorUpdateBuilder;
/**
* @brief Transaction: ConfidentialMPTMirrorUpdate
*
* Type: ttCONFIDENTIAL_MPT_MIRROR_UPDATE (92)
* Delegable: Delegation::Delegable
* Amendment: featureConfidentialMPTKeyRotation
* Privileges: Privilege::NoPriv
*
* Immutable wrapper around STTx providing type-safe field access.
* Use ConfidentialMPTMirrorUpdateBuilder to construct new transactions.
*/
class ConfidentialMPTMirrorUpdate : public TransactionBase
{
public:
static constexpr xrpl::TxType txType = ttCONFIDENTIAL_MPT_MIRROR_UPDATE;
/**
* @brief Construct a ConfidentialMPTMirrorUpdate transaction wrapper from an existing STTx object.
* @throws std::runtime_error if the transaction type doesn't match.
*/
explicit ConfidentialMPTMirrorUpdate(std::shared_ptr<STTx const> tx)
: TransactionBase(std::move(tx))
{
// Verify transaction type
if (tx_->getTxnType() != txType)
{
throw std::runtime_error("Invalid transaction type for ConfidentialMPTMirrorUpdate");
}
}
// Transaction-specific field getters
/**
* @brief Get sfMPTokenIssuanceID (SoeRequired)
* @return The field value.
*/
[[nodiscard]]
SF_UINT192::type::value_type
getMPTokenIssuanceID() const
{
return this->tx_->at(sfMPTokenIssuanceID);
}
/**
* @brief Get sfHolder (SoeOptional)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_ACCOUNT::type::value_type>
getHolder() const
{
if (hasHolder())
{
return this->tx_->at(sfHolder);
}
return std::nullopt;
}
/**
* @brief Check if sfHolder is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasHolder() const
{
return this->tx_->isFieldPresent(sfHolder);
}
/**
* @brief Get sfIssuerEncryptedAmount (SoeOptional)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_VL::type::value_type>
getIssuerEncryptedAmount() const
{
if (hasIssuerEncryptedAmount())
{
return this->tx_->at(sfIssuerEncryptedAmount);
}
return std::nullopt;
}
/**
* @brief Check if sfIssuerEncryptedAmount is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasIssuerEncryptedAmount() const
{
return this->tx_->isFieldPresent(sfIssuerEncryptedAmount);
}
/**
* @brief Get sfAuditorEncryptedAmount (SoeOptional)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_VL::type::value_type>
getAuditorEncryptedAmount() const
{
if (hasAuditorEncryptedAmount())
{
return this->tx_->at(sfAuditorEncryptedAmount);
}
return std::nullopt;
}
/**
* @brief Check if sfAuditorEncryptedAmount is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasAuditorEncryptedAmount() const
{
return this->tx_->isFieldPresent(sfAuditorEncryptedAmount);
}
/**
* @brief Get sfZKProof (SoeRequired)
* @return The field value.
*/
[[nodiscard]]
SF_VL::type::value_type
getZKProof() const
{
return this->tx_->at(sfZKProof);
}
};
/**
* @brief Builder for ConfidentialMPTMirrorUpdate transactions.
*
* Provides a fluent interface for constructing transactions with method chaining.
* Uses STObject internally for flexible transaction construction.
* Inherits common field setters from TransactionBuilderBase.
*/
class ConfidentialMPTMirrorUpdateBuilder : public TransactionBuilderBase<ConfidentialMPTMirrorUpdateBuilder>
{
public:
/**
* @brief Construct a new ConfidentialMPTMirrorUpdateBuilder with required fields.
* @param account The account initiating the transaction.
* @param mPTokenIssuanceID The sfMPTokenIssuanceID field value.
* @param zKProof The sfZKProof field value.
* @param sequence Optional sequence number for the transaction.
* @param fee Optional fee for the transaction.
*/
ConfidentialMPTMirrorUpdateBuilder(SF_ACCOUNT::type::value_type account,
std::decay_t<typename SF_UINT192::type::value_type> const& mPTokenIssuanceID, std::decay_t<typename SF_VL::type::value_type> const& zKProof, std::optional<SF_UINT32::type::value_type> sequence = std::nullopt,
std::optional<SF_AMOUNT::type::value_type> fee = std::nullopt
)
: TransactionBuilderBase<ConfidentialMPTMirrorUpdateBuilder>(ttCONFIDENTIAL_MPT_MIRROR_UPDATE, account, sequence, fee)
{
setMPTokenIssuanceID(mPTokenIssuanceID);
setZKProof(zKProof);
}
/**
* @brief Construct a ConfidentialMPTMirrorUpdateBuilder from an existing STTx object.
* @param tx The existing transaction to copy from.
* @throws std::runtime_error if the transaction type doesn't match.
*/
ConfidentialMPTMirrorUpdateBuilder(std::shared_ptr<STTx const> tx)
{
if (tx->getTxnType() != ttCONFIDENTIAL_MPT_MIRROR_UPDATE)
{
throw std::runtime_error("Invalid transaction type for ConfidentialMPTMirrorUpdateBuilder");
}
object_ = *tx;
}
/**
* @brief Transaction-specific field setters
*/
/**
* @brief Set sfMPTokenIssuanceID (SoeRequired)
* @return Reference to this builder for method chaining.
*/
ConfidentialMPTMirrorUpdateBuilder&
setMPTokenIssuanceID(std::decay_t<typename SF_UINT192::type::value_type> const& value)
{
object_[sfMPTokenIssuanceID] = value;
return *this;
}
/**
* @brief Set sfHolder (SoeOptional)
* @return Reference to this builder for method chaining.
*/
ConfidentialMPTMirrorUpdateBuilder&
setHolder(std::decay_t<typename SF_ACCOUNT::type::value_type> const& value)
{
object_[sfHolder] = value;
return *this;
}
/**
* @brief Set sfIssuerEncryptedAmount (SoeOptional)
* @return Reference to this builder for method chaining.
*/
ConfidentialMPTMirrorUpdateBuilder&
setIssuerEncryptedAmount(std::decay_t<typename SF_VL::type::value_type> const& value)
{
object_[sfIssuerEncryptedAmount] = value;
return *this;
}
/**
* @brief Set sfAuditorEncryptedAmount (SoeOptional)
* @return Reference to this builder for method chaining.
*/
ConfidentialMPTMirrorUpdateBuilder&
setAuditorEncryptedAmount(std::decay_t<typename SF_VL::type::value_type> const& value)
{
object_[sfAuditorEncryptedAmount] = value;
return *this;
}
/**
* @brief Set sfZKProof (SoeRequired)
* @return Reference to this builder for method chaining.
*/
ConfidentialMPTMirrorUpdateBuilder&
setZKProof(std::decay_t<typename SF_VL::type::value_type> const& value)
{
object_[sfZKProof] = value;
return *this;
}
/**
* @brief Build and return the ConfidentialMPTMirrorUpdate wrapper.
* @param publicKey The public key for signing.
* @param secretKey The secret key for signing.
* @return The constructed transaction wrapper.
*/
ConfidentialMPTMirrorUpdate
build(PublicKey const& publicKey, SecretKey const& secretKey)
{
sign(publicKey, secretKey);
return ConfidentialMPTMirrorUpdate{std::make_shared<STTx>(std::move(object_))};
}
};
} // namespace xrpl::transactions

View File

@@ -14,6 +14,7 @@
#include <xrpl/shamap/SHAMapItem.h>
#include <xrpl/shamap/SHAMapLeafNode.h>
#include <xrpl/shamap/SHAMapMissingNode.h>
#include <xrpl/shamap/SHAMapNodeID.h>
#include <xrpl/shamap/SHAMapTreeNode.h>
#include <condition_variable>
@@ -34,7 +35,6 @@
namespace xrpl {
class SHAMapNodeID;
class SHAMapSyncFilter;
/**
@@ -420,7 +420,107 @@ public:
invariants() const;
private:
using SharedPtrNodeStack = std::stack<std::pair<SHAMapTreeNodePtr, SHAMapNodeID>>;
/**
* A path from the root of the map down to some node, pairing each node with the ID naming its
* position.
*
* The two halves of an entry must agree, and the only way to get that wrong is to compute an ID
* from the wrong branch. So this type does not accept an ID at all: every push takes the branch
* being descended and derives the ID itself, so a node and its ID cannot disagree. Reads are
* exposed through the same accessors a std::stack would offer.
*/
class NodePathStack
{
public:
[[nodiscard]] bool
empty() const
{
return stack_.empty();
}
[[nodiscard]] std::size_t
size() const
{
return stack_.size();
}
[[nodiscard]] std::pair<SHAMapTreeNodePtr, SHAMapNodeID> const&
top() const
{
XRPL_ASSERT(!stack_.empty(), "xrpl::SHAMap::NodePathStack::top : non-empty stack");
return stack_.top();
}
void
pop()
{
XRPL_ASSERT(!stack_.empty(), "xrpl::SHAMap::NodePathStack::pop : non-empty stack");
stack_.pop();
}
void
clear()
{
stack_ = {};
}
/**
* Start a path at the root of the map, whose ID is the zero-depth ID by definition.
*/
void
pushRoot(SHAMapTreeNodePtr node)
{
XRPL_ASSERT(stack_.empty(), "xrpl::SHAMap::NodePathStack::pushRoot : empty stack");
stack_.emplace(std::move(node), SHAMapNodeID{});
}
/**
* Extend the path to the child of the current node reached by `branch`.
*
* A node keeps the depth it was reached at, never a normalized kLeafDepth. Only a leaf may
* sit at kLeafDepth, since an inner node there would have no branch left to select.
*/
void
pushChild(SHAMapTreeNodePtr node, unsigned int branch)
{
XRPL_ASSERT(node, "xrpl::SHAMap::NodePathStack::pushChild : non-null node input");
XRPL_ASSERT(
!stack_.empty(), "xrpl::SHAMap::NodePathStack::pushChild : non-empty stack");
auto childID = stack_.top().second.getChildNodeID(branch);
XRPL_ASSERT_IF(
node->isInner(),
childID.getDepth() < kLeafDepth,
"xrpl::SHAMap::NodePathStack::pushChild : inner node above leaf depth");
XRPL_ASSERT_IF(
node->isLeaf(),
childID.isPrefixOf(leafKey(*node)),
"xrpl::SHAMap::NodePathStack::pushChild : leaf key below branch");
stack_.emplace(std::move(node), std::move(childID));
}
/**
* Extend the path to a node lying on the path to `target`.
*
* For nodes not reached by descending a known branch: the walk tracks only the key it is
* heading for, or the node is newly created. Either way `target` selects the branch.
*/
void
pushNode(SHAMapTreeNodePtr node, uint256 const& target)
{
if (stack_.empty())
{
pushRoot(std::move(node));
}
else
{
pushChild(std::move(node), selectBranch(stack_.top().second, target));
}
}
private:
std::stack<std::pair<SHAMapTreeNodePtr, SHAMapNodeID>> stack_;
};
using DeltaRef =
std::pair<boost::intrusive_ptr<SHAMapItem const>, boost::intrusive_ptr<SHAMapItem const>>;
@@ -447,7 +547,7 @@ private:
* Update hashes up to the root
*/
void
dirtyUp(SharedPtrNodeStack& stack, uint256 const& target, SHAMapTreeNodePtr terminal);
dirtyUp(NodePathStack& stack, uint256 const& target, SHAMapTreeNodePtr terminal);
/**
* Walk towards the specified id, returning the node. Caller must check
@@ -455,7 +555,7 @@ private:
* id
*/
SHAMapLeafNode*
walkTowardsKey(uint256 const& id, SharedPtrNodeStack* stack = nullptr) const;
walkTowardsKey(uint256 const& id, NodePathStack* stack = nullptr) const;
/**
* Return nullptr if key not found
*/
@@ -482,27 +582,15 @@ private:
SHAMapTreeNodePtr
writeNode(NodeObjectType t, SHAMapTreeNodePtr node) const;
// returns the first item at or below this node
SHAMapLeafNode*
firstBelow(SHAMapTreeNodePtr node, SharedPtrNodeStack& stack, unsigned int branch = 0u) const;
// returns the last item at or below this node
SHAMapLeafNode*
lastBelow(
SHAMapTreeNodePtr node,
SharedPtrNodeStack& stack,
unsigned int branch = kBranchFactor) const;
// direction in which belowHelper scans an inner node's branches
// direction in which a scan walks an inner node's branches
enum class BelowDirection { First, Last };
// helper function for firstBelow and lastBelow
/**
* Returns the first or last item at or below the node already on top of `stack`, extending
* `stack` with the path walked to reach it.
*/
SHAMapLeafNode*
belowHelper(
SHAMapTreeNodePtr node,
SharedPtrNodeStack& stack,
unsigned int branch,
BelowDirection direction) const;
belowHelper(NodePathStack& stack, BelowDirection direction) const;
// Simple descent
// Get a child of the specified node
@@ -550,9 +638,9 @@ private:
hasLeafNode(uint256 const& tag, SHAMapHash const& hash) const;
SHAMapLeafNode const*
peekFirstItem(SharedPtrNodeStack& stack) const;
peekFirstItem(NodePathStack& stack) const;
SHAMapLeafNode const*
peekNextItem(uint256 const& id, SharedPtrNodeStack& stack) const;
peekNextItem(uint256 const& id, NodePathStack& stack) const;
bool
walkBranch(
SHAMapTreeNode* node,
@@ -697,7 +785,7 @@ public:
using pointer = value_type const*;
private:
SharedPtrNodeStack stack_;
NodePathStack stack_;
SHAMap const* map_ = nullptr;
pointer item_ = nullptr;
@@ -723,7 +811,7 @@ public:
private:
explicit ConstIterator(SHAMap const* map);
ConstIterator(SHAMap const* map, std::nullptr_t);
ConstIterator(SHAMap const* map, pointer item, SharedPtrNodeStack&& stack);
ConstIterator(SHAMap const* map, pointer item, NodePathStack&& stack);
friend bool
operator==(ConstIterator const& x, ConstIterator const& y);
@@ -742,10 +830,7 @@ inline SHAMap::ConstIterator::ConstIterator(SHAMap const* map, std::nullptr_t) :
{
}
inline SHAMap::ConstIterator::ConstIterator(
SHAMap const* map,
pointer item,
SharedPtrNodeStack&& stack)
inline SHAMap::ConstIterator::ConstIterator(SHAMap const* map, pointer item, NodePathStack&& stack)
: stack_(std::move(stack)), map_(map), item_(item)
{
}

View File

@@ -8,6 +8,7 @@
#include <xrpl/ledger/ApplyViewImpl.h>
#include <xrpl/ledger/OpenView.h>
#include <xrpl/ledger/RawView.h>
#include <xrpl/protocol/Book.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
@@ -129,6 +130,14 @@ public:
view_->rawDestroyXRP(fee);
}
/**
* Registers a newly-created order book directory with the shared,
* process-wide OrderBookDB, unless this transaction is being applied
* under TapDryRun.
*/
void
addOrderBook(Book const& book);
ApplyViewContext
getApplyViewContext()
{

View File

@@ -12,6 +12,7 @@
#include <xrpl/protocol/XRPAmount.h>
#include <cstdint>
#include <expected>
#include <optional>
#include <utility>
@@ -393,16 +394,21 @@ preclaim(PreflightResult const& preflightResult, ServiceRegistry& registry, Open
*
* No validation is done or implied by this function.
*
* Caller is responsible for handling any exceptions.
* Since none should be thrown, that will usually
* mean terminating.
*
* Callers do not expect this function to throw; exceptions from a transactor's
* `calculateBaseFee` are caught and reported as an error instead.
* @param view The current open ledger.
* @param tx The transaction to be checked.
*
* @return The base fee.
* @return The base fee on success. Returns `std::unexpected(temUNKNOWN)` if the transaction
* type is not recognized, and `std::unexpected(tefEXCEPTION)` if the transactor's
* `calculateBaseFee` threw.
*
* @note Failure is reported as an error rather than a fee of zero because a
* zero (or default) fee would pass checkFee and let the transaction be
* applied for less than it owes. Callers that only need a fee hint may fall
* back to a default; callers deciding whether to apply should reject.
*/
XRPAmount
[[nodiscard]] std::expected<XRPAmount, TER>
calculateBaseFee(ReadView const& view, STTx const& tx);
/**

View File

@@ -38,9 +38,11 @@ namespace xrpl {
* f. A Loan must reference a live `ltLOAN_BROKER`, and that broker must
* reference a live `ltVAULT`.
* g. Post-conditions for the Loan paid down by a successful `ttLOAN_PAY`:
* `PaymentRemaining > 0` after: `PrincipalOutstanding` and
* `PaymentRemaining` strictly decrease; `NextPaymentDueDate`
* advances by N * `PaymentInterval`, N > 0.
* `PaymentRemaining > 0` after: neither `PrincipalOutstanding` nor
* `TotalValueOutstanding` increases, and at least one of them
* strictly decreases;
* `PaymentRemaining` strictly decreases;
* `NextPaymentDueDate` advances by N * `PaymentInterval`, N > 0.
* `PaymentRemaining == 0` after: pinned by checks 1 and 5b.
*
*/

View File

@@ -0,0 +1,98 @@
#pragma once
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/XRPAmount.h>
#include <xrpl/tx/ApplyContext.h>
#include <xrpl/tx/Transactor.h>
namespace xrpl {
/**
* @brief Updates the encrypted mirror balances of a Confidential MPToken.
*
* @details
* This transaction updates a single holder's mirrored confidential balances
* (`sfIssuerEncryptedBalance` and/or `sfAuditorEncryptedBalance`) with the latest
* ElGamal public keys defined on the `MPTokenIssuance`.
*
* It supports both issuer and holder self-migration modes, each mode supports multiple flows:
* - Issuer mode: Submitted by the issuer.
* 1. Issuer Key Rotation Migration: Re-encrypts the
* holder's `sfIssuerEncryptedBalance` under the issuer's new ElGamal public key.
*
* 2. Auditor Key Rotation Migration: Re-encrypts the
* holder's `sfAuditorEncryptedBalance` under the auditor's new ElGamal public key.
*
* 3. Simultaneous Rotation Migration: Updates both the issuer
* and auditor encrypted balances in a single transaction to optimize network throughput.
*
* 4. Auditor Late-Registration Migration: When the issuer ElGamal
* public key is already registered on the `MPTokenIssuance` object, the issuer can
* register an auditor key at a later time through `MPTokenIssuanceSet`. Then the issuer uses this
* flow to set the holder's initial `sfAuditorEncryptedBalance` on `MPToken` object.
*
* - Holder self-migration mode: Submitted by the holder. The holder decrypts their own
* `sfConfidentialBalanceSpending` with holder's private key to recover the balance and
* re-encrypts it under the relevant new ElGamal public key(s). This mode is always
* available to the holder and is not conditioned on the issuer being unable to migrate
* them: the ledger cannot verify whether an issuer has really lost its private key. That
* loss is only the expected motivation, since an issuer that still holds its key can
* migrate holders itself in issuer mode.
* @note All holder migration flows strictly require the holder's
* `sfConfidentialBalanceInbox` to be canonically zero; the holder must run
* `ConfidentialMPTMergeInbox` first so the spending balance reflects the
* full balance.
*
* 5. Holder Issuer-Mirror Migration: Re-encrypts the holder's
* `sfIssuerEncryptedBalance` under the issuer's new ElGamal public key.
*
* 6. Holder Auditor-Mirror Migration: Re-encrypts the holder's
* `sfAuditorEncryptedBalance` under the auditor's new ElGamal public key, or
* sets it for the first time when the auditor key was late-registered. This is the
* holder-driven counterpart to flows 2 and 4, for when the issuer does not migrate
* the holder itself.
*
* 7. Simultaneous Holder Self-Migration: Updates both the issuer and auditor
* encrypted balances in a single transaction (both keys have rotated).
*/
class ConfidentialMPTMirrorUpdate : public Transactor
{
public:
static constexpr auto kConsequencesFactory = ConsequencesFactoryType::Normal;
explicit ConfidentialMPTMirrorUpdate(ApplyContext& ctx) : Transactor(ctx)
{
}
static bool
checkExtraFeatures(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);
static XRPAmount
calculateBaseFee(ReadView const& view, STTx const& tx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
void
visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override;
[[nodiscard]] bool
finalizeInvariants(
STTx const& tx,
TER result,
XRPAmount fee,
ReadView const& view,
beast::Journal const& j) override;
};
} // namespace xrpl

View File

@@ -15,7 +15,8 @@ package/
publish_pkg.py Uploads built packages to the XRPLF Nexus repositories (called by CI, and shipped in that image)
rpm/
xrpld.spec RPM spec
debian/ Debian control files (control, rules, copyright, xrpld.docs, xrpld.links, xrpld.lintian-overrides, source/format)
debian/ Debian control files (control.in, lintian-overrides.in, rules, copyright, docs, links, source/format).
The `.in` files are templates rendered by `build_pkg.py`; `docs` and `links` are staged under the package name
shared/
xrpld.service systemd unit file (used by both RPM and DEB)
xrpld.sysusers sysusers.d config (used by both RPM and DEB)
@@ -32,20 +33,74 @@ packaging job cannot drift apart. Today only `linux/amd64` is emitted. The map
pins the full container image in `image` — edit that field to move to a new
image and both CI and local builds pick it up — and names the format that image
builds in `type`, which CI passes to `build_pkg.py` as `--package-type`; the two
have to stay in step.
have to stay in step. An optional `variant` names a flavour of the package (see
[Package variants](#package-variants)), and CI passes it as `--variant`.
| Package type | Image (`configs.<distro>[].package.image` in `linux.json`) | Tools required |
| ------------ | ---------------------------------------------------------- | -------------------------------------------------------------- |
| RPM | `ghcr.io/xrplf/xrpld/packaging-rhel:sha-<sha>` | `rpmbuild`, `rpmsign` |
| DEB | `ghcr.io/xrplf/xrpld/packaging-debian:sha-<sha>` | `dpkg-buildpackage`, debhelper with compat level 13, `lintian` |
To print the full packaging matrix (artifact names and images) for the current
`linux.json`:
To print the full packaging matrix (artifact names, images and package names)
for the current `linux.json`:
```bash
./.github/scripts/strategy-matrix/generate.py --packaging
```
## Package variants
A config whose binaries are not the plain release build cannot be packaged as
`xrpld`: both would carry the same name and version, so whichever published last
would win. It is packaged as a **variant** instead — `variant: "assert"` in its
`package` map, which CI passes to `build_pkg.py` as `--variant assert`,
producing `xrpld-assert`. What the build option itself does is a build concern,
not a packaging one; see the options table in [`BUILD.md`](../BUILD.md).
A variant ships the same paths as `xrpld``/usr/bin/xrpld`, `/etc/xrpld`,
`xrpld.service`, `/etc/logrotate.d/xrpld` — differing only in the per-package
documentation directory, so it declares itself a stand-in for the plain package
rather than something installable next to it: `Conflicts`, `Replaces` and a
versioned `Provides: xrpld` on Debian, `Conflicts` and `Provides` on RPM.
Neither format declares `Obsoletes`, so `apt upgrade` and `dnf upgrade` keep an
installed flavour on its own flavour, and switching is always explicit:
```bash
apt-get install xrpld-assert # apt removes the plain package itself
dnf swap xrpld xrpld-VARIANT # 'dnf install' alone stops at the conflict
```
Only the DEB packages carry a variant today — `xrpld-assert` comes from the
`debian` config alone, there being no call for an assert build on RHEL-based
distributions — but the RPM side works the same way if one is added.
A switch is a removal plus an installation rather than an upgrade, so unlike a
version upgrade it stops the service: Debian's scriptlets start it again, while
on RPM the operator runs `systemctl start xrpld`. Configuration survives either
way, being conffiles on Debian and `%config(noreplace)` on RPM.
`dnf` installs the replacement before erasing the old flavour, whose `%preun`
would leave `xrpld.service` disabled, so `%postun` re-applies the preset when
the unit file outlives the erase — which, since rpm keeps a file another
installed package owns, happens only during a swap. The cost is that a
deliberate `systemctl disable` is not carried across an RPM switch.
The alternative is an `xrpld-common` package owning the unit, the sysusers and
tmpfiles snippets and the configuration, required by both flavours at an exact
version: nothing is erased mid-swap, so no scriptlet has to detect one. It is
not worth it for a single variant — it moves files out of the production
package, and a sanitizer flavour would likely need its own unit anyway, putting
the lifecycle back where it is now.
Adding a variant is the flavour in `VARIANTS` in `build_pkg.py`, which is the
list `--variant` accepts, plus a config in `linux.json` with the CMake arguments
and a `package` map naming it, for one format or for both: `generate.py
--packaging` emits the package names per format, and the `test-install-deb` and
`test-install-rpm` jobs install what their own format produced.
Operators switch between the flavours as described in
[`docs/install.md`](../docs/install.md#optional-the-assert-enabled-build).
## Building packages
### Via CI
@@ -56,9 +111,11 @@ Caller workflows (`on-pr.yml`, `on-tag.yml`, `on-trigger.yml`) call
1. `package` fans out one job per config carrying a `package` map, building and
signing in that config's container, and uploading `<config>-pkg` alongside
`<config>-pkg-debug` for the much larger debug symbols.
2. `test-install` installs `<config>-pkg` in the container of every distro the
packages target and runs the binaries there, so one that cannot be installed
never reaches Nexus.
2. `test-install-deb` and `test-install-rpm` call
[`reusable-package-test-install.yml`](../.github/workflows/reusable-package-test-install.yml)
with their format's package names and distro images, installing each package
in the container of every distro that format targets and running the binaries
there, so one that cannot be installed never reaches Nexus.
3. `publish` uploads both artifacts, or lists what it would upload.
The packaging script derives the package version from the downloaded binary's
@@ -104,6 +161,9 @@ docker run --rm \
# build/rpmbuild/RPMS/x86_64/*.rpm
```
Add `--variant assert` to package binaries built with `-Dassert=ON`; the package
is then named `xrpld-assert`.
### Via CMake (host-side target)
If you run CMake configure on a host that has `rpmbuild` or `dpkg-buildpackage`
@@ -133,11 +193,17 @@ The package version is not a CMake input on this path: `build_pkg.py` derives it
from the just-built `xrpld` binary's `xrpld --version` output. The package
release defaults to 1 and is overridable with `-Dpkg_release=N`.
`-Dassert=ON` passes `--variant assert`, so such a build packages as
`xrpld-assert` without anything else being asked for.
## Publishing packages
Packages are published to the XRPLF repositories on Sonatype Nexus at
`https://packages.xrplf.org`. The `release-info` action decides the channel from
the event, and `publish_pkg.py` maps that channel to its repositories:
Packages are published to the XRPLF repositories on Sonatype Nexus through
`https://packages-upload.xrplf.org`. Reads go through
`https://packages.xrplf.org`, which Cloudflare proxies to cache them and which
rejects request bodies over 100 MB, so uploads use the DNS-only host instead.
The `release-info` action decides the channel from the event, and
`publish_pkg.py` maps that channel to its repositories:
| Event | Version | Channel | DEB repository | RPM upload repository |
| ------------------------ | ----------------- | --------- | -------------- | --------------------- |
@@ -147,6 +213,9 @@ the event, and `publish_pkg.py` maps that channel to its repositories:
| push to `develop` | `xrpld --version` | `develop` | `deb-develop` | `rpm-develop-hosted` |
| tag, non-public codebase | _any_ | `private` | `deb-private` | `rpm-private-hosted` |
A variant is published to the same channel under its own name, so
`xrpld-assert` never overwrites `xrpld`.
Only a tag names a channel — do not extend that to `develop`, where
`BuildInfo.cpp`'s `versionString` moves through `-bN`, `-rcN` and even the final
version during a release cycle, which would send develop builds into `stable`.
@@ -160,7 +229,7 @@ the last, and the date and hash say which commit a package on
`packages.xrplf.org` came from. Both reach the packaging scripts as arguments,
so neither script derives anything itself.
Publishing is its own job, gated behind `test-install`, uploading from the same
Publishing is its own job, gated behind the install tests, uploading from the same
image that built the packages with the `publish_pkg.py` shipped in it — the
same copy other repositories run. Without `publish: true` the job is a
`--dry-run`, listing the uploads it would make without needing credentials, so
@@ -175,7 +244,7 @@ Nexus owns the repository metadata; nothing here indexes anything. Worth knowing
- Each apt-hosted repository needs a distribution (ours use `any`) and a PGP
signing keypair configured in Nexus, which rejects one created without a
keypair. Nexus signs the apt metadata with it, never the packages.
- Hosted yum repositories cannot be signed by Nexus, so each `rpm-<channel>-hosted`
- yum-hosted repositories cannot be signed by Nexus, so each `rpm-<channel>-hosted`
repository sits behind a `rpm-<channel>` yum group repository whose metadata
Nexus signs. Uploads go to the hosted repository; clients point at the group
and verify the metadata with `repo_gpgcheck=1`. Nexus never signs the RPMs
@@ -244,6 +313,19 @@ pre-release ordering convention, so RPM filenames/NVRs begin with forms like
`xrpld-3.2.0~b1-...` and `xrpld-3.2.0~rc1-...` instead of encoding
pre-releases with an older `0.<release>.<suffix>` RPM `Release` value.
`--variant` is the flavour of the package, empty by default and accepting only
the flavours in `VARIANTS`; see [Package variants](#package-variants). The RPM
path passes it to the spec as the `pkg_variant` macro, which suffixes `Name` and
adds the `Conflicts`/`Provides` pair. Debian control files have no conditionals, so the DEB path renders
`debian/control.in` and `debian/lintian-overrides.in` instead, substituting
`@PKG@` with the package name and `@VARIANT_FIELDS@` with the
`Conflicts`/`Replaces`/`Provides` block, empty for the plain package; a token
with no value fails the build rather than reaching dpkg. The files debhelper
keys by package name (`docs`, `links`, and the units) are staged under that same
name. The paths inside the package are unchanged either way, so `debian/rules`
reads its package name from `dh_listpackages` and names the unit, sysusers,
tmpfiles and logrotate files with `--name xrpld`.
The package format is `--package-type`, either `deb` or `rpm`. It is required,
so a job never silently builds the wrong format for the image it runs in; the
matching build tool still has to be on PATH.
@@ -286,8 +368,13 @@ service restart.
1. Creates a staging source tree at `debbuild/source/` inside the build directory.
2. Stages the binaries, configs, `README.md`, `LICENSE.md`, and
`validator-keys-LICENSE`.
3. Copies `package/debian/` control files into `debbuild/source/debian/`.
4. Copies shared service/sysusers/tmpfiles/logrotate into `debian/` where `dh_installsystemd`, `dh_installsysusers`, `dh_installtmpfiles` and `dh_installlogrotate` pick them up automatically.
3. Stages `package/debian/` into `debbuild/source/debian/`: the `.in` templates
are rendered, and the files debhelper keys by package name (`docs`, `links`,
`lintian-overrides`) are staged under the name being built.
4. Copies shared service/sysusers/tmpfiles/logrotate into `debian/` as
`<package>.xrpld.*`, which `dh_installsystemd`, `dh_installsysusers`,
`dh_installtmpfiles` and `dh_installlogrotate` read because `debian/rules`
passes them `--name xrpld`.
5. Generates a minimal `debian/changelog` using `${pkg_version}-${PKG_RELEASE}`,
where `pkg_version` is derived from the binary-reported `xrpld` version.
6. Runs `dpkg-buildpackage -b --no-sign -d` (`-d` skips the build-dependency check, since the binary is already built). `debian/rules` uses manual `install` commands.

View File

@@ -21,6 +21,14 @@ SRC_DIR = Path(__file__).resolve().parents[1]
PRE_RELEASE = re.compile(r"^(b|rc)(0|[1-9][0-9]*)(\+.*)?$")
# The package name a variant suffixes, and the name every variant keeps for its
# on-disk paths (/usr/bin/xrpld, /etc/xrpld, xrpld.service).
BASE_NAME = "xrpld"
# The flavours that can be built, '' being the plain xrpld package. A variant
# needs a config in linux.json to be built by CI; see package/README.md.
VARIANTS = ("", "assert")
# Files both packaging systems consume, staged under the same names.
STAGED_FROM_BUILD = ("xrpld", "validator-keys", "validator-keys-LICENSE")
STAGED_FROM_SRC = {
@@ -31,6 +39,18 @@ STAGED_FROM_SRC = {
}
STAGED_UNITS = ("xrpld.service", "xrpld.sysusers", "xrpld.tmpfiles", "xrpld.logrotate")
# debian/ files debhelper keys by package name, staged as '<package>.<name>'.
DEBIAN_PKG_FILES = ("docs", "links")
# Debian control files have no conditionals, so what makes a variant replace the
# plain package is rendered into control.in rather than written there.
DEB_VARIANT_FIELDS = """\
Conflicts: xrpld
Replaces: xrpld
Provides: xrpld (= ${binary:Version})"""
TOKEN = re.compile(r"@[A-Z_]+@")
def run(*command: object, cwd: Path | None = None) -> None:
"""Echo a command and run it."""
@@ -75,6 +95,28 @@ def package_version(reported: str) -> str:
return version
def render(template: Path, dest: Path, values: dict[str, str]) -> None:
"""Write template to dest with its @TOKEN@ placeholders substituted.
A token left without a value fails the build rather than reaching dpkg.
"""
text = template.read_text()
for token, value in values.items():
text = text.replace(f"@{token}@", value)
missing = sorted(set(TOKEN.findall(text)))
assert not missing, f"{template}: no value for {', '.join(missing)}"
# An empty value at the end of a stanza would otherwise leave a blank line,
# which is what ends a stanza.
dest.write_text(text.rstrip("\n") + "\n")
def package_name(variant: str) -> str:
"""The binary package name for a variant: '' -> xrpld, 'assert' -> xrpld-assert."""
return f"{BASE_NAME}-{variant}" if variant else BASE_NAME
def read_version(xrpld: Path) -> str:
"""Read the version from the binary that is about to be packaged."""
fields = capture(xrpld, "--version").partition("\n")[0].split()
@@ -135,17 +177,18 @@ def stage_common(build_dir: Path, dest: Path) -> None:
shutil.copy2(SRC_DIR / source, dest / name)
def stage_units(dest: Path) -> None:
def stage_units(dest: Path, *, prefix: str = "") -> None:
"""Copy the systemd, sysusers, tmpfiles and logrotate files into dest.
Each format wants them somewhere else: rpmbuild reads them from SOURCES,
debhelper from debian/.
Each format wants them somewhere else: rpmbuild reads them from SOURCES by
path, debhelper from debian/ by package name -- hence 'prefix', which makes
the copies 'xrpld-assert.xrpld.service' and so on.
"""
for name in STAGED_UNITS:
shutil.copy2(SRC_DIR / "package" / "shared" / name, dest / name)
shutil.copy2(SRC_DIR / "package" / "shared" / name, dest / f"{prefix}{name}")
def build_rpm(build_dir: Path, *, version: str, pkg_release: str) -> None:
def build_rpm(build_dir: Path, *, version: str, pkg_release: str, variant: str) -> None:
"""Stage the spec and its sources, then build the binary RPMs."""
topdir = build_dir / "rpmbuild"
for name in ("BUILD", "BUILDROOT", "RPMS", "SOURCES", "SPECS", "SRPMS"):
@@ -156,6 +199,9 @@ def build_rpm(build_dir: Path, *, version: str, pkg_release: str) -> None:
stage_common(build_dir, topdir / "SOURCES")
stage_units(topdir / "SOURCES")
# The spec defaults it to nothing, so a plain build is unchanged.
variant_defines = ["--define", f"pkg_variant {variant}"] if variant else []
run(
"rpmbuild",
"-bb",
@@ -168,10 +214,29 @@ def build_rpm(build_dir: Path, *, version: str, pkg_release: str) -> None:
# The image tracks the newest distro, but the packages target el9.
"--define",
"dist .el9",
*variant_defines,
spec,
)
def stage_debian(dest: Path, name: str) -> None:
"""Stage the debian directory for the package name being built."""
source = SRC_DIR / "package" / "debian"
shutil.copytree(
source, dest, ignore=shutil.ignore_patterns("*.in", *DEBIAN_PKG_FILES)
)
values = {
"PKG": name,
"VARIANT_FIELDS": "" if name == BASE_NAME else DEB_VARIANT_FIELDS,
}
render(source / "control.in", dest / "control", values)
render(source / "lintian-overrides.in", dest / f"{name}.lintian-overrides", values)
for suffix in DEBIAN_PKG_FILES:
shutil.copy2(source / suffix, dest / f"{name}.{suffix}")
def build_deb(
build_dir: Path,
*,
@@ -180,21 +245,23 @@ def build_deb(
pkg_release: str,
channel: str,
epoch: int,
name: str,
) -> None:
"""Stage the debian directory and its sources, then build the binary DEBs."""
staging = build_dir / "debbuild" / "source"
stage_common(build_dir, staging)
shutil.copytree(SRC_DIR / "package" / "debian", staging / "debian")
stage_debian(staging / "debian", name)
# debhelper picks these up from debian/ automatically.
stage_units(staging / "debian")
# Prefixed whether it is a variant's name or not: debian/rules names them
# explicitly either way.
stage_units(staging / "debian", prefix=f"{name}.")
date = datetime.fromtimestamp(epoch, timezone.utc).strftime(
"%a, %d %b %Y %H:%M:%S %z"
)
# The leading spaces are significant to dpkg.
changelog = textwrap.dedent(f"""\
xrpld ({version}-{pkg_release}) {channel}; urgency=medium
{name} ({version}-{pkg_release}) {channel}; urgency=medium
* Release {reported}.
-- XRPL Foundation <contact@xrplf.org> {date}
@@ -223,6 +290,14 @@ def main() -> None:
default="1",
help="package release iteration (default: %(default)s)",
)
parser.add_argument(
"--variant",
default="",
choices=VARIANTS,
help="the flavour of the package to build: 'assert' produces "
"xrpld-assert, which ships the same paths as xrpld and replaces it "
"(default: the plain xrpld package)",
)
parser.add_argument(
"--channel",
required=True,
@@ -234,6 +309,8 @@ def main() -> None:
build_dir: Path = args.build_dir.resolve()
pkg_release: str = args.pkg_release
channel: str = args.channel
variant: str = args.variant
name = package_name(variant)
assert build_dir.is_dir(), (
f"build directory not found: {build_dir}. Build the binaries before "
@@ -253,6 +330,8 @@ def main() -> None:
for tree in ("debbuild", "rpmbuild"):
shutil.rmtree(build_dir / tree, ignore_errors=True)
print(f"Building {package_type} {name} {version}-{pkg_release}", flush=True)
if package_type == "deb":
build_deb(
build_dir,
@@ -261,9 +340,10 @@ def main() -> None:
pkg_release=pkg_release,
channel=channel,
epoch=epoch,
name=name,
)
else:
build_rpm(build_dir, version=version, pkg_release=pkg_release)
build_rpm(build_dir, version=version, pkg_release=pkg_release, variant=variant)
if __name__ == "__main__":

View File

@@ -1,4 +1,4 @@
Source: xrpld
Source: @PKG@
Section: net
Priority: optional
Maintainer: XRPL Foundation <contact@xrplf.org>
@@ -11,7 +11,7 @@ Homepage: https://github.com/XRPLF/rippled
Vcs-Git: https://github.com/XRPLF/rippled.git
Vcs-Browser: https://github.com/XRPLF/rippled
Package: xrpld
Package: @PKG@
Architecture: any
Depends:
${shlibs:Depends},
@@ -22,3 +22,4 @@ Description: XRP Ledger daemon
transactions, and maintains the ledger database.
This package also includes the validator-keys tool for validator key
management.
@VARIANT_FIELDS@

View File

@@ -0,0 +1,6 @@
# The /usr/local/bin/rippled symlink is deliberate compatibility for pre-FHS
# layouts, so the Policy 9.1.2 tags it raises are expected.
# TODO: remove alongside debian/links after rippled fully deprecated.
@PKG@: dir-in-usr-local [usr/local/bin/]
@PKG@: file-in-usr-local [usr/local/bin/rippled]
@PKG@: file-in-unusual-dir [usr/local/bin/rippled]

View File

@@ -8,33 +8,58 @@ export DH_VERBOSE = 1
# the binaries actually run on.
LIBC_MIN = 2.31
# The binary package's name, which a variant build changes to e.g. xrpld-assert,
# and the directory debhelper expects its files staged in.
PKG := $(firstword $(shell dh_listpackages))
PKG_DIR = debian/$(PKG)
# The base name, which every package ships under whatever it is called itself.
BASE_NAME = xrpld
# What build_pkg.py stages beside this directory, each installed under its own
# name. The binaries are also the ones checked against LIBC_MIN below.
BINARIES = $(BASE_NAME) validator-keys
CONFIGS = $(BASE_NAME).cfg validators.txt
%:
dh $@
override_dh_auto_configure override_dh_auto_build override_dh_auto_test:
@:
# The unit, sysusers, tmpfiles and logrotate files are named after the daemon
# rather than after the package, so a variant still ships xrpld.service and
# /etc/logrotate.d/xrpld. debhelper only reads debian/$(PKG).$(BASE_NAME).* when told
# the name.
override_dh_installsystemd:
dh_installsystemd --no-stop-on-upgrade xrpld.service
dh_installsystemd --no-stop-on-upgrade --name $(BASE_NAME)
# The tmpfiles snippet sets ownership to the xrpld user, so the sysusers snippet
# has to be emitted first: run it early and make its own sequence slot a no-op.
execute_before_dh_installtmpfiles:
dh_installsysusers
dh_installsysusers --name $(BASE_NAME)
override_dh_installsysusers:
override_dh_installtmpfiles:
dh_installtmpfiles --name $(BASE_NAME)
override_dh_installlogrotate:
dh_installlogrotate --name $(BASE_NAME)
override_dh_install:
install -D -m 0755 xrpld debian/xrpld/usr/bin/xrpld
install -D -m 0755 validator-keys debian/xrpld/usr/bin/validator-keys
install -D -m 0644 xrpld.cfg debian/xrpld/etc/xrpld/xrpld.cfg
install -D -m 0644 validators.txt debian/xrpld/etc/xrpld/validators.txt
for binary in $(BINARIES); do \
install -D -m 0755 "$$binary" "$(PKG_DIR)/usr/bin/$$binary"; \
done
for config in $(CONFIGS); do \
install -D -m 0644 "$$config" "$(PKG_DIR)/etc/$(BASE_NAME)/$$config"; \
done
override_dh_shlibdeps:
dh_shlibdeps
# Guards against the toolchain moving past LIBC_MIN and the packages then
# claiming a floor they do not meet.
for binary in xrpld validator-keys; do \
for binary in $(BINARIES); do \
needed=$$(readelf --dyn-syms --wide $$binary \
| grep -o 'GLIBC_[0-9.]*' | sed 's/GLIBC_//' | sort -uV | tail -1); \
if [ -z "$$needed" ]; then \
@@ -46,7 +71,7 @@ override_dh_shlibdeps:
exit 1; \
fi; \
done
sed -i 's/libc6 (>= [0-9.]*)/libc6 (>= $(LIBC_MIN))/' debian/xrpld.substvars
sed -i 's/libc6 (>= [0-9.]*)/libc6 (>= $(LIBC_MIN))/' debian/$(PKG).substvars
override_dh_dwz:
@:

View File

@@ -1,6 +0,0 @@
# The /usr/local/bin/rippled symlink is deliberate compatibility for pre-FHS
# layouts, so the Policy 9.1.2 tags it raises are expected.
# TODO: remove alongside debian/xrpld.links after rippled fully deprecated.
xrpld: dir-in-usr-local [usr/local/bin/]
xrpld: file-in-usr-local [usr/local/bin/rippled]
xrpld: file-in-unusual-dir [usr/local/bin/rippled]

View File

@@ -98,7 +98,7 @@ def main() -> None:
)
parser.add_argument(
"--nexus-url",
default="https://packages.xrplf.org",
default="https://packages-upload.xrplf.org",
help="the Nexus instance to publish to (default: %(default)s)",
)
parser.add_argument(

View File

@@ -6,10 +6,14 @@
%{error:pkg_release must be defined}
%endif
Name: xrpld
# The base name, which every package ships under. A variant build
# (build_pkg.py --variant) only suffixes the package name, e.g. xrpld-assert.
%global base_name xrpld
Name: %{base_name}%{?pkg_variant:-%{pkg_variant}}
Version: %{pkg_version}
Release: %{pkg_release}%{?dist}
Summary: XRP Ledger daemon
Summary: XRP Ledger daemon%{?pkg_variant: (%{pkg_variant} build)}
License: ISC
URL: https://github.com/XRPLF/rippled
@@ -17,6 +21,12 @@ URL: https://github.com/XRPLF/rippled
ExclusiveArch: x86_64 aarch64
BuildRequires: systemd-rpm-macros
# A variant owns the same paths, so it stands in for the plain package.
%if "%{?pkg_variant}" != ""
Conflicts: %{base_name}
Provides: %{base_name} = %{version}-%{release}
%endif
# These have to precede %%debug_package: it opens the debuginfo subpackage, and
# any tag after it is silently dropped from the main package.
%{?systemd_requires}
@@ -52,22 +62,22 @@ management.
:
%install
install -Dm0755 %{_sourcedir}/xrpld %{buildroot}%{_bindir}/%{name}
install -Dm0755 %{_sourcedir}/xrpld %{buildroot}%{_bindir}/%{base_name}
install -Dm0755 %{_sourcedir}/validator-keys %{buildroot}%{_bindir}/validator-keys
install -Dm0644 %{_sourcedir}/xrpld.cfg %{buildroot}%{_sysconfdir}/%{name}/xrpld.cfg
install -Dm0644 %{_sourcedir}/validators.txt %{buildroot}%{_sysconfdir}/%{name}/validators.txt
install -Dm0644 %{_sourcedir}/xrpld.cfg %{buildroot}%{_sysconfdir}/%{base_name}/xrpld.cfg
install -Dm0644 %{_sourcedir}/validators.txt %{buildroot}%{_sysconfdir}/%{base_name}/validators.txt
# systemd units, sysusers, tmpfiles, preset
install -Dm0644 %{_sourcedir}/xrpld.service %{buildroot}%{_unitdir}/xrpld.service
install -Dm0644 %{_sourcedir}/xrpld.sysusers %{buildroot}%{_sysusersdir}/xrpld.conf
install -Dm0644 %{_sourcedir}/xrpld.tmpfiles %{buildroot}%{_tmpfilesdir}/xrpld.conf
install -d %{buildroot}%{_presetdir}
cat >%{buildroot}%{_presetdir}/50-xrpld.preset <<'EOF'
cat >%{buildroot}%{_presetdir}/50-%{base_name}.preset <<'EOF'
enable xrpld.service
EOF
# Logrotate config
install -Dm0644 %{_sourcedir}/xrpld.logrotate %{buildroot}%{_sysconfdir}/logrotate.d/%{name}
install -Dm0644 %{_sourcedir}/xrpld.logrotate %{buildroot}%{_sysconfdir}/logrotate.d/%{base_name}
# Docs
install -Dm0644 %{_sourcedir}/LICENSE.md %{buildroot}%{_docdir}/%{name}/LICENSE.md
@@ -78,13 +88,13 @@ install -Dm0644 %{_sourcedir}/validator-keys-LICENSE %{buildroot}%{_docdir}/%{na
# Legacy compatibility for pre-FHS package layouts.
# TODO: remove after rippled fully deprecated.
install -d %{buildroot}/usr/local/bin
ln -s %{_bindir}/%{name} %{buildroot}/usr/local/bin/rippled
ln -s %{_bindir}/%{base_name} %{buildroot}/usr/local/bin/rippled
%pre
%sysusers_create_package %{name} %{_sourcedir}/xrpld.sysusers
%sysusers_create_package %{base_name} %{_sourcedir}/xrpld.sysusers
%post
%tmpfiles_create_package %{name} %{_sourcedir}/xrpld.tmpfiles
%tmpfiles_create_package %{base_name} %{_sourcedir}/xrpld.tmpfiles
%systemd_post xrpld.service
%preun
@@ -92,6 +102,13 @@ ln -s %{_bindir}/%{name} %{buildroot}/usr/local/bin/rippled
%postun
%systemd_postun xrpld.service
# A flavour swap installs the replacement before erasing this package, so the
# %%preun above has just disabled a unit the replacement still owns. rpm keeps a
# file that another installed package owns, so the unit outliving our own erase
# means exactly that; a plain erase takes it with us and re-presets nothing.
if [ $1 -eq 0 ] && [ -f %{_unitdir}/xrpld.service ]; then
systemctl preset xrpld.service >/dev/null 2>&1 || :
fi
%files
%attr(0755,root,root) %dir %{_docdir}/%{name}
@@ -99,18 +116,18 @@ ln -s %{_bindir}/%{name} %{buildroot}/usr/local/bin/rippled
%license %{_docdir}/%{name}/validator-keys-LICENSE
%doc %{_docdir}/%{name}/README.md
%attr(0755,root,root) %dir %{_sysconfdir}/%{name}
%attr(0755,root,root) %dir %{_sysconfdir}/%{base_name}
%{_bindir}/%{name}
%{_bindir}/%{base_name}
%{_bindir}/validator-keys
%config(noreplace) %{_sysconfdir}/%{name}/xrpld.cfg
%config(noreplace) %{_sysconfdir}/%{name}/validators.txt
%config(noreplace) %{_sysconfdir}/logrotate.d/%{name}
%config(noreplace) %{_sysconfdir}/%{base_name}/xrpld.cfg
%config(noreplace) %{_sysconfdir}/%{base_name}/validators.txt
%config(noreplace) %{_sysconfdir}/logrotate.d/%{base_name}
%{_unitdir}/xrpld.service
%attr(0644,root,root) %{_presetdir}/50-xrpld.preset
%attr(0644,root,root) %{_presetdir}/50-%{base_name}.preset
%{_sysusersdir}/xrpld.conf
%{_tmpfilesdir}/xrpld.conf
%ghost %dir /var/lib/xrpld

View File

@@ -179,7 +179,7 @@ isPaymentLate(ReadView const& view, SLE::const_ref loanSle)
: ExpiryComparison::Inclusive);
}
namespace accrual {
namespace instant_recognition {
AccountingDeltas
loanOriginationDeltas(Number const& principalRequested, Number const& interestDue)
@@ -217,7 +217,7 @@ loanPaymentDeltas(LoanPaymentParts const& parts)
.debtTotalDelta = (parts.principalPaid + parts.interestPaid) - parts.valueChange};
}
} // namespace accrual
} // namespace instant_recognition
namespace cash_basis {
@@ -250,8 +250,8 @@ namespace {
// Cash-basis accounting applies only when featureLendingProtocolV1_1 is
// enabled AND the specific Vault was created under it (LEVersion ==
// VaultVersion::CashBasis). Vaults created before activation keep accrual-basis
// accounting forever, even after the amendment later turns on.
// VaultVersion::CashBasis). Vaults created before activation keep instant
// interest recognition forever, even after the amendment later turns on.
bool
cashBasisEnabled(SLE::const_ref vaultSle)
{
@@ -268,7 +268,7 @@ loanOriginationDeltas(
{
return cashBasisEnabled(vaultSle)
? cash_basis::loanOriginationDeltas(principalRequested)
: accrual::loanOriginationDeltas(principalRequested, interestDue);
: instant_recognition::loanOriginationDeltas(principalRequested, interestDue);
}
bool
@@ -283,21 +283,22 @@ loanOriginationExceedsVaultMaximum(
return false;
auto const vaultMaximum = vaultSle->at(sfAssetsMaximum);
return accrual::loanOriginationExceedsVaultMaximum(vaultMaximum, vaultTotal, interestDue);
return instant_recognition::loanOriginationExceedsVaultMaximum(
vaultMaximum, vaultTotal, interestDue);
}
Number
loanVaultExposure(SLE::const_ref vaultSle, SLE::const_ref loanSle)
{
return cashBasisEnabled(vaultSle) ? cash_basis::loanVaultExposure(loanSle)
: accrual::loanVaultExposure(loanSle);
: instant_recognition::loanVaultExposure(loanSle);
}
AccountingDeltas
loanPaymentDeltas(SLE::const_ref vaultSle, LoanPaymentParts const& parts)
{
return cashBasisEnabled(vaultSle) ? cash_basis::loanPaymentDeltas(parts)
: accrual::loanPaymentDeltas(parts);
: instant_recognition::loanPaymentDeltas(parts);
}
namespace detail {

View File

@@ -107,8 +107,12 @@ Config::makeConfig(
else
{
config.outPeers = *limits.outPeers;
config.inPeers = *limits.inPeers;
config.maxPeers = 0;
// Inbound slots only exist if we accept incoming connections, and
// `maxPeers` is the total across both directions. The legacy branch
// above upholds the same two invariants.
config.inPeers = config.wantIncoming ? *limits.inPeers : 0;
config.maxPeers = config.inPeers + config.outPeers;
}
// This will cause servers configured as validators to request that

View File

@@ -23,7 +23,7 @@ namespace {
//------------------------------------------------------------------------------
// clang-format off
// NOLINTNEXTLINE(readability-identifier-naming)
char const* const versionString = "3.4.0-rc1"
char const* const versionString = "3.5.0-b0"
// clang-format on
;

View File

@@ -6,9 +6,11 @@
#include <xrpl/basics/contract.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/Protocol.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STBlob.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/STObject.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/UintTypes.h>
@@ -397,6 +399,79 @@ checkEncryptedAmountFormat(STObject const& object)
return tesSUCCESS;
}
bool
isIssuerMirrorCurrent(SLE const& issuance, SLE const& mptoken)
{
XRPL_ASSERT(
issuance.getType() == ltMPTOKEN_ISSUANCE,
"xrpl::isIssuerMirrorCurrent : issuance MPTokenIssuance object");
XRPL_ASSERT(
mptoken.getType() == ltMPTOKEN, "xrpl::isIssuerMirrorCurrent : mptoken MPToken object");
return mptoken.isFieldPresent(sfIssuerEncryptedBalance) &&
mptoken[~sfIssuerKeyMirrorEpoch].value_or(0) == issuance[~sfIssuerKeyEpoch].value_or(0);
}
bool
isAuditorMirrorCurrent(SLE const& issuance, SLE const& mptoken)
{
XRPL_ASSERT(
issuance.getType() == ltMPTOKEN_ISSUANCE,
"xrpl::isAuditorMirrorCurrent : issuance MPTokenIssuance object");
XRPL_ASSERT(
mptoken.getType() == ltMPTOKEN, "xrpl::isAuditorMirrorCurrent : mptoken MPToken object");
if (!issuance.isFieldPresent(sfAuditorEncryptionKey))
return true;
return mptoken.isFieldPresent(sfAuditorEncryptedBalance) &&
mptoken[~sfAuditorKeyMirrorEpoch].value_or(0) == issuance[~sfAuditorKeyEpoch].value_or(0);
}
bool
areMirrorsCurrent(SLE const& issuance, SLE const& mptoken)
{
return isIssuerMirrorCurrent(issuance, mptoken) && isAuditorMirrorCurrent(issuance, mptoken);
}
void
setIssuerMirrorEpoch(SLE const& issuance, SLE& mptoken)
{
XRPL_ASSERT(
issuance.getType() == ltMPTOKEN_ISSUANCE,
"xrpl::setIssuerMirrorEpoch : issuance MPTokenIssuance object");
XRPL_ASSERT(
mptoken.getType() == ltMPTOKEN, "xrpl::setIssuerMirrorEpoch : mptoken MPToken object");
// Unlike the auditor mirror, the issuer mirror is not optional: every
// confidential MPToken carries one, so there is no existence check here.
if (auto const epoch = issuance[~sfIssuerKeyEpoch].value_or(0); epoch != 0)
mptoken[sfIssuerKeyMirrorEpoch] = epoch;
}
void
setAuditorMirrorEpoch(SLE const& issuance, SLE& mptoken)
{
XRPL_ASSERT(
issuance.getType() == ltMPTOKEN_ISSUANCE,
"xrpl::setAuditorMirrorEpoch : issuance MPTokenIssuance object");
XRPL_ASSERT(
mptoken.getType() == ltMPTOKEN, "xrpl::setAuditorMirrorEpoch : mptoken MPToken object");
if (!mptoken.isFieldPresent(sfAuditorEncryptedBalance))
return;
if (auto const epoch = issuance[~sfAuditorKeyEpoch].value_or(0); epoch != 0)
mptoken[sfAuditorKeyMirrorEpoch] = epoch;
}
void
setMirrorEpochs(SLE const& issuance, SLE& mptoken)
{
setIssuerMirrorEpoch(issuance, mptoken);
setAuditorMirrorEpoch(issuance, mptoken);
}
TER
verifySchnorrProof(Slice const& pubKeySlice, Slice const& proofSlice, uint256 const& contextHash)
{

View File

@@ -1,6 +1,8 @@
#include <xrpl/protocol/STPathSet.h>
#include <xrpl/basics/CountedObject.h>
#include <xrpl/basics/Log.h>
#include <xrpl/basics/UnorderedContainers.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/basics/contract.h>
#include <xrpl/beast/hash/uhash.h>
@@ -11,10 +13,12 @@
#include <xrpl/protocol/STBase.h>
#include <xrpl/protocol/Serializer.h>
#include <xrpl/protocol/UintTypes.h>
#include <xrpl/protocol/detail/STVar.h>
#include <xrpl/protocol/jss.h>
#include <algorithm>
#include <cstddef>
#include <memory>
#include <stdexcept>
#include <utility>
#include <vector>
@@ -31,6 +35,11 @@ STPathElement::getHash(STPathElement const& element)
// NIKB NOTE: This doesn't have to be a secure hash as speed is more
// important. We don't even really need to fully hash the whole
// base_uint here, as a few bytes would do for our use.
//
// The note above is only true because the result of this function reaches
// nothing but STPathElement::operator==, where it is a fast-reject
// prefilter ahead of the field comparisons that decide the answer. Do not
// use it to key a container.
for (auto const x : element.getAccountID())
hashAccount += (hashAccount * 257) ^ x;
@@ -51,10 +60,49 @@ STPathElement::getHash(STPathElement const& element)
return (hashAccount ^ hashCurrency ^ hashIssuer);
}
[[nodiscard]] size_t
STPathElement::getHash() const
// For guidance on deciding which option to pursue:
// 1. Try to decrease the size of the STPathSet first. For instance, if a std::optional was
// injected into the type, could you get the same functionality using a std::unique_ptr instead?
// 2. If the size of the STPathSet is already as small as it can be, then consider what the cost
// of increasing STVar::kMaxSize would be on all the other STVar types. Each of those types
// will carry the additional cost of accommodating the larger STPathSet in their SBO.
// 3. If the cost of increasing STVar::kMaxSize is too high, then heap allocate the STPathSet and
// remove this static_assert.
static_assert(
sizeof(STPathSet) <= detail::STVar::kMaxSize,
"STPathSet is too large to fit in STVar's small object optimization. Please verify if it "
"should, if the kMaxSize should be increased, or if STPathSet should be stored on the heap "
"instead of in STVar.");
STPathSet::STPathSet(DeduplicationTag) : seen_{std::make_unique<hardened_hash_set<STPath>>()}
{
return STPathElement::getHash(*this);
}
STPathSet::STPathSet(STPathSet const& other)
: STBase{other}
, CountedObject<STPathSet>{other}
, value_{other.value_}
, seen_{
other.seen_ != nullptr ? std::make_unique<hardened_hash_set<STPath>>(*other.seen_)
: nullptr}
{
}
STPathSet&
STPathSet::operator=(STPathSet const& other)
{
if (this == &other)
{
return *this;
}
auto newSeen = other.seen_ != nullptr
? std::make_unique<hardened_hash_set<STPath>>(*other.seen_)
: nullptr;
STBase::operator=(other);
CountedObject<STPathSet>::operator=(other);
value_ = other.value_;
seen_ = std::move(newSeen);
return *this;
}
STPathSet::STPathSet(SerialIter& sit, SField const& name) : STBase(name)
@@ -72,7 +120,8 @@ STPathSet::STPathSet(SerialIter& sit, SField const& name) : STBase(name)
Throw<std::runtime_error>("empty path");
}
pushBack(path);
// Move rather than converting the vector to an STPath by copy.
value_.emplace_back(std::move(path));
path.clear();
if (iType == STPathElement::TypeNone)
@@ -132,16 +181,10 @@ STPathSet::move(std::size_t n, void* buf)
bool
STPathSet::assembleAdd(STPath const& base, STPathElement const& tail)
{ // assemble base+tail and add it to the set if it's not a duplicate
XRPL_ASSERT(seen_ != nullptr, "xrpl::STPathSet::assembleAdd : DeduplicationTag");
STPath combined = base;
combined.pushBack(tail);
if (!seenHashes_.insert(combined).second)
{
return false;
}
value_.push_back(std::move(combined));
return true;
return appendUnique([&](auto& value) { value.push_back(std::move(combined)); });
}
bool

View File

@@ -1,6 +1,7 @@
#include <xrpl/protocol/STValidation.h>
#include <xrpl/basics/Blob.h>
#include <xrpl/basics/Log.h>
#include <xrpl/basics/Slice.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/basics/chrono.h>
@@ -15,6 +16,7 @@
#include <xrpl/protocol/Serializer.h>
#include <cstddef>
#include <exception>
#include <utility>
namespace xrpl {
@@ -108,11 +110,42 @@ STValidation::isValid() const noexcept
publicKeyType(getSignerPublic()) == KeyType::Secp256k1,
"xrpl::STValidation::isValid : valid key type");
valid_ = verifyDigest(
getSignerPublic(),
getSigningHash(),
makeSlice(getFieldVL(sfSignature)),
(getFlags() & kVfFullyCanonicalSig) != 0u);
// Log that the signature was never checked, so an operator does not
// read this as a bad key. The log is guarded because it can throw too.
auto reportUncheckable = [this](char const* reason) noexcept {
try
{
JLOG(debugLog().error())
<< "Cannot check the signature of the validation for ledger " << getLedgerHash()
<< ": " << reason;
}
catch (...) // NOLINT(bugprone-empty-catch)
{
// Nothing can be reported when reporting is what failed.
}
};
// The signing hash re-serializes the fields, which can fail. This
// function is noexcept, so report the validation as invalid instead of
// throwing. valid_ stays unset, so a later call checks again.
try
{
valid_ = verifyDigest(
getSignerPublic(),
getSigningHash(),
makeSlice(getFieldVL(sfSignature)),
(getFlags() & kVfFullyCanonicalSig) != 0u);
}
catch (std::exception const& e)
{
reportUncheckable(e.what());
return false;
}
catch (...)
{
reportUncheckable("unknown exception");
return false;
}
}
return valid_.value();

View File

@@ -143,10 +143,10 @@ Serializer::addFieldID(int type, int name)
}
int
Serializer::add8(unsigned char byte)
Serializer::add8(unsigned char byteValue)
{
int const ret = data_.size();
data_.push_back(byte);
data_.push_back(byteValue);
return ret;
}
@@ -210,109 +210,138 @@ Serializer::addVL(void const* ptr, int len)
int
Serializer::addEncoded(int length)
{
std::array<std::uint8_t, 4> bytes{};
// Without this, a negative length would fall into the 1 byte case below and
// be cast to a first byte no header uses. A size too big for int arrives
// here negative as well, since callers pass sizes through this parameter.
if (length < kMinValueOfLengthFor1ByteHeader)
Throw<std::overflow_error>("addEncoded: length is negative or did not fit in an int");
std::array<std::byte, kMaxNumberOfBytesInHeader> bytes{};
int numBytes = 0;
if (length <= 192)
if (length <= kMaxValueOfLengthFor1ByteHeader)
{
bytes[0] = static_cast<unsigned char>(length);
bytes[0] = static_cast<std::byte>(length);
numBytes = 1;
}
else if (length <= 12480)
else if (length <= kMaxValueOfLengthFor2ByteHeader)
{
length -= 193;
bytes[0] = 193 + static_cast<unsigned char>(length >> 8);
bytes[1] = static_cast<unsigned char>(length & 0xff);
// Count from the smallest length a 2 byte header covers.
int const offset = length - kMinValueOfLengthFor2ByteHeader;
bytes[0] = static_cast<std::byte>(
kMinValueOfFirstByteFor2ByteHeader + (offset / kNumberOfValuesInOneByte));
bytes[1] = static_cast<std::byte>(offset % kNumberOfValuesInOneByte);
numBytes = 2;
}
else if (length <= 918744)
else if (length <= kMaxValueOfLengthFor3ByteHeader)
{
length -= 12481;
bytes[0] = 241 + static_cast<unsigned char>(length >> 16);
bytes[1] = static_cast<unsigned char>((length >> 8) & 0xff);
bytes[2] = static_cast<unsigned char>(length & 0xff);
int const offset = length - kMinValueOfLengthFor3ByteHeader;
bytes[0] = static_cast<std::byte>(
kMinValueOfFirstByteFor3ByteHeader + (offset / kNumberOfValuesInTwoBytes));
bytes[1] =
static_cast<std::byte>((offset / kNumberOfValuesInOneByte) % kNumberOfValuesInOneByte);
bytes[2] = static_cast<std::byte>(offset % kNumberOfValuesInOneByte);
numBytes = 3;
}
else
{
Throw<std::overflow_error>("lenlen");
Throw<std::overflow_error>("addEncoded: length is too large to encode");
}
return addRaw(&bytes[0], numBytes);
return addRaw(bytes.data(), numBytes);
}
int
Serializer::encodeLengthLength(int length)
{
if (length < 0)
Throw<std::overflow_error>("len<0");
if (length < kMinValueOfLengthFor1ByteHeader)
{
Throw<std::overflow_error>(
"encodeLengthLength: length is negative or did not fit in an int");
}
if (length <= 192)
if (length <= kMaxValueOfLengthFor1ByteHeader)
return 1;
if (length <= 12480)
if (length <= kMaxValueOfLengthFor2ByteHeader)
return 2;
if (length <= 918744)
if (length <= kMaxValueOfLengthFor3ByteHeader)
return 3;
Throw<std::overflow_error>("len>918744");
return 0; // Silence compiler warning.
Throw<std::overflow_error>("encodeLengthLength: length is too large to encode");
}
int
Serializer::decodeLengthLength(int b1)
Serializer::decodeLengthLength(std::byte firstByte)
{
if (b1 < 0)
Throw<std::overflow_error>("b1<0");
int const firstByteValue = std::to_integer<int>(firstByte);
if (b1 <= 192)
if (firstByteValue <= kMaxValueOfFirstByteFor1ByteHeader)
return 1;
if (b1 <= 240)
if (firstByteValue <= kMaxValueOfFirstByteFor2ByteHeader)
return 2;
if (b1 <= 254)
if (firstByteValue <= kMaxValueOfFirstByteFor3ByteHeader)
return 3;
Throw<std::overflow_error>("b1>254");
return 0; // Silence compiler warning.
Throw<std::overflow_error>("decodeLengthLength: first byte does not start any header");
}
int
Serializer::decodeVLLength(int b1)
Serializer::decodeVLLength(std::byte firstByte)
{
if (b1 < 0)
Throw<std::overflow_error>("b1<0");
int const length = std::to_integer<int>(firstByte);
if (b1 > 254)
Throw<std::overflow_error>("b1>254");
// A bigger value means a longer header, so it is not a length by itself.
if (length > kMaxValueOfLengthFor1ByteHeader)
Throw<std::overflow_error>("decodeVLLength 1 byte: first byte is not a length");
return b1;
return length;
}
int
Serializer::decodeVLLength(int b1, int b2)
Serializer::decodeVLLength(std::byte firstByte, std::byte secondByte)
{
if (b1 < 193)
Throw<std::overflow_error>("b1<193");
int const firstByteValue = std::to_integer<int>(firstByte);
if (b1 > 240)
Throw<std::overflow_error>("b1>240");
if (firstByteValue < kMinValueOfFirstByteFor2ByteHeader)
Throw<std::overflow_error>("decodeVLLength 2 byte: first byte is below the range");
return 193 + ((b1 - 193) * 256) + b2;
if (firstByteValue > kMaxValueOfFirstByteFor2ByteHeader)
Throw<std::overflow_error>("decodeVLLength 2 byte: first byte is above the range");
// Both bytes are bounded by their own type, and the first one is bounded to
// the 2 byte range above, so this cannot leave the range the header covers.
return kMinValueOfLengthFor2ByteHeader +
((firstByteValue - kMinValueOfFirstByteFor2ByteHeader) * kNumberOfValuesInOneByte) +
std::to_integer<int>(secondByte);
}
int
Serializer::decodeVLLength(int b1, int b2, int b3)
Serializer::decodeVLLength(std::byte firstByte, std::byte secondByte, std::byte thirdByte)
{
if (b1 < 241)
Throw<std::overflow_error>("b1<241");
int const firstByteValue = std::to_integer<int>(firstByte);
if (b1 > 254)
Throw<std::overflow_error>("b1>254");
if (firstByteValue < kMinValueOfFirstByteFor3ByteHeader)
Throw<std::overflow_error>("decodeVLLength 3 byte: first byte is below the range");
return 12481 + ((b1 - 241) * 65536) + (b2 * 256) + b3;
if (firstByteValue > kMaxValueOfFirstByteFor3ByteHeader)
Throw<std::overflow_error>("decodeVLLength 3 byte: first byte is above the range");
int const length = kMinValueOfLengthFor3ByteHeader +
((firstByteValue - kMinValueOfFirstByteFor3ByteHeader) * kNumberOfValuesInTwoBytes) +
(std::to_integer<int>(secondByte) * kNumberOfValuesInOneByte) +
std::to_integer<int>(thirdByte);
// A 3 byte header reaches further than kMaxValueOfLengthFor3ByteHeader, which
// is as far as the encoder goes. Refuse the rest, so every length accepted
// here is one that can be written back.
if (length > kMaxValueOfLengthFor3ByteHeader)
Throw<std::overflow_error>("decodeVLLength 3 byte: length is too large to re-encode");
return length;
}
//------------------------------------------------------------------------------
@@ -471,24 +500,24 @@ SerialIter::getRaw(int size)
int
SerialIter::getVLDataLength()
{
int const b1 = get8();
std::byte const firstByte{get8()};
int datLen = 0;
int const lenLen = Serializer::decodeLengthLength(b1);
int const lenLen = Serializer::decodeLengthLength(firstByte);
if (lenLen == 1)
{
datLen = Serializer::decodeVLLength(b1);
datLen = Serializer::decodeVLLength(firstByte);
}
else if (lenLen == 2)
{
int const b2 = get8();
datLen = Serializer::decodeVLLength(b1, b2);
std::byte const secondByte{get8()};
datLen = Serializer::decodeVLLength(firstByte, secondByte);
}
else
{
XRPL_ASSERT(lenLen == 3, "xrpl::SerialIter::getVLDataLength : lenLen is 3");
int const b2 = get8();
int const b3 = get8();
datLen = Serializer::decodeVLLength(b1, b2, b3);
std::byte const secondByte{get8()};
std::byte const thirdByte{get8()};
datLen = Serializer::decodeVLLength(firstByte, secondByte, thirdByte);
}
return datLen;
}

View File

@@ -97,7 +97,7 @@ SHAMap::snapShot(bool isMutable) const
}
void
SHAMap::dirtyUp(SharedPtrNodeStack& stack, uint256 const& target, SHAMapTreeNodePtr child)
SHAMap::dirtyUp(NodePathStack& stack, uint256 const& target, SHAMapTreeNodePtr child)
{
// walk the tree up from through the inner nodes to the root_
// update hashes and links
@@ -126,29 +126,34 @@ SHAMap::dirtyUp(SharedPtrNodeStack& stack, uint256 const& target, SHAMapTreeNode
}
SHAMapLeafNode*
SHAMap::walkTowardsKey(uint256 const& id, SharedPtrNodeStack* stack) const
SHAMap::walkTowardsKey(uint256 const& id, NodePathStack* stack) const
{
XRPL_ASSERT(
stack == nullptr || stack->empty(), "xrpl::SHAMap::walkTowardsKey : empty stack input");
auto inNode = root_;
SHAMapNodeID nodeID;
// Every node on this walk lies on the path to `id`, so the stack can derive each ID from the
// branch `id` selects at the node above it.
auto pushCurrent = [&] {
if (stack != nullptr)
stack->pushNode(inNode, id);
};
while (inNode->isInner())
{
if (stack != nullptr)
stack->emplace(inNode, nodeID);
pushCurrent();
auto const inner = intr_ptr::staticPointerCast<SHAMapInnerNode>(inNode);
auto& inner = safeDowncast<SHAMapInnerNode&>(*inNode);
auto const branch = selectBranch(nodeID, id);
if (inner->isEmptyBranch(branch))
if (inner.isEmptyBranch(branch))
return nullptr;
inNode = descendThrow(*inner, branch);
inNode = descendThrow(inner, branch);
nodeID = nodeID.getChildNodeID(branch);
}
if (stack != nullptr)
stack->emplace(inNode, nodeID);
pushCurrent();
return safeDowncast<SHAMapLeafNode*>(inNode.get());
}
@@ -428,65 +433,40 @@ SHAMap::unshareNode(intr_ptr::SharedPtr<Node> node, SHAMapNodeID const& nodeID)
}
SHAMapLeafNode*
SHAMap::belowHelper(
SHAMapTreeNodePtr node,
SharedPtrNodeStack& stack,
unsigned int branch,
BelowDirection direction) const
SHAMap::belowHelper(NodePathStack& stack, BelowDirection direction) const
{
if (node->isLeaf())
{
auto n = intr_ptr::staticPointerCast<SHAMapLeafNode>(node);
stack.push({node, {kLeafDepth, n->peekItem()->key()}});
return n.get();
}
auto inner = intr_ptr::staticPointerCast<SHAMapInnerNode>(node);
if (stack.empty())
{
stack.emplace(inner, SHAMapNodeID{});
}
else
{
stack.emplace(inner, stack.top().second.getChildNodeID(branch));
}
// `scanned` counts how many branches of `inner` we have examined; the branch we look at is
// derived from it, so no index ever goes out of range.
XRPL_ASSERT(!stack.empty(), "xrpl::SHAMap::belowHelper : non-empty stack input");
if (auto const& top = stack.top().first; top->isLeaf())
return safeDowncast<SHAMapLeafNode*>(top.get());
// The stack owns the node/ID pairing, so descending is only ever "push the branch we took".
// `scanned` counts how many branches of the current node we have examined; the branch we look
// at is derived from it, so no index ever goes out of range. `inner` tracks the node on top of
// the stack, which keeps it alive, so it only needs recomputing after a push.
auto* inner = safeDowncast<SHAMapInnerNode*>(stack.top().first.get());
for (auto scanned = 0u; scanned < kBranchFactor;)
{
auto const childBranch =
(direction == BelowDirection::Last) ? (kBranchFactor - 1u - scanned) : scanned;
if (!inner->isEmptyBranch(childBranch))
{
node.adopt(descendThrow(inner.get(), childBranch));
XRPL_ASSERT(!stack.empty(), "xrpl::SHAMap::belowHelper : non-empty stack");
if (node->isLeaf())
{
auto n = intr_ptr::staticPointerCast<SHAMapLeafNode>(node);
stack.push({n, {kLeafDepth, n->peekItem()->key()}});
return n.get();
}
inner = intr_ptr::staticPointerCast<SHAMapInnerNode>(node);
stack.emplace(inner, stack.top().second.getChildNodeID(branch));
scanned = 0u; // descend and restart the scan on the new node
}
else
if (inner->isEmptyBranch(childBranch))
{
++scanned; // scan next branch
continue;
}
stack.pushChild(descendThrow(*inner, childBranch), childBranch);
auto const& child = stack.top().first;
if (child->isLeaf())
return safeDowncast<SHAMapLeafNode*>(child.get());
inner = safeDowncast<SHAMapInnerNode*>(child.get());
scanned = 0u; // descend and restart the scan on the new node
}
return nullptr;
}
SHAMapLeafNode*
SHAMap::lastBelow(SHAMapTreeNodePtr node, SharedPtrNodeStack& stack, unsigned int branch) const
{
return belowHelper(node, stack, branch, BelowDirection::Last);
}
SHAMapLeafNode*
SHAMap::firstBelow(SHAMapTreeNodePtr node, SharedPtrNodeStack& stack, unsigned int branch) const
{
return belowHelper(node, stack, branch, BelowDirection::First);
}
static boost::intrusive_ptr<SHAMapItem const> const kNoItem;
boost::intrusive_ptr<SHAMapItem const> const&
@@ -529,36 +509,36 @@ SHAMap::onlyBelow(SHAMapTreeNode* node) const
}
SHAMapLeafNode const*
SHAMap::peekFirstItem(SharedPtrNodeStack& stack) const
SHAMap::peekFirstItem(NodePathStack& stack) const
{
XRPL_ASSERT(stack.empty(), "xrpl::SHAMap::peekFirstItem : empty stack input");
SHAMapLeafNode const* node = firstBelow(root_, stack);
stack.pushRoot(root_);
SHAMapLeafNode const* node = belowHelper(stack, BelowDirection::First);
if (node == nullptr)
{
while (!stack.empty())
stack.pop();
stack.clear();
return nullptr;
}
return node;
}
SHAMapLeafNode const*
SHAMap::peekNextItem(uint256 const& id, SharedPtrNodeStack& stack) const
SHAMap::peekNextItem(uint256 const& id, NodePathStack& stack) const
{
XRPL_ASSERT(!stack.empty(), "xrpl::SHAMap::peekNextItem : non-empty stack input");
XRPL_ASSERT(stack.top().first->isLeaf(), "xrpl::SHAMap::peekNextItem : stack starts with leaf");
stack.pop();
while (!stack.empty())
{
auto [node, nodeID] = stack.top();
auto const [node, nodeID] = stack.top();
XRPL_ASSERT(!node->isLeaf(), "xrpl::SHAMap::peekNextItem : another node is not leaf");
auto inner = intr_ptr::staticPointerCast<SHAMapInnerNode>(node);
auto& inner = safeDowncast<SHAMapInnerNode&>(*node);
for (auto i = selectBranch(nodeID, id) + 1; i < kBranchFactor; ++i)
{
if (!inner->isEmptyBranch(i))
if (!inner.isEmptyBranch(i))
{
node = descendThrow(*inner, i);
auto leaf = firstBelow(node, stack, i);
stack.pushChild(descendThrow(inner, i), i);
auto leaf = belowHelper(stack, BelowDirection::First);
if (leaf == nullptr)
Throw<SHAMapMissingNode>(type_, id);
XRPL_ASSERT(leaf->isLeaf(), "xrpl::SHAMap::peekNextItem : leaf is valid");
@@ -597,11 +577,11 @@ SHAMap::peekItem(uint256 const& id, SHAMapHash& hash) const
SHAMap::ConstIterator
SHAMap::upperBound(uint256 const& id) const
{
SharedPtrNodeStack stack;
NodePathStack stack;
walkTowardsKey(id, &stack);
while (!stack.empty())
{
auto [node, nodeID] = stack.top();
auto const [node, nodeID] = stack.top();
if (node->isLeaf())
{
auto leaf = safeDowncast<SHAMapLeafNode*>(node.get());
@@ -610,13 +590,13 @@ SHAMap::upperBound(uint256 const& id) const
}
else
{
auto inner = intr_ptr::staticPointerCast<SHAMapInnerNode>(node);
auto& inner = safeDowncast<SHAMapInnerNode&>(*node);
for (auto branch = selectBranch(nodeID, id) + 1; branch < kBranchFactor; ++branch)
{
if (!inner->isEmptyBranch(branch))
if (!inner.isEmptyBranch(branch))
{
node = descendThrow(*inner, branch);
auto leaf = firstBelow(node, stack, branch);
stack.pushChild(descendThrow(inner, branch), branch);
auto leaf = belowHelper(stack, BelowDirection::First);
if (leaf == nullptr)
Throw<SHAMapMissingNode>(type_, id);
return ConstIterator(this, leaf->peekItem().get(), std::move(stack));
@@ -630,11 +610,11 @@ SHAMap::upperBound(uint256 const& id) const
SHAMap::ConstIterator
SHAMap::lowerBound(uint256 const& id) const
{
SharedPtrNodeStack stack;
NodePathStack stack;
walkTowardsKey(id, &stack);
while (!stack.empty())
{
auto [node, nodeID] = stack.top();
auto const [node, nodeID] = stack.top();
if (node->isLeaf())
{
auto leaf = safeDowncast<SHAMapLeafNode*>(node.get());
@@ -643,14 +623,14 @@ SHAMap::lowerBound(uint256 const& id) const
}
else
{
auto inner = intr_ptr::staticPointerCast<SHAMapInnerNode>(node);
auto& inner = safeDowncast<SHAMapInnerNode&>(*node);
for (auto branch = selectBranch(nodeID, id); branch > 0u;)
{
--branch;
if (!inner->isEmptyBranch(branch))
if (!inner.isEmptyBranch(branch))
{
node = descendThrow(*inner, branch);
auto leaf = lastBelow(node, stack, branch);
stack.pushChild(descendThrow(inner, branch), branch);
auto leaf = belowHelper(stack, BelowDirection::Last);
if (leaf == nullptr)
Throw<SHAMapMissingNode>(type_, id);
return ConstIterator(this, leaf->peekItem().get(), std::move(stack));
@@ -675,7 +655,7 @@ SHAMap::delItem(uint256 const& id)
// delete the item with this ID
XRPL_ASSERT(state_ != SHAMapState::Immutable, "xrpl::SHAMap::delItem : not immutable");
SharedPtrNodeStack stack;
NodePathStack stack;
walkTowardsKey(id, &stack);
if (stack.empty())
@@ -761,7 +741,7 @@ SHAMap::addGiveItem(SHAMapNodeType type, boost::intrusive_ptr<SHAMapItem const>
// add the specified item, does not update
uint256 const tag = item->key();
SharedPtrNodeStack stack;
NodePathStack stack;
walkTowardsKey(tag, &stack);
if (stack.empty())
@@ -801,7 +781,7 @@ SHAMap::addGiveItem(SHAMapNodeType type, boost::intrusive_ptr<SHAMapItem const>
while ((b1 = selectBranch(nodeID, tag)) == (b2 = selectBranch(nodeID, otherItem->key())))
{
stack.emplace(node, nodeID);
stack.pushNode(node, tag);
// we need a new inner node, since both go on same branch at this
// level
@@ -848,7 +828,7 @@ SHAMap::updateGiveItem(SHAMapNodeType type, boost::intrusive_ptr<SHAMapItem cons
XRPL_ASSERT(state_ != SHAMapState::Immutable, "xrpl::SHAMap::updateGiveItem : not immutable");
SharedPtrNodeStack stack;
NodePathStack stack;
walkTowardsKey(tag, &stack);
if (stack.empty())
@@ -1170,7 +1150,7 @@ SHAMap::invariants() const
auto node = root_.get();
XRPL_ASSERT(node, "xrpl::SHAMap::invariants : non-null root node");
XRPL_ASSERT(!node->isLeaf(), "xrpl::SHAMap::invariants : root node is not leaf");
SharedPtrNodeStack stack;
NodePathStack stack;
for (auto leaf = peekFirstItem(stack); leaf != nullptr;
leaf = peekNextItem(leaf->peekItem()->key(), stack))
;

View File

@@ -793,7 +793,7 @@ SHAMap::hasLeafNode(uint256 const& tag, SHAMapHash const& targetNodeHash) const
std::optional<std::vector<Blob>>
SHAMap::getProofPath(uint256 const& key) const
{
SharedPtrNodeStack stack;
NodePathStack stack;
walkTowardsKey(key, &stack);
if (stack.empty())

View File

@@ -6,6 +6,8 @@
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/OpenView.h>
#include <xrpl/ledger/OrderBookDB.h>
#include <xrpl/protocol/Book.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/TxMeta.h>
@@ -54,6 +56,13 @@ ApplyContext::apply(TER ter)
return view_->apply(base_, tx, ter, parentBatchId_, (flags_ & TapDryRun) != 0u, journal);
}
void
ApplyContext::addOrderBook(Book const& book)
{
if ((flags_ & TapDryRun) == TapNone)
registry.get().getOrderBookDB().addOrderBook(book);
}
std::size_t
ApplyContext::size()
{

View File

@@ -17,6 +17,7 @@
#include <cstdint>
#include <exception>
#include <expected>
#include <memory>
#include <optional>
#include <utility>
@@ -195,7 +196,12 @@ invokePreclaim(PreclaimContext const& ctx)
}())
return preSigResult;
if (TER const result = T::checkFee(ctx, calculateBaseFee(ctx.view, ctx.tx)))
// We can't check the fee if we can't compute it, so reject.
auto const baseFee = calculateBaseFee(ctx.view, ctx.tx);
if (!baseFee)
return baseFee.error();
if (TER const result = T::checkFee(ctx, *baseFee))
return result;
}
@@ -223,13 +229,12 @@ invokePreclaim(PreclaimContext const& ctx)
*
* @param view The ledger view to use for fee calculation.
* @param tx The transaction for which the base fee is to be calculated.
* @return The calculated base fee as an XRPAmount.
* @return The calculated base fee. Returns `std::unexpected(temUNKNOWN)` if the transaction
* type is not recognized, and `std::unexpected(tefEXCEPTION)` if the transactor's
* `calculateBaseFee` threw.
*
* @throws std::exception If an error occurs during fee calculation, including
* but not limited to unknown transaction types or internal errors, the function
* logs an error and returns an XRPAmount of zero.
*/
static XRPAmount
static std::expected<XRPAmount, TER>
invokeCalculateBaseFee(ReadView const& view, STTx const& tx)
{
try
@@ -238,13 +243,25 @@ invokeCalculateBaseFee(ReadView const& view, STTx const& tx)
return T::calculateBaseFee(view, tx);
});
}
catch (UnknownTxnType const& e)
catch (UnknownTxnType const&)
{
// LCOV_EXCL_START
UNREACHABLE("xrpl::invoke_calculateBaseFee : unknown transaction type");
return XRPAmount{0};
return std::unexpected(temUNKNOWN);
// LCOV_EXCL_STOP
}
catch (std::exception const& e)
{
JLOG(debugLog().error()) << "calculateBaseFee: " << tx.getTransactionID()
<< " threw an exception: " << e.what();
return std::unexpected(tefEXCEPTION);
}
catch (...)
{
JLOG(debugLog().error()) << "calculateBaseFee: " << tx.getTransactionID()
<< " threw an unknown exception";
return std::unexpected(tefEXCEPTION);
}
}
TxConsequences::TxConsequences(NotTEC pfResult)
@@ -416,7 +433,7 @@ preclaim(PreflightResult const& preflightResult, ServiceRegistry& registry, Open
}
}
XRPAmount
std::expected<XRPAmount, TER>
calculateBaseFee(ReadView const& view, STTx const& tx)
{
return invokeCalculateBaseFee(view, tx);
@@ -441,13 +458,26 @@ doApply(PreclaimResult const& preclaimResult, ServiceRegistry& registry, OpenVie
{
if (!preclaimResult.likelyToClaimFee)
return {preclaimResult.ter, false};
// For any tx with a real account, preclaim already computed this fee
// successfully against this same view.
auto const baseFee = calculateBaseFee(view, preclaimResult.tx);
if (!baseFee)
{
// LCOV_EXCL_START
JLOG(preclaimResult.j.error())
<< "apply: could not compute base fee: " << transToken(baseFee.error());
return {tefINTERNAL, false};
// LCOV_EXCL_STOP
}
ApplyContext ctx(
registry,
view,
preclaimResult.parentBatchId,
preclaimResult.tx,
preclaimResult.ter,
calculateBaseFee(view, preclaimResult.tx),
*baseFee,
preclaimResult.flags,
preclaimResult.j);
return invokeApply(ctx);

View File

@@ -231,17 +231,38 @@ ValidLoan::finalize(
// must show that payment in its balance and schedule. A payment that clears
// the loan outright instead drives PaymentRemaining to zero, which the
// fully-paid-off and zero due-date checks above pin.
//
// PrincipalOutstanding may stay put on a non-final pay: at integer
// scale, fixCleanup3_2_0 rounds principal up so a fractional
// amortization step does not reduce it. Interest (TVO) still falls.
// Neither balance may grow: a payment never adds to what is owed,
// since late-payment penalties are charged in the same transaction
// rather than tracked in TotalValueOutstanding.
if (isTesSuccess(result) && txType == ttLOAN_PAY)
{
if (before && after->at(sfPaymentRemaining) != 0)
{
if (!(after->at(sfPrincipalOutstanding) < before->at(sfPrincipalOutstanding)))
if (after->at(sfPrincipalOutstanding) > before->at(sfPrincipalOutstanding))
{
JLOG(j.fatal()) << "Invariant failed: loan pay must strictly decrease "
JLOG(j.fatal()) << "Invariant failed: loan pay must not increase "
"PrincipalOutstanding on a non-full-repayment";
return false;
}
if (!(after->at(sfPaymentRemaining) < before->at(sfPaymentRemaining)))
if (after->at(sfTotalValueOutstanding) > before->at(sfTotalValueOutstanding))
{
JLOG(j.fatal()) << "Invariant failed: loan pay must not increase "
"TotalValueOutstanding on a non-full-repayment";
return false;
}
if (after->at(sfPrincipalOutstanding) == before->at(sfPrincipalOutstanding) &&
after->at(sfTotalValueOutstanding) == before->at(sfTotalValueOutstanding))
{
JLOG(j.fatal()) << "Invariant failed: loan pay must decrease "
"PrincipalOutstanding or TotalValueOutstanding "
"on a non-full-repayment";
return false;
}
if (after->at(sfPaymentRemaining) >= before->at(sfPaymentRemaining))
{
JLOG(j.fatal()) << "Invariant failed: loan pay must decrease "
"PaymentRemaining on a non-full-repayment";

View File

@@ -40,6 +40,7 @@ constexpr auto kConfidentialMptTxTypes = std::to_array<TxType>({
ttCONFIDENTIAL_MPT_CONVERT_BACK,
ttCONFIDENTIAL_MPT_MERGE_INBOX,
ttCONFIDENTIAL_MPT_CLAWBACK,
ttCONFIDENTIAL_MPT_MIRROR_UPDATE,
});
// Clamp to the cap (== INT64_MAX) before the signed conversion. Invariant
@@ -299,27 +300,46 @@ ValidMPTIssuance::finalize(
return false;
}
}
else if (lendingProtocolEnabled && (mptokensCreated_ + mptokensDeleted_) > 1)
else
{
JLOG(j.fatal()) << "Invariant failed: MPT authorize succeeded "
"but created/deleted bad number mptokens";
return false;
}
else if (submittedByIssuer && (mptokensCreated_ > 0 || mptokensDeleted_ > 0))
{
JLOG(j.fatal()) << "Invariant failed: MPT authorize submitted by issuer "
"succeeded but created/deleted mptokens";
return false;
}
else if (
!submittedByIssuer && hasPrivilege(tx, Privilege::MustAuthorizeMpt) &&
(mptokensCreated_ + mptokensDeleted_ != 1))
{
// if the holder submitted this tx, then a mptoken must be
// either created or deleted.
JLOG(j.fatal()) << "Invariant failed: MPT authorize submitted by holder "
"succeeded but created/deleted bad number of mptokens";
return false;
// Cap on MPToken creates and deletes while featureLendingProtocol is enabled.
// - LoanSet: at most two creates and no deletes.
// - VaultWithdraw: at most one create and one delete.
// - Other MayAuthorizeMpt types: created + deleted <= 1.
// - MustAuthorizeMpt still requires exactly one create or delete below.
auto const mptokensExceedAuthorizeCap = [&] {
if (!lendingProtocolEnabled)
return false;
if (rules.enabled(fixCleanup3_4_0))
{
if (txnType == ttLOAN_SET)
return mptokensDeleted_ != 0 || mptokensCreated_ > 2;
if (txnType == ttVAULT_WITHDRAW)
return mptokensCreated_ > 1 || mptokensDeleted_ > 1;
}
return (mptokensCreated_ + mptokensDeleted_) > 1;
};
if (mptokensExceedAuthorizeCap())
{
JLOG(j.fatal()) << "Invariant failed: MPT authorize succeeded "
"but created/deleted bad number mptokens";
return false;
}
if (submittedByIssuer && (mptokensCreated_ > 0 || mptokensDeleted_ > 0))
{
JLOG(j.fatal()) << "Invariant failed: MPT authorize submitted by issuer "
"succeeded but created/deleted mptokens";
return false;
}
if (!submittedByIssuer && hasPrivilege(tx, Privilege::MustAuthorizeMpt) &&
(mptokensCreated_ + mptokensDeleted_ != 1))
{
// if the holder submitted this tx, then a mptoken must be
// either created or deleted.
JLOG(j.fatal()) << "Invariant failed: MPT authorize submitted by holder "
"succeeded but created/deleted bad number of mptokens";
return false;
}
}
return true;

View File

@@ -439,6 +439,12 @@ CheckCash::doApply()
AccountID const& deliverIssuer = flowDeliver.getIssuer();
auto const err = flowDeliver.asset().visit(
[&](Issue const& issue) -> std::optional<TER> {
// An issuer needs no holder-limit waiver to receive its own currency.
if (deliverIssuer == accountID_ && ctx_.view().rules().enabled(fixCleanup3_4_0))
{
return std::nullopt;
}
// If a trust line does not exist yet create one.
Issue const& trustLineIssue = issue;
AccountID const truster = deliverIssuer == accountID_ ? srcId : accountID_;

View File

@@ -2,8 +2,6 @@
#include <xrpl/basics/Log.h>
#include <xrpl/beast/utility/Zero.h>
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/OrderBookDB.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/Sandbox.h>
#include <xrpl/ledger/View.h>
@@ -397,7 +395,7 @@ applyCreate(ApplyContext& ctx, Sandbox& sb, AccountID const& account, beast::Jou
Book const book{assetIn, assetOut, std::nullopt};
auto const dir = keylet::quality(keylet::book(book), uRate);
if (auto const bookExisted = static_cast<bool>(sb.read(dir)); !bookExisted)
ctx.registry.get().getOrderBookDB().addOrderBook(book);
ctx.addOrderBook(book);
};
addOrderBook(amount.asset(), amount2.asset(), getRate(amount2, amount));
addOrderBook(amount2.asset(), amount.asset(), getRate(amount, amount2));

View File

@@ -6,7 +6,6 @@
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/OrderBookDB.h>
#include <xrpl/ledger/PaymentSandbox.h>
#include <xrpl/ledger/Sandbox.h>
#include <xrpl/ledger/View.h>
@@ -634,7 +633,7 @@ OfferCreate::applyHybrid(
bookArr.pushBack(std::move(bookInfo));
if (!bookExists)
ctx_.registry.get().getOrderBookDB().addOrderBook(book);
ctx_.addOrderBook(book);
sleOffer->setFieldArray(sfAdditionalBooks, bookArr);
return tesSUCCESS;
@@ -1014,7 +1013,7 @@ OfferCreate::applyGuts(Sandbox& sb, Sandbox& sbCancel)
sb.insert(sleOffer);
if (!bookExisted)
ctx_.registry.get().getOrderBookDB().addOrderBook(book);
ctx_.addOrderBook(book);
JLOG(j_.debug()) << "final result: success";

View File

@@ -36,6 +36,15 @@
namespace xrpl {
namespace {
// Returns true if the transaction's payment amount is malformed. A loan
// payment must be strictly positive: zero would move nothing, and a negative
// amount is not a payment at all.
bool
isPaymentAmountInvalid(STAmount const& amount)
{
return amount <= beast::kZero;
}
// Returns the account's true, unclamped balance in `asset`, for use only in
// fund-conservation checks. accountHolds(..., SpendableHandling::FullBalance)
// cannot be used for this: for XRP it always defers to xrpLiquid, which
@@ -81,7 +90,7 @@ LoanPay::preflight(PreflightContext const& ctx)
if (ctx.tx[sfLoanID] == beast::kZero)
return temINVALID;
if (ctx.tx[sfAmount] <= beast::kZero)
if (isPaymentAmountInvalid(ctx.tx[sfAmount]))
return temBAD_AMOUNT;
// The loan payment flags are all mutually exclusive. If more than one is
@@ -103,10 +112,19 @@ LoanPay::preflight(PreflightContext const& ctx)
XRPAmount
LoanPay::calculateBaseFee(ReadView const& view, STTx const& tx)
{
auto fixEnabled313 = view.rules().enabled(fixCleanup3_1_3);
auto fixEnabled340 = view.rules().enabled(fixCleanup3_4_0);
using namespace lending;
auto const normalCost = Transactor::calculateBaseFee(view, tx);
if (fixEnabled340 && isPaymentAmountInvalid(tx[sfAmount]))
{
// Let preflight worry about the error for this
return normalCost;
}
if (tx.isFlag(tfLoanFullPayment) || tx.isFlag(tfLoanLatePayment))
{
// The loan will be making one set of calculations for one full or late
@@ -179,8 +197,7 @@ LoanPay::calculateBaseFee(ReadView const& view, STTx const& tx)
static constexpr std::int64_t kMaxFeeIncrements =
kLoanMaximumPaymentsPerTransaction / kLoanPaymentsPerFeeIncrement;
if (view.rules().enabled(fixCleanup3_1_3) &&
amount >= regularPayment * kLoanMaximumPaymentsPerTransaction)
if (fixEnabled313 && amount >= regularPayment * kLoanMaximumPaymentsPerTransaction)
{
// The payment handler will never process more than
// loanMaximumPaymentsPerTransaction payments (including overpayments),

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