mirror of
https://github.com/XRPLF/rippled.git
synced 2026-09-24 06:00:17 +00:00
Compare commits
36 Commits
token-payc
...
bthomee/sh
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6c0195ad8c | ||
|
|
c617b7cf4f | ||
|
|
a9a0dd1f69 | ||
|
|
c2b1c5a551 | ||
|
|
19d8ff8ff5 | ||
|
|
66ead1a7e0 | ||
|
|
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
|
||||
@@ -286,10 +285,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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -86,12 +86,16 @@ public:
|
||||
requires std::convertible_to<TT*, T*>
|
||||
SharedIntrusive(SharedIntrusive<TT> const& rhs);
|
||||
|
||||
SharedIntrusive(SharedIntrusive&& rhs);
|
||||
// noexcept so that a std::vector of these relocates by moving. Without it, move_if_noexcept
|
||||
// copies each element instead, since this type is also copy constructible, and every copy is an
|
||||
// atomic increment on the pointee's refcount followed by a release on the original. The body is
|
||||
// a std::exchange on a raw pointer, so it provably cannot throw.
|
||||
SharedIntrusive(SharedIntrusive&& rhs) noexcept;
|
||||
|
||||
template <class TT>
|
||||
requires std::convertible_to<TT*, T*>
|
||||
SharedIntrusive(
|
||||
SharedIntrusive<TT>&& rhs); // NOLINT(cppcoreguidelines-rvalue-reference-param-not-moved)
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-rvalue-reference-param-not-moved)
|
||||
SharedIntrusive(SharedIntrusive<TT>&& rhs) noexcept;
|
||||
|
||||
SharedIntrusive&
|
||||
operator=(SharedIntrusive const& rhs);
|
||||
@@ -529,11 +533,48 @@ staticPointerCast(TT const& v)
|
||||
return SharedPtr<T>(StaticCastTagSharedIntrusive{}, v);
|
||||
}
|
||||
|
||||
/**
|
||||
* Statically cast an intrusive pointer the caller is giving up, moving out of
|
||||
* it.
|
||||
*
|
||||
* The parameter names the wrapped type rather than taking a bare `TT&&`. A
|
||||
* bare one would be a forwarding reference, so it would also bind to lvalues
|
||||
* in preference to the `const&` overload above and move out of a caller's live
|
||||
* variable on what looks like a copy call.
|
||||
*
|
||||
* @param v the pointer to cast, left empty afterwards.
|
||||
* @return a pointer of the requested type to the same object.
|
||||
*/
|
||||
template <class T, class TT>
|
||||
SharedPtr<T>
|
||||
staticPointerCast(SharedIntrusive<TT>&& v)
|
||||
{
|
||||
return SharedPtr<T>(StaticCastTagSharedIntrusive{}, std::move(v));
|
||||
}
|
||||
|
||||
template <class T, class TT>
|
||||
SharedPtr<T>
|
||||
dynamicPointerCast(TT const& v)
|
||||
{
|
||||
return SharedPtr<T>(DynamicCastTagSharedIntrusive{}, v);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dynamically cast an intrusive pointer the caller is giving up, moving out of
|
||||
* it.
|
||||
*
|
||||
* Tied to `SharedIntrusive<TT>&&` for the reason given above.
|
||||
*
|
||||
* @param v the pointer to cast, left empty afterwards if the cast succeeds and
|
||||
* left owning the object if it does not.
|
||||
* @return a pointer of the requested type, or an empty one if the object is
|
||||
* not of that type.
|
||||
*/
|
||||
template <class T, class TT>
|
||||
SharedPtr<T>
|
||||
dynamicPointerCast(SharedIntrusive<TT>&& v)
|
||||
{
|
||||
return SharedPtr<T>(DynamicCastTagSharedIntrusive{}, std::move(v));
|
||||
}
|
||||
} // namespace intr_ptr
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -43,7 +43,7 @@ SharedIntrusive<T>::SharedIntrusive(SharedIntrusive<TT> const& rhs)
|
||||
}
|
||||
|
||||
template <class T>
|
||||
SharedIntrusive<T>::SharedIntrusive(SharedIntrusive&& rhs)
|
||||
SharedIntrusive<T>::SharedIntrusive(SharedIntrusive&& rhs) noexcept
|
||||
: ptr_{std::move(rhs).unsafeExchange(nullptr)}
|
||||
{
|
||||
}
|
||||
@@ -51,7 +51,7 @@ SharedIntrusive<T>::SharedIntrusive(SharedIntrusive&& rhs)
|
||||
template <class T>
|
||||
template <class TT>
|
||||
requires std::convertible_to<TT*, T*>
|
||||
SharedIntrusive<T>::SharedIntrusive(SharedIntrusive<TT>&& rhs)
|
||||
SharedIntrusive<T>::SharedIntrusive(SharedIntrusive<TT>&& rhs) noexcept
|
||||
: ptr_{std::move(rhs).unsafeExchange(nullptr)}
|
||||
{
|
||||
}
|
||||
|
||||
@@ -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,63 @@ 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 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,
|
||||
|
||||
@@ -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},
|
||||
}))
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
/**
|
||||
@@ -71,20 +71,31 @@ enum class SHAMapState {
|
||||
};
|
||||
|
||||
/**
|
||||
* A SHAMap is both a radix tree with a fan-out of 16 and a Merkle tree.
|
||||
* A SHAMap is both a trie with a fan-out of 16 and a Merkle tree.
|
||||
*
|
||||
* A radix tree is a tree with two properties:
|
||||
* A trie keeps a key in the position of its nodes rather than in the nodes
|
||||
* themselves: the path from the root down to a node spells out the prefix
|
||||
* every key below it shares (the "prefix property"). A SHAMap spends one
|
||||
* nibble of the 256-bit key per level, so each inner node has at most 16
|
||||
* children, which is the fan-out, and a leaf sits at depth 64 at the deepest.
|
||||
* A leaf also carries its own full key, which is what lets a reader check
|
||||
* that it was reached through the branches that key names.
|
||||
*
|
||||
* 1. The key for a node is represented by the node's position in the tree
|
||||
* (the "prefix property").
|
||||
* 2. A node with only one child is merged with that child
|
||||
* (the "merge property")
|
||||
* A radix tree adds a second property: a node with only one child is merged
|
||||
* with that child (the "merge property"), which is what makes it a compressed
|
||||
* trie. A SHAMap does not maintain that, so it is a trie and not a radix
|
||||
* tree. Adding an item creates an inner node at every nibble the two keys
|
||||
* share, however long that run is, and never merges one away. Deleting does
|
||||
* merge: a chain that reduces to a single leaf collapses, pulling that leaf
|
||||
* up to one nibble below the nearest ancestor still holding two branches.
|
||||
* Either way no edge spans more than one nibble, so two keys agreeing on
|
||||
* their first 63 nibbles give 63 single-child inner nodes, an inner node at
|
||||
* depth 63 holding both branches, and the two leaves at depth 64: one entry
|
||||
* per level with no gaps, and 65 entries at the most. Traversal relies on
|
||||
* that, since it is what makes a path's length name each node's depth.
|
||||
*
|
||||
* These properties result in a significantly smaller memory footprint for
|
||||
* a radix tree.
|
||||
*
|
||||
* A fan-out of 16 means that each node in the tree has at most 16
|
||||
* children. See https://en.wikipedia.org/wiki/Radix_tree
|
||||
* See https://en.wikipedia.org/wiki/Trie and
|
||||
* https://en.wikipedia.org/wiki/Radix_tree
|
||||
*
|
||||
* A Merkle tree is a tree where each non-leaf node is labelled with the hash
|
||||
* of the combined labels of its children nodes.
|
||||
@@ -133,8 +144,7 @@ private:
|
||||
|
||||
public:
|
||||
/**
|
||||
* Number of children each non-leaf node has (the 'radix tree' part of the
|
||||
* map)
|
||||
* Number of children each non-leaf node has, which is the trie's fan-out
|
||||
*/
|
||||
static constexpr unsigned int kBranchFactor = SHAMapInnerNode::kBranchFactor;
|
||||
|
||||
@@ -420,7 +430,327 @@ public:
|
||||
invariants() const;
|
||||
|
||||
private:
|
||||
using SharedPtrNodeStack = std::stack<std::pair<SHAMapTreeNodePtr, SHAMapNodeID>>;
|
||||
/**
|
||||
* Whether placing `node` one level below `parentDepth` leaves it no room.
|
||||
*
|
||||
* Only a leaf may sit at kLeafDepth, since an inner node there would have
|
||||
* no branch left to select. Both of the places that bound a descent call
|
||||
* this, so a walk with a caller-supplied path and one without cannot drift
|
||||
* apart and refuse at different nodes.
|
||||
*
|
||||
* The depth is tested before the node's type so the virtual call runs only
|
||||
* where the bound can bite, which is the last level of a 65-level walk.
|
||||
*
|
||||
* @param parentDepth the depth of the node being descended from.
|
||||
* @param node the node about to be placed one level below it.
|
||||
* @return whether that placement is past the deepest level this kind of
|
||||
* node may occupy.
|
||||
*/
|
||||
[[nodiscard]] static bool
|
||||
pastLeafDepth(unsigned int parentDepth, SHAMapTreeNode const& node)
|
||||
{
|
||||
return parentDepth + 1u >= kLeafDepth && (node.isInner() || parentDepth >= kLeafDepth);
|
||||
}
|
||||
|
||||
/**
|
||||
* A root-down path of nodes through the map.
|
||||
*
|
||||
* The path itself names each node's position: entry `i` sits at depth
|
||||
* `i`, since a SHAMap does not merge a single-child node away (see the
|
||||
* class docstring above), so every nibble down to a leaf has an inner
|
||||
* node of its own. Nothing is therefore stored per entry but the node.
|
||||
* No consumer needs a whole SHAMapNodeID: every one of them wants a
|
||||
* depth, and reads the nibbles it cares about from the key it already
|
||||
* holds.
|
||||
*
|
||||
* Storing an ID alongside each node would add a second answer to "where
|
||||
* does this node sit", which could then disagree with the first.
|
||||
* Deriving it cannot.
|
||||
*/
|
||||
class NodePathStack
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @return whether the path holds no node at all.
|
||||
*/
|
||||
[[nodiscard]] bool
|
||||
empty() const
|
||||
{
|
||||
return path_.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return how many nodes the path holds, which is one more than its
|
||||
* last node's depth.
|
||||
*/
|
||||
[[nodiscard]] std::size_t
|
||||
size() const
|
||||
{
|
||||
return path_.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* The node at the end of the path.
|
||||
*
|
||||
* Reading an empty path would be undefined, and the assert alone is
|
||||
* stripped in release, so an empty path yields a null node the caller
|
||||
* can test instead.
|
||||
*
|
||||
* @return a reference into the path, which a later push may
|
||||
* invalidate by reallocating. Callers that push and then want
|
||||
* the node again ask for it again; the node itself does not
|
||||
* move, only the slot holding the pointer to it.
|
||||
*/
|
||||
[[nodiscard]] SHAMapTreeNodePtr const&
|
||||
top() const
|
||||
{
|
||||
if (path_.empty())
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
UNREACHABLE("xrpl::SHAMap::NodePathStack::top : empty stack");
|
||||
static SHAMapTreeNodePtr const kEmpty;
|
||||
return kEmpty;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
return path_.back();
|
||||
}
|
||||
|
||||
/**
|
||||
* The depth of the node at the end of the path.
|
||||
*
|
||||
* @return the depth, which is the entry's own index; zero on an empty
|
||||
* path, which a caller must not read but which must not be an
|
||||
* out-of-range subtraction either.
|
||||
*/
|
||||
[[nodiscard]] unsigned int
|
||||
topDepth() const
|
||||
{
|
||||
if (path_.empty())
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
UNREACHABLE("xrpl::SHAMap::NodePathStack::topDepth : empty stack");
|
||||
return 0;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
return static_cast<unsigned int>(path_.size() - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shorten the path by one node.
|
||||
*
|
||||
* Popping an empty path would be undefined, and the assert alone is
|
||||
* stripped in release, so an empty path is left alone instead.
|
||||
*/
|
||||
void
|
||||
pop()
|
||||
{
|
||||
if (path_.empty())
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
UNREACHABLE("xrpl::SHAMap::NodePathStack::pop : empty stack");
|
||||
return;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
path_.pop_back();
|
||||
}
|
||||
|
||||
/**
|
||||
* Discard the whole path.
|
||||
*
|
||||
* For a walk that pushed a node it then found unusable: the node never
|
||||
* became a meaningful path entry, so it must not be mistaken for one
|
||||
* by whatever the caller does next with an empty-vs-nonempty check.
|
||||
*/
|
||||
void
|
||||
clear()
|
||||
{
|
||||
path_.clear();
|
||||
pathKey_ = uint256{};
|
||||
}
|
||||
|
||||
/**
|
||||
* Shorten the path by one node and hand that node to the caller.
|
||||
*
|
||||
* Reading a node out and then popping copies it, which costs an atomic
|
||||
* increment on its refcount. Moving it out does not.
|
||||
*
|
||||
* @return the node that was at the end of the path, or an empty
|
||||
* pointer if there was none.
|
||||
*/
|
||||
[[nodiscard]] SHAMapTreeNodePtr
|
||||
releaseNode()
|
||||
{
|
||||
if (path_.empty())
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
UNREACHABLE("xrpl::SHAMap::NodePathStack::releaseNode : empty stack");
|
||||
return {};
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
auto node = std::move(path_.back());
|
||||
path_.pop_back();
|
||||
return node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a path at the root of the map, which sits at depth zero by
|
||||
* definition.
|
||||
*
|
||||
* @return false, leaving the path unchanged, if a path was already
|
||||
* started. A malformed call must not abort a release build,
|
||||
* so callers stop rather than overwrite it.
|
||||
*/
|
||||
[[nodiscard]] bool
|
||||
pushRoot(SHAMapTreeNodePtr node)
|
||||
{
|
||||
if (!path_.empty())
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
UNREACHABLE("xrpl::SHAMap::NodePathStack::pushRoot : non-empty stack");
|
||||
return false;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
path_.push_back(std::move(node));
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extend the path to the child of the node at its end reached by
|
||||
* `branch`.
|
||||
*
|
||||
* The branch is not stored. It is only used to judge the node offered,
|
||||
* since the child's position is this path one level longer whichever
|
||||
* branch reached it.
|
||||
*
|
||||
* Only a leaf may sit at kLeafDepth, since an inner node there would
|
||||
* have no branch left to select.
|
||||
*
|
||||
* @param node the child to append.
|
||||
* @param branch the branch of the current node that `node` was
|
||||
* reached through.
|
||||
* @return false if there is no node to descend from, no node to push,
|
||||
* no branch of that number, no room left below for the kind
|
||||
* of node offered, or a leaf whose own key does not select
|
||||
* `branch`. A malformed call or a malformed map must not abort
|
||||
* a release build, so callers stop walking instead. The path
|
||||
* keeps its nodes, though the recorded branch chain may
|
||||
* already name `branch`, which no later read reaches.
|
||||
*/
|
||||
[[nodiscard]] bool
|
||||
pushChild(SHAMapTreeNodePtr node, unsigned int branch)
|
||||
{
|
||||
if (path_.empty() || !node || branch >= kBranchFactor)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
UNREACHABLE("xrpl::SHAMap::NodePathStack::pushChild : no child to push");
|
||||
return false;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
// Only a leaf may sit at kLeafDepth, so an inner child must land one level short of
|
||||
// it, tighter than the plain depth bound a leaf child needs.
|
||||
//
|
||||
// Reachable, for the same reason the misplaced-leaf case below is: a node resolved from
|
||||
// the local store has had neither its position nor its type judged. The two-argument
|
||||
// SHAMap::descend fetches by the parent's recorded child hash and hooks what comes
|
||||
// back, and a parsed node adopts that hash rather than recomputing it, so an inner node
|
||||
// can arrive one level too deep. So this refuses rather than aborting a build.
|
||||
//
|
||||
auto const parentDepth = topDepth();
|
||||
bool const tooDeep = pastLeafDepth(parentDepth, *node);
|
||||
SOMETIMES(tooDeep, "xrpl::SHAMap::NodePathStack::pushChild : child past leaf depth");
|
||||
if (tooDeep)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Record the branch, then judge a leaf against every branch recorded so far. Testing
|
||||
// only this step's nibble would accept a whole subtree hung under the wrong branch:
|
||||
// one wrong child pointer in one inner node leaves every leaf below it agreeing at its
|
||||
// own final nibble, because the subtree is internally well formed, and disagreeing only
|
||||
// at the level where the pointer is wrong.
|
||||
//
|
||||
// This is the one thing a path cannot derive. Its length gives every depth, but whether
|
||||
// the caller descended the branches it says it did is only visible against a real key.
|
||||
//
|
||||
// Reachable for the same reason, and by a wider route: a node arriving through a sync
|
||||
// filter is judged by hash, and a hash says nothing about position. The paths that hook
|
||||
// a node reject a misplaced one first (see SHAMap::descend and gmnProcessNodes), but a
|
||||
// map read lazily from the local store never passes through them.
|
||||
setNibble(parentDepth, branch);
|
||||
|
||||
bool const misplaced = node->isLeaf() &&
|
||||
!SHAMapNodeID::createID(parentDepth + 1u, pathKey_).isPrefixOf(leafKey(*node));
|
||||
SOMETIMES(
|
||||
misplaced, "xrpl::SHAMap::NodePathStack::pushChild : leaf key outside branch");
|
||||
if (misplaced)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
path_.push_back(std::move(node));
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extend the path by one node lying on the way to `target`, starting
|
||||
* it if it is empty.
|
||||
*
|
||||
* 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` names the branch.
|
||||
*
|
||||
* @param node the node to append.
|
||||
* @param target the key the walk is heading for.
|
||||
* @return whatever pushRoot or pushChild returned.
|
||||
*/
|
||||
[[nodiscard]] bool
|
||||
pushNode(SHAMapTreeNodePtr node, uint256 const& target)
|
||||
{
|
||||
if (path_.empty())
|
||||
{
|
||||
return pushRoot(std::move(node));
|
||||
}
|
||||
return pushChild(std::move(node), selectBranch(topDepth(), target));
|
||||
}
|
||||
|
||||
private:
|
||||
/**
|
||||
* Write `branch` as the nibble at `depth` of the recorded branch chain.
|
||||
*
|
||||
* @param depth the nibble index to write, which is the depth the
|
||||
* branch was taken from.
|
||||
* @param branch the branch taken, which the caller has already bounded.
|
||||
*/
|
||||
void
|
||||
setNibble(unsigned int depth, unsigned int branch)
|
||||
{
|
||||
auto& byte = *(pathKey_.begin() + (depth / 2));
|
||||
if ((depth & 1) != 0u)
|
||||
{
|
||||
byte = static_cast<unsigned char>((byte & 0xF0u) | branch);
|
||||
}
|
||||
else
|
||||
{
|
||||
byte = static_cast<unsigned char>((byte & 0x0Fu) | (branch << 4));
|
||||
}
|
||||
}
|
||||
|
||||
// path_[i] holds the node at depth i, by construction: pushRoot starts at depth 0 and
|
||||
// pushChild only ever appends one level.
|
||||
std::vector<SHAMapTreeNodePtr> path_;
|
||||
|
||||
// The branches descended, one nibble per level: nibble i is the branch taken from depth i.
|
||||
// One record for the whole path rather than an ID per entry, so path_.size() stays the only
|
||||
// answer to where a node sits and this is only the claim being checked against it.
|
||||
//
|
||||
// A pop leaves the nibbles above the path's end as they were, because no read can reach
|
||||
// them: createID masks this at parentDepth + 1, so a check reads nibbles 0 through
|
||||
// parentDepth only, and those are always the current path's. Level j + 1 exists only if a
|
||||
// push at depth j wrote nibble j, and re-descending at j overwrites it.
|
||||
uint256 pathKey_;
|
||||
};
|
||||
|
||||
using DeltaRef =
|
||||
std::pair<boost::intrusive_ptr<SHAMapItem const>, boost::intrusive_ptr<SHAMapItem const>>;
|
||||
|
||||
@@ -447,15 +777,21 @@ 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
|
||||
* if the return is nullptr, and if not, if the node->peekItem()->key() ==
|
||||
* id
|
||||
* Walk towards the specified id, returning the node.
|
||||
*
|
||||
* @param id the key to walk towards, which need not be in the map.
|
||||
* @param stack records the path walked, or nullptr to skip recording it.
|
||||
* Lookups that only want the leaf (see findKey) omit it to
|
||||
* avoid building a path they would immediately discard.
|
||||
* @return the leaf the walk ended on, or nullptr if it ended on an inner
|
||||
* node or was refused. A returned leaf need not hold `id`, so
|
||||
* callers compare its key themselves.
|
||||
*/
|
||||
SHAMapLeafNode*
|
||||
walkTowardsKey(uint256 const& id, SharedPtrNodeStack* stack = nullptr) const;
|
||||
walkTowardsKey(uint256 const& id, NodePathStack* stack = nullptr) const;
|
||||
/**
|
||||
* Return nullptr if key not found
|
||||
*/
|
||||
@@ -464,10 +800,17 @@ private:
|
||||
|
||||
/**
|
||||
* Unshare the node, allowing it to be modified
|
||||
*
|
||||
* @param node the node to unshare.
|
||||
* @param depth the depth the node sits at, which says whether it is the
|
||||
* root. A clone of the root has to be adopted as the new root; a
|
||||
* clone of any other node is hooked up by the caller walking back
|
||||
* up the path.
|
||||
* @return the node, cloned if it was shared.
|
||||
*/
|
||||
template <class Node>
|
||||
intr_ptr::SharedPtr<Node>
|
||||
unshareNode(intr_ptr::SharedPtr<Node>, SHAMapNodeID const& nodeID);
|
||||
unshareNode(intr_ptr::SharedPtr<Node> node, unsigned int depth);
|
||||
|
||||
/**
|
||||
* prepare a node to be modified before flushing
|
||||
@@ -482,27 +825,33 @@ 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.
|
||||
*
|
||||
* @param stack the path to extend, whose last node the search starts from.
|
||||
* @param direction whether to take the lowest or the highest branch at
|
||||
* each level.
|
||||
* @return the leaf found, or nullptr if no leaf lies below that node.
|
||||
*/
|
||||
SHAMapLeafNode*
|
||||
belowHelper(
|
||||
SHAMapTreeNodePtr node,
|
||||
SharedPtrNodeStack& stack,
|
||||
unsigned int branch,
|
||||
BelowDirection direction) const;
|
||||
belowHelper(NodePathStack& stack, BelowDirection direction) const;
|
||||
|
||||
/**
|
||||
* The nearest item on one side of `id`, which upperBound and lowerBound
|
||||
* both answer.
|
||||
*
|
||||
* @param id the key to search around, which need not be in the map.
|
||||
* @param direction First for the nearest key greater than `id`, Last for
|
||||
* the nearest lesser.
|
||||
* @return an iterator at that item, or end() if the map holds no key on
|
||||
* that side.
|
||||
*/
|
||||
[[nodiscard]] ConstIterator
|
||||
boundHelper(uint256 const& id, BelowDirection direction) const;
|
||||
|
||||
// Simple descent
|
||||
// Get a child of the specified node
|
||||
@@ -550,9 +899,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,
|
||||
@@ -626,10 +975,30 @@ private:
|
||||
};
|
||||
|
||||
// getMissingNodes helper functions
|
||||
|
||||
/**
|
||||
* Examine the remaining branches of one inner node, recording or
|
||||
* requesting what is missing.
|
||||
*
|
||||
* @param mn the walk's shared state, which collects the missing nodes.
|
||||
* @param node the walk's current position, updated to the node to process
|
||||
* next.
|
||||
*/
|
||||
void
|
||||
gmnProcessNodes(MissingNodes&, MissingNodes::StackEntry& node);
|
||||
static void
|
||||
gmnProcessDeferredReads(MissingNodes&);
|
||||
gmnProcessNodes(MissingNodes& mn, MissingNodes::StackEntry& node);
|
||||
|
||||
/**
|
||||
* Wait for every read this pass posted, then hook up or record what each
|
||||
* one resolved.
|
||||
*
|
||||
* Drains all of them even after judging the map, since an outstanding
|
||||
* read holds a pointer to `mn` and this is the only thing that waits for
|
||||
* it.
|
||||
*
|
||||
* @param mn the walk's shared state, holding the posted reads.
|
||||
*/
|
||||
void
|
||||
gmnProcessDeferredReads(MissingNodes& mn);
|
||||
|
||||
// fetch from DB helper function
|
||||
SHAMapTreeNodePtr
|
||||
@@ -697,7 +1066,7 @@ public:
|
||||
using pointer = value_type const*;
|
||||
|
||||
private:
|
||||
SharedPtrNodeStack stack_;
|
||||
NodePathStack stack_;
|
||||
SHAMap const* map_ = nullptr;
|
||||
pointer item_ = nullptr;
|
||||
|
||||
@@ -723,7 +1092,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 +1111,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)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -19,7 +19,8 @@ class SHAMapInnerNode final : public SHAMapTreeNode, public CountedObject<SHAMap
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Each inner node has 16 children (the 'radix tree' part of the map)
|
||||
* Each inner node has 16 children, which is the fan-out of the trie: one
|
||||
* branch per value of the key nibble that level selects on.
|
||||
*/
|
||||
static constexpr unsigned int kBranchFactor = 16;
|
||||
|
||||
|
||||
@@ -75,4 +75,22 @@ leafKey(SHAMapTreeNode const& node)
|
||||
return safeDowncast<SHAMapLeafNode const&>(node).peekItem()->key();
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a node may occupy a position in a SHAMap.
|
||||
*
|
||||
* A leaf's own key names its position, so an ID that is not a prefix of that
|
||||
* key names a different subtree than the one the leaf belongs to. An inner
|
||||
* node carries no key, so every position is consistent with it and the
|
||||
* caller's own depth rules are what bound it.
|
||||
*
|
||||
* @param nodeID the position the node is claimed to occupy.
|
||||
* @param node the node to judge.
|
||||
* @return whether the node's own key agrees with that position.
|
||||
*/
|
||||
[[nodiscard]] inline bool
|
||||
belongsAt(SHAMapNodeID const& nodeID, SHAMapTreeNode const& node)
|
||||
{
|
||||
return !node.isLeaf() || nodeID.isPrefixOf(leafKey(node));
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -144,9 +144,31 @@ deserializeSHAMapNodeID(std::string_view s)
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* Returns the branch that would contain the given hash
|
||||
* Returns the branch at the given depth that would contain the given hash
|
||||
*
|
||||
* Only the depth of a position matters here, since the nibble selected is read
|
||||
* from `hash`. Callers holding a depth rather than a whole ID use this one.
|
||||
*
|
||||
* @param depth the depth of the node whose branch to select.
|
||||
* @param hash the key whose nibble at that depth names the branch.
|
||||
* @return the branch containing the hash.
|
||||
*/
|
||||
[[nodiscard]] unsigned int
|
||||
selectBranch(SHAMapNodeID const& id, uint256 const& hash);
|
||||
selectBranch(unsigned int depth, uint256 const& hash);
|
||||
|
||||
/**
|
||||
* Returns the branch that would contain the given hash
|
||||
*
|
||||
* Reads only the depth of `id`, never its own key bits.
|
||||
*
|
||||
* @param id the node whose depth to read.
|
||||
* @param hash the key whose nibble at that depth names the branch.
|
||||
* @return the branch containing the hash.
|
||||
*/
|
||||
[[nodiscard]] inline unsigned int
|
||||
selectBranch(SHAMapNodeID const& id, uint256 const& hash)
|
||||
{
|
||||
return selectBranch(id.getDepth(), hash);
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -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,59 @@ 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
|
||||
setMirrorEpochs(SLE const& issuance, SLE& mptoken)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
issuance.getType() == ltMPTOKEN_ISSUANCE,
|
||||
"xrpl::setMirrorEpochs : issuance MPTokenIssuance object");
|
||||
XRPL_ASSERT(mptoken.getType() == ltMPTOKEN, "xrpl::setMirrorEpochs : mptoken MPToken object");
|
||||
|
||||
if (auto const epoch = issuance[~sfIssuerKeyEpoch].value_or(0); epoch != 0)
|
||||
mptoken[sfIssuerKeyMirrorEpoch] = epoch;
|
||||
|
||||
if (mptoken.isFieldPresent(sfAuditorEncryptedBalance))
|
||||
{
|
||||
if (auto const epoch = issuance[~sfAuditorKeyEpoch].value_or(0); epoch != 0)
|
||||
mptoken[sfAuditorKeyMirrorEpoch] = epoch;
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
@@ -111,14 +111,20 @@ SHAMap::dirtyUp(SharedPtrNodeStack& stack, uint256 const& target, SHAMapTreeNode
|
||||
|
||||
while (!stack.empty())
|
||||
{
|
||||
auto node = intr_ptr::dynamicPointerCast<SHAMapInnerNode>(stack.top().first);
|
||||
SHAMapNodeID const nodeID = stack.top().second;
|
||||
stack.pop();
|
||||
XRPL_ASSERT(node, "xrpl::SHAMap::dirtyUp : non-null node");
|
||||
auto const depth = stack.topDepth();
|
||||
auto top = stack.releaseNode();
|
||||
if (!top->isInner())
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
UNREACHABLE("xrpl::SHAMap::dirtyUp : node is not inner");
|
||||
Throw<SHAMapMissingNode>(type_, target);
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
auto node = intr_ptr::staticPointerCast<SHAMapInnerNode>(std::move(top));
|
||||
|
||||
auto const branch = selectBranch(nodeID, target);
|
||||
auto const branch = selectBranch(depth, target);
|
||||
|
||||
node = unshareNode(std::move(node), nodeID);
|
||||
node = unshareNode(std::move(node), depth);
|
||||
node->setChild(branch, std::move(child));
|
||||
|
||||
child = std::move(node);
|
||||
@@ -126,29 +132,71 @@ 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");
|
||||
if (stack != nullptr && !stack->empty())
|
||||
{
|
||||
// A plain XRPL_ASSERT here is a no-op under NDEBUG; without this guard a non-empty stack
|
||||
// would be appended to below, leaving the caller with a path that starts mid-walk instead
|
||||
// of at the root.
|
||||
// LCOV_EXCL_START
|
||||
UNREACHABLE("xrpl::SHAMap::walkTowardsKey : non-empty stack input");
|
||||
stack->clear();
|
||||
return nullptr;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
auto inNode = root_;
|
||||
SHAMapNodeID nodeID;
|
||||
unsigned int noStackDepth = 0;
|
||||
|
||||
// Without a caller-supplied stack, `noStackDepth` is the only record of position, so it is
|
||||
// counted directly here instead of read back from a push. A push fails when the map is
|
||||
// malformed, by holding a leaf outside the branch it was reached through or a node with no room
|
||||
// left below it, not because `id` is merely absent; the stack is cleared rather than left
|
||||
// holding a node that never became a real path entry. Callers tell the two apart by the path,
|
||||
// which is empty only in the first case.
|
||||
auto pushCurrent = [&]() -> bool {
|
||||
if (stack == nullptr || stack->pushNode(inNode, id))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
stack->clear();
|
||||
return false;
|
||||
};
|
||||
|
||||
while (inNode->isInner())
|
||||
{
|
||||
if (stack != nullptr)
|
||||
stack->emplace(inNode, nodeID);
|
||||
if (!pushCurrent())
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto const inner = intr_ptr::staticPointerCast<SHAMapInnerNode>(inNode);
|
||||
auto const branch = selectBranch(nodeID, id);
|
||||
if (inner->isEmptyBranch(branch))
|
||||
auto& inner = safeDowncast<SHAMapInnerNode&>(*inNode);
|
||||
auto const depth = stack != nullptr ? stack->topDepth() : noStackDepth;
|
||||
auto const branch = selectBranch(depth, id);
|
||||
if (inner.isEmptyBranch(branch))
|
||||
return nullptr;
|
||||
|
||||
inNode = descendThrow(*inner, branch);
|
||||
nodeID = nodeID.getChildNodeID(branch);
|
||||
inNode = descendThrow(inner, branch);
|
||||
if (stack == nullptr)
|
||||
{
|
||||
// Shares pastLeafDepth with pushChild, so this mode and the one with a
|
||||
// caller-supplied path refuse at the same node. Reachable for the reason that helper
|
||||
// gives, so it refuses rather than aborts.
|
||||
bool const tooDeep = pastLeafDepth(depth, *inNode);
|
||||
SOMETIMES(tooDeep, "xrpl::SHAMap::walkTowardsKey : child too deep");
|
||||
if (tooDeep)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
++noStackDepth;
|
||||
}
|
||||
}
|
||||
|
||||
if (stack != nullptr)
|
||||
stack->emplace(inNode, nodeID);
|
||||
if (!pushCurrent())
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
return safeDowncast<SHAMapLeafNode*>(inNode.get());
|
||||
}
|
||||
|
||||
@@ -352,12 +400,29 @@ SHAMap::descend(
|
||||
!parent->isEmptyBranch(branch), "xrpl::SHAMap::descend : parent branch is non-empty");
|
||||
|
||||
SHAMapTreeNode* child = parent->getChildPointer(branch); // NOLINT(misc-const-correctness)
|
||||
auto childID = parentID.getChildNodeID(branch);
|
||||
|
||||
if (child == nullptr)
|
||||
{
|
||||
auto const& childHash = parent->getChildHash(branch);
|
||||
SHAMapTreeNodePtr childNode = fetchNodeNT(childHash, filter);
|
||||
|
||||
if (childNode && !belongsAt(childID, *childNode))
|
||||
{
|
||||
// A node arriving through the filter is judged by hash, and a hash covers a node's
|
||||
// contents rather than its position, so this is where a leaf that belongs elsewhere
|
||||
// enters the map. Judged before canonicalizeChild, after which every later walk would
|
||||
// see it as part of the tree.
|
||||
//
|
||||
// The map is the verdict rather than the node, because refusing one node would only
|
||||
// make the walk fetch the same thing again: the filter answers from a local cache, so
|
||||
// the next attempt resolves the same blob to the same place.
|
||||
JLOG(journal_.warn()) << "Leaf " << childHash << " does not belong at " << childID
|
||||
<< ", map is invalid";
|
||||
state_ = SHAMapState::Invalid;
|
||||
return std::make_pair(nullptr, std::move(childID));
|
||||
}
|
||||
|
||||
if (childNode)
|
||||
{
|
||||
childNode = parent->canonicalizeChild(branch, std::move(childNode));
|
||||
@@ -365,7 +430,7 @@ SHAMap::descend(
|
||||
}
|
||||
}
|
||||
|
||||
return std::make_pair(child, parentID.getChildNodeID(branch));
|
||||
return std::make_pair(child, std::move(childID));
|
||||
}
|
||||
|
||||
SHAMapTreeNode*
|
||||
@@ -412,7 +477,7 @@ SHAMap::descendAsync(
|
||||
|
||||
template <class Node>
|
||||
intr_ptr::SharedPtr<Node>
|
||||
SHAMap::unshareNode(intr_ptr::SharedPtr<Node> node, SHAMapNodeID const& nodeID)
|
||||
SHAMap::unshareNode(intr_ptr::SharedPtr<Node> node, unsigned int depth)
|
||||
{
|
||||
// make sure the node is suitable for the intended operation (copy on write)
|
||||
XRPL_ASSERT(node->cowid() <= cowid_, "xrpl::SHAMap::unshareNode : node valid for cowid");
|
||||
@@ -421,72 +486,72 @@ SHAMap::unshareNode(intr_ptr::SharedPtr<Node> node, SHAMapNodeID const& nodeID)
|
||||
// have a CoW
|
||||
XRPL_ASSERT(state_ != SHAMapState::Immutable, "xrpl::SHAMap::unshareNode : not immutable");
|
||||
node = intr_ptr::staticPointerCast<Node>(node->clone(cowid_));
|
||||
if (nodeID.isRoot())
|
||||
if (depth == 0)
|
||||
root_ = node;
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
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);
|
||||
XRPL_ASSERT(!stack.empty(), "xrpl::SHAMap::belowHelper : non-empty stack input");
|
||||
if (stack.empty())
|
||||
{
|
||||
stack.emplace(inner, SHAMapNodeID{});
|
||||
// LCOV_EXCL_START
|
||||
return nullptr;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
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.
|
||||
if (auto const& top = stack.top(); top->isLeaf())
|
||||
return safeDowncast<SHAMapLeafNode*>(top.get());
|
||||
|
||||
// The path names each node's position, so descending is only ever "push the node we reached".
|
||||
// `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().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;
|
||||
}
|
||||
|
||||
auto const parentDepth = stack.topDepth();
|
||||
auto descended = descendThrow(*inner, childBranch);
|
||||
if (!stack.pushChild(std::move(descended), childBranch))
|
||||
{
|
||||
// A refused push means the map holds a node that cannot be walked, which is not the
|
||||
// same as a subtree with no leaf below it. Throwing keeps nullptr meaning only the
|
||||
// latter, so begin() cannot report such a map as empty while an iterator increment
|
||||
// throws on the same condition. SHAMapMissingNode describes a resident node poorly,
|
||||
// but descendThrow above throws it too, so every caller already handles it.
|
||||
//
|
||||
// The map is deliberately NOT condemned here. Every caller of belowHelper is a const
|
||||
// read on an immutable snapshot, called from several RPC threads at once, and no
|
||||
// reader checks isValid(); the callers that do are on the acquisition path. So the
|
||||
// write would buy nothing, would race those readers, and would make a later compare()
|
||||
// trip its own isValid() assertion. A map from peer data is judged where it is
|
||||
// assembled (see SHAMap::descend and gmnProcessNodes).
|
||||
JLOG(journal_.warn()) << "Cannot walk below depth " << parentDepth << " at branch "
|
||||
<< childBranch;
|
||||
Throw<SHAMapMissingNode>(type_, inner->getChildHash(childBranch));
|
||||
}
|
||||
|
||||
auto const& child = stack.top();
|
||||
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 +594,53 @@ 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);
|
||||
if (!stack.pushRoot(root_))
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
return nullptr;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
SHAMapLeafNode const* node = belowHelper(stack, BelowDirection::First);
|
||||
if (node == nullptr)
|
||||
{
|
||||
while (!stack.empty())
|
||||
stack.pop();
|
||||
// Whether the map was empty or belowHelper's walk otherwise failed to find a leaf, the
|
||||
// stack is cleared rather than left holding a partial path the caller cannot use.
|
||||
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");
|
||||
if (stack.empty())
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
return nullptr;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
XRPL_ASSERT(stack.top()->isLeaf(), "xrpl::SHAMap::peekNextItem : stack starts with leaf");
|
||||
stack.pop();
|
||||
while (!stack.empty())
|
||||
{
|
||||
auto [node, nodeID] = stack.top();
|
||||
auto const& node = stack.top();
|
||||
XRPL_ASSERT(!node->isLeaf(), "xrpl::SHAMap::peekNextItem : another node is not leaf");
|
||||
auto inner = intr_ptr::staticPointerCast<SHAMapInnerNode>(node);
|
||||
for (auto i = selectBranch(nodeID, id) + 1; i < kBranchFactor; ++i)
|
||||
auto& inner = safeDowncast<SHAMapInnerNode&>(*node);
|
||||
for (auto i = selectBranch(stack.topDepth(), id) + 1; i < kBranchFactor; ++i)
|
||||
{
|
||||
if (!inner->isEmptyBranch(i))
|
||||
if (!inner.isEmptyBranch(i))
|
||||
{
|
||||
node = descendThrow(*inner, i);
|
||||
auto leaf = firstBelow(node, stack, i);
|
||||
auto child = descendThrow(inner, i);
|
||||
if (!stack.pushChild(std::move(child), i))
|
||||
{
|
||||
Throw<SHAMapMissingNode>(type_, id);
|
||||
}
|
||||
auto leaf = belowHelper(stack, BelowDirection::First);
|
||||
if (leaf == nullptr)
|
||||
Throw<SHAMapMissingNode>(type_, id);
|
||||
XRPL_ASSERT(leaf->isLeaf(), "xrpl::SHAMap::peekNextItem : leaf is valid");
|
||||
@@ -595,72 +677,72 @@ SHAMap::peekItem(uint256 const& id, SHAMapHash& hash) const
|
||||
}
|
||||
|
||||
SHAMap::ConstIterator
|
||||
SHAMap::upperBound(uint256 const& id) const
|
||||
SHAMap::boundHelper(uint256 const& id, BelowDirection direction) const
|
||||
{
|
||||
SharedPtrNodeStack stack;
|
||||
// Walk back up the path to `id` looking for the nearest leaf on the requested side. At each
|
||||
// inner node the candidates are the branches on that side of the one `id` takes: the higher
|
||||
// ones searching forward, the lower ones searching back. The nearest non-empty candidate holds
|
||||
// the answer, which is its lowest leaf searching forward and its highest searching back.
|
||||
auto const searchingForward = direction == BelowDirection::First;
|
||||
|
||||
NodePathStack stack;
|
||||
walkTowardsKey(id, &stack);
|
||||
|
||||
// An empty path means the walk refused a node, not that the map is empty: an empty map still
|
||||
// leaves its root on the path. end() is the positive claim that no key lies on the requested
|
||||
// side of `id`, so it must not stand in for "cannot answer", which is what every other entry
|
||||
// point reports by throwing.
|
||||
if (stack.empty())
|
||||
Throw<SHAMapMissingNode>(type_, id);
|
||||
|
||||
while (!stack.empty())
|
||||
{
|
||||
auto [node, nodeID] = stack.top();
|
||||
auto const& node = stack.top();
|
||||
if (node->isLeaf())
|
||||
{
|
||||
auto leaf = safeDowncast<SHAMapLeafNode*>(node.get());
|
||||
if (leaf->peekItem()->key() > id)
|
||||
return ConstIterator(this, leaf->peekItem().get(), std::move(stack));
|
||||
auto const& item = safeDowncast<SHAMapLeafNode const&>(*node).peekItem();
|
||||
if (searchingForward ? (item->key() > id) : (item->key() < id))
|
||||
return ConstIterator(this, item.get(), std::move(stack));
|
||||
}
|
||||
else
|
||||
{
|
||||
auto inner = intr_ptr::staticPointerCast<SHAMapInnerNode>(node);
|
||||
for (auto branch = selectBranch(nodeID, id) + 1; branch < kBranchFactor; ++branch)
|
||||
auto& inner = safeDowncast<SHAMapInnerNode&>(*node);
|
||||
auto const taken = selectBranch(stack.topDepth(), id);
|
||||
auto const remaining = searchingForward ? (kBranchFactor - 1u - taken) : taken;
|
||||
|
||||
for (auto scanned = 0u; scanned < remaining; ++scanned)
|
||||
{
|
||||
if (!inner->isEmptyBranch(branch))
|
||||
auto const branch =
|
||||
searchingForward ? (taken + 1u + scanned) : (taken - 1u - scanned);
|
||||
if (inner.isEmptyBranch(branch))
|
||||
continue;
|
||||
|
||||
auto child = descendThrow(inner, branch);
|
||||
if (!stack.pushChild(std::move(child), branch))
|
||||
{
|
||||
node = descendThrow(*inner, branch);
|
||||
auto leaf = firstBelow(node, stack, branch);
|
||||
if (leaf == nullptr)
|
||||
Throw<SHAMapMissingNode>(type_, id);
|
||||
return ConstIterator(this, leaf->peekItem().get(), std::move(stack));
|
||||
Throw<SHAMapMissingNode>(type_, id);
|
||||
}
|
||||
auto const leaf = belowHelper(stack, direction);
|
||||
if (leaf == nullptr)
|
||||
Throw<SHAMapMissingNode>(type_, id);
|
||||
return ConstIterator(this, leaf->peekItem().get(), std::move(stack));
|
||||
}
|
||||
}
|
||||
stack.pop();
|
||||
}
|
||||
return end();
|
||||
}
|
||||
|
||||
SHAMap::ConstIterator
|
||||
SHAMap::upperBound(uint256 const& id) const
|
||||
{
|
||||
return boundHelper(id, BelowDirection::First);
|
||||
}
|
||||
|
||||
SHAMap::ConstIterator
|
||||
SHAMap::lowerBound(uint256 const& id) const
|
||||
{
|
||||
SharedPtrNodeStack stack;
|
||||
walkTowardsKey(id, &stack);
|
||||
while (!stack.empty())
|
||||
{
|
||||
auto [node, nodeID] = stack.top();
|
||||
if (node->isLeaf())
|
||||
{
|
||||
auto leaf = safeDowncast<SHAMapLeafNode*>(node.get());
|
||||
if (leaf->peekItem()->key() < id)
|
||||
return ConstIterator(this, leaf->peekItem().get(), std::move(stack));
|
||||
}
|
||||
else
|
||||
{
|
||||
auto inner = intr_ptr::staticPointerCast<SHAMapInnerNode>(node);
|
||||
for (auto branch = selectBranch(nodeID, id); branch > 0u;)
|
||||
{
|
||||
--branch;
|
||||
if (!inner->isEmptyBranch(branch))
|
||||
{
|
||||
node = descendThrow(*inner, branch);
|
||||
auto leaf = lastBelow(node, stack, branch);
|
||||
if (leaf == nullptr)
|
||||
Throw<SHAMapMissingNode>(type_, id);
|
||||
return ConstIterator(this, leaf->peekItem().get(), std::move(stack));
|
||||
}
|
||||
}
|
||||
}
|
||||
stack.pop();
|
||||
}
|
||||
// TODO: what to return here?
|
||||
return end();
|
||||
return boundHelper(id, BelowDirection::Last);
|
||||
}
|
||||
|
||||
bool
|
||||
@@ -675,16 +757,21 @@ 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())
|
||||
Throw<SHAMapMissingNode>(type_, id);
|
||||
|
||||
auto leaf = intr_ptr::dynamicPointerCast<SHAMapLeafNode>(stack.top().first);
|
||||
stack.pop();
|
||||
// An absent id leaves an inner node on top rather than a leaf, which is a "not found" answer
|
||||
// and not a fault, so it is tested rather than cast through. Matches the three sibling sites in
|
||||
// this commit, so no traversal path is left paying for a dynamic_cast.
|
||||
auto top = stack.releaseNode();
|
||||
if (!top->isLeaf())
|
||||
return false;
|
||||
auto leaf = intr_ptr::staticPointerCast<SHAMapLeafNode>(std::move(top));
|
||||
|
||||
if (!leaf || (leaf->peekItem()->key() != id))
|
||||
if (leaf->peekItem()->key() != id)
|
||||
return false;
|
||||
|
||||
SHAMapNodeType const type = leaf->getType();
|
||||
@@ -694,19 +781,26 @@ SHAMap::delItem(uint256 const& id)
|
||||
|
||||
while (!stack.empty())
|
||||
{
|
||||
auto node = intr_ptr::staticPointerCast<SHAMapInnerNode>(stack.top().first);
|
||||
SHAMapNodeID const nodeID = stack.top().second;
|
||||
stack.pop();
|
||||
auto const depth = stack.topDepth();
|
||||
auto top = stack.releaseNode();
|
||||
if (!top->isInner())
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
UNREACHABLE("xrpl::SHAMap::delItem : node is not inner");
|
||||
Throw<SHAMapMissingNode>(type_, id);
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
auto node = intr_ptr::staticPointerCast<SHAMapInnerNode>(std::move(top));
|
||||
|
||||
node = unshareNode(std::move(node), nodeID);
|
||||
node = unshareNode(std::move(node), depth);
|
||||
node->setChild(
|
||||
selectBranch(nodeID, id), std::move(prevNode)); // NOLINT(bugprone-use-after-move)
|
||||
selectBranch(depth, id), std::move(prevNode)); // NOLINT(bugprone-use-after-move)
|
||||
|
||||
XRPL_ASSERT(
|
||||
not prevNode, // NOLINT(bugprone-use-after-move)
|
||||
"xrpl::SHAMap::delItem : prevNode should be nullptr after std::move");
|
||||
|
||||
if (!nodeID.isRoot())
|
||||
if (depth != 0)
|
||||
{
|
||||
// we may have made this a node with 1 or 0 children
|
||||
// And, if so, we need to remove this branch
|
||||
@@ -761,14 +855,14 @@ 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())
|
||||
Throw<SHAMapMissingNode>(type_, tag);
|
||||
|
||||
auto [node, nodeID] = stack.top();
|
||||
stack.pop();
|
||||
auto depth = stack.topDepth();
|
||||
auto node = stack.releaseNode();
|
||||
|
||||
if (node->isLeaf())
|
||||
{
|
||||
@@ -776,12 +870,12 @@ SHAMap::addGiveItem(SHAMapNodeType type, boost::intrusive_ptr<SHAMapItem const>
|
||||
if (leaf->peekItem()->key() == tag)
|
||||
return false;
|
||||
}
|
||||
node = unshareNode(std::move(node), nodeID);
|
||||
node = unshareNode(std::move(node), depth);
|
||||
if (node->isInner())
|
||||
{
|
||||
// easy case, we end on an inner node
|
||||
auto inner = intr_ptr::staticPointerCast<SHAMapInnerNode>(node);
|
||||
auto const branch = selectBranch(nodeID, tag);
|
||||
auto const branch = selectBranch(depth, tag);
|
||||
XRPL_ASSERT(
|
||||
inner->isEmptyBranch(branch), "xrpl::SHAMap::addGiveItem : inner branch is empty");
|
||||
inner->setChild(branch, makeTypedLeaf(type, std::move(item), cowid_));
|
||||
@@ -799,13 +893,22 @@ SHAMap::addGiveItem(SHAMapNodeType type, boost::intrusive_ptr<SHAMapItem const>
|
||||
|
||||
auto b1 = 0u, b2 = 0u;
|
||||
|
||||
while ((b1 = selectBranch(nodeID, tag)) == (b2 = selectBranch(nodeID, otherItem->key())))
|
||||
while ((b1 = selectBranch(depth, tag)) == (b2 = selectBranch(depth, otherItem->key())))
|
||||
{
|
||||
stack.emplace(node, nodeID);
|
||||
if (!stack.pushNode(node, tag))
|
||||
{
|
||||
// The node pushed here is freshly made and inner, so only the depth bound could
|
||||
// refuse it, and the loop cannot reach that bound: it advances only while the two
|
||||
// keys agree at the current nibble, and keys agreeing at all 64 nibbles are equal,
|
||||
// which the caller already returned false for.
|
||||
// LCOV_EXCL_START
|
||||
Throw<SHAMapMissingNode>(type_, tag);
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
// we need a new inner node, since both go on same branch at this
|
||||
// level
|
||||
nodeID = nodeID.getChildNodeID(b1);
|
||||
++depth;
|
||||
node = intr_ptr::makeShared<SHAMapInnerNode>(cowid_);
|
||||
}
|
||||
|
||||
@@ -848,22 +951,32 @@ 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())
|
||||
Throw<SHAMapMissingNode>(type_, tag);
|
||||
|
||||
auto node = intr_ptr::dynamicPointerCast<SHAMapLeafNode>(stack.top().first);
|
||||
auto nodeID = stack.top().second;
|
||||
stack.pop();
|
||||
auto const depth = stack.topDepth();
|
||||
auto top = stack.releaseNode();
|
||||
|
||||
if (!node || (node->peekItem()->key() != tag))
|
||||
// walkTowardsKey pushes an inner node's own entry before testing whether the branch it needs
|
||||
// is empty, so a tag absent from the map leaves that inner node on top rather than a leaf.
|
||||
// No in-tree caller reaches this, since each checks the item exists first, but the API is
|
||||
// public and permits the call, which is why it returns false rather than reporting UNREACHABLE.
|
||||
// The static cast below is also safe only once this is confirmed.
|
||||
if (!top->isLeaf())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
auto node = intr_ptr::staticPointerCast<SHAMapLeafNode>(std::move(top));
|
||||
|
||||
// The other shape an absent tag takes: the walk ends on the leaf it reached, whose key need not
|
||||
// be `tag`, which is why findKey discards such a leaf. Both shapes mean the same thing to a
|
||||
// caller, so both answer false rather than reporting UNREACHABLE.
|
||||
if (node->peekItem()->key() != tag)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
UNREACHABLE("xrpl::SHAMap::updateGiveItem : invalid node");
|
||||
return false;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
if (node->getType() != type)
|
||||
@@ -872,7 +985,7 @@ SHAMap::updateGiveItem(SHAMapNodeType type, boost::intrusive_ptr<SHAMapItem cons
|
||||
return false;
|
||||
}
|
||||
|
||||
node = unshareNode(std::move(node), nodeID);
|
||||
node = unshareNode(std::move(node), depth);
|
||||
|
||||
if (node->setItem(item))
|
||||
dirtyUp(stack, tag, node);
|
||||
@@ -1170,7 +1283,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))
|
||||
;
|
||||
|
||||
@@ -144,16 +144,18 @@ deserializeSHAMapNodeID(void const* data, std::size_t size)
|
||||
}
|
||||
|
||||
[[nodiscard]] unsigned int
|
||||
selectBranch(SHAMapNodeID const& id, uint256 const& hash)
|
||||
selectBranch(unsigned int depth, uint256 const& hash)
|
||||
{
|
||||
XRPL_ASSERT(id.getDepth() < SHAMap::kLeafDepth, "xrpl::selectBranch : depth below leaf depth");
|
||||
XRPL_ASSERT(depth < SHAMap::kLeafDepth, "xrpl::selectBranch : depth below leaf depth");
|
||||
|
||||
// A depth-64 ID has no nibble left to select. Callers must not ask, but clamp anyway to keep
|
||||
// the read below the end of the 32-byte key.
|
||||
auto const depth = std::min(id.getDepth(), SHAMap::kLeafDepth - 1u);
|
||||
auto branch = static_cast<unsigned int>(*(hash.begin() + (depth / 2)));
|
||||
// A depth-64 position has no nibble left to select. Callers must not ask, but clamp anyway to
|
||||
// keep the read below the end of the 32-byte key.
|
||||
auto const clamped = std::min(depth, SHAMap::kLeafDepth - 1u);
|
||||
auto branch = static_cast<unsigned int>(*(hash.begin() + (clamped / 2)));
|
||||
|
||||
if ((depth & 1) != 0u)
|
||||
// Both reads take the clamped depth. Taking the byte from one and the nibble from the other
|
||||
// would select the high nibble at depth 64 where depth 63 selects the low one.
|
||||
if ((clamped & 1) != 0u)
|
||||
{
|
||||
branch &= 0xf;
|
||||
}
|
||||
|
||||
@@ -238,6 +238,28 @@ SHAMap::gmnProcessNodes(MissingNodes& mn, MissingNodes::StackEntry& se)
|
||||
if (--mn.max <= 0)
|
||||
return;
|
||||
}
|
||||
// Only a leaf has a position of its own to judge, so the type is tested first: that
|
||||
// also keeps getChildNodeID, which builds a SHAMapNodeID, off every inner child on the
|
||||
// walk. The depth is tested next so the ID is only asked for a child that can exist.
|
||||
else if (
|
||||
d->isLeaf() && nodeID.getDepth() < kLeafDepth &&
|
||||
!belongsAt(nodeID.getChildNodeID(branch), *d))
|
||||
{
|
||||
// The same judgment SHAMap::descend makes, for the path that consults the filter
|
||||
// through descendAsync instead. descendAsync hooks what it resolves, so the node is
|
||||
// already part of the tree and refusing it here would not remove it.
|
||||
//
|
||||
// `fullBelow` is cleared first, as on the missing-node path above. It is a
|
||||
// reference into the caller's stack entry, and this node is left on that stack, so
|
||||
// a later pass over its remaining branches would otherwise reach the full-below
|
||||
// test with it still set and record this subtree's hash as complete in the
|
||||
// family-wide cache, where another map would trust it.
|
||||
JLOG(journal_.warn()) << "Leaf " << childHash << " does not belong below " << nodeID
|
||||
<< " at branch " << branch << ", map is invalid";
|
||||
fullBelow = false;
|
||||
state_ = SHAMapState::Invalid;
|
||||
return;
|
||||
}
|
||||
else if (d->isInner() && !safeDowncast<SHAMapInnerNode*>(d)->isFullBelow(mn.generation))
|
||||
{
|
||||
mn.stack.push(se);
|
||||
@@ -291,6 +313,29 @@ SHAMap::gmnProcessDeferredReads(MissingNodes& mn)
|
||||
auto nodePtr = std::get<3>(deferredNode);
|
||||
auto const& nodeHash = parent->getChildHash(branch);
|
||||
|
||||
// Guarded on depth for the same reason as the sibling test in gmnProcessNodes: a deferred
|
||||
// entry carries the position the walk held when it posted the read, and the `pending`
|
||||
// branch there records that position without building a child ID from it. So a child ID is
|
||||
// asked for here only where the tree has room for one, which is the bound getChildNodeID
|
||||
// keeps for itself.
|
||||
if (nodePtr && nodePtr->isLeaf() && parentID.getDepth() < kLeafDepth &&
|
||||
!belongsAt(parentID.getChildNodeID(branch), *nodePtr))
|
||||
{
|
||||
// The same judgment the two synchronous paths make (see SHAMap::descend and the
|
||||
// descendAsync case in gmnProcessNodes), for a node an async read resolved. Every site
|
||||
// that knows the position a node is about to take judges it here, which is what lets
|
||||
// the traversal treat a misplaced leaf as a rarity rather than a routine case.
|
||||
//
|
||||
// Skips this node rather than returning: the reads still outstanding hold a pointer to
|
||||
// `mn`, which lives in getMissingNodes' frame, and this loop is the only thing that
|
||||
// waits for them. Returning early would let that frame go while a read was still due
|
||||
// to write through it.
|
||||
JLOG(journal_.warn()) << "Leaf " << nodeHash << " does not belong below " << parentID
|
||||
<< " at branch " << branch << ", map is invalid";
|
||||
state_ = SHAMapState::Invalid;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (nodePtr)
|
||||
{ // Got the node
|
||||
nodePtr = parent->canonicalizeChild(branch, std::move(nodePtr));
|
||||
@@ -328,10 +373,15 @@ SHAMap::getMissingNodes(int max, SHAMapSyncFilter const* filter)
|
||||
512, // number of async reads per pass
|
||||
f_.getFullBelowCache()->getGeneration());
|
||||
|
||||
// Guarded with isValid() for the same reason the late return below is: clearSynching() moves
|
||||
// the state to Modifying, which would erase a verdict an earlier walk already reached. No path
|
||||
// to that was found, since every site that condemns the map also clears the fullBelow flag this
|
||||
// return reads, but the rule holds either way and one conjunct is what it costs.
|
||||
if (!root_->isInner() ||
|
||||
intr_ptr::staticPointerCast<SHAMapInnerNode>(root_)->isFullBelow(mn.generation))
|
||||
{
|
||||
clearSynching();
|
||||
if (isValid())
|
||||
clearSynching();
|
||||
return std::move(mn.missingNodes);
|
||||
}
|
||||
|
||||
@@ -416,7 +466,11 @@ SHAMap::getMissingNodes(int max, SHAMapSyncFilter const* filter)
|
||||
|
||||
} while (node != nullptr);
|
||||
|
||||
if (mn.missingNodes.empty())
|
||||
// An empty result does not mean the map is complete when the walk judged it impossible on the
|
||||
// way down: clearSynching() moves the state to Modifying, which would erase that verdict and
|
||||
// report the map as satisfied. Asking nothing is the only part this has to get right, since
|
||||
// clearSynching() is what a later walk would read.
|
||||
if (mn.missingNodes.empty() && isValid())
|
||||
clearSynching();
|
||||
|
||||
return std::move(mn.missingNodes);
|
||||
@@ -569,10 +623,6 @@ SHAMap::addKnownNode(
|
||||
{
|
||||
XRPL_ASSERT(!nodeID.isRoot(), "xrpl::SHAMap::addKnownNode : valid node");
|
||||
XRPL_ASSERT(treeNode, "xrpl::SHAMap::addKnownNode : non-null tree node");
|
||||
XRPL_ASSERT_IF(
|
||||
treeNode->isLeaf(),
|
||||
nodeID.isPrefixOf(leafKey(*treeNode)),
|
||||
"xrpl::SHAMap::addKnownNode : leaf position consistent with node ID");
|
||||
|
||||
if (!isSynching())
|
||||
{
|
||||
@@ -606,6 +656,17 @@ SHAMap::addKnownNode(
|
||||
auto prevNode = inner;
|
||||
std::tie(currNode, currNodeID) = descend(inner, currNodeID, branch, filter);
|
||||
|
||||
if (!isValid())
|
||||
{
|
||||
// descend judged a node on the way down and condemned the map. Stops here rather than
|
||||
// falling through, for two reasons: `childHash` was read before that descent, so the
|
||||
// hash comparison below would report a corrupt node against a sender that sent nothing
|
||||
// wrong, and if the node descend refused is the one offered here, that comparison would
|
||||
// instead succeed and hook it after all.
|
||||
JLOG(journal_.warn()) << "Node " << nodeID << " cannot be hooked into an invalid map";
|
||||
return SHAMapAddNode::invalid();
|
||||
}
|
||||
|
||||
if (currNode != nullptr)
|
||||
continue;
|
||||
|
||||
@@ -637,6 +698,19 @@ SHAMap::addKnownNode(
|
||||
return SHAMapAddNode::useful();
|
||||
}
|
||||
|
||||
// A leaf's own key names its position, so a leaf offered for this slot has to agree with
|
||||
// the ID it was offered under. The hash test above already proves the parent records this
|
||||
// exact leaf here, so a disagreement is a property of the map rather than of the sender.
|
||||
// This was an entry assertion, which is stripped under NDEBUG, and the node is hooked
|
||||
// immediately below.
|
||||
if (!belongsAt(nodeID, *treeNode))
|
||||
{
|
||||
JLOG(journal_.warn()) << "Leaf " << treeNode->getHash() << " does not belong at "
|
||||
<< nodeID << ", map is invalid";
|
||||
state_ = SHAMapState::Invalid;
|
||||
return SHAMapAddNode::invalid();
|
||||
}
|
||||
|
||||
if (backed_)
|
||||
canonicalize(childHash, treeNode);
|
||||
|
||||
@@ -793,7 +867,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())
|
||||
@@ -802,7 +876,7 @@ SHAMap::getProofPath(uint256 const& key) const
|
||||
return {};
|
||||
}
|
||||
|
||||
if (auto const& node = stack.top().first; !node || node->isInner() ||
|
||||
if (auto const& node = stack.top(); !node || node->isInner() ||
|
||||
intr_ptr::staticPointerCast<SHAMapLeafNode>(node)->peekItem()->key() != key)
|
||||
{
|
||||
JLOG(journal_.debug()) << "no path to " << key;
|
||||
@@ -814,7 +888,7 @@ SHAMap::getProofPath(uint256 const& key) const
|
||||
while (!stack.empty())
|
||||
{
|
||||
Serializer s;
|
||||
stack.top().first->serializeForWire(s);
|
||||
stack.top()->serializeForWire(s);
|
||||
path.emplace_back(std::move(s.modData()));
|
||||
stack.pop();
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -299,27 +299,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