mirror of
https://github.com/XRPLF/rippled.git
synced 2026-09-23 05:30:13 +00:00
Compare commits
35 Commits
timothyban
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5a5ad8673a | ||
|
|
8f4e9c25d8 | ||
|
|
d8870162eb | ||
|
|
8b1a2282c3 | ||
|
|
0219c01b33 | ||
|
|
00606bec1a | ||
|
|
2bc17c3e73 | ||
|
|
0229c294a9 | ||
|
|
184fe173fb | ||
|
|
16f7b263fd | ||
|
|
f6b51f0b8b | ||
|
|
07b36871c7 | ||
|
|
638ae08eeb | ||
|
|
24be48ee84 | ||
|
|
e20f448a71 | ||
|
|
f6c80fef68 | ||
|
|
a0c12420b5 | ||
|
|
1245254cac | ||
|
|
b60636169a | ||
|
|
d6022fbc4d | ||
|
|
295b74da1c | ||
|
|
faa2bf583f | ||
|
|
da260fa42b | ||
|
|
53788b193d | ||
|
|
227f1b4d9c | ||
|
|
26b66957ec | ||
|
|
54e62a621f | ||
|
|
53628b70c0 | ||
|
|
6fec2c11bf | ||
|
|
04108a030c | ||
|
|
551a19b10d | ||
|
|
b0a940a383 | ||
|
|
e302e4eeed | ||
|
|
1a4a40ebb8 | ||
|
|
7f55dd390c |
@@ -64,6 +64,7 @@ words:
|
||||
- blindings
|
||||
- bookdir
|
||||
- Bougalis
|
||||
- bthomee
|
||||
- Britto
|
||||
- Btrfs
|
||||
- Buildx
|
||||
@@ -141,6 +142,7 @@ words:
|
||||
- hwrap
|
||||
- ifndef
|
||||
- inequation
|
||||
- Injectivity
|
||||
- insuf
|
||||
- insuff
|
||||
- invasively
|
||||
|
||||
@@ -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
|
||||
|
||||
46
.github/scripts/strategy-matrix/generate.py
vendored
46
.github/scripts/strategy-matrix/generate.py
vendored
@@ -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(
|
||||
|
||||
13
.github/scripts/strategy-matrix/linux.json
vendored
13
.github/scripts/strategy-matrix/linux.json
vendored
@@ -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"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
|
||||
7
.github/workflows/on-pr.yml
vendored
7
.github/workflows/on-pr.yml
vendored
@@ -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:
|
||||
|
||||
1
.github/workflows/on-trigger.yml
vendored
1
.github/workflows/on-trigger.yml
vendored
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
120
.github/workflows/reusable-package-test-install.yml
vendored
Normal file
120
.github/workflows/reusable-package-test-install.yml
vendored
Normal 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
|
||||
185
.github/workflows/reusable-package.yml
vendored
185
.github/workflows/reusable-package.yml
vendored
@@ -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"]
|
||||
|
||||
2
.github/workflows/reusable-rust.yml
vendored
2
.github/workflows/reusable-rust.yml
vendored
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
144
bin/pre-commit/fix_gtest_names.py
Executable 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())
|
||||
259
bin/pre-commit/test_fix_gtest_names.py
Executable file
259
bin/pre-commit/test_fix_gtest_names.py
Executable 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())
|
||||
@@ -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"
|
||||
|
||||
@@ -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/*",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
45
include/xrpl/ledger/entries/AMMEntry.h
Normal file
45
include/xrpl/ledger/entries/AMMEntry.h
Normal 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
|
||||
35
include/xrpl/ledger/entries/AccountRootEntry.h
Normal file
35
include/xrpl/ledger/entries/AccountRootEntry.h
Normal 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
|
||||
33
include/xrpl/ledger/entries/AmendmentsEntry.h
Normal file
33
include/xrpl/ledger/entries/AmendmentsEntry.h
Normal 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
|
||||
36
include/xrpl/ledger/entries/BridgeEntry.h
Normal file
36
include/xrpl/ledger/entries/BridgeEntry.h
Normal 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
|
||||
46
include/xrpl/ledger/entries/CheckEntry.h
Normal file
46
include/xrpl/ledger/entries/CheckEntry.h
Normal 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
|
||||
47
include/xrpl/ledger/entries/CredentialEntry.h
Normal file
47
include/xrpl/ledger/entries/CredentialEntry.h
Normal 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
|
||||
35
include/xrpl/ledger/entries/DIDEntry.h
Normal file
35
include/xrpl/ledger/entries/DIDEntry.h
Normal 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
|
||||
36
include/xrpl/ledger/entries/DelegateEntry.h
Normal file
36
include/xrpl/ledger/entries/DelegateEntry.h
Normal 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
|
||||
58
include/xrpl/ledger/entries/DepositPreauthEntry.h
Normal file
58
include/xrpl/ledger/entries/DepositPreauthEntry.h
Normal 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
|
||||
50
include/xrpl/ledger/entries/DirectoryNodeEntry.h
Normal file
50
include/xrpl/ledger/entries/DirectoryNodeEntry.h
Normal 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
|
||||
37
include/xrpl/ledger/entries/EscrowEntry.h
Normal file
37
include/xrpl/ledger/entries/EscrowEntry.h
Normal 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
|
||||
33
include/xrpl/ledger/entries/FeeSettingsEntry.h
Normal file
33
include/xrpl/ledger/entries/FeeSettingsEntry.h
Normal 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
|
||||
33
include/xrpl/ledger/entries/LedgerHashesEntry.h
Normal file
33
include/xrpl/ledger/entries/LedgerHashesEntry.h
Normal 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
|
||||
46
include/xrpl/ledger/entries/LoanBrokerEntry.h
Normal file
46
include/xrpl/ledger/entries/LoanBrokerEntry.h
Normal 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
|
||||
45
include/xrpl/ledger/entries/LoanEntry.h
Normal file
45
include/xrpl/ledger/entries/LoanEntry.h
Normal 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
|
||||
55
include/xrpl/ledger/entries/MPTokenEntry.h
Normal file
55
include/xrpl/ledger/entries/MPTokenEntry.h
Normal 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
|
||||
56
include/xrpl/ledger/entries/MPTokenIssuanceEntry.h
Normal file
56
include/xrpl/ledger/entries/MPTokenIssuanceEntry.h
Normal 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
|
||||
46
include/xrpl/ledger/entries/NFTokenOfferEntry.h
Normal file
46
include/xrpl/ledger/entries/NFTokenOfferEntry.h
Normal 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
|
||||
37
include/xrpl/ledger/entries/NFTokenPageEntry.h
Normal file
37
include/xrpl/ledger/entries/NFTokenPageEntry.h
Normal 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
|
||||
33
include/xrpl/ledger/entries/NegativeUNLEntry.h
Normal file
33
include/xrpl/ledger/entries/NegativeUNLEntry.h
Normal 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
|
||||
46
include/xrpl/ledger/entries/OfferEntry.h
Normal file
46
include/xrpl/ledger/entries/OfferEntry.h
Normal 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
|
||||
38
include/xrpl/ledger/entries/OracleEntry.h
Normal file
38
include/xrpl/ledger/entries/OracleEntry.h
Normal 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
|
||||
38
include/xrpl/ledger/entries/PayChannelEntry.h
Normal file
38
include/xrpl/ledger/entries/PayChannelEntry.h
Normal 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
|
||||
46
include/xrpl/ledger/entries/PermissionedDomainEntry.h
Normal file
46
include/xrpl/ledger/entries/PermissionedDomainEntry.h
Normal 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
|
||||
48
include/xrpl/ledger/entries/RippleStateEntry.h
Normal file
48
include/xrpl/ledger/entries/RippleStateEntry.h
Normal 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
|
||||
503
include/xrpl/ledger/entries/SLEBase.h
Normal file
503
include/xrpl/ledger/entries/SLEBase.h
Normal 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
|
||||
35
include/xrpl/ledger/entries/SignerListEntry.h
Normal file
35
include/xrpl/ledger/entries/SignerListEntry.h
Normal 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
|
||||
36
include/xrpl/ledger/entries/SponsorshipEntry.h
Normal file
36
include/xrpl/ledger/entries/SponsorshipEntry.h
Normal 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
|
||||
46
include/xrpl/ledger/entries/TicketEntry.h
Normal file
46
include/xrpl/ledger/entries/TicketEntry.h
Normal 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
|
||||
46
include/xrpl/ledger/entries/VaultEntry.h
Normal file
46
include/xrpl/ledger/entries/VaultEntry.h
Normal 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
|
||||
38
include/xrpl/ledger/entries/XChainOwnedClaimIDEntry.h
Normal file
38
include/xrpl/ledger/entries/XChainOwnedClaimIDEntry.h
Normal 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
|
||||
@@ -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
|
||||
@@ -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(
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -92,7 +92,7 @@ public:
|
||||
void
|
||||
importDatabase(Database& source) override
|
||||
{
|
||||
importInternal(*backend_.get(), source);
|
||||
importInternal(*backend_, source);
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
@@ -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};
|
||||
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -436,6 +436,8 @@ LEDGER_ENTRY(ltMPTOKEN, 0x007f, MPToken, mptoken, ({
|
||||
{sfConfidentialBalanceVersion, SoeDefault},
|
||||
{sfIssuerEncryptedBalance, SoeOptional},
|
||||
{sfAuditorEncryptedBalance, SoeOptional},
|
||||
{sfIssuerKeyMirrorEpoch, SoeOptional},
|
||||
{sfAuditorKeyMirrorEpoch, SoeOptional},
|
||||
{sfHolderEncryptionKey, SoeOptional},
|
||||
}))
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
@@ -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)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
|
||||
/**
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -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
|
||||
@@ -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.
|
||||
|
||||
@@ -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__":
|
||||
|
||||
@@ -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@
|
||||
6
package/debian/lintian-overrides.in
Normal file
6
package/debian/lintian-overrides.in
Normal 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]
|
||||
@@ -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:
|
||||
@:
|
||||
|
||||
@@ -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]
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
;
|
||||
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
;
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user