mirror of
https://github.com/XRPLF/rippled.git
synced 2026-09-16 04:18:33 +00:00
Compare commits
42 Commits
pratik/led
...
token-payc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4258eca938 | ||
|
|
01919230a5 | ||
|
|
9403736199 | ||
|
|
028783661d | ||
|
|
21890d9daf | ||
|
|
060957ed39 | ||
|
|
1381483c7a | ||
|
|
3e4bdf2782 | ||
|
|
e3c8996e44 | ||
|
|
d5bfe94f15 | ||
|
|
2ad4def35f | ||
|
|
f7f50caa6e | ||
|
|
3e54e7d00b | ||
|
|
827b50f169 | ||
|
|
58a59c37ed | ||
|
|
986065c16f | ||
|
|
37cb4cdbe3 | ||
|
|
2ddf6ee148 | ||
|
|
8ce4f71427 | ||
|
|
6e1eb88e6e | ||
|
|
49cdc105de | ||
|
|
636d2d4851 | ||
|
|
7d7275847d | ||
|
|
f0fd6ad85e | ||
|
|
5d8fd9824e | ||
|
|
346ea40f69 | ||
|
|
2ba6f4cc68 | ||
|
|
c5ae59c4a0 | ||
|
|
1ac1d40d06 | ||
|
|
fb10515cff | ||
|
|
3c85a0180f | ||
|
|
096fa967da | ||
|
|
1ced7dc8f3 | ||
|
|
df1458a136 | ||
|
|
cdd5ebf4c1 | ||
|
|
1a9a11fc31 | ||
|
|
a9430d4778 | ||
|
|
fed5093488 | ||
|
|
6c74fc1a7d | ||
|
|
939f8b1a49 | ||
|
|
1f8c462c42 | ||
|
|
47df026dcc |
@@ -366,7 +366,6 @@ words:
|
||||
- venv
|
||||
- vfalco
|
||||
- vinnie
|
||||
- vkeylet
|
||||
- wasmi
|
||||
- wextra
|
||||
- wptr
|
||||
|
||||
9
.github/actions/setup-nix-env/action.yml
vendored
9
.github/actions/setup-nix-env/action.yml
vendored
@@ -40,10 +40,11 @@ runs:
|
||||
# Unlike the Linux nix images, macOS needs no SSL_CERT_FILE: it has its
|
||||
# own trust store, and pinning would break TLS to hosts relying on it.
|
||||
|
||||
# Workspace-local, so `cleanup-workspace` clears it, but not the
|
||||
# `.conan2` prepare-runner hands the system toolchain: that Conan is a
|
||||
# different version, and the two would migrate each other's cache.
|
||||
echo "CONAN_HOME=${{ github.workspace }}/.conan2-nix" >>"${GITHUB_ENV}"
|
||||
# In RUNNER_TEMP, which the runner empties per job, like the `.conan2`
|
||||
# prepare-runner hands the system toolchain - but under its own name:
|
||||
# that Conan is a different version, and the two would migrate each
|
||||
# other's cache.
|
||||
echo "CONAN_HOME=${RUNNER_TEMP}/.conan2-nix" >>"${GITHUB_ENV}"
|
||||
|
||||
# Config, profiles and remote, exactly as the dev shell sets them up on
|
||||
# entry; the `setup-conan` action is skipped for this toolchain.
|
||||
|
||||
4
.github/pull_request_template.md
vendored
4
.github/pull_request_template.md
vendored
@@ -18,7 +18,7 @@ If too broad, please consider splitting into multiple PRs.
|
||||
If there is a relevant task or issue, please link it here.
|
||||
-->
|
||||
|
||||
### Context of Change
|
||||
## Context of Change
|
||||
|
||||
<!--
|
||||
Please include the context of a change.
|
||||
@@ -29,7 +29,7 @@ If a refactor, how is this better than the previous implementation?
|
||||
If there is a spec or design document for this feature, please link it here.
|
||||
-->
|
||||
|
||||
### API Impact
|
||||
## API Impact
|
||||
|
||||
<!--
|
||||
Please check [x] relevant options, delete irrelevant ones.
|
||||
|
||||
6
.github/scripts/strategy-matrix/linux.json
vendored
6
.github/scripts/strategy-matrix/linux.json
vendored
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"image_tag": "sha-473fe44",
|
||||
"image_tag": "sha-060957e",
|
||||
"configs": {
|
||||
"ubuntu": [
|
||||
{
|
||||
@@ -74,7 +74,7 @@
|
||||
"extra_cmake_args": "-Dvalidator_keys=ON",
|
||||
"package": {
|
||||
"type": "deb",
|
||||
"image": "ghcr.io/xrplf/xrpld/packaging-debian:sha-b6a8995"
|
||||
"image": "ghcr.io/xrplf/xrpld/packaging-debian:sha-49cdc10"
|
||||
}
|
||||
}
|
||||
],
|
||||
@@ -88,7 +88,7 @@
|
||||
"extra_cmake_args": "-Dvalidator_keys=ON",
|
||||
"package": {
|
||||
"type": "rpm",
|
||||
"image": "ghcr.io/xrplf/xrpld/packaging-rhel:sha-b6a8995"
|
||||
"image": "ghcr.io/xrplf/xrpld/packaging-rhel:sha-49cdc10"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
8
.github/workflows/build-nix-images.yml
vendored
8
.github/workflows/build-nix-images.yml
vendored
@@ -5,15 +5,13 @@ on:
|
||||
branches:
|
||||
- develop
|
||||
paths:
|
||||
- ".github/workflows/build-nix-images.yml"
|
||||
- "flake.nix"
|
||||
- "flake.lock"
|
||||
- "rust-toolchain.toml"
|
||||
- "nix/**"
|
||||
- "!nix/docker/README.md"
|
||||
- "!nix/devshell.nix"
|
||||
- "!nix/check-tools/*.txt"
|
||||
- "bin/check-tools.sh"
|
||||
- "!nix/check-tools/**"
|
||||
- "bin/default-loader-path.sh"
|
||||
- "bin/install-sanitizer-libs.sh"
|
||||
pull_request:
|
||||
@@ -25,7 +23,7 @@ on:
|
||||
- "nix/**"
|
||||
- "!nix/docker/README.md"
|
||||
- "!nix/devshell.nix"
|
||||
- "!nix/check-tools/*.txt"
|
||||
- "!nix/check-tools/**"
|
||||
- "bin/check-tools.sh"
|
||||
- "bin/default-loader-path.sh"
|
||||
- "bin/install-sanitizer-libs.sh"
|
||||
@@ -60,7 +58,7 @@ jobs:
|
||||
base_image: debian:bookworm
|
||||
- name: rhel
|
||||
base_image: registry.access.redhat.com/ubi9/ubi:latest
|
||||
uses: XRPLF/actions/.github/workflows/build-multiarch-image.yml@65d5a0bd72be4ecea95cff0673a6e0672ab5243a
|
||||
uses: XRPLF/actions/.github/workflows/build-multiarch-image.yml@034e87065fcd0100214cf0672923bd38d193cf78
|
||||
with:
|
||||
image_name: xrpld/nix-${{ matrix.distro.name }}
|
||||
dockerfile: nix/docker/Dockerfile
|
||||
|
||||
2
.github/workflows/build-packaging-images.yml
vendored
2
.github/workflows/build-packaging-images.yml
vendored
@@ -41,7 +41,7 @@ jobs:
|
||||
# AlmaLinux rather than UBI, which does not ship rpm-sign.
|
||||
- name: rhel
|
||||
base_image: almalinux:10
|
||||
uses: XRPLF/actions/.github/workflows/build-multiarch-image.yml@65d5a0bd72be4ecea95cff0673a6e0672ab5243a
|
||||
uses: XRPLF/actions/.github/workflows/build-multiarch-image.yml@034e87065fcd0100214cf0672923bd38d193cf78
|
||||
with:
|
||||
image_name: xrpld/packaging-${{ matrix.distro.name }}
|
||||
dockerfile: package/docker/Dockerfile
|
||||
|
||||
2
.github/workflows/build-pre-commit-image.yml
vendored
2
.github/workflows/build-pre-commit-image.yml
vendored
@@ -30,7 +30,7 @@ jobs:
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
uses: XRPLF/actions/.github/workflows/build-multiarch-image.yml@65d5a0bd72be4ecea95cff0673a6e0672ab5243a
|
||||
uses: XRPLF/actions/.github/workflows/build-multiarch-image.yml@034e87065fcd0100214cf0672923bd38d193cf78
|
||||
with:
|
||||
image_name: xrpld/pre-commit
|
||||
dockerfile: bin/pre-commit/Dockerfile
|
||||
|
||||
2
.github/workflows/cargo-audit.yml
vendored
2
.github/workflows/cargo-audit.yml
vendored
@@ -34,7 +34,7 @@ permissions:
|
||||
jobs:
|
||||
audit:
|
||||
runs-on: ubuntu-latest
|
||||
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-473fe44
|
||||
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-060957e
|
||||
permissions:
|
||||
contents: read
|
||||
# Needed to open an issue on scheduled failures.
|
||||
|
||||
2
.github/workflows/check-tools.yml
vendored
2
.github/workflows/check-tools.yml
vendored
@@ -79,7 +79,7 @@ jobs:
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Prepare runner
|
||||
uses: XRPLF/actions/prepare-runner@7bf7ceca5932114abdd0d43493c3c30c5a654e13
|
||||
uses: XRPLF/actions/prepare-runner@b3e255d74d785d053e4903da8ac90983cd7d9e82
|
||||
with:
|
||||
enable_ccache: false
|
||||
|
||||
|
||||
2
.github/workflows/pre-commit.yml
vendored
2
.github/workflows/pre-commit.yml
vendored
@@ -14,7 +14,7 @@ on:
|
||||
jobs:
|
||||
# Call the workflow in the XRPLF/actions repo that runs the pre-commit hooks.
|
||||
run-hooks:
|
||||
uses: XRPLF/actions/.github/workflows/pre-commit.yml@f1952595d212e86169935135efc66294b4574131
|
||||
uses: XRPLF/actions/.github/workflows/pre-commit.yml@279ec358f4a1be4088be3e024b07916fa97c75b6
|
||||
with:
|
||||
runs_on: ubuntu-latest
|
||||
container: '{ "image": "ghcr.io/xrplf/xrpld/pre-commit:sha-473fe44" }'
|
||||
|
||||
6
.github/workflows/publish-docs.yml
vendored
6
.github/workflows/publish-docs.yml
vendored
@@ -41,13 +41,13 @@ env:
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-473fe44
|
||||
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-060957e
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Prepare runner
|
||||
uses: XRPLF/actions/prepare-runner@7bf7ceca5932114abdd0d43493c3c30c5a654e13
|
||||
uses: XRPLF/actions/prepare-runner@b3e255d74d785d053e4903da8ac90983cd7d9e82
|
||||
with:
|
||||
enable_ccache: false
|
||||
|
||||
@@ -91,4 +91,4 @@ jobs:
|
||||
steps:
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deploy
|
||||
uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5.0.0
|
||||
uses: actions/deploy-pages@368f82528645a54fb793d4d04e342629a3f51346 # v5.0.1
|
||||
|
||||
@@ -129,7 +129,7 @@ jobs:
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Prepare runner
|
||||
uses: XRPLF/actions/prepare-runner@7bf7ceca5932114abdd0d43493c3c30c5a654e13
|
||||
uses: XRPLF/actions/prepare-runner@b3e255d74d785d053e4903da8ac90983cd7d9e82
|
||||
with:
|
||||
enable_ccache: ${{ inputs.ccache_enabled }}
|
||||
|
||||
|
||||
4
.github/workflows/reusable-clang-tidy.yml
vendored
4
.github/workflows/reusable-clang-tidy.yml
vendored
@@ -34,7 +34,7 @@ jobs:
|
||||
needs: [determine-files]
|
||||
if: ${{ needs.determine-files.outputs.cpp_changed_files != '' || needs.determine-files.outputs.need_full_run == 'true' }}
|
||||
runs-on: ["self-hosted", "Linux", "X64", "heavy"]
|
||||
container: "ghcr.io/xrplf/xrpld/nix-debian:sha-473fe44"
|
||||
container: "ghcr.io/xrplf/xrpld/nix-debian:sha-060957e"
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
@@ -43,7 +43,7 @@ jobs:
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Prepare runner
|
||||
uses: XRPLF/actions/prepare-runner@7bf7ceca5932114abdd0d43493c3c30c5a654e13
|
||||
uses: XRPLF/actions/prepare-runner@b3e255d74d785d053e4903da8ac90983cd7d9e82
|
||||
with:
|
||||
enable_ccache: false
|
||||
|
||||
|
||||
192
.github/workflows/reusable-package.yml
vendored
192
.github/workflows/reusable-package.yml
vendored
@@ -1,11 +1,14 @@
|
||||
# Build Linux packages from the pre-built xrpld and validator-keys artifacts:
|
||||
# Build, verify and publish Linux packages from the pre-built xrpld and
|
||||
# validator-keys artifacts, in three stages:
|
||||
#
|
||||
# - one job per config that carries a "package" map in linux.json
|
||||
# - that map names the container image and the format it builds there
|
||||
# - every job ends with the image's publish_pkg.py, uploading what it built
|
||||
# with 'publish: true' and doing a --dry-run otherwise
|
||||
# - '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
|
||||
# - 'publish' uploads with the image's publish_pkg.py, doing a --dry-run
|
||||
# unless 'publish: true'
|
||||
#
|
||||
# Only linux/amd64 is supported; the runner is hardcoded in the job below.
|
||||
# Only linux/amd64 is supported; the runner is hardcoded in the jobs below.
|
||||
name: Package
|
||||
|
||||
on:
|
||||
@@ -39,6 +42,7 @@ defaults:
|
||||
|
||||
env:
|
||||
BUILD_DIR: build
|
||||
PACKAGE_DIR: packages
|
||||
|
||||
jobs:
|
||||
generate-matrix:
|
||||
@@ -70,14 +74,14 @@ jobs:
|
||||
contents: read
|
||||
runs-on: ["self-hosted", "Linux", "X64", "heavy"]
|
||||
container: ${{ matrix.image }}
|
||||
timeout-minutes: 30
|
||||
timeout-minutes: 10
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Prepare runner
|
||||
uses: XRPLF/actions/prepare-runner@7bf7ceca5932114abdd0d43493c3c30c5a654e13
|
||||
uses: XRPLF/actions/prepare-runner@b3e255d74d785d053e4903da8ac90983cd7d9e82
|
||||
with:
|
||||
enable_ccache: false
|
||||
|
||||
@@ -112,24 +116,184 @@ jobs:
|
||||
--pkg-release "${PKG_RELEASE}" \
|
||||
--channel "${CHANNEL}"
|
||||
|
||||
# Before the upload, so the artifact and the published package are the
|
||||
# same bytes. DEBs are not signed, so the key is never set on that job.
|
||||
# Before the upload, so the artifact, the tested package and the published
|
||||
# package are the same bytes.
|
||||
- name: Sign RPM
|
||||
if: ${{ inputs.publish && matrix.package_type == 'rpm' }}
|
||||
env:
|
||||
PKG_SIGNING_KEY: ${{ secrets.signing_key }}
|
||||
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.
|
||||
- name: Upload package artifact
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: ${{ matrix.xrpld_artifact_name }}-pkg
|
||||
path: |
|
||||
${{ env.BUILD_DIR }}/debbuild/*.deb
|
||||
${{ env.BUILD_DIR }}/debbuild/*.ddeb
|
||||
${{ env.BUILD_DIR }}/rpmbuild/RPMS/**/*.rpm
|
||||
${{ env.BUILD_DIR }}/debbuild/xrpld_*.deb
|
||||
${{ env.BUILD_DIR }}/rpmbuild/RPMS/**/xrpld-[0-9]*.rpm
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Upload debug symbol artifact
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
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
|
||||
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
|
||||
|
||||
- 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
|
||||
|
||||
publish:
|
||||
needs: [generate-matrix, package, test-install]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix: ${{ fromJson(needs.generate-matrix.outputs.matrix) }}
|
||||
name: "publish ${{ matrix.xrpld_artifact_name }}"
|
||||
permissions:
|
||||
contents: read
|
||||
runs-on: ["self-hosted", "Linux", "X64", "heavy"]
|
||||
container: ${{ matrix.image }}
|
||||
timeout-minutes: 30
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Prepare runner
|
||||
uses: XRPLF/actions/prepare-runner@b3e255d74d785d053e4903da8ac90983cd7d9e82
|
||||
with:
|
||||
enable_ccache: false
|
||||
|
||||
# Both artifacts, so the debug symbols are published alongside the package.
|
||||
- name: Download package artifacts
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
pattern: ${{ matrix.xrpld_artifact_name }}-pkg*
|
||||
merge-multiple: true
|
||||
path: ${{ env.PACKAGE_DIR }}
|
||||
|
||||
- name: Determine release info
|
||||
id: release_info
|
||||
uses: ./.github/actions/release-info
|
||||
|
||||
- name: Publish package
|
||||
env:
|
||||
CHANNEL: ${{ steps.release_info.outputs.channel }}
|
||||
@@ -140,6 +304,6 @@ jobs:
|
||||
run: |
|
||||
publish_pkg.py \
|
||||
--channel "${CHANNEL}" \
|
||||
--package-dir "${BUILD_DIR}" \
|
||||
--package-dir "${PACKAGE_DIR}" \
|
||||
--nexus-url "${NEXUS_URL}" \
|
||||
${DRY_RUN_OPTION}
|
||||
|
||||
14
.github/workflows/reusable-rust.yml
vendored
14
.github/workflows/reusable-rust.yml
vendored
@@ -1,8 +1,9 @@
|
||||
# Clippy, coverage and documentation for the Rust crates in crates/. Each runs
|
||||
# as an independent job on a GitHub-hosted runner, but inside the same container
|
||||
# image used to build the crates in the C++/Corrosion path, so the toolchain
|
||||
# (and therefore the lints, coverage instrumentation and the cargo cache) matches
|
||||
# what production builds use.
|
||||
# (and therefore the lints and the cargo cache) matches what production builds
|
||||
# use. Coverage is the exception: it needs the nightly rustc that honours
|
||||
# #[coverage(off)], which the image carries alongside the pinned stable.
|
||||
#
|
||||
# Rust unit tests are deliberately NOT run here. They run as part of the C++
|
||||
# build (reusable-build-test-config.yml), which already compiles the crates on a
|
||||
@@ -27,7 +28,7 @@ permissions:
|
||||
jobs:
|
||||
clippy:
|
||||
runs-on: ubuntu-latest
|
||||
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-473fe44
|
||||
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-060957e
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
@@ -40,11 +41,14 @@ jobs:
|
||||
|
||||
coverage:
|
||||
runs-on: ubuntu-latest
|
||||
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-473fe44
|
||||
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-060957e
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Use the nightly Rust toolchain
|
||||
run: rust-nightly path >>"${GITHUB_PATH}"
|
||||
|
||||
- name: Use cargo artifacts cache
|
||||
uses: ./.github/actions/cargo-cache
|
||||
|
||||
@@ -66,7 +70,7 @@ jobs:
|
||||
|
||||
doc:
|
||||
runs-on: ubuntu-latest
|
||||
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-473fe44
|
||||
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-060957e
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
4
.github/workflows/reusable-upload-recipe.yml
vendored
4
.github/workflows/reusable-upload-recipe.yml
vendored
@@ -40,7 +40,7 @@ defaults:
|
||||
jobs:
|
||||
upload:
|
||||
runs-on: ubuntu-latest
|
||||
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-473fe44
|
||||
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-060957e
|
||||
env:
|
||||
REMOTE_NAME: ${{ inputs.remote_name }}
|
||||
CONAN_LOGIN_USERNAME_XRPLF: ${{ secrets.remote_username }}
|
||||
@@ -50,7 +50,7 @@ jobs:
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Prepare runner
|
||||
uses: XRPLF/actions/prepare-runner@7bf7ceca5932114abdd0d43493c3c30c5a654e13
|
||||
uses: XRPLF/actions/prepare-runner@b3e255d74d785d053e4903da8ac90983cd7d9e82
|
||||
with:
|
||||
enable_ccache: false
|
||||
|
||||
|
||||
2
.github/workflows/upload-conan-deps.yml
vendored
2
.github/workflows/upload-conan-deps.yml
vendored
@@ -68,7 +68,7 @@ jobs:
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Prepare runner
|
||||
uses: XRPLF/actions/prepare-runner@7bf7ceca5932114abdd0d43493c3c30c5a654e13
|
||||
uses: XRPLF/actions/prepare-runner@b3e255d74d785d053e4903da8ac90983cd7d9e82
|
||||
with:
|
||||
enable_ccache: false
|
||||
|
||||
|
||||
@@ -70,6 +70,11 @@ repos:
|
||||
language: system
|
||||
types: [rust]
|
||||
pass_filenames: false # rustfmt formats the whole workspace
|
||||
- id: check-coverage-attrs
|
||||
name: check Rust coverage attributes
|
||||
entry: ./bin/pre-commit/check_rust_coverage_attrs.py
|
||||
language: python
|
||||
files: ^crates/.*\.rs$
|
||||
|
||||
- repo: https://github.com/BlankSpruce/gersemi-pre-commit
|
||||
rev: e98930bdc210d3387007f9252d8c1694ea7e410f # frozen: 0.27.7
|
||||
|
||||
@@ -22,14 +22,46 @@ API version 2 is available in `xrpld` version 2.0.0 and later. See [API-VERSION-
|
||||
|
||||
This version is supported by all `xrpld` versions. For WebSocket and HTTP JSON-RPC requests, it is currently the default API version used when no `api_version` is specified.
|
||||
|
||||
## Unreleased
|
||||
## XRP Ledger server version 3.4.0
|
||||
|
||||
This section contains changes targeting a future version.
|
||||
Version 3.4.0 is not yet released. These changes are available in the 3.4.0 beta releases.
|
||||
|
||||
### Additions
|
||||
### Additions in 3.4.0
|
||||
|
||||
- `ledger`: `nftoken_id`, `nftoken_ids`, and `offer_id` are now included in transaction metadata when transactions are expanded (`expand`, or admin-only `full`), matching the `tx`, `account_tx`, and `subscribe` (`transactions` stream) responses. ([#5706](https://github.com/XRPLF/rippled/pull/5706))
|
||||
|
||||
### Bugfixes in 3.4.0
|
||||
|
||||
- `sign`, `sign_for`, `submit`: `signature_target` now returns `invalidParams` unless it names `CounterpartySignature` or `SponsorSignature`. It previously accepted any inner object field, such as `Book` or `NFToken`, and signed into it.
|
||||
- `sign`, `sign_for`, `submit`, `submit_multisigned`: With `fixCleanup3_4_0` enabled, a signature in `CounterpartySignature` or `SponsorSignature` covers a different prefix than the transaction's own signature, so a signature can no longer be moved from one of those roles into another. Clients that build these signatures themselves must use the new prefixes: `CPT` and `CPM` (single- and multi-signing) for `CounterpartySignature`, and `SPN` and `SPM` for `SponsorSignature`.
|
||||
- `get_aggregate_price`: Duplicate entries in the `oracles` request array are now ignored. [#6586](https://github.com/XRPLF/rippled/pull/6586)
|
||||
- `vault_info`: Errors now identify what the request got wrong instead of reporting every failure as the unregistered token `malformedRequest`, and the `error`, `error_code` and `error_message` fields now agree with each other. An invalid `vault_id` or `seq` returns `invalidParams`, an invalid `owner` returns `actMalformed`, and a request that mixes `vault_id` with `owner`/`seq` or supplies neither returns `invalidParams` with a message naming the accepted combinations. [#8015](https://github.com/XRPLF/rippled/pull/8015)
|
||||
- `vault_info`: A well-formed all-zero `vault_id` now returns `entryNotFound` instead of being rejected as malformed, and `entryNotFound` responses now include `error_code` and `error_message`. Clients that request `ripplerpc` 3.0 or above therefore receive HTTP 400 with that error rather than HTTP 200. [#8015](https://github.com/XRPLF/rippled/pull/8015)
|
||||
- `vault_info`: `vault_id` and `owner` must now be strings, matching how `ledger_entry` reads the same fields. An object or an array in either field previously produced an internal error, and a number was silently converted to its decimal text; `vault_id` now returns `invalidParams` and `owner` returns `actMalformed`. [#8015](https://github.com/XRPLF/rippled/pull/8015)
|
||||
- `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)
|
||||
|
||||
## XRP Ledger server version 3.3.0
|
||||
|
||||
[Version 3.3.0](https://github.com/XRPLF/rippled/releases/tag/3.3.0) was released on Aug 6, 2026.
|
||||
|
||||
### Additions in 3.3.0
|
||||
|
||||
- `account_tx`: Added an optional `delegate` request object to filter delegated transactions. The object requires `delegate_filter`, which must be either `actor` for transactions owned by the requested account but signed by another account, or `authorizer` for transactions signed by the requested account on behalf of another account. The optional `counter_party` account narrows the results to a specific signer/delegate for `actor` or a specific owner/delegator for `authorizer`. Malformed `delegate`, `delegate_filter`, and `counter_party` values return standard invalid field errors, and invalid account IDs return `actMalformed`. When paginating delegate-filtered queries, a marker from a delegate-filtered query includes a `delegate` flag and is only valid for follow-up requests that also supply `delegate` (mixing marker conventions returns `invalidParams`). Because filtering is applied after the ledger scan, a page may contain fewer results than `limit` (possibly zero) while still returning a marker, so callers must continue until no marker is present. ([#6126](https://github.com/XRPLF/rippled/pull/6126))
|
||||
|
||||
## XRP Ledger server version 3.2.1
|
||||
|
||||
[Version 3.2.1](https://github.com/XRPLF/rippled/releases/tag/3.2.1) was released on Aug 1, 2026.
|
||||
|
||||
This release contains bug fixes only and no API changes.
|
||||
|
||||
## XRP Ledger server version 3.2.0
|
||||
|
||||
[Version 3.2.0](https://github.com/XRPLF/rippled/releases/tag/3.2.0) was released on Jun 16, 2026.
|
||||
|
||||
### Additions in 3.2.0
|
||||
|
||||
- `account_tx`: Added an optional `delegate` request object to filter delegated transactions. The object requires `delegate_filter`, which must be either `actor` for transactions owned by the requested account but signed by another account, or `authorizer` for transactions signed by the requested account on behalf of another account. The optional `counter_party` account narrows the results to a specific signer/delegate for `actor` or a specific owner/delegator for `authorizer`. Malformed `delegate`, `delegate_filter`, and `counter_party` values return standard invalid field errors, and invalid account IDs return `actMalformed`.
|
||||
When paginating delegate-filtered queries, a marker from a delegate-filtered query includes a `delegate` flag and is only valid for follow-up requests that also supply `delegate` (mixing marker conventions returns `invalidParams`). Because filtering is applied after the ledger scan, a page may contain fewer results than `limit` (possibly zero) while still returning a marker, so callers must continue until no marker is present.
|
||||
- `ledger_entry`, `account_objects`: The `Delegate` ledger entry now includes an optional `DestinationNode` field, which stores the index into the authorized account's owner directory. This field is present on entries created after bidirectional directory tracking was introduced and may appear in RPC responses for those entries. ([#6681](https://github.com/XRPLF/rippled/pull/6681))
|
||||
- `server_definitions`: Added the following new sections to the response ([#6321](https://github.com/XRPLF/rippled/pull/6321)):
|
||||
- `TRANSACTION_FORMATS`: Describes the fields and their optionality for each transaction type, including common fields shared across all transactions.
|
||||
@@ -37,11 +69,9 @@ This section contains changes targeting a future version.
|
||||
- `TRANSACTION_FLAGS`: Maps transaction type names to their supported flags and flag values.
|
||||
- `LEDGER_ENTRY_FLAGS`: Maps ledger entry type names to their flags and flag values.
|
||||
- `ACCOUNT_SET_FLAGS`: Maps AccountSet flag names (asf flags) to their numeric values.
|
||||
- `ledger`: `nftoken_id`, `nftoken_ids`, and `offer_id` are now included in transaction metadata when transactions are expanded (`expand`, or admin-only `full`), matching the `tx`, `account_tx`, and `subscribe` (`transactions` stream) responses. ([#5706](https://github.com/XRPLF/rippled/pull/5706))
|
||||
|
||||
### Bugfixes
|
||||
### Bugfixes in 3.2.0
|
||||
|
||||
- `get_aggregate_price`: Duplicate entries in the `oracles` request array are now ignored. [#6586](https://github.com/XRPLF/rippled/pull/6586)
|
||||
- Peer Crawler: The `port` field in `overlay.active[]` now consistently returns an integer instead of a string for outbound peers. [#6318](https://github.com/XRPLF/rippled/pull/6318)
|
||||
- `ping`: The `ip` field is no longer returned as an empty string for proxied connections without a forwarded-for header. It is now omitted, consistent with the behavior for identified connections. [#6730](https://github.com/XRPLF/rippled/pull/6730)
|
||||
- gRPC `GetLedgerDiff`: Fixed error message that incorrectly said "base ledger not validated" when the desired ledger was not validated. [#6730](https://github.com/XRPLF/rippled/pull/6730)
|
||||
@@ -53,12 +83,24 @@ This section contains changes targeting a future version.
|
||||
- `submit`: The `fail_hard` field now returns an error if the value is not a boolean. [#6529](https://github.com/XRPLF/rippled/pull/6529)
|
||||
- `subscribe`: The `taker` field in the `books` array now returns `actMalformed` instead of `badIssuer` if the value is not a valid account. [#6529](https://github.com/XRPLF/rippled/pull/6529)
|
||||
- Fixed a bug in `Forwarded` HTTP header parsing where the extracted IP address could be incorrect when no comma or semicolon delimiter follows the address. This could cause the server to misidentify a client's IP address when operating behind a reverse proxy. [#6529](https://github.com/XRPLF/rippled/pull/6529)
|
||||
- `vault_info`: Errors now identify what the request got wrong instead of reporting every failure as the unregistered token `malformedRequest`, and the `error`, `error_code` and `error_message` fields now agree with each other. An invalid `vault_id` or `seq` returns `invalidParams`, an invalid `owner` returns `actMalformed`, and a request that mixes `vault_id` with `owner`/`seq` or supplies neither returns `invalidParams` with a message naming the accepted combinations. [#8015](https://github.com/XRPLF/rippled/pull/8015)
|
||||
- `vault_info`: A well-formed all-zero `vault_id` now returns `entryNotFound` instead of being rejected as malformed, and `entryNotFound` responses now include `error_code` and `error_message`. Clients that request `ripplerpc` 3.0 or above therefore receive HTTP 400 with that error rather than HTTP 200. [#8015](https://github.com/XRPLF/rippled/pull/8015)
|
||||
- `vault_info`: `vault_id` and `owner` must now be strings, matching how `ledger_entry` reads the same fields. An object or an array in either field previously produced an internal error, and a number was silently converted to its decimal text; `vault_id` now returns `invalidParams` and `owner` returns `actMalformed`. [#8015](https://github.com/XRPLF/rippled/pull/8015)
|
||||
- `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)
|
||||
|
||||
## XRP Ledger server version 3.1.3
|
||||
|
||||
[Version 3.1.3](https://github.com/XRPLF/rippled/releases/tag/3.1.3) was released on May 8, 2026.
|
||||
|
||||
This release contains bug fixes only and no API changes.
|
||||
|
||||
## XRP Ledger server version 3.1.2
|
||||
|
||||
[Version 3.1.2](https://github.com/XRPLF/rippled/releases/tag/3.1.2) was released on Mar 12, 2026.
|
||||
|
||||
This release contains bug fixes only and no API changes.
|
||||
|
||||
## XRP Ledger server version 3.1.1
|
||||
|
||||
[Version 3.1.1](https://github.com/XRPLF/rippled/releases/tag/3.1.1) was released on Feb 23, 2026.
|
||||
|
||||
This release contains bug fixes only and no API changes.
|
||||
|
||||
## XRP Ledger server version 3.1.0
|
||||
|
||||
|
||||
@@ -158,6 +158,7 @@ if [ "${os}" = "linux" ] || [ "${os}" = "macos" ]; then
|
||||
check cargo-nextest cargo nextest --version
|
||||
check clippy-driver
|
||||
check rust-analyzer
|
||||
check rust-nightly rust-nightly run rustc --version
|
||||
check rustc
|
||||
check rustfmt
|
||||
fi
|
||||
|
||||
@@ -25,7 +25,9 @@ esac
|
||||
# Packaging runs in a vanilla distro image, so the tooling comes from the distro's
|
||||
# archive rather than from nixpkgs:
|
||||
#
|
||||
# - debhelper and dpkg-dev build the DEB
|
||||
# - debhelper and dpkg-dev build the DEB, and lintian checks it
|
||||
# - binutils gives debian/rules the readelf its glibc-floor check runs; it
|
||||
# already arrives via dpkg-dev, but that tool is called directly
|
||||
# - rpm-build builds the RPM, with systemd-rpm-macros and redhat-rpm-config
|
||||
# supplying the systemd and find-debuginfo macros the spec uses
|
||||
# - rpm-sign and gnupg2 sign the built RPM
|
||||
@@ -37,11 +39,13 @@ function install() {
|
||||
debian | ubuntu)
|
||||
apt-get update -y
|
||||
apt-get install -y --no-install-recommends \
|
||||
binutils \
|
||||
ca-certificates \
|
||||
debhelper \
|
||||
debhelper-compat \
|
||||
dpkg-dev \
|
||||
git \
|
||||
lintian \
|
||||
python3
|
||||
;;
|
||||
|
||||
|
||||
149
bin/pre-commit/check_rust_coverage_attrs.py
Executable file
149
bin/pre-commit/check_rust_coverage_attrs.py
Executable file
@@ -0,0 +1,149 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Check that Rust unit tests stay out of the coverage report.
|
||||
|
||||
cargo-llvm-cov instruments the test code along with everything else, so a test
|
||||
module that is not excluded counts its own body as covered and inflates the
|
||||
reported number. Excluding it takes two attributes:
|
||||
|
||||
* every `#[cfg(test)]` module carries
|
||||
`#[cfg_attr(coverage_nightly, coverage(off))]`;
|
||||
* every crate root (lib.rs, main.rs) carries
|
||||
`#![cfg_attr(coverage_nightly, feature(coverage_attribute))]`, which the
|
||||
attribute above needs in order to compile.
|
||||
|
||||
Both are inert outside the coverage job: cargo-llvm-cov defines
|
||||
`coverage_nightly` only when it runs on a nightly toolchain.
|
||||
|
||||
The crate-root gate is checked even in a crate that has no tests yet, because
|
||||
that is what lets the first test module added later carry the attribute without
|
||||
a build failure. Missing it is a hard error, so it cannot go unnoticed; a
|
||||
missing `coverage(off)` fails open, which is why this check exists.
|
||||
|
||||
Matching is on exact attribute text, which works because `cargo fmt` runs over
|
||||
the whole workspace in the hook ahead of this one: rustfmt puts every attribute
|
||||
on its own line and normalizes what is inside it, turning `#[cfg( test )]`
|
||||
and `#[cfg(test,)]` alike into `#[cfg(test)]`. So there is nothing here that
|
||||
parses Rust. The price is that a cfg this file does not spell out literally --
|
||||
`all(test, ...)`, `any(test, ...)`, `not(test)` -- is reported rather than
|
||||
classified, on the grounds that guessing at coverage semantics is how a check
|
||||
like this ends up quietly wrong.
|
||||
|
||||
Usage: ./bin/pre-commit/check_rust_coverage_attrs.py <file1> <file2> ...
|
||||
|
||||
Exit status is non-zero if any violation is found.
|
||||
"""
|
||||
|
||||
import re
|
||||
import sys
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
|
||||
CRATE_ROOTS = {"lib.rs", "main.rs"}
|
||||
|
||||
FEATURE_ATTR = "#![cfg_attr(coverage_nightly, feature(coverage_attribute))]"
|
||||
COVERAGE_OFF_ATTR = "#[cfg_attr(coverage_nightly, coverage(off))]"
|
||||
CFG_TEST_ATTR = "#[cfg(test)]"
|
||||
|
||||
# Any other cfg that mentions `test`. String literals are blanked before this
|
||||
# runs, so `feature = "test"` does not read as the `test` cfg.
|
||||
RE_CFG_MENTIONS_TEST = re.compile(r"^#\[cfg\(.*\btest\b.*\)\]$")
|
||||
RE_STRING = re.compile(r'"(?:[^"\\]|\\.)*"')
|
||||
RE_MOD = re.compile(r"^(?:pub(?:\([^)]*\))?\s+)?mod\s+([A-Za-z_]\w*)")
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Finding:
|
||||
line: int
|
||||
label: str
|
||||
message: str
|
||||
|
||||
|
||||
def _check_module(attrs: list[str], line: int, name: str) -> list[Finding]:
|
||||
"""Findings for one module, given the attributes attached to it."""
|
||||
if COVERAGE_OFF_ATTR in attrs:
|
||||
return [] # excluded from coverage; which cfg gates it does not matter
|
||||
if CFG_TEST_ATTR in attrs:
|
||||
return [
|
||||
Finding(
|
||||
line,
|
||||
"missing-coverage-off",
|
||||
f"`mod {name}` is #[cfg(test)] but not excluded from coverage; "
|
||||
f"add {COVERAGE_OFF_ATTR}",
|
||||
)
|
||||
]
|
||||
unclassified = [
|
||||
attr for attr in attrs if RE_CFG_MENTIONS_TEST.match(RE_STRING.sub('""', attr))
|
||||
]
|
||||
if unclassified:
|
||||
return [
|
||||
Finding(
|
||||
line,
|
||||
"unclassified-cfg",
|
||||
f"`mod {name}` is gated on {unclassified[0]}, which this check "
|
||||
f"cannot tell apart from a module that ships in the library; "
|
||||
f"add {COVERAGE_OFF_ATTR} if it is test-only, or teach this "
|
||||
f"check the cfg if it is not",
|
||||
)
|
||||
]
|
||||
return []
|
||||
|
||||
|
||||
def _check_test_modules(lines: list[str]) -> list[Finding]:
|
||||
"""Findings for every test module that is not excluded from coverage."""
|
||||
findings: list[Finding] = []
|
||||
attrs: list[str] = []
|
||||
attrs_line = 0
|
||||
for number, raw in enumerate(lines, start=1):
|
||||
stripped = raw.strip()
|
||||
# Blank lines and comments are allowed between an attribute and its item.
|
||||
if not stripped or stripped.startswith("//"):
|
||||
continue
|
||||
if stripped.startswith("#["):
|
||||
if not attrs:
|
||||
attrs_line = number
|
||||
attrs.append(stripped)
|
||||
continue
|
||||
module = RE_MOD.match(stripped)
|
||||
if module is not None and attrs:
|
||||
findings += _check_module(attrs, attrs_line, module.group(1))
|
||||
attrs = []
|
||||
return findings
|
||||
|
||||
|
||||
def _check_crate_root(name: str, lines: list[str]) -> list[Finding]:
|
||||
"""A finding if a crate root is missing the coverage_attribute feature gate."""
|
||||
if name not in CRATE_ROOTS:
|
||||
return []
|
||||
if any(line.strip() == FEATURE_ATTR for line in lines):
|
||||
return []
|
||||
return [
|
||||
Finding(
|
||||
1,
|
||||
"missing-feature-gate",
|
||||
f"crate root is missing {FEATURE_ATTR}",
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
def check_source(name: str, text: str) -> list[Finding]:
|
||||
"""Findings for one file's contents; `name` is its base name (lib.rs, ...)."""
|
||||
lines = text.splitlines()
|
||||
return _check_crate_root(name, lines) + _check_test_modules(lines)
|
||||
|
||||
|
||||
def check_file(path: Path) -> list[Finding]:
|
||||
return check_source(path.name, path.read_text(encoding="utf-8"))
|
||||
|
||||
|
||||
def main() -> int:
|
||||
total = 0
|
||||
for path in (Path(name) for name in sys.argv[1:]):
|
||||
for finding in check_file(path):
|
||||
total += 1
|
||||
print(f"{path}:{finding.line}: {finding.label}: {finding.message}")
|
||||
return 1 if total else 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -8,6 +8,9 @@ cxx = { version = "1.0.198", features = ["c++20"] }
|
||||
[workspace.package]
|
||||
edition = "2024"
|
||||
|
||||
[workspace.lints.rust]
|
||||
unexpected_cfgs = { level = "warn", check-cfg = [ 'cfg(coverage)', 'cfg(coverage_nightly)' ] }
|
||||
|
||||
[profile.release]
|
||||
opt-level = 3
|
||||
overflow-checks = true
|
||||
|
||||
@@ -8,3 +8,6 @@ crate-type = ["staticlib"]
|
||||
|
||||
[dependencies]
|
||||
cxx.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#![cfg_attr(coverage_nightly, feature(coverage_attribute))]
|
||||
|
||||
#[cxx::bridge(namespace = "rs::hello_world")]
|
||||
mod ffi {
|
||||
extern "Rust" {
|
||||
@@ -8,3 +10,14 @@ mod ffi {
|
||||
pub fn hello_world() -> String {
|
||||
"hello_world".to_string()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[cfg_attr(coverage_nightly, coverage(off))]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn hello_world_returns_hello_world() {
|
||||
assert_eq!(hello_world(), "hello_world")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +34,10 @@ enum class HashRouterFlags : std::uint16_t {
|
||||
PRIVATE4 = 0x0800,
|
||||
// Used in EscrowFinish.cpp
|
||||
PRIVATE5 = 0x1000,
|
||||
PRIVATE6 = 0x2000
|
||||
PRIVATE6 = 0x2000,
|
||||
// Used in apply.cpp
|
||||
PRIVATE7 = 0x4000,
|
||||
PRIVATE8 = 0x8000
|
||||
};
|
||||
|
||||
constexpr HashRouterFlags
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
#include <xrpl/protocol/STVector256.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <utility>
|
||||
@@ -34,32 +33,6 @@ checkExpired(SLE const& sleCredential, NetClock::time_point const& closed);
|
||||
[[nodiscard]] TER
|
||||
deleteSLE(ApplyView& view, SLE::ref sleCredential, beast::Journal j);
|
||||
|
||||
/**
|
||||
* @brief Remove credentials pinned to a pseudo-account's owner directory.
|
||||
*
|
||||
* Cleans up credentials that were linked to a pseudo-account (Vault, LoanBroker,
|
||||
* AMM), which such an account can neither accept nor delete. Only credentials
|
||||
* are removed; every other object is left in place. The walk visits at most
|
||||
* @p maxNodesToDelete directory entries and charges the ones it leaves alone
|
||||
* against that budget too, so a directory holding other objects yields fewer
|
||||
* than @p maxNodesToDelete deletions. On reaching the bound the result is
|
||||
* `tecINCOMPLETE` and the caller must propagate it so a later transaction
|
||||
* resumes.
|
||||
*
|
||||
* @param view Mutable ledger view.
|
||||
* @param pseudoAcct The pseudo-account whose directory is cleaned.
|
||||
* @param maxNodesToDelete Upper bound on directory entries processed in one call.
|
||||
* @param j Journal for diagnostics.
|
||||
* @return tesSUCCESS once no credentials remain, tecINCOMPLETE if the bound was
|
||||
* reached, or a deletion error.
|
||||
*/
|
||||
[[nodiscard]] TER
|
||||
deletePseudoAccountCredentials(
|
||||
ApplyView& view,
|
||||
AccountID const& pseudoAcct,
|
||||
std::uint16_t maxNodesToDelete,
|
||||
beast::Journal j);
|
||||
|
||||
// Amendment and parameters checks for sfCredentialIDs field
|
||||
NotTEC
|
||||
checkFields(STTx const& tx, Rules const& rules, beast::Journal j);
|
||||
|
||||
@@ -2,8 +2,10 @@
|
||||
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/beast/utility/Zero.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
|
||||
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
|
||||
@@ -27,6 +29,291 @@
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
/**
|
||||
* Validate that @p account may lock @p amount of a token for later delivery
|
||||
* to @p dest.
|
||||
*
|
||||
* The lock-side counterpart of escrowUnlockPreclaimHelper: every issuer
|
||||
* control (locking opt-in, authorization, freeze/lock, transferability,
|
||||
* spendable balance) that gates locking token value lives here, so any
|
||||
* transactor that locks funds applies the same rules. The signature is
|
||||
* view-based rather than PreclaimContext-based so it can also run from
|
||||
* doApply.
|
||||
*/
|
||||
template <ValidIssueType T>
|
||||
TER
|
||||
escrowLockPreclaimHelper(
|
||||
ReadView const& view,
|
||||
AccountID const& account,
|
||||
AccountID const& dest,
|
||||
STAmount const& amount,
|
||||
beast::Journal j);
|
||||
|
||||
template <>
|
||||
inline TER
|
||||
escrowLockPreclaimHelper<Issue>(
|
||||
ReadView const& view,
|
||||
AccountID const& account,
|
||||
AccountID const& dest,
|
||||
STAmount const& amount,
|
||||
beast::Journal j)
|
||||
{
|
||||
auto const& issue = amount.get<Issue>();
|
||||
auto const& issuer = amount.getIssuer();
|
||||
// If the issuer is the same as the account, return tecNO_PERMISSION
|
||||
if (issuer == account)
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
// If the lsfAllowTrustLineLocking is not enabled, return tecNO_PERMISSION
|
||||
auto const sleIssuer = view.read(keylet::account(issuer));
|
||||
if (!sleIssuer)
|
||||
return tecNO_ISSUER;
|
||||
if (!sleIssuer->isFlag(lsfAllowTrustLineLocking))
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
// If the account does not have a trustline to the issuer, return tecNO_LINE
|
||||
auto const sleRippleState = view.read(keylet::trustLine(account, issuer, issue.currency));
|
||||
if (!sleRippleState)
|
||||
return tecNO_LINE;
|
||||
|
||||
STAmount const balance = (*sleRippleState)[sfBalance];
|
||||
|
||||
// If balance is positive, issuer must have higher address than account
|
||||
if (balance > beast::kZero && issuer < account)
|
||||
return tecNO_PERMISSION; // LCOV_EXCL_LINE
|
||||
|
||||
// If balance is negative, issuer must have lower address than account
|
||||
if (balance < beast::kZero && issuer > account)
|
||||
return tecNO_PERMISSION; // LCOV_EXCL_LINE
|
||||
|
||||
// If the issuer has requireAuth set, check if the account is authorized
|
||||
if (auto const ter = requireAuth(view, issue, account); !isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
// If the issuer has requireAuth set, check if the destination is authorized
|
||||
if (auto const ter = requireAuth(view, issue, dest); !isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
// If the issuer has frozen the account, return tecFROZEN
|
||||
if (isFrozen(view, account, issue))
|
||||
return tecFROZEN;
|
||||
|
||||
// If the issuer has frozen the destination, return tecFROZEN
|
||||
if (isFrozen(view, dest, issue))
|
||||
return tecFROZEN;
|
||||
|
||||
STAmount const spendableAmount =
|
||||
accountHolds(view, account, issue.currency, issuer, FreezeHandling::IgnoreFreeze, j);
|
||||
|
||||
// If the balance is less than or equal to 0, return tecINSUFFICIENT_FUNDS
|
||||
if (spendableAmount <= beast::kZero)
|
||||
return tecINSUFFICIENT_FUNDS;
|
||||
|
||||
// If the spendable amount is less than the amount, return
|
||||
// tecINSUFFICIENT_FUNDS
|
||||
if (spendableAmount < amount)
|
||||
return tecINSUFFICIENT_FUNDS;
|
||||
|
||||
// If the amount is not addable to the balance, return tecPRECISION_LOSS
|
||||
if (!canAdd(spendableAmount, amount))
|
||||
return tecPRECISION_LOSS;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
template <>
|
||||
inline TER
|
||||
escrowLockPreclaimHelper<MPTIssue>(
|
||||
ReadView const& view,
|
||||
AccountID const& account,
|
||||
AccountID const& dest,
|
||||
STAmount const& amount,
|
||||
beast::Journal j)
|
||||
{
|
||||
AccountID const issuer = amount.getIssuer();
|
||||
// If the issuer is the same as the account, return tecNO_PERMISSION
|
||||
if (issuer == account)
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
// If the mpt does not exist, return tecOBJECT_NOT_FOUND
|
||||
auto const issuanceKey = keylet::mptokenIssuance(amount.get<MPTIssue>().getMptID());
|
||||
auto const sleIssuance = view.read(issuanceKey);
|
||||
if (!sleIssuance)
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
|
||||
// If the lsfMPTCanEscrow is not enabled, return tecNO_PERMISSION
|
||||
if (!sleIssuance->isFlag(lsfMPTCanEscrow))
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
// If the issuer is not the same as the issuer of the mpt, return
|
||||
// tecNO_PERMISSION
|
||||
if (sleIssuance->getAccountID(sfIssuer) != issuer)
|
||||
return tecNO_PERMISSION; // LCOV_EXCL_LINE
|
||||
|
||||
// If the account does not have the mpt, return tecOBJECT_NOT_FOUND
|
||||
if (!view.exists(keylet::mptoken(issuanceKey.key, account)))
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
|
||||
// If the issuer has requireAuth set, check if the account is
|
||||
// authorized
|
||||
auto const& mptIssue = amount.get<MPTIssue>();
|
||||
if (auto const ter = requireAuth(view, mptIssue, account, AuthType::WeakAuth);
|
||||
!isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
// If the issuer has requireAuth set, check if the destination is
|
||||
// authorized
|
||||
if (auto const ter = requireAuth(view, mptIssue, dest, AuthType::WeakAuth); !isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
// If the issuer has frozen the account, return tecLOCKED
|
||||
if (isFrozen(view, account, *sleIssuance))
|
||||
return tecLOCKED;
|
||||
|
||||
// If the issuer has frozen the destination, return tecLOCKED
|
||||
if (isFrozen(view, dest, *sleIssuance))
|
||||
return tecLOCKED;
|
||||
|
||||
// If the mpt cannot be transferred, return tecNO_AUTH
|
||||
if (auto const ter = canTransfer(view, mptIssue, account, dest); !isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
STAmount const spendableAmount = accountHolds(
|
||||
view,
|
||||
account,
|
||||
amount.get<MPTIssue>(),
|
||||
FreezeHandling::IgnoreFreeze,
|
||||
AuthHandling::IgnoreAuth,
|
||||
j);
|
||||
|
||||
// If the balance is less than or equal to 0, return tecINSUFFICIENT_FUNDS
|
||||
if (spendableAmount <= beast::kZero)
|
||||
return tecINSUFFICIENT_FUNDS;
|
||||
|
||||
// If the spendable amount is less than the amount, return
|
||||
// tecINSUFFICIENT_FUNDS
|
||||
if (spendableAmount < amount)
|
||||
return tecINSUFFICIENT_FUNDS;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
template <ValidIssueType T>
|
||||
TER
|
||||
escrowLockApplyHelper(
|
||||
ApplyView& view,
|
||||
AccountID const& issuer,
|
||||
AccountID const& sender,
|
||||
STAmount const& amount,
|
||||
beast::Journal journal);
|
||||
|
||||
template <>
|
||||
inline TER
|
||||
escrowLockApplyHelper<Issue>(
|
||||
ApplyView& view,
|
||||
AccountID const& issuer,
|
||||
AccountID const& sender,
|
||||
STAmount const& amount,
|
||||
beast::Journal journal)
|
||||
{
|
||||
// Defensive: Issuer cannot create an escrow
|
||||
if (issuer == sender)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const ter =
|
||||
directSendNoFee(view, sender, issuer, amount, !amount.holds<MPTIssue>(), journal);
|
||||
if (!isTesSuccess(ter))
|
||||
return ter; // LCOV_EXCL_LINE
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
template <>
|
||||
inline TER
|
||||
escrowLockApplyHelper<MPTIssue>(
|
||||
ApplyView& view,
|
||||
AccountID const& issuer,
|
||||
AccountID const& sender,
|
||||
STAmount const& amount,
|
||||
beast::Journal journal)
|
||||
{
|
||||
// Defensive: Issuer cannot create an escrow
|
||||
if (issuer == sender)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const ter = lockEscrowMPT(view, sender, amount, journal);
|
||||
if (!isTesSuccess(ter))
|
||||
return ter; // LCOV_EXCL_LINE
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
template <ValidIssueType T>
|
||||
TER
|
||||
escrowUnlockPreclaimHelper(
|
||||
ReadView const& view,
|
||||
AccountID const& account,
|
||||
STAmount const& amount,
|
||||
bool checkFreeze = true);
|
||||
|
||||
template <>
|
||||
inline TER
|
||||
escrowUnlockPreclaimHelper<Issue>(
|
||||
ReadView const& view,
|
||||
AccountID const& account,
|
||||
STAmount const& amount,
|
||||
bool checkFreeze)
|
||||
{
|
||||
AccountID const& issuer = amount.getIssuer();
|
||||
// If the issuer is the same as the account, return tesSUCCESS
|
||||
if (issuer == account)
|
||||
return tesSUCCESS;
|
||||
|
||||
// If the issuer has requireAuth set, check if the destination is authorized
|
||||
if (auto const ter = requireAuth(view, amount.get<Issue>(), account); !isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
// If the issuer has deep frozen the destination, return tecFROZEN
|
||||
if (checkFreeze &&
|
||||
isDeepFrozen(view, account, amount.get<Issue>().currency, amount.getIssuer()))
|
||||
return tecFROZEN;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
template <>
|
||||
inline TER
|
||||
escrowUnlockPreclaimHelper<MPTIssue>(
|
||||
ReadView const& view,
|
||||
AccountID const& account,
|
||||
STAmount const& amount,
|
||||
bool checkFreeze)
|
||||
{
|
||||
AccountID const& issuer = amount.getIssuer();
|
||||
// If the issuer is the same as the account, return tesSUCCESS
|
||||
if (issuer == account)
|
||||
return tesSUCCESS;
|
||||
|
||||
// If the mpt does not exist, return tecOBJECT_NOT_FOUND
|
||||
auto const issuanceKey = keylet::mptokenIssuance(amount.get<MPTIssue>().getMptID());
|
||||
auto const sleIssuance = view.read(issuanceKey);
|
||||
if (!sleIssuance)
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
|
||||
// If the issuer has requireAuth set, check if the account is
|
||||
// authorized
|
||||
auto const& mptIssue = amount.get<MPTIssue>();
|
||||
if (auto const ter = requireAuth(view, mptIssue, account, AuthType::WeakAuth);
|
||||
!isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
// If the issuer has frozen the account, return tecLOCKED
|
||||
if (checkFreeze && isFrozen(view, account, *sleIssuance))
|
||||
return tecLOCKED;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
template <ValidIssueType T>
|
||||
TER
|
||||
escrowUnlockApplyHelper(
|
||||
@@ -55,9 +342,6 @@ escrowUnlockApplyHelper<Issue>(
|
||||
bool createAsset,
|
||||
beast::Journal journal)
|
||||
{
|
||||
auto const& issue = amount.get<Issue>();
|
||||
Keylet const trustLineKey = keylet::trustLine(receiver, issue);
|
||||
bool const recvLow = issuer > receiver;
|
||||
bool const senderIssuer = issuer == sender;
|
||||
bool const receiverIssuer = issuer == receiver;
|
||||
|
||||
@@ -67,6 +351,10 @@ escrowUnlockApplyHelper<Issue>(
|
||||
if (receiverIssuer)
|
||||
return tesSUCCESS;
|
||||
|
||||
auto const& issue = amount.get<Issue>();
|
||||
Keylet const trustLineKey = keylet::trustLine(receiver, issue);
|
||||
bool const recvLow = issuer > receiver;
|
||||
|
||||
if (!ctx.view.exists(trustLineKey) && createAsset)
|
||||
{
|
||||
// Can the account cover the trust line's reserve?
|
||||
|
||||
@@ -2,28 +2,85 @@
|
||||
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/beast/utility/Zero.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Concepts.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Issue.h>
|
||||
#include <xrpl/protocol/MPTAmount.h>
|
||||
#include <xrpl/protocol/MPTIssue.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
#include <xrpl/protocol/Rules.h>
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/UintTypes.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
/**
|
||||
* Validate the token amount of a PaymentChannelCreate or PaymentChannelFund
|
||||
* transaction during preflight.
|
||||
*
|
||||
* @param rules The current ledger rules used to check amendment status.
|
||||
* @param amount The channel or funding amount from the transaction.
|
||||
* @return tesSUCCESS if the amount is valid; temBAD_AMOUNT, temBAD_CURRENCY,
|
||||
* or temDISABLED otherwise.
|
||||
*/
|
||||
template <ValidIssueType T>
|
||||
NotTEC
|
||||
payChanAmountPreflightHelper(Rules const& rules, STAmount const& amount);
|
||||
|
||||
template <>
|
||||
inline NotTEC
|
||||
payChanAmountPreflightHelper<Issue>(Rules const&, STAmount const& amount)
|
||||
{
|
||||
if (amount.native() || amount <= beast::kZero)
|
||||
return temBAD_AMOUNT;
|
||||
|
||||
if (badCurrency() == amount.get<Issue>().currency)
|
||||
return temBAD_CURRENCY;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
template <>
|
||||
inline NotTEC
|
||||
payChanAmountPreflightHelper<MPTIssue>(Rules const& rules, STAmount const& amount)
|
||||
{
|
||||
if (!rules.enabled(fixCleanup3_2_0) && !rules.enabled(featureMPTokensV1))
|
||||
return temDISABLED;
|
||||
|
||||
if (amount.native() || amount.mpt() > MPTAmount{kMaxMpTokenAmount} || amount <= beast::kZero)
|
||||
return temBAD_AMOUNT;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Close a payment channel and return its remaining funds to the channel owner.
|
||||
*
|
||||
* @param slep The SLE for the PayChannel object to close.
|
||||
* @param view The apply view in which ledger state modifications are made.
|
||||
* @param key The ledger key identifying the PayChannel entry.
|
||||
* @param j Journal used for fatal-level diagnostic messages.
|
||||
* @return tesSUCCESS on success; tefBAD_LEDGER if a directory removal
|
||||
* fails; tefINTERNAL if the source account SLE cannot be found.
|
||||
* @param slep The SLE for the PayChannel object to close.
|
||||
* @param ctx The apply view context (view and transaction) in which ledger
|
||||
* state modifications are made.
|
||||
* @param key The ledger key identifying the PayChannel entry.
|
||||
* @param txAccount The account submitting the transaction that closes the
|
||||
* channel.
|
||||
* @param j Journal used for fatal-level diagnostic messages.
|
||||
* @return tesSUCCESS on success; tefBAD_LEDGER if a directory removal
|
||||
* fails; tefINTERNAL if the source account SLE cannot be found.
|
||||
*/
|
||||
TER
|
||||
closeChannel(SLE::ref slep, ApplyView& view, uint256 const& key, beast::Journal j);
|
||||
closeChannel(
|
||||
SLE::ref slep,
|
||||
ApplyViewContext ctx,
|
||||
uint256 const& key,
|
||||
AccountID const& txAccount,
|
||||
beast::Journal j);
|
||||
|
||||
/**
|
||||
* Add two uint32_t values with saturation at UINT32_MAX.
|
||||
|
||||
@@ -38,6 +38,12 @@ enum class FreezeHandling { IgnoreFreeze, ZeroIfFrozen };
|
||||
*/
|
||||
enum class AuthHandling { IgnoreAuth, ZeroIfUnauthorized };
|
||||
|
||||
/**
|
||||
* Controls whether the recipient owner-reserve check is enforced when
|
||||
* auto-creating a trustline or MPToken during AMMWithdraw or AMMClawback.
|
||||
*/
|
||||
enum class ReserveHandling : bool { EnforceReserve, IgnoreReserve };
|
||||
|
||||
/**
|
||||
* Controls whether to include the account's full spendable balance
|
||||
*/
|
||||
|
||||
@@ -92,6 +92,26 @@ enum class HashPrefix : std::uint32_t {
|
||||
* Batch
|
||||
*/
|
||||
Batch = detail::makeHashPrefix('B', 'C', 'H'),
|
||||
|
||||
/**
|
||||
* inner transaction to sign as the counterparty
|
||||
*/
|
||||
CounterpartyTxSign = detail::makeHashPrefix('C', 'P', 'T'),
|
||||
|
||||
/**
|
||||
* inner transaction to multi-sign as the counterparty
|
||||
*/
|
||||
CounterpartyTxMultiSign = detail::makeHashPrefix('C', 'P', 'M'),
|
||||
|
||||
/**
|
||||
* inner transaction to sign as the sponsor
|
||||
*/
|
||||
SponsorTxSign = detail::makeHashPrefix('S', 'P', 'N'),
|
||||
|
||||
/**
|
||||
* inner transaction to multi-sign as the sponsor
|
||||
*/
|
||||
SponsorTxMultiSign = detail::makeHashPrefix('S', 'P', 'M'),
|
||||
};
|
||||
|
||||
template <class Hasher>
|
||||
|
||||
@@ -1,10 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/beast/utility/Zero.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/HashPrefix.h>
|
||||
#include <xrpl/protocol/IOUAmount.h>
|
||||
#include <xrpl/protocol/Issue.h>
|
||||
#include <xrpl/protocol/MPTAmount.h>
|
||||
#include <xrpl/protocol/MPTIssue.h>
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
#include <xrpl/protocol/Serializer.h>
|
||||
#include <xrpl/protocol/UintTypes.h>
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
inline void
|
||||
@@ -15,4 +25,70 @@ serializePayChanAuthorization(Serializer& msg, uint256 const& key, XRPAmount con
|
||||
msg.add64(amt.drops());
|
||||
}
|
||||
|
||||
inline void
|
||||
serializePayChanAuthorization(
|
||||
Serializer& msg,
|
||||
uint256 const& key,
|
||||
IOUAmount const& amt,
|
||||
Currency const& cur,
|
||||
AccountID const& iss)
|
||||
{
|
||||
msg.add32(HashPrefix::PaymentChannelClaim);
|
||||
msg.addBitString(key);
|
||||
if (amt == beast::kZero)
|
||||
{
|
||||
msg.add64(STAmount::kIssuedCurrency);
|
||||
}
|
||||
else if (amt.signum() == -1)
|
||||
{ // 512 = not native; the sign is encoded by omitting the 256 flag, so
|
||||
// the mantissa must be serialized as its absolute value
|
||||
msg.add64(
|
||||
static_cast<std::uint64_t>(-amt.mantissa()) |
|
||||
(static_cast<std::uint64_t>(amt.exponent() + 512 + 97) << (64 - 10)));
|
||||
}
|
||||
else
|
||||
{ // 256 = positive
|
||||
msg.add64(
|
||||
amt.mantissa() |
|
||||
(static_cast<std::uint64_t>(amt.exponent() + 512 + 256 + 97) << (64 - 10)));
|
||||
}
|
||||
msg.addBitString(cur);
|
||||
msg.addBitString(iss);
|
||||
}
|
||||
|
||||
inline void
|
||||
serializePayChanAuthorization(
|
||||
Serializer& msg,
|
||||
uint256 const& key,
|
||||
MPTAmount const& amt,
|
||||
MPTID const& mptID,
|
||||
AccountID const& iss)
|
||||
{
|
||||
msg.add32(HashPrefix::PaymentChannelClaim);
|
||||
msg.addBitString(key);
|
||||
msg.add64(amt.value());
|
||||
msg.addBitString(mptID);
|
||||
msg.addBitString(iss);
|
||||
}
|
||||
|
||||
inline void
|
||||
serializePayChanAuthorization(Serializer& msg, uint256 const& key, STAmount const& amt)
|
||||
{
|
||||
if (amt.native())
|
||||
{
|
||||
serializePayChanAuthorization(msg, key, amt.xrp());
|
||||
}
|
||||
else if (amt.holds<Issue>())
|
||||
{
|
||||
serializePayChanAuthorization(
|
||||
msg, key, amt.iou(), amt.get<Issue>().currency, amt.get<Issue>().account);
|
||||
}
|
||||
else if (amt.holds<MPTIssue>())
|
||||
{
|
||||
auto const& mpt = amt.get<MPTIssue>();
|
||||
auto const& mptID = mpt.getMptID();
|
||||
serializePayChanAuthorization(msg, key, amt.mpt(), mptID, amt.getIssuer());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include <chrono>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
@@ -407,16 +408,6 @@ using TxID = uint256;
|
||||
*/
|
||||
constexpr std::uint16_t kMaxDeletableAmmTrustLines = 512;
|
||||
|
||||
/**
|
||||
* The maximum number of owner-directory entries to walk when clearing
|
||||
* credentials pinned to a pseudo-account, in a single transaction.
|
||||
*
|
||||
* The walk stops after this many entries whether or not each one turns out to
|
||||
* be a credential, so a directory that also holds other objects yields fewer
|
||||
* deletions per transaction.
|
||||
*/
|
||||
constexpr std::uint16_t kMaxDeletablePseudoAccountCredentials = 512;
|
||||
|
||||
/**
|
||||
* The maximum length of a URI inside an Oracle
|
||||
*/
|
||||
@@ -554,6 +545,11 @@ constexpr std::size_t kEcClawbackProofLength = SECP256K1_COMPACT_CLAWBACK_PROOF_
|
||||
*/
|
||||
constexpr std::uint32_t kConfidentialFeeMultiplier = 9;
|
||||
|
||||
/**
|
||||
* Maximum value a confidential MPT key epoch may reach.
|
||||
*/
|
||||
constexpr std::uint32_t kMaxKeyEpoch = std::numeric_limits<std::uint32_t>::max();
|
||||
|
||||
/**
|
||||
* Compressed EC point prefix for even y-coordinate
|
||||
*/
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/HashPrefix.h>
|
||||
#include <xrpl/protocol/PublicKey.h>
|
||||
#include <xrpl/protocol/Rules.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
@@ -13,6 +14,7 @@
|
||||
#include <xrpl/protocol/SecretKey.h>
|
||||
#include <xrpl/protocol/SeqProxy.h>
|
||||
#include <xrpl/protocol/Serializer.h>
|
||||
#include <xrpl/protocol/Sign.h>
|
||||
#include <xrpl/protocol/TxFormats.h>
|
||||
|
||||
#include <boost/container/flat_set.hpp>
|
||||
@@ -105,14 +107,36 @@ public:
|
||||
[[nodiscard]] json::Value
|
||||
getJson(JsonOptions options, bool binary) const;
|
||||
|
||||
/**
|
||||
* Sign the transaction as its account.
|
||||
*
|
||||
* @param publicKey The public key for signing.
|
||||
* @param secretKey The secret key for signing.
|
||||
*/
|
||||
void
|
||||
sign(PublicKey const& publicKey, SecretKey const& secretKey);
|
||||
|
||||
/**
|
||||
* Sign the transaction in one of its signature fields.
|
||||
*
|
||||
* The signature is bound to the role that made it, so it cannot be moved
|
||||
* into another role.
|
||||
*
|
||||
* @param publicKey The public key for signing.
|
||||
* @param secretKey The secret key for signing.
|
||||
* @param role The role signing the transaction.
|
||||
* @param rules The current ledger rules.
|
||||
*/
|
||||
void
|
||||
sign(
|
||||
PublicKey const& publicKey,
|
||||
SecretKey const& secretKey,
|
||||
std::optional<std::reference_wrapper<SField const>> signatureTarget = {});
|
||||
SignatureRole role,
|
||||
Rules const& rules);
|
||||
|
||||
/**
|
||||
* Check the signature.
|
||||
*
|
||||
* @param rules The current ledger rules.
|
||||
* @return `true` if valid signature. If invalid, the error message string.
|
||||
*/
|
||||
@@ -120,7 +144,7 @@ public:
|
||||
checkSign(Rules const& rules) const;
|
||||
|
||||
[[nodiscard]] std::expected<void, std::string>
|
||||
checkBatchSign(Rules const& rules) const;
|
||||
checkBatchSign() const;
|
||||
|
||||
// SQL Functions with metadata.
|
||||
static std::string const&
|
||||
@@ -162,28 +186,28 @@ public:
|
||||
private:
|
||||
/**
|
||||
* Check the signature.
|
||||
*
|
||||
* @param rules The current ledger rules.
|
||||
* @param sigObject Reference to object that contains the signature fields.
|
||||
* Will be *this more often than not.
|
||||
* @param role The role that made the signature in sigObject. Determines
|
||||
* the signing prefix, which binds the signature to that role.
|
||||
* @return `true` if valid signature. If invalid, the error message string.
|
||||
*/
|
||||
[[nodiscard]] std::expected<void, std::string>
|
||||
checkSign(Rules const& rules, STObject const& sigObject) const;
|
||||
checkSign(Rules const& rules, STObject const& sigObject, SignatureRole role) const;
|
||||
|
||||
[[nodiscard]] std::expected<void, std::string>
|
||||
checkSingleSign(STObject const& sigObject) const;
|
||||
checkSingleSign(STObject const& sigObject, HashPrefix prefix) const;
|
||||
|
||||
[[nodiscard]] std::expected<void, std::string>
|
||||
checkMultiSign(Rules const& rules, STObject const& sigObject) const;
|
||||
checkMultiSign(STObject const& sigObject, HashPrefix prefix) const;
|
||||
|
||||
[[nodiscard]] std::expected<void, std::string>
|
||||
checkBatchSingleSign(STObject const& batchSigner, std::vector<uint256> const& txIds) const;
|
||||
|
||||
[[nodiscard]] std::expected<void, std::string>
|
||||
checkBatchMultiSign(
|
||||
STObject const& batchSigner,
|
||||
Rules const& rules,
|
||||
std::vector<uint256> const& txIds) const;
|
||||
checkBatchMultiSign(STObject const& batchSigner, std::vector<uint256> const& txIds) const;
|
||||
|
||||
void
|
||||
buildBatchTxns();
|
||||
|
||||
@@ -4,13 +4,65 @@
|
||||
#include <xrpl/protocol/HashPrefix.h>
|
||||
#include <xrpl/protocol/KeyType.h>
|
||||
#include <xrpl/protocol/PublicKey.h>
|
||||
#include <xrpl/protocol/Rules.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STObject.h>
|
||||
#include <xrpl/protocol/SecretKey.h>
|
||||
#include <xrpl/protocol/Serializer.h>
|
||||
|
||||
#include <optional>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
/**
|
||||
* The signature slots on a transaction.
|
||||
*
|
||||
* Each role signs different bytes, so a signature cannot be moved from the
|
||||
* role that made it into another role. See signingPrefix.
|
||||
*/
|
||||
enum class SignatureRole {
|
||||
/**
|
||||
* The transaction's own signature, in sfTxnSignature or sfSigners.
|
||||
*/
|
||||
Transaction,
|
||||
/**
|
||||
* The counterparty's signature, in sfCounterpartySignature.
|
||||
*/
|
||||
Counterparty,
|
||||
/**
|
||||
* The sponsor's signature, in sfSponsorSignature.
|
||||
*/
|
||||
Sponsor
|
||||
};
|
||||
|
||||
/**
|
||||
* The field that holds this role's signature.
|
||||
*
|
||||
* @return The signature field, or nullptr for SignatureRole::Transaction,
|
||||
* whose signature lives at the top level of the transaction.
|
||||
*/
|
||||
[[nodiscard]] SField const*
|
||||
signatureField(SignatureRole role);
|
||||
|
||||
/**
|
||||
* The role that signs into the given field.
|
||||
*
|
||||
* @return The role, or an unseated optional if the field does not hold a
|
||||
* transaction signature.
|
||||
*/
|
||||
[[nodiscard]] std::optional<SignatureRole>
|
||||
signatureRole(SField const& sigField);
|
||||
|
||||
/**
|
||||
* The hash prefix that binds a transaction signature to the role that made it.
|
||||
*
|
||||
* @param role The role making the signature.
|
||||
* @param multiSigning Whether the signature is a multi-signature.
|
||||
* @param rules The current ledger rules.
|
||||
*/
|
||||
[[nodiscard]] HashPrefix
|
||||
signingPrefix(SignatureRole role, bool multiSigning, Rules const& rules);
|
||||
|
||||
/**
|
||||
* Sign an STObject
|
||||
*
|
||||
@@ -49,9 +101,12 @@ verify(
|
||||
|
||||
/**
|
||||
* Return a Serializer suitable for computing a multisigning TxnSignature.
|
||||
*
|
||||
* @param prefix Prefix to insert before the serialized object. Get it from
|
||||
* signingPrefix, so that the signature is bound to the role making it.
|
||||
*/
|
||||
Serializer
|
||||
buildMultiSigningData(STObject const& obj, AccountID const& signingID);
|
||||
buildMultiSigningData(STObject const& obj, AccountID const& signingID, HashPrefix prefix);
|
||||
|
||||
/**
|
||||
* Break the multi-signing hash computation into 2 parts for optimization.
|
||||
@@ -67,7 +122,7 @@ buildMultiSigningData(STObject const& obj, AccountID const& signingID);
|
||||
* signer's unique data.
|
||||
*/
|
||||
Serializer
|
||||
startMultiSigningData(STObject const& obj);
|
||||
startMultiSigningData(STObject const& obj, HashPrefix prefix);
|
||||
|
||||
inline void
|
||||
finishMultiSigningData(AccountID const& signingID, Serializer& s)
|
||||
|
||||
@@ -129,8 +129,11 @@ enum TEMcodes : TERUnderlyingType {
|
||||
temARRAY_TOO_LARGE,
|
||||
temBAD_TRANSFER_FEE,
|
||||
temINVALID_INNER_BATCH,
|
||||
|
||||
temBAD_MPT,
|
||||
temBAD_CIPHERTEXT,
|
||||
temINVALID_BYTECODE,
|
||||
temTEMP_DISABLED,
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -179,6 +182,8 @@ enum TEFcodes : TERUnderlyingType {
|
||||
tefINVALID_LEDGER_FIX_TYPE,
|
||||
tefNO_DST_PARTIAL,
|
||||
tefBAD_PATH_COUNT,
|
||||
tefNO_BYTECODE,
|
||||
tefBYTECODE_NOT_INCLUDED,
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -370,6 +375,8 @@ enum TECcodes : TERUnderlyingType {
|
||||
tecNO_DELEGATE_PERMISSION = 198,
|
||||
tecBAD_PROOF = 199,
|
||||
tecNO_SPONSOR_PERMISSION = 200,
|
||||
tecOUT_OF_GAS = 201,
|
||||
tecBYTECODE_REJECTED = 202,
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
// Keep it sorted in reverse chronological order.
|
||||
|
||||
XRPL_FIX (Cleanup3_4_0, Supported::Yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(TokenPaychan, Supported::Yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(Sponsor, Supported::Yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(BatchV1_1, Supported::Yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(LendingProtocolV1_1, Supported::Yes, VoteBehavior::DefaultNo)
|
||||
@@ -144,3 +145,7 @@ XRPL_RETIRE_FEATURE(SortedDirectories)
|
||||
XRPL_RETIRE_FEATURE(TicketBatch)
|
||||
XRPL_RETIRE_FEATURE(TickSize)
|
||||
XRPL_RETIRE_FEATURE(TrustSetAuth)
|
||||
XRPL_FEATURE(SmartEscrow, Supported::No, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(LendingProtocolV1_2, Supported::No, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (Cleanup3_5_0, Supported::Yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(ConfidentialMPTKeyRotation, Supported::No, VoteBehavior::DefaultNo)
|
||||
|
||||
@@ -309,6 +309,11 @@ LEDGER_ENTRY(ltFEE_SETTINGS, 0x0073, FeeSettings, fee, ({
|
||||
{sfBaseFeeDrops, SoeOptional},
|
||||
{sfReserveBaseDrops, SoeOptional},
|
||||
{sfReserveIncrementDrops, SoeOptional},
|
||||
// Smart Escrow fields
|
||||
{sfGasLimit, SoeOptional},
|
||||
{sfBytecodeSizeLimit, SoeOptional},
|
||||
{sfGasPrice, SoeOptional},
|
||||
|
||||
{sfPreviousTxnID, SoeOptional},
|
||||
{sfPreviousTxnLgrSeq, SoeOptional},
|
||||
}))
|
||||
@@ -339,6 +344,8 @@ LEDGER_ENTRY(ltESCROW, 0x0075, Escrow, escrow, ({
|
||||
{sfCondition, SoeOptional},
|
||||
{sfCancelAfter, SoeOptional},
|
||||
{sfFinishAfter, SoeOptional},
|
||||
{sfBytecode, SoeOptional},
|
||||
{sfData, SoeOptional},
|
||||
{sfSourceTag, SoeOptional},
|
||||
{sfDestinationTag, SoeOptional},
|
||||
{sfOwnerNode, SoeRequired},
|
||||
@@ -369,6 +376,8 @@ LEDGER_ENTRY(ltPAYCHAN, 0x0078, PayChannel, payment_channel, ({
|
||||
{sfPreviousTxnID, SoeRequired},
|
||||
{sfPreviousTxnLgrSeq, SoeRequired},
|
||||
{sfDestinationNode, SoeOptional},
|
||||
{sfTransferRate, SoeOptional},
|
||||
{sfIssuerNode, SoeOptional},
|
||||
}))
|
||||
|
||||
/** The ledger object which tracks the AMM.
|
||||
@@ -408,6 +417,8 @@ LEDGER_ENTRY(ltMPTOKEN_ISSUANCE, 0x007e, MPTokenIssuance, mpt_issuance, ({
|
||||
{sfReferenceHolding, SoeOptional},
|
||||
{sfIssuerEncryptionKey, SoeOptional},
|
||||
{sfAuditorEncryptionKey, SoeOptional},
|
||||
{sfIssuerKeyEpoch, SoeOptional},
|
||||
{sfAuditorKeyEpoch, SoeOptional},
|
||||
{sfConfidentialOutstandingAmount, SoeDefault},
|
||||
}))
|
||||
|
||||
|
||||
@@ -119,6 +119,15 @@ TYPED_SFIELD(sfRemainingOwnerCount, UINT32, 73)
|
||||
TYPED_SFIELD(sfSponsorFlags, UINT32, 74)
|
||||
TYPED_SFIELD(sfSubscriptionDate, UINT32, 75)
|
||||
TYPED_SFIELD(sfRedemptionDate, UINT32, 76)
|
||||
TYPED_SFIELD(sfIssuerKeyEpoch, UINT32, 77)
|
||||
TYPED_SFIELD(sfAuditorKeyEpoch, UINT32, 78)
|
||||
TYPED_SFIELD(sfIssuerKeyMirrorEpoch, UINT32, 79)
|
||||
TYPED_SFIELD(sfAuditorKeyMirrorEpoch, UINT32, 80)
|
||||
TYPED_SFIELD(sfGasLimit, UINT32, 81)
|
||||
TYPED_SFIELD(sfBytecodeSizeLimit, UINT32, 82)
|
||||
TYPED_SFIELD(sfGasPrice, UINT32, 83)
|
||||
TYPED_SFIELD(sfGas, UINT32, 84)
|
||||
TYPED_SFIELD(sfGasUsed, UINT32, 85)
|
||||
|
||||
// 64-bit integers (common)
|
||||
TYPED_SFIELD(sfIndexNext, UINT64, 1)
|
||||
@@ -234,6 +243,7 @@ TYPED_SFIELD(sfManagementFeeOutstanding, NUMBER, 17, SField::kSmdNeedsAsset
|
||||
// 32-bit signed (common)
|
||||
TYPED_SFIELD(sfLoanScale, INT32, 1)
|
||||
TYPED_SFIELD(sfRemainingOwnerCountDelta, INT32, 2)
|
||||
TYPED_SFIELD(sfVMReturnCode, INT32, 3)
|
||||
|
||||
// currency amount (common)
|
||||
TYPED_SFIELD(sfAmount, AMOUNT, 1)
|
||||
@@ -318,6 +328,7 @@ TYPED_SFIELD(sfAuditorEncryptedAmount, VL, 43)
|
||||
TYPED_SFIELD(sfAuditorEncryptionKey, VL, 44)
|
||||
TYPED_SFIELD(sfAmountCommitment, VL, 45)
|
||||
TYPED_SFIELD(sfBalanceCommitment, VL, 46)
|
||||
TYPED_SFIELD(sfBytecode, VL, 47)
|
||||
|
||||
// account (common)
|
||||
TYPED_SFIELD(sfAccount, ACCOUNT, 1)
|
||||
|
||||
@@ -66,11 +66,13 @@ TRANSACTION(ttPAYMENT, 0, Payment,
|
||||
#endif
|
||||
TRANSACTION(ttESCROW_CREATE, 1, EscrowCreate, ({.delegable = Delegation::Delegable}), ({
|
||||
{sfDestination, SoeRequired},
|
||||
{sfDestinationTag, SoeOptional},
|
||||
{sfAmount, SoeRequired, SoeMptSupported},
|
||||
{sfCondition, SoeOptional},
|
||||
{sfCancelAfter, SoeOptional},
|
||||
{sfFinishAfter, SoeOptional},
|
||||
{sfDestinationTag, SoeOptional},
|
||||
{sfBytecode, SoeOptional},
|
||||
{sfData, SoeOptional},
|
||||
}))
|
||||
|
||||
/** This transaction type completes an existing escrow. */
|
||||
@@ -83,6 +85,7 @@ TRANSACTION(ttESCROW_FINISH, 2, EscrowFinish, ({.delegable = Delegation::Delegab
|
||||
{sfFulfillment, SoeOptional},
|
||||
{sfCondition, SoeOptional},
|
||||
{sfCredentialIDs, SoeOptional},
|
||||
{sfGas, SoeOptional},
|
||||
}))
|
||||
|
||||
|
||||
@@ -179,7 +182,7 @@ TRANSACTION(ttSIGNER_LIST_SET, 12, SignerListSet,
|
||||
#endif
|
||||
TRANSACTION(ttPAYCHAN_CREATE, 13, PaymentChannelCreate, ({.delegable = Delegation::Delegable}), ({
|
||||
{sfDestination, SoeRequired},
|
||||
{sfAmount, SoeRequired},
|
||||
{sfAmount, SoeRequired, SoeMptSupported},
|
||||
{sfSettleDelay, SoeRequired},
|
||||
{sfPublicKey, SoeRequired},
|
||||
{sfCancelAfter, SoeOptional},
|
||||
@@ -192,7 +195,7 @@ TRANSACTION(ttPAYCHAN_CREATE, 13, PaymentChannelCreate, ({.delegable = Delegatio
|
||||
#endif
|
||||
TRANSACTION(ttPAYCHAN_FUND, 14, PaymentChannelFund, ({.delegable = Delegation::Delegable}), ({
|
||||
{sfChannel, SoeRequired},
|
||||
{sfAmount, SoeRequired},
|
||||
{sfAmount, SoeRequired, SoeMptSupported},
|
||||
{sfExpiration, SoeOptional},
|
||||
}))
|
||||
|
||||
@@ -202,8 +205,8 @@ TRANSACTION(ttPAYCHAN_FUND, 14, PaymentChannelFund, ({.delegable = Delegation::D
|
||||
#endif
|
||||
TRANSACTION(ttPAYCHAN_CLAIM, 15, PaymentChannelClaim, ({.delegable = Delegation::Delegable}), ({
|
||||
{sfChannel, SoeRequired},
|
||||
{sfAmount, SoeOptional},
|
||||
{sfBalance, SoeOptional},
|
||||
{sfAmount, SoeOptional, SoeMptSupported},
|
||||
{sfBalance, SoeOptional, SoeMptSupported},
|
||||
{sfSignature, SoeOptional},
|
||||
{sfPublicKey, SoeOptional},
|
||||
{sfCredentialIDs, SoeOptional},
|
||||
@@ -1161,6 +1164,10 @@ TRANSACTION(ttFEE, 101, SetFee,
|
||||
{sfBaseFeeDrops, SoeOptional},
|
||||
{sfReserveBaseDrops, SoeOptional},
|
||||
{sfReserveIncrementDrops, SoeOptional},
|
||||
// Smart Escrow fields
|
||||
{sfGasLimit, SoeOptional},
|
||||
{sfBytecodeSizeLimit, SoeOptional},
|
||||
{sfGasPrice, SoeOptional},
|
||||
}))
|
||||
|
||||
/** This system-generated transaction type is used to update the network's negative UNL
|
||||
|
||||
@@ -174,6 +174,54 @@ public:
|
||||
return this->sle_->isFieldPresent(sfFinishAfter);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfBytecode (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_VL::type::value_type>
|
||||
getBytecode() const
|
||||
{
|
||||
if (hasBytecode())
|
||||
return this->sle_->at(sfBytecode);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfBytecode is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasBytecode() const
|
||||
{
|
||||
return this->sle_->isFieldPresent(sfBytecode);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfData (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_VL::type::value_type>
|
||||
getData() const
|
||||
{
|
||||
if (hasData())
|
||||
return this->sle_->at(sfData);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfData is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasData() const
|
||||
{
|
||||
return this->sle_->isFieldPresent(sfData);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfSourceTag (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
@@ -453,6 +501,28 @@ public:
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfBytecode (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
EscrowBuilder&
|
||||
setBytecode(std::decay_t<typename SF_VL::type::value_type> const& value)
|
||||
{
|
||||
object_[sfBytecode] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfData (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
EscrowBuilder&
|
||||
setData(std::decay_t<typename SF_VL::type::value_type> const& value)
|
||||
{
|
||||
object_[sfData] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfSourceTag (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
|
||||
@@ -213,6 +213,78 @@ public:
|
||||
return this->sle_->isFieldPresent(sfReserveIncrementDrops);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfGasLimit (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_UINT32::type::value_type>
|
||||
getGasLimit() const
|
||||
{
|
||||
if (hasGasLimit())
|
||||
return this->sle_->at(sfGasLimit);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfGasLimit is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasGasLimit() const
|
||||
{
|
||||
return this->sle_->isFieldPresent(sfGasLimit);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfBytecodeSizeLimit (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_UINT32::type::value_type>
|
||||
getBytecodeSizeLimit() const
|
||||
{
|
||||
if (hasBytecodeSizeLimit())
|
||||
return this->sle_->at(sfBytecodeSizeLimit);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfBytecodeSizeLimit is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasBytecodeSizeLimit() const
|
||||
{
|
||||
return this->sle_->isFieldPresent(sfBytecodeSizeLimit);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfGasPrice (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_UINT32::type::value_type>
|
||||
getGasPrice() const
|
||||
{
|
||||
if (hasGasPrice())
|
||||
return this->sle_->at(sfGasPrice);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfGasPrice is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasGasPrice() const
|
||||
{
|
||||
return this->sle_->isFieldPresent(sfGasPrice);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfPreviousTxnID (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
@@ -375,6 +447,39 @@ public:
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfGasLimit (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
FeeSettingsBuilder&
|
||||
setGasLimit(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
{
|
||||
object_[sfGasLimit] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfBytecodeSizeLimit (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
FeeSettingsBuilder&
|
||||
setBytecodeSizeLimit(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
{
|
||||
object_[sfBytecodeSizeLimit] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfGasPrice (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
FeeSettingsBuilder&
|
||||
setGasPrice(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
{
|
||||
object_[sfGasPrice] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfPreviousTxnID (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
|
||||
@@ -351,6 +351,54 @@ public:
|
||||
return this->sle_->isFieldPresent(sfAuditorEncryptionKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfIssuerKeyEpoch (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_UINT32::type::value_type>
|
||||
getIssuerKeyEpoch() const
|
||||
{
|
||||
if (hasIssuerKeyEpoch())
|
||||
return this->sle_->at(sfIssuerKeyEpoch);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfIssuerKeyEpoch is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasIssuerKeyEpoch() const
|
||||
{
|
||||
return this->sle_->isFieldPresent(sfIssuerKeyEpoch);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfAuditorKeyEpoch (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_UINT32::type::value_type>
|
||||
getAuditorKeyEpoch() const
|
||||
{
|
||||
if (hasAuditorKeyEpoch())
|
||||
return this->sle_->at(sfAuditorKeyEpoch);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfAuditorKeyEpoch is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasAuditorKeyEpoch() const
|
||||
{
|
||||
return this->sle_->isFieldPresent(sfAuditorKeyEpoch);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfConfidentialOutstandingAmount (SoeDefault)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
@@ -600,6 +648,28 @@ public:
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfIssuerKeyEpoch (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
MPTokenIssuanceBuilder&
|
||||
setIssuerKeyEpoch(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
{
|
||||
object_[sfIssuerKeyEpoch] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfAuditorKeyEpoch (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
MPTokenIssuanceBuilder&
|
||||
setAuditorKeyEpoch(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
{
|
||||
object_[sfAuditorKeyEpoch] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfConfidentialOutstandingAmount (SoeDefault)
|
||||
* @return Reference to this builder for method chaining.
|
||||
|
||||
@@ -287,6 +287,54 @@ public:
|
||||
{
|
||||
return this->sle_->isFieldPresent(sfDestinationNode);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfTransferRate (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_UINT32::type::value_type>
|
||||
getTransferRate() const
|
||||
{
|
||||
if (hasTransferRate())
|
||||
return this->sle_->at(sfTransferRate);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfTransferRate is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasTransferRate() const
|
||||
{
|
||||
return this->sle_->isFieldPresent(sfTransferRate);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfIssuerNode (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_UINT64::type::value_type>
|
||||
getIssuerNode() const
|
||||
{
|
||||
if (hasIssuerNode())
|
||||
return this->sle_->at(sfIssuerNode);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfIssuerNode is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasIssuerNode() const
|
||||
{
|
||||
return this->sle_->isFieldPresent(sfIssuerNode);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -508,6 +556,28 @@ public:
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfTransferRate (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
PayChannelBuilder&
|
||||
setTransferRate(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
{
|
||||
object_[sfTransferRate] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfIssuerNode (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
PayChannelBuilder&
|
||||
setIssuerNode(std::decay_t<typename SF_UINT64::type::value_type> const& value)
|
||||
{
|
||||
object_[sfIssuerNode] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Build and return the completed PayChannel wrapper.
|
||||
* @param index The ledger entry index.
|
||||
|
||||
@@ -58,6 +58,32 @@ public:
|
||||
return this->tx_->at(sfDestination);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfDestinationTag (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_UINT32::type::value_type>
|
||||
getDestinationTag() const
|
||||
{
|
||||
if (hasDestinationTag())
|
||||
{
|
||||
return this->tx_->at(sfDestinationTag);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfDestinationTag is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasDestinationTag() const
|
||||
{
|
||||
return this->tx_->isFieldPresent(sfDestinationTag);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfAmount (SoeRequired)
|
||||
* @note This field supports MPT (Multi-Purpose Token) amounts.
|
||||
@@ -149,29 +175,55 @@ public:
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfDestinationTag (SoeOptional)
|
||||
* @brief Get sfBytecode (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_UINT32::type::value_type>
|
||||
getDestinationTag() const
|
||||
protocol_autogen::Optional<SF_VL::type::value_type>
|
||||
getBytecode() const
|
||||
{
|
||||
if (hasDestinationTag())
|
||||
if (hasBytecode())
|
||||
{
|
||||
return this->tx_->at(sfDestinationTag);
|
||||
return this->tx_->at(sfBytecode);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfDestinationTag is present.
|
||||
* @brief Check if sfBytecode is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasDestinationTag() const
|
||||
hasBytecode() const
|
||||
{
|
||||
return this->tx_->isFieldPresent(sfDestinationTag);
|
||||
return this->tx_->isFieldPresent(sfBytecode);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfData (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_VL::type::value_type>
|
||||
getData() const
|
||||
{
|
||||
if (hasData())
|
||||
{
|
||||
return this->tx_->at(sfData);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfData is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasData() const
|
||||
{
|
||||
return this->tx_->isFieldPresent(sfData);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -232,6 +284,17 @@ public:
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfDestinationTag (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
EscrowCreateBuilder&
|
||||
setDestinationTag(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
{
|
||||
object_[sfDestinationTag] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfAmount (SoeRequired)
|
||||
* @note This field supports MPT (Multi-Purpose Token) amounts.
|
||||
@@ -278,13 +341,24 @@ public:
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfDestinationTag (SoeOptional)
|
||||
* @brief Set sfBytecode (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
EscrowCreateBuilder&
|
||||
setDestinationTag(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
setBytecode(std::decay_t<typename SF_VL::type::value_type> const& value)
|
||||
{
|
||||
object_[sfDestinationTag] = value;
|
||||
object_[sfBytecode] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfData (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
EscrowCreateBuilder&
|
||||
setData(std::decay_t<typename SF_VL::type::value_type> const& value)
|
||||
{
|
||||
object_[sfData] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
@@ -146,6 +146,32 @@ public:
|
||||
{
|
||||
return this->tx_->isFieldPresent(sfCredentialIDs);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfGas (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_UINT32::type::value_type>
|
||||
getGas() const
|
||||
{
|
||||
if (hasGas())
|
||||
{
|
||||
return this->tx_->at(sfGas);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfGas is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasGas() const
|
||||
{
|
||||
return this->tx_->isFieldPresent(sfGas);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -249,6 +275,17 @@ public:
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfGas (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
EscrowFinishBuilder&
|
||||
setGas(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
{
|
||||
object_[sfGas] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Build and return the EscrowFinish wrapper.
|
||||
* @param publicKey The public key for signing.
|
||||
|
||||
@@ -60,6 +60,7 @@ public:
|
||||
|
||||
/**
|
||||
* @brief Get sfAmount (SoeOptional)
|
||||
* @note This field supports MPT (Multi-Purpose Token) amounts.
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
@@ -86,6 +87,7 @@ public:
|
||||
|
||||
/**
|
||||
* @brief Get sfBalance (SoeOptional)
|
||||
* @note This field supports MPT (Multi-Purpose Token) amounts.
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
@@ -246,6 +248,7 @@ public:
|
||||
|
||||
/**
|
||||
* @brief Set sfAmount (SoeOptional)
|
||||
* @note This field supports MPT (Multi-Purpose Token) amounts.
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
PaymentChannelClaimBuilder&
|
||||
@@ -257,6 +260,7 @@ public:
|
||||
|
||||
/**
|
||||
* @brief Set sfBalance (SoeOptional)
|
||||
* @note This field supports MPT (Multi-Purpose Token) amounts.
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
PaymentChannelClaimBuilder&
|
||||
|
||||
@@ -60,6 +60,7 @@ public:
|
||||
|
||||
/**
|
||||
* @brief Get sfAmount (SoeRequired)
|
||||
* @note This field supports MPT (Multi-Purpose Token) amounts.
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
@@ -207,6 +208,7 @@ public:
|
||||
|
||||
/**
|
||||
* @brief Set sfAmount (SoeRequired)
|
||||
* @note This field supports MPT (Multi-Purpose Token) amounts.
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
PaymentChannelCreateBuilder&
|
||||
|
||||
@@ -60,6 +60,7 @@ public:
|
||||
|
||||
/**
|
||||
* @brief Get sfAmount (SoeRequired)
|
||||
* @note This field supports MPT (Multi-Purpose Token) amounts.
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
@@ -155,6 +156,7 @@ public:
|
||||
|
||||
/**
|
||||
* @brief Set sfAmount (SoeRequired)
|
||||
* @note This field supports MPT (Multi-Purpose Token) amounts.
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
PaymentChannelFundBuilder&
|
||||
|
||||
@@ -254,6 +254,84 @@ public:
|
||||
{
|
||||
return this->tx_->isFieldPresent(sfReserveIncrementDrops);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfGasLimit (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_UINT32::type::value_type>
|
||||
getGasLimit() const
|
||||
{
|
||||
if (hasGasLimit())
|
||||
{
|
||||
return this->tx_->at(sfGasLimit);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfGasLimit is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasGasLimit() const
|
||||
{
|
||||
return this->tx_->isFieldPresent(sfGasLimit);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfBytecodeSizeLimit (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_UINT32::type::value_type>
|
||||
getBytecodeSizeLimit() const
|
||||
{
|
||||
if (hasBytecodeSizeLimit())
|
||||
{
|
||||
return this->tx_->at(sfBytecodeSizeLimit);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfBytecodeSizeLimit is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasBytecodeSizeLimit() const
|
||||
{
|
||||
return this->tx_->isFieldPresent(sfBytecodeSizeLimit);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfGasPrice (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_UINT32::type::value_type>
|
||||
getGasPrice() const
|
||||
{
|
||||
if (hasGasPrice())
|
||||
{
|
||||
return this->tx_->at(sfGasPrice);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfGasPrice is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasGasPrice() const
|
||||
{
|
||||
return this->tx_->isFieldPresent(sfGasPrice);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -386,6 +464,39 @@ public:
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfGasLimit (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
SetFeeBuilder&
|
||||
setGasLimit(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
{
|
||||
object_[sfGasLimit] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfBytecodeSizeLimit (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
SetFeeBuilder&
|
||||
setBytecodeSizeLimit(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
{
|
||||
object_[sfBytecodeSizeLimit] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfGasPrice (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
SetFeeBuilder&
|
||||
setGasPrice(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
{
|
||||
object_[sfGasPrice] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Build and return the SetFee wrapper.
|
||||
* @param publicKey The public key for signing.
|
||||
|
||||
@@ -109,6 +109,11 @@ public:
|
||||
* @param lpTokens current LPT balance
|
||||
* @param lpTokensWithdraw amount of tokens to withdraw
|
||||
* @param tfee trading fee in basis points
|
||||
* @param freezeHandling whether a frozen balance is reported as zero
|
||||
* @param authHandling whether an unauthorized MPT balance is reported as
|
||||
* zero
|
||||
* @param reserveHandling whether the recipient owner-reserve check is
|
||||
* enforced when a trustline or MPToken has to be auto-created
|
||||
* @param withdrawAll if withdrawing all lptokens
|
||||
* @param priorBalance balance before fees
|
||||
* @return
|
||||
@@ -128,6 +133,7 @@ public:
|
||||
std::uint16_t tfee,
|
||||
FreezeHandling freezeHandling,
|
||||
AuthHandling authHandling,
|
||||
ReserveHandling reserveHandling,
|
||||
WithdrawAll withdrawAll,
|
||||
XRPAmount const& priorBalance,
|
||||
beast::Journal const& journal);
|
||||
@@ -150,6 +156,11 @@ public:
|
||||
* @param lpTokensAMMBalance current AMM LPT balance
|
||||
* @param lpTokensWithdraw amount of lptokens to withdraw
|
||||
* @param tfee trading fee in basis points
|
||||
* @param freezeHandling whether a frozen balance is reported as zero
|
||||
* @param authHandling whether an unauthorized MPT balance is reported as
|
||||
* zero
|
||||
* @param reserveHandling whether the recipient owner-reserve check is
|
||||
* enforced when a trustline or MPToken has to be auto-created
|
||||
* @param withdrawAll if withdraw all lptokens
|
||||
* @param priorBalance balance before fees
|
||||
* @return
|
||||
@@ -169,6 +180,7 @@ public:
|
||||
std::uint16_t tfee,
|
||||
FreezeHandling freezeHandling,
|
||||
AuthHandling authHandling,
|
||||
ReserveHandling reserveHandling,
|
||||
WithdrawAll withdrawAll,
|
||||
XRPAmount const& priorBalance,
|
||||
beast::Journal const& journal);
|
||||
|
||||
@@ -23,6 +23,9 @@ public:
|
||||
static TxConsequences
|
||||
makeTxConsequences(PreflightContext const& ctx);
|
||||
|
||||
static bool
|
||||
checkExtraFeatures(PreflightContext const& ctx);
|
||||
|
||||
static NotTEC
|
||||
preflight(PreflightContext const& ctx);
|
||||
|
||||
|
||||
@@ -23,9 +23,15 @@ public:
|
||||
static TxConsequences
|
||||
makeTxConsequences(PreflightContext const& ctx);
|
||||
|
||||
static bool
|
||||
checkExtraFeatures(PreflightContext const& ctx);
|
||||
|
||||
static NotTEC
|
||||
preflight(PreflightContext const& ctx);
|
||||
|
||||
static TER
|
||||
preclaim(PreclaimContext const& ctx);
|
||||
|
||||
TER
|
||||
doApply() override;
|
||||
|
||||
|
||||
@@ -131,6 +131,9 @@ Rust toolchain:
|
||||
✅ rust-analyzer
|
||||
rust-analyzer 1.97.1 (8bab26f4 2026-07-14)
|
||||
/nix/store/j6apc5pmd0giy15da9p650r8zklslmvi-rust-analyzer-preview-1.97.1-aarch64-apple-darwin/bin/rust-analyzer
|
||||
✅ rust-nightly
|
||||
rustc 1.99.0-nightly (87e5904f5 2026-07-20)
|
||||
/nix/store/fqpjz4l0nsnji8b2pz57mnj0akbp6hcl-rust-nightly/bin/rust-nightly
|
||||
✅ rustc
|
||||
rustc 1.97.1 (8bab26f4f 2026-07-14)
|
||||
/nix/store/bnfk1sl4s9angb0vj1cj9a5y5zvqinwy-rust-minimal-1.97.1/bin/rustc
|
||||
@@ -140,4 +143,4 @@ Rust toolchain:
|
||||
|
||||
Skipping git-over-HTTPS check (CHECK_TOOLS_SKIP_CLONE is set).
|
||||
|
||||
✅ All 44 checked tools are present and runnable.
|
||||
✅ All 45 checked tools are present and runnable.
|
||||
|
||||
@@ -131,6 +131,9 @@ Rust toolchain:
|
||||
✅ rust-analyzer
|
||||
rust-analyzer 1.97.1 (8bab26f 2026-07-14)
|
||||
/nix/store/lr3m97p3hx1k22a7c44pb0wa7rbayhfi-rust-analyzer-preview-1.97.1-x86_64-unknown-linux-gnu/bin/rust-analyzer
|
||||
✅ rust-nightly
|
||||
rustc 1.99.0-nightly (87e5904f5 2026-07-20)
|
||||
/nix/store/j7kf7a5h4xypzp6x1skg4dsdx2k4fwb3-rust-nightly/bin/rust-nightly
|
||||
✅ rustc
|
||||
rustc 1.97.1 (8bab26f4f 2026-07-14)
|
||||
/nix/store/40d3mzka7r1ps71l0yv2fs6616nbw85m-rust-minimal-1.97.1/bin/rustc
|
||||
@@ -168,4 +171,4 @@ Mold:
|
||||
|
||||
Skipping git-over-HTTPS check (CHECK_TOOLS_SKIP_CLONE is set).
|
||||
|
||||
✅ All 52 checked tools are present and runnable.
|
||||
✅ All 53 checked tools are present and runnable.
|
||||
|
||||
@@ -131,6 +131,9 @@ Rust toolchain:
|
||||
✅ rust-analyzer
|
||||
rust-analyzer 1.97.1 (8bab26f 2026-07-14)
|
||||
/nix/store/262830dlw2517lnagfx7i7agqgl4fmsd-rust-analyzer-preview-1.97.1-aarch64-unknown-linux-gnu/bin/rust-analyzer
|
||||
✅ rust-nightly
|
||||
rustc 1.99.0-nightly (87e5904f5 2026-07-20)
|
||||
/nix/store/c59pxk1yikdlf129qwyg4fplmxcrha0k-rust-nightly/bin/rust-nightly
|
||||
✅ rustc
|
||||
rustc 1.97.1 (8bab26f4f 2026-07-14)
|
||||
/nix/store/a6p27cg6b8szfixfyvkssx6l0c345zw8-rust-minimal-1.97.1/bin/rustc
|
||||
@@ -168,4 +171,4 @@ Mold:
|
||||
|
||||
Skipping git-over-HTTPS check (CHECK_TOOLS_SKIP_CLONE is set).
|
||||
|
||||
✅ All 52 checked tools are present and runnable.
|
||||
✅ All 53 checked tools are present and runnable.
|
||||
|
||||
@@ -10,6 +10,8 @@ RUN mkdir -p ~/.config/nix && \
|
||||
COPY nix/ci-env.nix /tmp/build/nix/ci-env.nix
|
||||
COPY nix/linux.nix /tmp/build/nix/linux.nix
|
||||
COPY nix/packages.nix /tmp/build/nix/packages.nix
|
||||
COPY nix/rust-nightly.sh /tmp/build/nix/rust-nightly.sh
|
||||
COPY nix/rust.nix /tmp/build/nix/rust.nix
|
||||
COPY nix/utils.nix /tmp/build/nix/utils.nix
|
||||
COPY flake.nix /tmp/build/
|
||||
COPY flake.lock /tmp/build/
|
||||
|
||||
@@ -16,23 +16,7 @@ let
|
||||
exec ${pkgs.python3}/bin/python3 ${llvmPackages.clang-unwrapped}/bin/run-clang-tidy "$@"
|
||||
'';
|
||||
|
||||
# rust-overlay's toolchain propagates the *default* stdenv.cc onto the PATH (so
|
||||
# cargo has a linker). That default may be different from the clang we pin here,
|
||||
# so it shadows our clang and the build can silently use a different compiler
|
||||
# version. Drop that cc from every propagation channel instead of pinning a
|
||||
# replacement: the toolchain then carries no compiler and cargo just uses the
|
||||
# active shell's stdenv cc. Must cover all channels — rust-overlay uses both
|
||||
# propagatedBuildInputs and depsHostHostPropagated.
|
||||
rustToolchainBase = pkgs.rust-bin.fromRustupToolchainFile ../rust-toolchain.toml;
|
||||
rustToolchain =
|
||||
let
|
||||
defaultCc = pkgs.stdenv.cc; # default compiler from nixpkgs stdenv
|
||||
withoutDefaultCc = builtins.filter (dep: (dep.outPath or "") != defaultCc.outPath);
|
||||
in
|
||||
rustToolchainBase.overrideAttrs (old: {
|
||||
propagatedBuildInputs = withoutDefaultCc (old.propagatedBuildInputs or [ ]);
|
||||
depsHostHostPropagated = withoutDefaultCc (old.depsHostHostPropagated or [ ]);
|
||||
});
|
||||
rust = import ./rust.nix { inherit pkgs; };
|
||||
|
||||
# Nix wraps its toolchain so that binaries are exposed only under unsuffixed
|
||||
# names (gcc, g++, clang-tidy, ...). Several tools probe for a
|
||||
@@ -108,41 +92,38 @@ in
|
||||
mkGcov
|
||||
;
|
||||
|
||||
commonPackages = with pkgs; [
|
||||
clangToolLinks
|
||||
runClangTidyLink
|
||||
ccache
|
||||
clangbuildanalyzer
|
||||
clangTools
|
||||
cmake
|
||||
conan
|
||||
curlMinimal # needed for codecov/codecov-action
|
||||
doxygen
|
||||
file # needed for cpack in Clio
|
||||
gcovr
|
||||
gh
|
||||
git
|
||||
git-cliff
|
||||
git-lfs
|
||||
gnumake
|
||||
gnupg # needed for signing commits & codecov/codecov-action
|
||||
graphviz
|
||||
less # needed for git diff
|
||||
mold
|
||||
nettools # provides netstat, used to debug failures in CI
|
||||
ninja
|
||||
patchelf
|
||||
perl # needed for openssl
|
||||
pkg-config
|
||||
pre-commit
|
||||
python3
|
||||
runClangTidy
|
||||
vim
|
||||
zip
|
||||
# Rust packages
|
||||
cargo-audit
|
||||
cargo-llvm-cov
|
||||
cargo-nextest
|
||||
rustToolchain
|
||||
];
|
||||
commonPackages =
|
||||
(with pkgs; [
|
||||
clangToolLinks
|
||||
runClangTidyLink
|
||||
ccache
|
||||
clangbuildanalyzer
|
||||
clangTools
|
||||
cmake
|
||||
conan
|
||||
curlMinimal # needed for codecov/codecov-action
|
||||
doxygen
|
||||
file # needed for cpack in Clio
|
||||
gcovr
|
||||
gh
|
||||
git
|
||||
git-cliff
|
||||
git-lfs
|
||||
gnumake
|
||||
gnupg # needed for signing commits & codecov/codecov-action
|
||||
graphviz
|
||||
less # needed for git diff
|
||||
mold
|
||||
nettools # provides netstat, used to debug failures in CI
|
||||
ninja
|
||||
patchelf
|
||||
perl # needed for openssl
|
||||
pkg-config
|
||||
pre-commit
|
||||
python3
|
||||
runClangTidy
|
||||
vim
|
||||
zip
|
||||
])
|
||||
++ rust.packages;
|
||||
}
|
||||
|
||||
23
nix/rust-nightly.sh
Normal file
23
nix/rust-nightly.sh
Normal file
@@ -0,0 +1,23 @@
|
||||
#!@runtimeShell@
|
||||
# Reaches the nightly Rust toolchain, which is deliberately kept off PATH.
|
||||
# Packaged by nix/rust.nix, which explains why.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
usage() {
|
||||
echo "usage: rust-nightly (path | run <command>...)" >&2
|
||||
exit 2
|
||||
}
|
||||
|
||||
case "${1-}" in
|
||||
path) printf '%s\n' "@rustNightlyBin@" ;;
|
||||
run)
|
||||
shift
|
||||
if [[ $# -eq 0 ]]; then
|
||||
usage
|
||||
fi
|
||||
export PATH="@rustNightlyBin@:${PATH}"
|
||||
exec "$@"
|
||||
;;
|
||||
*) usage ;;
|
||||
esac
|
||||
84
nix/rust.nix
Normal file
84
nix/rust.nix
Normal file
@@ -0,0 +1,84 @@
|
||||
# The Rust half of the tool set shared by the CI environment and the dev shell:
|
||||
# the stable toolchain pinned by rust-toolchain.toml, the nightly the Rust
|
||||
# coverage job needs, and the cargo plugins. Consumed by packages.nix.
|
||||
{ pkgs }:
|
||||
let
|
||||
# rust-overlay's toolchain propagates the *default* stdenv.cc onto the PATH (so
|
||||
# cargo has a linker). That default may be different from the clang we pin
|
||||
# elsewhere, so it shadows our clang and the build can silently use a different
|
||||
# compiler version. Drop that cc from every propagation channel instead of
|
||||
# pinning a replacement: the toolchain then carries no compiler and cargo just
|
||||
# uses the active shell's stdenv cc.
|
||||
#
|
||||
# The channel list is every list mkDerivation propagates to a dependent's
|
||||
# environment (including the two legacy aliases). rust-overlay currently only
|
||||
# uses propagatedBuildInputs and depsHostHostPropagated, but covering all of
|
||||
# them means an upstream switch to another channel cannot quietly put the
|
||||
# compiler back on PATH.
|
||||
dropDefaultCc =
|
||||
toolchain:
|
||||
let
|
||||
defaultCc = pkgs.stdenv.cc; # default compiler from nixpkgs stdenv
|
||||
withoutDefaultCc = builtins.filter (dep: (dep.outPath or "") != defaultCc.outPath);
|
||||
in
|
||||
toolchain.overrideAttrs (
|
||||
old:
|
||||
pkgs.lib.genAttrs [
|
||||
"depsBuildBuildPropagated"
|
||||
"propagatedNativeBuildInputs" # alias of depsBuildHostPropagated
|
||||
"depsBuildTargetPropagated"
|
||||
"depsHostHostPropagated"
|
||||
"propagatedBuildInputs" # alias of depsHostTargetPropagated
|
||||
"depsTargetTargetPropagated"
|
||||
] (channel: withoutDefaultCc (old.${channel} or [ ]))
|
||||
);
|
||||
|
||||
rustToolchain = dropDefaultCc (pkgs.rust-bin.fromRustupToolchainFile ../rust-toolchain.toml);
|
||||
|
||||
# cargo-llvm-cov honours the #[coverage(off)] that keeps unit tests out of the
|
||||
# coverage report only under a nightly rustc, and looks for llvm-profdata and
|
||||
# llvm-cov in that same toolchain's sysroot — hence llvm-tools-preview.
|
||||
#
|
||||
# Not every nightly ships every component, so `nightly.latest` breaks on the
|
||||
# days llvm-tools-preview is absent; selectLatestNightlyWith walks back to the
|
||||
# newest one that has it. The result is the newest such nightly *known to the
|
||||
# locked rust-overlay*, which means updating flake.lock moves the compiler that
|
||||
# produces the coverage numbers — and with it the rustc version recorded in
|
||||
# nix/check-tools/*.txt, so those snapshots need regenerating alongside.
|
||||
rustNightly = dropDefaultCc (
|
||||
pkgs.rust-bin.selectLatestNightlyWith (
|
||||
toolchain: toolchain.minimal.override { extensions = [ "llvm-tools-preview" ]; }
|
||||
)
|
||||
);
|
||||
|
||||
# A second toolchain cannot go on PATH: its cargo and rustc would collide with
|
||||
# the pinned stable's in the ci-env buildEnv, which resolves collisions by
|
||||
# picking one silently. Reaching the nightly only through this wrapper keeps it
|
||||
# in the image closure (the Docker build copies the whole closure, not just
|
||||
# what is linked into /bin) while leaving it inactive everywhere that does not
|
||||
# ask for it.
|
||||
#
|
||||
# The script's `path` subcommand exists for scopes wider than one command — a
|
||||
# CI job appending to $GITHUB_PATH, so that the cargo cache action's own
|
||||
# `rustc -vV` probe, which runs in a step of its own, agrees with the toolchain
|
||||
# the build will use.
|
||||
rustNightlyScript = pkgs.replaceVarsWith {
|
||||
name = "rust-nightly";
|
||||
src = ./rust-nightly.sh;
|
||||
dir = "bin";
|
||||
isExecutable = true;
|
||||
replacements = {
|
||||
inherit (pkgs) runtimeShell;
|
||||
rustNightlyBin = "${rustNightly}/bin";
|
||||
};
|
||||
};
|
||||
in
|
||||
{
|
||||
packages = [
|
||||
pkgs.cargo-audit
|
||||
pkgs.cargo-llvm-cov
|
||||
pkgs.cargo-nextest
|
||||
rustNightlyScript
|
||||
rustToolchain
|
||||
];
|
||||
}
|
||||
@@ -15,7 +15,7 @@ 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, source/format)
|
||||
debian/ Debian control files (control, rules, copyright, xrpld.docs, xrpld.links, xrpld.lintian-overrides, source/format)
|
||||
shared/
|
||||
xrpld.service systemd unit file (used by both RPM and DEB)
|
||||
xrpld.sysusers sysusers.d config (used by both RPM and DEB)
|
||||
@@ -34,10 +34,10 @@ image and both CI and local builds pick it up — and names the format that imag
|
||||
builds in `type`, which CI passes to `build_pkg.py` as `--package-type`; the two
|
||||
have to stay in step.
|
||||
|
||||
| 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 |
|
||||
| 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`:
|
||||
@@ -51,13 +51,19 @@ To print the full packaging matrix (artifact names and images) for the current
|
||||
### Via CI
|
||||
|
||||
Caller workflows (`on-pr.yml`, `on-tag.yml`, `on-trigger.yml`) call
|
||||
`reusable-package.yml`. That workflow generates its own packaging matrix from
|
||||
the configs that carry a `package` map (via `generate.py --packaging`) and fans
|
||||
out one job per distro. Each job downloads the pre-built `xrpld` and
|
||||
`validator-keys` binary artifacts and runs in that distro's container, building
|
||||
the format `package.type` declares. The packaging script derives the package
|
||||
version from the downloaded binary's `xrpld --version` output; no CMake
|
||||
configure or build step is needed inside the packaging job.
|
||||
`reusable-package.yml`, which runs in three stages:
|
||||
|
||||
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.
|
||||
3. `publish` uploads both artifacts, or lists what it would upload.
|
||||
|
||||
The packaging script derives the package version from the downloaded binary's
|
||||
`xrpld --version` output; no CMake configure or build step is needed inside the
|
||||
packaging job.
|
||||
|
||||
The binaries come from the `debian` and `rhel` build configs themselves — the
|
||||
ones carrying the `package` map — which pass `-Dvalidator_keys=ON` so that the
|
||||
@@ -94,8 +100,7 @@ docker run --rm \
|
||||
--pkg-release "${PKG_RELEASE}" \
|
||||
--channel UNRELEASED
|
||||
|
||||
# Output:
|
||||
# build/debbuild/*.deb (DEB + dbgsym; Debian names both .deb)
|
||||
# Output (the deb image writes build/debbuild/*.deb instead):
|
||||
# build/rpmbuild/RPMS/x86_64/*.rpm
|
||||
```
|
||||
|
||||
@@ -155,9 +160,9 @@ 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 the last step of each packaging job, uploading from the container
|
||||
that built the packages with the `publish_pkg.py` shipped in the image — the
|
||||
same copy other repositories run. Without `publish: true` the step is a
|
||||
Publishing is its own job, gated behind `test-install`, 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
|
||||
any run that builds packages also exercises the upload routing. `on-trigger.yml`
|
||||
passes `publish: true` for develop pushes in `XRPLF/rippled` and `on-tag.yml`
|
||||
@@ -202,6 +207,9 @@ the final release. If that normalized package version still contains `-`,
|
||||
packaging fails because RPM forbids `-` in `Version`, and Debian uses `-` as
|
||||
the upstream/revision separator.
|
||||
|
||||
> [!NOTE]
|
||||
> Debug and sanitizer builds are not packaged yet.
|
||||
|
||||
`pkg_version` is the normalized package metadata version derived inside
|
||||
`build_pkg.py` from the binary-reported `xrpld` version (`-` pre-release
|
||||
separator converted to `~`). It is not a separate user input.
|
||||
@@ -279,37 +287,45 @@ service restart.
|
||||
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 into `debian/` where `dh_installsystemd`, `dh_installsysusers`, and `dh_installtmpfiles` pick them up automatically.
|
||||
4. Copies shared service/sysusers/tmpfiles/logrotate into `debian/` where `dh_installsystemd`, `dh_installsysusers`, `dh_installtmpfiles` and `dh_installlogrotate` pick them up automatically.
|
||||
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.
|
||||
|
||||
It also rewrites the `libc6` bound to `LIBC_MIN` in `debian/rules`, the glibc
|
||||
the Nix toolchain builds against. `dpkg-shlibdeps` would otherwise derive it
|
||||
from the build host's symbols file — on trixie that yields `libc6 (>= 2.34)`
|
||||
because of `sysconf`, locking out distros the binaries run on. A check fails
|
||||
the build if either binary outgrows `LIBC_MIN`.
|
||||
|
||||
7. Output: `debbuild/*.deb`, the binary package and the `-dbgsym` package.
|
||||
Debian gives dbgsym packages a `.deb` extension; only Ubuntu uses `.ddeb`.
|
||||
|
||||
## Post-build verification
|
||||
|
||||
```bash
|
||||
# DEB
|
||||
dpkg-deb -c debbuild/*.deb | grep -E 'systemd|sysusers|tmpfiles'
|
||||
# DEB (one invocation per package: the dbgsym package is a .deb too)
|
||||
for deb in debbuild/*.deb; do dpkg-deb -c "${deb}"; done | grep -E 'systemd|sysusers|tmpfiles'
|
||||
lintian -I debbuild/*.deb
|
||||
|
||||
# RPM
|
||||
rpm -qlp rpmbuild/RPMS/x86_64/*.rpm
|
||||
|
||||
# Optional, and not in the packaging image: apt-get install -y lintian
|
||||
lintian -I debbuild/*.deb
|
||||
```
|
||||
|
||||
`lintian` still reports `embedded-library zlib`, `no-manual-page` and
|
||||
`initial-upload-closes-no-bugs`; only the `/usr/local` tags are overridden.
|
||||
|
||||
## Reproducibility
|
||||
|
||||
`build_pkg.py` sets `SOURCE_DATE_EPOCH` from the latest git commit time and
|
||||
exports it; the RPM spec clamps file modification times to it via
|
||||
`%build_mtime_policy`. The remaining variables
|
||||
below further improve reproducibility but are _not_ set by the script — export
|
||||
them yourself if needed:
|
||||
Both formats build reproducibly as they are: the same binaries at the same
|
||||
commit give byte-identical packages on a rebuild, and nothing has to be
|
||||
exported by hand.
|
||||
|
||||
```bash
|
||||
export TZ=UTC
|
||||
export LC_ALL=C.UTF-8
|
||||
export GZIP=-n
|
||||
export DEB_BUILD_OPTIONS="noautodbgsym reproducible=+fixfilepath"
|
||||
```
|
||||
`build_pkg.py` sets `SOURCE_DATE_EPOCH` from the latest git commit time.
|
||||
`dpkg-buildpackage` honours it on its own; the RPM spec sets three macros:
|
||||
|
||||
- `%clamp_mtime_to_source_date_epoch` — file modification times, from
|
||||
`SOURCE_DATE_EPOCH`.
|
||||
- `%use_source_date_epoch_as_buildtime` — the `BUILDTIME` header, from the
|
||||
same.
|
||||
- `%_buildhost` — pinned, so the builder's hostname stays out of the header.
|
||||
|
||||
@@ -19,7 +19,7 @@ from pathlib import Path
|
||||
# This script lives in the repository it packages.
|
||||
SRC_DIR = Path(__file__).resolve().parents[1]
|
||||
|
||||
PRE_RELEASE = re.compile(r"^(b0|b[1-9][0-9]*|rc[0-9]+)(\+.*)?$")
|
||||
PRE_RELEASE = re.compile(r"^(b|rc)(0|[1-9][0-9]*)(\+.*)?$")
|
||||
|
||||
# Files both packaging systems consume, staged under the same names.
|
||||
STAGED_FROM_BUILD = ("xrpld", "validator-keys", "validator-keys-LICENSE")
|
||||
@@ -133,6 +133,14 @@ def stage_common(build_dir: Path, dest: Path) -> None:
|
||||
shutil.copy2(build_dir / name, dest / name)
|
||||
for source, name in STAGED_FROM_SRC.items():
|
||||
shutil.copy2(SRC_DIR / source, dest / name)
|
||||
|
||||
|
||||
def stage_units(dest: Path) -> 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/.
|
||||
"""
|
||||
for name in STAGED_UNITS:
|
||||
shutil.copy2(SRC_DIR / "package" / "shared" / name, dest / name)
|
||||
|
||||
@@ -146,6 +154,7 @@ def build_rpm(build_dir: Path, *, version: str, pkg_release: str) -> None:
|
||||
spec = topdir / "SPECS" / "xrpld.spec"
|
||||
shutil.copy2(SRC_DIR / "package" / "rpm" / "xrpld.spec", spec)
|
||||
stage_common(build_dir, topdir / "SOURCES")
|
||||
stage_units(topdir / "SOURCES")
|
||||
|
||||
run(
|
||||
"rpmbuild",
|
||||
@@ -178,8 +187,7 @@ def build_deb(
|
||||
shutil.copytree(SRC_DIR / "package" / "debian", staging / "debian")
|
||||
|
||||
# debhelper picks these up from debian/ automatically.
|
||||
for name in STAGED_UNITS:
|
||||
shutil.copy2(staging / name, staging / "debian" / name)
|
||||
stage_units(staging / "debian")
|
||||
|
||||
date = datetime.fromtimestamp(epoch, timezone.utc).strftime(
|
||||
"%a, %d %b %Y %H:%M:%S %z"
|
||||
@@ -193,8 +201,6 @@ def build_deb(
|
||||
""")
|
||||
(staging / "debian" / "changelog").write_text(changelog)
|
||||
|
||||
(staging / "debian" / "rules").chmod(0o755)
|
||||
|
||||
run("dpkg-buildpackage", "-b", "--no-sign", "-d", cwd=staging)
|
||||
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ Priority: optional
|
||||
Maintainer: XRPL Foundation <contact@xrplf.org>
|
||||
Rules-Requires-Root: no
|
||||
Build-Depends:
|
||||
binutils,
|
||||
debhelper-compat (= 13)
|
||||
Standards-Version: 4.7.0
|
||||
Homepage: https://github.com/XRPLF/rippled
|
||||
@@ -11,8 +12,6 @@ Vcs-Git: https://github.com/XRPLF/rippled.git
|
||||
Vcs-Browser: https://github.com/XRPLF/rippled
|
||||
|
||||
Package: xrpld
|
||||
Section: net
|
||||
Priority: optional
|
||||
Architecture: any
|
||||
Depends:
|
||||
${shlibs:Depends},
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||
Upstream-Name: rippled
|
||||
Upstream-Name: xrpld
|
||||
Source: https://github.com/XRPLF/rippled
|
||||
|
||||
Files: *
|
||||
@@ -15,7 +15,7 @@ Copyright: 2016, Ripple Labs Inc.
|
||||
2009-2010, Satoshi Nakamoto
|
||||
2011, The Bitcoin developers
|
||||
2003-2005, Tom Wu
|
||||
License: ISC
|
||||
License: ISC and BSL-1.0 and MIT and Tom-Wu
|
||||
Comment: Built from https://github.com/ripple/validator-keys-tool at the commit
|
||||
pinned in cmake/XrplValidatorKeys.cmake. Besides ISC-licensed code it
|
||||
incorporates work under the Boost Software License 1.0 (ASIO), the MIT/X11
|
||||
@@ -35,3 +35,74 @@ License: ISC
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
License: BSL-1.0
|
||||
Boost Software License - Version 1.0 - August 17th, 2003
|
||||
.
|
||||
Permission is hereby granted, free of charge, to any person or organization
|
||||
obtaining a copy of the software and accompanying documentation covered by
|
||||
this license (the "Software") to use, reproduce, display, distribute,
|
||||
execute, and transmit the Software, and to prepare derivative works of the
|
||||
Software, and to permit third-parties to whom the Software is furnished to
|
||||
do so, all subject to the following:
|
||||
.
|
||||
The copyright notices in the Software and this entire statement, including
|
||||
the above license grant, this restriction and the following disclaimer,
|
||||
must be included in all copies of the Software, in whole or in part, and
|
||||
all derivative works of the Software, unless such copies or derivative
|
||||
works are solely in the form of machine-executable object code generated by
|
||||
a source language processor.
|
||||
.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
|
||||
SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
|
||||
FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
|
||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
|
||||
License: MIT
|
||||
Permission is hereby granted, free of charge, to any person obtaining a
|
||||
copy of this software and associated documentation files (the "Software"),
|
||||
to deal in the Software without restriction, including without limitation
|
||||
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
and/or sell copies of the Software, and to permit persons to whom the
|
||||
Software is furnished to do so, subject to the following conditions:
|
||||
.
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
|
||||
License: Tom-Wu
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
.
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
.
|
||||
THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY
|
||||
WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
|
||||
.
|
||||
IN NO EVENT SHALL TOM WU BE LIABLE FOR ANY SPECIAL, INCIDENTAL,
|
||||
INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, OR ANY DAMAGES WHATSOEVER
|
||||
RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER OR NOT ADVISED OF
|
||||
THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF LIABILITY, ARISING OUT
|
||||
OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
.
|
||||
In addition, the following condition applies:
|
||||
.
|
||||
All redistributions must retain an intact copy of this copyright notice
|
||||
and disclaimer.
|
||||
|
||||
26
package/debian/rules
Normal file → Executable file
26
package/debian/rules
Normal file → Executable file
@@ -2,6 +2,12 @@
|
||||
|
||||
export DH_VERBOSE = 1
|
||||
|
||||
# The glibc the Nix toolchain builds against, and so the real floor for the
|
||||
# binaries. dpkg-shlibdeps would instead derive libc6 (>= 2.34) from the build
|
||||
# host's symbols file, where sysconf carries that minver, locking out distros
|
||||
# the binaries actually run on.
|
||||
LIBC_MIN = 2.31
|
||||
|
||||
%:
|
||||
dh $@
|
||||
|
||||
@@ -11,6 +17,8 @@ override_dh_auto_configure override_dh_auto_build override_dh_auto_test:
|
||||
override_dh_installsystemd:
|
||||
dh_installsystemd --no-stop-on-upgrade xrpld.service
|
||||
|
||||
# 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
|
||||
|
||||
@@ -22,5 +30,23 @@ override_dh_install:
|
||||
install -D -m 0644 xrpld.cfg debian/xrpld/etc/xrpld/xrpld.cfg
|
||||
install -D -m 0644 validators.txt debian/xrpld/etc/xrpld/validators.txt
|
||||
|
||||
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 \
|
||||
needed=$$(readelf --dyn-syms --wide $$binary \
|
||||
| grep -o 'GLIBC_[0-9.]*' | sed 's/GLIBC_//' | sort -uV | tail -1); \
|
||||
if [ -z "$$needed" ]; then \
|
||||
echo "$$binary: no GLIBC_ symbol versions read, cannot check LIBC_MIN" >&2; \
|
||||
exit 1; \
|
||||
fi; \
|
||||
if dpkg --compare-versions "$$needed" gt "$(LIBC_MIN)"; then \
|
||||
echo "$$binary needs glibc $$needed, above LIBC_MIN $(LIBC_MIN)" >&2; \
|
||||
exit 1; \
|
||||
fi; \
|
||||
done
|
||||
sed -i 's/libc6 (>= [0-9.]*)/libc6 (>= $(LIBC_MIN))/' debian/xrpld.substvars
|
||||
|
||||
override_dh_dwz:
|
||||
@:
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
README.md
|
||||
LICENSE.md
|
||||
validator-keys-LICENSE
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
# Legacy compat symlinks (remove next major release)
|
||||
# Legacy compatibility for pre-FHS package layouts.
|
||||
# TODO: remove after rippled fully deprecated.
|
||||
usr/bin/xrpld usr/local/bin/rippled
|
||||
|
||||
6
package/debian/xrpld.lintian-overrides
Normal file
6
package/debian/xrpld.lintian-overrides
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/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]
|
||||
@@ -2,9 +2,9 @@ ARG BASE_IMAGE=debian:trixie
|
||||
|
||||
FROM ${BASE_IMAGE}
|
||||
|
||||
COPY bin/install-packaging-tools.sh /tmp/install-packaging-tools.sh
|
||||
|
||||
RUN /tmp/install-packaging-tools.sh
|
||||
# Bind-mounted rather than copied in, so the installer never lands in a layer.
|
||||
RUN --mount=type=bind,source=bin/install-packaging-tools.sh,target=/install-packaging-tools.sh \
|
||||
/install-packaging-tools.sh
|
||||
|
||||
# See ../README.md, "Publishing from other repositories".
|
||||
COPY package/docker/publish_pkg.py /usr/local/bin/publish_pkg.py
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Publish built DEB and RPM packages to the XRPLF repositories on Nexus.
|
||||
|
||||
Takes packages and a channel, and nothing else, so it publishes whatever built
|
||||
them; see package/README.md, "Publishing from other repositories".
|
||||
Knows nothing about what it uploads beyond the channel, so it publishes whatever
|
||||
built the packages; see package/README.md, "Publishing from other repositories".
|
||||
|
||||
RPMs are uploaded to the hosted repository, but yum clients install from the
|
||||
'rpm-<channel>' group repository in front of it, which serves signed metadata.
|
||||
@@ -29,6 +29,9 @@ STALL_TIMEOUT = 300
|
||||
ATTEMPTS = 4
|
||||
RETRY_DELAY = 5
|
||||
|
||||
# 429 is Nexus asking to slow down, not a rejection, so it retries like a 5xx.
|
||||
RETRYABLE_STATUSES = (429,)
|
||||
|
||||
|
||||
def build_opener() -> urllib.request.OpenerDirector:
|
||||
"""An opener with no redirect handler, so a 3xx raises instead of being followed.
|
||||
@@ -47,9 +50,9 @@ def build_opener() -> urllib.request.OpenerDirector:
|
||||
def upload(url: str, method: str, headers: dict[str, str], package: Path) -> None:
|
||||
"""Send one package, retrying only what is worth retrying.
|
||||
|
||||
A 4xx is a deterministic rejection, so it is reported at once rather than
|
||||
re-sending the whole body three more times. Nexus explains what it rejected
|
||||
in the response body, so that body is always surfaced.
|
||||
A 4xx other than 429 is a deterministic rejection, so it is reported at once
|
||||
rather than re-sending the whole body three more times. Nexus explains what
|
||||
it rejected in the response body, so that body is always surfaced.
|
||||
"""
|
||||
opener = build_opener()
|
||||
|
||||
@@ -67,7 +70,7 @@ def upload(url: str, method: str, headers: dict[str, str], package: Path) -> Non
|
||||
except urllib.error.HTTPError as error:
|
||||
detail = error.read().decode(errors="replace").strip()
|
||||
reason = f"HTTP {error.code}: {detail}"
|
||||
retryable = error.code >= 500
|
||||
retryable = error.code >= 500 or error.code in RETRYABLE_STATUSES
|
||||
except (urllib.error.URLError, OSError) as error:
|
||||
reason = str(error)
|
||||
retryable = True
|
||||
@@ -121,6 +124,8 @@ def main() -> None:
|
||||
token = base64.b64encode(f"{username}:{password}".encode()).decode()
|
||||
auth = {"Authorization": f"Basic {token}"}
|
||||
|
||||
# Deliberately not shared with sign_rpm.py: this script ships standalone in
|
||||
# the packaging image for other repositories to run.
|
||||
packages = sorted(
|
||||
path
|
||||
for path in package_dir.rglob("*")
|
||||
|
||||
@@ -17,6 +17,11 @@ URL: https://github.com/XRPLF/rippled
|
||||
ExclusiveArch: x86_64 aarch64
|
||||
BuildRequires: systemd-rpm-macros
|
||||
|
||||
# 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}
|
||||
%{?sysusers_requires_compat}
|
||||
|
||||
%undefine _debugsource_packages
|
||||
%debug_package
|
||||
# Level 3 rather than the el9 default of 19: it shrinks the multi-gigabyte
|
||||
@@ -25,10 +30,13 @@ BuildRequires: systemd-rpm-macros
|
||||
%global _binary_payload w3.zstdio
|
||||
%global _find_debuginfo_dwz_opts %{nil}
|
||||
|
||||
%build_mtime_policy clamp_to_source_date_epoch
|
||||
# Reproducibility: the first two take their value from the SOURCE_DATE_EPOCH
|
||||
# build_pkg.py exports. Without these the header records the wall clock and the
|
||||
# build container's hostname, so two builds of the same commit differ.
|
||||
%global clamp_mtime_to_source_date_epoch 1
|
||||
%global use_source_date_epoch_as_buildtime 1
|
||||
%global _buildhost xrplf.org
|
||||
|
||||
%{?systemd_requires}
|
||||
%{?sysusers_requires_compat}
|
||||
|
||||
%description
|
||||
xrpld is the reference implementation of the XRP Ledger protocol. It
|
||||
@@ -53,7 +61,7 @@ install -Dm0644 %{_sourcedir}/validators.txt %{buildroot}%{_sysconfdir}/%{
|
||||
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 -Dm0644 /dev/null %{buildroot}%{_presetdir}/50-xrpld.preset
|
||||
install -d %{buildroot}%{_presetdir}
|
||||
cat >%{buildroot}%{_presetdir}/50-xrpld.preset <<'EOF'
|
||||
enable xrpld.service
|
||||
EOF
|
||||
@@ -76,7 +84,7 @@ ln -s %{_bindir}/%{name} %{buildroot}/usr/local/bin/rippled
|
||||
%sysusers_create_package %{name} %{_sourcedir}/xrpld.sysusers
|
||||
|
||||
%post
|
||||
systemd-tmpfiles --create %{_tmpfilesdir}/xrpld.conf || :
|
||||
%tmpfiles_create_package %{name} %{_sourcedir}/xrpld.tmpfiles
|
||||
%systemd_post xrpld.service
|
||||
|
||||
%preun
|
||||
@@ -86,11 +94,12 @@ systemd-tmpfiles --create %{_tmpfilesdir}/xrpld.conf || :
|
||||
%systemd_postun xrpld.service
|
||||
|
||||
%files
|
||||
%attr(0755,root,root) %dir %{_docdir}/%{name}
|
||||
%license %{_docdir}/%{name}/LICENSE.md
|
||||
%license %{_docdir}/%{name}/validator-keys-LICENSE
|
||||
%doc %{_docdir}/%{name}/README.md
|
||||
|
||||
%dir %{_sysconfdir}/%{name}
|
||||
%attr(0755,root,root) %dir %{_sysconfdir}/%{name}
|
||||
|
||||
%{_bindir}/%{name}
|
||||
%{_bindir}/validator-keys
|
||||
@@ -101,7 +110,7 @@ systemd-tmpfiles --create %{_tmpfilesdir}/xrpld.conf || :
|
||||
|
||||
|
||||
%{_unitdir}/xrpld.service
|
||||
%{_presetdir}/50-xrpld.preset
|
||||
%attr(0644,root,root) %{_presetdir}/50-xrpld.preset
|
||||
%{_sysusersdir}/xrpld.conf
|
||||
%{_tmpfilesdir}/xrpld.conf
|
||||
%ghost %dir /var/lib/xrpld
|
||||
|
||||
@@ -17,6 +17,8 @@ ProtectHome=true
|
||||
PrivateTmp=true
|
||||
User=xrpld
|
||||
Group=xrpld
|
||||
# xrpld.tmpfiles creates these at install and boot; these recreate them on
|
||||
# every start, so a removed directory does not stop the service.
|
||||
StateDirectory=xrpld
|
||||
StateDirectoryMode=0750
|
||||
LogsDirectory=xrpld
|
||||
|
||||
@@ -107,6 +107,8 @@ def main() -> None:
|
||||
args = parser.parse_args()
|
||||
package_dir: Path = args.package_dir
|
||||
|
||||
# Deliberately not shared with publish_pkg.py, which ships standalone in the
|
||||
# packaging image.
|
||||
rpms = sorted(path for path in package_dir.rglob("*.rpm") if path.is_file())
|
||||
# Signing nothing would otherwise look like a successful signing.
|
||||
assert rpms, f"no RPMs found in {package_dir}"
|
||||
|
||||
@@ -543,12 +543,19 @@ doWithdraw(
|
||||
{
|
||||
auto const dstSle = ctx.view.read(keylet::account(dstAcct));
|
||||
|
||||
// Create trust line or MPToken for the receiving account
|
||||
// Create a trust line or MPToken for a self-destination only when there
|
||||
// is a payout to credit. Post-fixCleanup3_4_0, a zero-value withdraw
|
||||
// (e.g. share redemption from a fully impaired vault) must not insert
|
||||
// an empty holding: that records a one-sided zero delta and can also
|
||||
// create+delete MPTokens in the same transaction.
|
||||
if (dstAcct == senderAcct)
|
||||
{
|
||||
if (auto const ter = addEmptyHolding(ctx, senderAcct, priorBalance, amount.asset(), j);
|
||||
!isTesSuccess(ter) && ter != tecDUPLICATE)
|
||||
return ter;
|
||||
if (amount > beast::kZero || !ctx.view.rules().enabled(fixCleanup3_4_0))
|
||||
{
|
||||
if (auto const ter = addEmptyHolding(ctx, senderAcct, priorBalance, amount.asset(), j);
|
||||
!isTesSuccess(ter) && ter != tecDUPLICATE)
|
||||
return ter;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/Sandbox.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/CredentialHelpers.h>
|
||||
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
|
||||
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
@@ -691,12 +690,6 @@ deleteAMMTrustLines(
|
||||
|
||||
return {deleteAMMTrustLine(sb, sleItem, ammAccountID, j), SkipEntry::No};
|
||||
}
|
||||
// A credential naming the pseudo-account as subject can't be
|
||||
// accepted or deleted by it and would otherwise permanently pin the
|
||||
// AMM. Clean it up here, inside the same bounded walk, so the
|
||||
// pinned AMM can still be deleted.
|
||||
if (sb.rules().enabled(fixCleanup3_4_0) && nodeType == ltCREDENTIAL)
|
||||
return {credentials::deleteSLE(sb, sleItem, j), SkipEntry::No};
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j.error()) << "deleteAMMObjects: deleting non-trustline or non-MPT " << nodeType;
|
||||
return {tecINTERNAL, SkipEntry::No};
|
||||
@@ -774,8 +767,6 @@ deleteAMMAccount(Sandbox& sb, Asset const& asset, Asset const& asset2, beast::Jo
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
// deleteAMMTrustLines also removes any credentials pinned to the AMM
|
||||
// pseudo-account, within its bounded walk.
|
||||
if (auto const ter = deleteAMMTrustLines(sb, ammAccountID, kMaxDeletableAmmTrustLines, j);
|
||||
!isTesSuccess(ter))
|
||||
return ter;
|
||||
@@ -917,11 +908,6 @@ isOnlyLiquidityProvider(ReadView const& view, Issue const& ammIssue, AccountID c
|
||||
++nMPT;
|
||||
continue;
|
||||
}
|
||||
// A credential naming the pseudo-account as subject can be pinned
|
||||
// to its owner directory. Ignore it here; deleteAMMTrustLines
|
||||
// removes it when the AMM is deleted.
|
||||
if (view.rules().enabled(fixCleanup3_4_0) && entryType == ltCREDENTIAL)
|
||||
continue;
|
||||
if (entryType != ltRIPPLE_STATE)
|
||||
return std::unexpected<TER>(tecINTERNAL); // LCOV_EXCL_LINE
|
||||
auto const lowLimit = sle->getFieldAmount(sfLowLimit);
|
||||
|
||||
@@ -5,10 +5,8 @@
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/basics/chrono.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/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
@@ -129,36 +127,6 @@ deleteSLE(ApplyView& view, SLE::ref sleCredential, beast::Journal j)
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
deletePseudoAccountCredentials(
|
||||
ApplyView& view,
|
||||
AccountID const& pseudoAcct,
|
||||
std::uint16_t maxNodesToDelete,
|
||||
beast::Journal j)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
isPseudoAccount(view.read(keylet::account(pseudoAcct))),
|
||||
"xrpl::credentials::deletePseudoAccountCredentials : is a pseudo-account");
|
||||
|
||||
// Delete the credentials linked into the pseudo-account's owner directory,
|
||||
// visiting at most maxNodesToDelete entries. Any other object is left in
|
||||
// place; the caller's own checks decide whether the remaining directory
|
||||
// blocks deletion. If the bound is reached, cleanupOnAccountDelete returns
|
||||
// tecINCOMPLETE and the caller propagates it so a later transaction resumes.
|
||||
return cleanupOnAccountDelete(
|
||||
view,
|
||||
keylet::ownerDir(pseudoAcct),
|
||||
[&view, &j](LedgerEntryType nodeType, uint256 const&, SLE::pointer& sleItem)
|
||||
-> std::pair<TER, SkipEntry> {
|
||||
if (nodeType == ltCREDENTIAL)
|
||||
return {deleteSLE(view, sleItem, j), SkipEntry::No};
|
||||
|
||||
return {tesSUCCESS, SkipEntry::Yes};
|
||||
},
|
||||
j,
|
||||
maxNodesToDelete);
|
||||
}
|
||||
|
||||
NotTEC
|
||||
checkFields(STTx const& tx, Rules const& rules, beast::Journal j)
|
||||
{
|
||||
|
||||
@@ -3,14 +3,19 @@
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/beast/utility/Zero.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/EscrowHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Concepts.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/Rate.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
|
||||
@@ -18,12 +23,19 @@
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <optional>
|
||||
#include <variant>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
TER
|
||||
closeChannel(SLE::ref slep, ApplyView& view, uint256 const& key, beast::Journal j)
|
||||
closeChannel(
|
||||
SLE::ref slep,
|
||||
ApplyViewContext ctx,
|
||||
uint256 const& key,
|
||||
AccountID const& txAccount,
|
||||
beast::Journal j)
|
||||
{
|
||||
ApplyView& view = ctx.view;
|
||||
AccountID const src = (*slep)[sfAccount];
|
||||
// Remove PayChan from owner directory
|
||||
{
|
||||
@@ -38,9 +50,9 @@ closeChannel(SLE::ref slep, ApplyView& view, uint256 const& key, beast::Journal
|
||||
}
|
||||
|
||||
// Remove PayChan from recipient's owner directory, if present.
|
||||
AccountID const dst = (*slep)[sfDestination];
|
||||
if (auto const page = (*slep)[~sfDestinationNode])
|
||||
{
|
||||
auto const dst = (*slep)[sfDestination];
|
||||
if (!view.dirRemove(keylet::ownerDir(dst), *page, key, true))
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
@@ -57,7 +69,63 @@ closeChannel(SLE::ref slep, ApplyView& view, uint256 const& key, beast::Journal
|
||||
|
||||
XRPL_ASSERT(
|
||||
(*slep)[sfAmount] >= (*slep)[sfBalance], "xrpl::closeChannel : minimum channel amount");
|
||||
(*sle)[sfBalance] = (*sle)[sfBalance] + (*slep)[sfAmount] - (*slep)[sfBalance];
|
||||
|
||||
auto const reqDelta = (*slep)[sfAmount] - (*slep)[sfBalance];
|
||||
auto const& issuer = reqDelta.getIssuer();
|
||||
|
||||
// Only update the balance if there is a positive delta.
|
||||
if (reqDelta > beast::kZero)
|
||||
{
|
||||
if (isXRP(reqDelta))
|
||||
{
|
||||
(*sle)[sfBalance] = (*sle)[sfBalance] + reqDelta;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!view.rules().enabled(featureTokenPaychan))
|
||||
return temDISABLED;
|
||||
|
||||
if (auto const ret = std::visit(
|
||||
[&]<typename T>(T const&) {
|
||||
return escrowUnlockPreclaimHelper<T>(view, src, reqDelta, false);
|
||||
},
|
||||
reqDelta.asset().value());
|
||||
!isTesSuccess(ret))
|
||||
return ret;
|
||||
|
||||
bool const createAsset = src == txAccount;
|
||||
if (auto const ret = std::visit(
|
||||
[&]<typename T>(T const&) {
|
||||
return escrowUnlockApplyHelper<T>(
|
||||
ctx,
|
||||
kParityRate,
|
||||
sle,
|
||||
STAmount{(*sle)[sfBalance]}.xrp(),
|
||||
reqDelta,
|
||||
issuer,
|
||||
src,
|
||||
src,
|
||||
createAsset,
|
||||
j);
|
||||
},
|
||||
reqDelta.asset().value());
|
||||
!isTesSuccess(ret))
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove PayChan from issuer's owner directory, if present.
|
||||
if (auto const optPage = (*slep)[~sfIssuerNode])
|
||||
{
|
||||
if (!view.dirRemove(keylet::ownerDir(issuer), *optPage, key, true))
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j.fatal()) << "Could not remove paychan from issuer owner directory";
|
||||
return tefBAD_LEDGER;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
}
|
||||
|
||||
decreaseOwnerCountForObject(view, sle, slep, 1, j);
|
||||
view.update(sle);
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace {
|
||||
//------------------------------------------------------------------------------
|
||||
// clang-format off
|
||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||
char const* const versionString = "3.4.0-b3"
|
||||
char const* const versionString = "3.4.0-rc1"
|
||||
// clang-format on
|
||||
;
|
||||
|
||||
|
||||
@@ -168,10 +168,10 @@ STTx::getMentionedAccounts() const
|
||||
}
|
||||
|
||||
static Blob
|
||||
getSigningData(STTx const& that)
|
||||
getSigningData(STTx const& that, HashPrefix prefix)
|
||||
{
|
||||
Serializer s;
|
||||
s.add32(HashPrefix::TxSign);
|
||||
s.add32(prefix);
|
||||
that.addWithoutSigningFields(s);
|
||||
return s.getData();
|
||||
}
|
||||
@@ -212,30 +212,42 @@ STTx::getSeqProxy() const
|
||||
return SeqProxy::rawTicket(*ticketSeq);
|
||||
}
|
||||
|
||||
void
|
||||
STTx::sign(PublicKey const& publicKey, SecretKey const& secretKey)
|
||||
{
|
||||
// The account's own signature always covers the plain transaction prefix;
|
||||
// see signingPrefix for the role signatures that do not.
|
||||
auto const data = getSigningData(*this, HashPrefix::TxSign);
|
||||
|
||||
setFieldVL(sfTxnSignature, xrpl::sign(publicKey, secretKey, makeSlice(data)));
|
||||
tid_ = getHash(HashPrefix::TransactionId);
|
||||
}
|
||||
|
||||
void
|
||||
STTx::sign(
|
||||
PublicKey const& publicKey,
|
||||
SecretKey const& secretKey,
|
||||
std::optional<std::reference_wrapper<SField const>> signatureTarget)
|
||||
SignatureRole role,
|
||||
Rules const& rules)
|
||||
{
|
||||
auto const data = getSigningData(*this);
|
||||
auto const data = getSigningData(*this, signingPrefix(role, false, rules));
|
||||
|
||||
auto const sig = xrpl::sign(publicKey, secretKey, makeSlice(data));
|
||||
|
||||
if (signatureTarget)
|
||||
if (auto const target = signatureField(role))
|
||||
{
|
||||
auto& target = peekFieldObject(*signatureTarget);
|
||||
target.setFieldVL(sfTxnSignature, sig);
|
||||
peekFieldObject(*target).setFieldVL(sfTxnSignature, sig);
|
||||
}
|
||||
else
|
||||
{
|
||||
setFieldVL(sfTxnSignature, sig);
|
||||
}
|
||||
|
||||
tid_ = getHash(HashPrefix::TransactionId);
|
||||
}
|
||||
|
||||
std::expected<void, std::string>
|
||||
STTx::checkSign(Rules const& rules, STObject const& sigObject) const
|
||||
STTx::checkSign(Rules const& rules, STObject const& sigObject, SignatureRole role) const
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -244,8 +256,10 @@ STTx::checkSign(Rules const& rules, STObject const& sigObject) const
|
||||
// multi-signing. Otherwise we're single-signing.
|
||||
|
||||
Blob const& signingPubKey = sigObject.getFieldVL(sfSigningPubKey);
|
||||
return signingPubKey.empty() ? checkMultiSign(rules, sigObject)
|
||||
: checkSingleSign(sigObject);
|
||||
bool const multiSigning = signingPubKey.empty();
|
||||
auto const prefix = signingPrefix(role, multiSigning, rules);
|
||||
return multiSigning ? checkMultiSign(sigObject, prefix)
|
||||
: checkSingleSign(sigObject, prefix);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
@@ -256,20 +270,20 @@ STTx::checkSign(Rules const& rules, STObject const& sigObject) const
|
||||
std::expected<void, std::string>
|
||||
STTx::checkSign(Rules const& rules) const
|
||||
{
|
||||
if (auto const ret = checkSign(rules, *this); !ret)
|
||||
if (auto const ret = checkSign(rules, *this, SignatureRole::Transaction); !ret)
|
||||
return ret;
|
||||
|
||||
if (isFieldPresent(sfCounterpartySignature))
|
||||
{
|
||||
auto const counterSig = getFieldObject(sfCounterpartySignature);
|
||||
if (auto const ret = checkSign(rules, counterSig); !ret)
|
||||
if (auto const ret = checkSign(rules, counterSig, SignatureRole::Counterparty); !ret)
|
||||
return std::unexpected("Counterparty: " + ret.error());
|
||||
}
|
||||
|
||||
if (isFieldPresent(sfSponsorSignature))
|
||||
{
|
||||
auto const sponsorSignatureObj = getFieldObject(sfSponsorSignature);
|
||||
if (auto const ret = checkSign(rules, sponsorSignatureObj); !ret)
|
||||
if (auto const ret = checkSign(rules, sponsorSignatureObj, SignatureRole::Sponsor); !ret)
|
||||
return std::unexpected("Sponsor: " + ret.error());
|
||||
}
|
||||
|
||||
@@ -277,14 +291,14 @@ STTx::checkSign(Rules const& rules) const
|
||||
// of signature checking.
|
||||
if (isFieldPresent(sfBatchSigners))
|
||||
{
|
||||
if (auto const ret = checkBatchSign(rules); !ret)
|
||||
if (auto const ret = checkBatchSign(); !ret)
|
||||
return ret;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
std::expected<void, std::string>
|
||||
STTx::checkBatchSign(Rules const& rules) const
|
||||
STTx::checkBatchSign() const
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -318,7 +332,7 @@ STTx::checkBatchSign(Rules const& rules) const
|
||||
for (auto const& signer : signers)
|
||||
{
|
||||
Blob const& signingPubKey = signer.getFieldVL(sfSigningPubKey);
|
||||
auto const result = signingPubKey.empty() ? checkBatchMultiSign(signer, rules, txIds)
|
||||
auto const result = signingPubKey.empty() ? checkBatchMultiSign(signer, txIds)
|
||||
: checkBatchSingleSign(signer, txIds);
|
||||
|
||||
if (!result)
|
||||
@@ -447,9 +461,9 @@ singleSignHelper(STObject const& sigObject, Slice const& data)
|
||||
}
|
||||
|
||||
std::expected<void, std::string>
|
||||
STTx::checkSingleSign(STObject const& sigObject) const
|
||||
STTx::checkSingleSign(STObject const& sigObject, HashPrefix prefix) const
|
||||
{
|
||||
auto const data = getSigningData(*this);
|
||||
auto const data = getSigningData(*this, prefix);
|
||||
return singleSignHelper(sigObject, makeSlice(data));
|
||||
}
|
||||
|
||||
@@ -467,8 +481,7 @@ std::expected<void, std::string>
|
||||
multiSignHelper(
|
||||
STObject const& sigObject,
|
||||
std::optional<AccountID> txnAccountID,
|
||||
std::function<Serializer(AccountID const&)> makeMsg,
|
||||
Rules const& rules)
|
||||
std::function<Serializer(AccountID const&)> makeMsg)
|
||||
{
|
||||
// Make sure the MultiSigners are present. Otherwise they are not
|
||||
// attempting multi-signing and we just have a bad SigningPubKey.
|
||||
@@ -541,10 +554,7 @@ multiSignHelper(
|
||||
}
|
||||
|
||||
std::expected<void, std::string>
|
||||
STTx::checkBatchMultiSign(
|
||||
STObject const& batchSigner,
|
||||
Rules const& rules,
|
||||
std::vector<uint256> const& txIds) const
|
||||
STTx::checkBatchMultiSign(STObject const& batchSigner, std::vector<uint256> const& txIds) const
|
||||
{
|
||||
XRPL_ASSERT(getTxnType() == ttBATCH, "STTx::checkBatchMultiSign : batch transaction");
|
||||
// We can ease the computational load inside the loop a bit by
|
||||
@@ -555,18 +565,15 @@ STTx::checkBatchMultiSign(
|
||||
serializeBatch(dataStart, getAccountID(sfAccount), getSeqProxy().value(), getFlags(), txIds);
|
||||
dataStart.addBitString(batchSignerAccount);
|
||||
return multiSignHelper(
|
||||
batchSigner,
|
||||
batchSignerAccount,
|
||||
[&dataStart](AccountID const& accountID) -> Serializer {
|
||||
batchSigner, batchSignerAccount, [&dataStart](AccountID const& accountID) -> Serializer {
|
||||
Serializer s = dataStart;
|
||||
finishMultiSigningData(accountID, s);
|
||||
return s;
|
||||
},
|
||||
rules);
|
||||
});
|
||||
}
|
||||
|
||||
std::expected<void, std::string>
|
||||
STTx::checkMultiSign(Rules const& rules, STObject const& sigObject) const
|
||||
STTx::checkMultiSign(STObject const& sigObject, HashPrefix prefix) const
|
||||
{
|
||||
// Used inside the loop in multiSignHelper to enforce that
|
||||
// the account owner may not multisign for themselves.
|
||||
@@ -578,16 +585,13 @@ STTx::checkMultiSign(Rules const& rules, STObject const& sigObject) const
|
||||
// We can ease the computational load inside the loop a bit by
|
||||
// pre-constructing part of the data that we hash. Fill a Serializer
|
||||
// with the stuff that stays constant from signature to signature.
|
||||
Serializer dataStart = startMultiSigningData(*this);
|
||||
Serializer dataStart = startMultiSigningData(*this, prefix);
|
||||
return multiSignHelper(
|
||||
sigObject,
|
||||
txnAccountID,
|
||||
[&dataStart](AccountID const& accountID) -> Serializer {
|
||||
sigObject, txnAccountID, [&dataStart](AccountID const& accountID) -> Serializer {
|
||||
Serializer s = dataStart;
|
||||
finishMultiSigningData(accountID, s);
|
||||
return s;
|
||||
},
|
||||
rules);
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
@@ -59,6 +59,10 @@ STValidation::validationFormat()
|
||||
{sfBaseFeeDrops, SoeOptional},
|
||||
{sfReserveBaseDrops, SoeOptional},
|
||||
{sfReserveIncrementDrops, SoeOptional},
|
||||
// featureSmartEscrow
|
||||
{sfGasLimit, SoeOptional},
|
||||
{sfBytecodeSizeLimit, SoeOptional},
|
||||
{sfGasPrice, SoeOptional},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
|
||||
@@ -1,17 +1,77 @@
|
||||
#include <xrpl/protocol/Sign.h>
|
||||
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/HashPrefix.h>
|
||||
#include <xrpl/protocol/KeyType.h>
|
||||
#include <xrpl/protocol/PublicKey.h>
|
||||
#include <xrpl/protocol/Rules.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STExchange.h>
|
||||
#include <xrpl/protocol/STObject.h>
|
||||
#include <xrpl/protocol/SecretKey.h>
|
||||
#include <xrpl/protocol/Serializer.h>
|
||||
|
||||
#include <optional>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
SField const*
|
||||
signatureField(SignatureRole role)
|
||||
{
|
||||
switch (role)
|
||||
{
|
||||
case SignatureRole::Transaction:
|
||||
return nullptr;
|
||||
case SignatureRole::Counterparty:
|
||||
return &sfCounterpartySignature;
|
||||
case SignatureRole::Sponsor:
|
||||
return &sfSponsorSignature;
|
||||
}
|
||||
UNREACHABLE("xrpl::signatureField : unknown SignatureRole");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::optional<SignatureRole>
|
||||
signatureRole(SField const& sigField)
|
||||
{
|
||||
if (sigField == sfCounterpartySignature)
|
||||
return SignatureRole::Counterparty;
|
||||
if (sigField == sfSponsorSignature)
|
||||
return SignatureRole::Sponsor;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// Signature validity depends on fixCleanup3_4_0: a role signature covers
|
||||
// different bytes before and after the amendment activates. checkValidity
|
||||
// caches its verdict per transaction ID, so it keeps two separate cache slots
|
||||
// for role-signature transactions (kSfSiggoodOldPrefix / kSfSigbadOldPrefix in
|
||||
// tx/apply.cpp) to keep a pre-fix verdict from being reused in the post-fix
|
||||
// era, and vice versa. See the block comment in tx/apply.cpp for the details
|
||||
// and the reason both directions matter.
|
||||
HashPrefix
|
||||
signingPrefix(SignatureRole role, bool multiSigning, Rules const& rules)
|
||||
{
|
||||
// Before fixCleanup3_4_0 every signature on a transaction covered the same
|
||||
// bytes, so a signature could be moved from one role to another.
|
||||
if (!rules.enabled(fixCleanup3_4_0))
|
||||
return multiSigning ? HashPrefix::TxMultiSign : HashPrefix::TxSign;
|
||||
|
||||
switch (role)
|
||||
{
|
||||
case SignatureRole::Transaction:
|
||||
return multiSigning ? HashPrefix::TxMultiSign : HashPrefix::TxSign;
|
||||
case SignatureRole::Counterparty:
|
||||
return multiSigning ? HashPrefix::CounterpartyTxMultiSign
|
||||
: HashPrefix::CounterpartyTxSign;
|
||||
case SignatureRole::Sponsor:
|
||||
return multiSigning ? HashPrefix::SponsorTxMultiSign : HashPrefix::SponsorTxSign;
|
||||
}
|
||||
UNREACHABLE("xrpl::signingPrefix : unknown SignatureRole");
|
||||
return multiSigning ? HashPrefix::TxMultiSign : HashPrefix::TxSign;
|
||||
}
|
||||
|
||||
void
|
||||
sign(
|
||||
STObject& st,
|
||||
@@ -70,18 +130,18 @@ verify(STObject const& st, HashPrefix const& prefix, PublicKey const& pk, SF_VL
|
||||
// So, if we support multiple levels of signing, then we'll need to
|
||||
// incorporate the "signing for" accounts into the signing data as well.
|
||||
Serializer
|
||||
buildMultiSigningData(STObject const& obj, AccountID const& signingID)
|
||||
buildMultiSigningData(STObject const& obj, AccountID const& signingID, HashPrefix prefix)
|
||||
{
|
||||
Serializer s{startMultiSigningData(obj)};
|
||||
Serializer s{startMultiSigningData(obj, prefix)};
|
||||
finishMultiSigningData(signingID, s);
|
||||
return s;
|
||||
}
|
||||
|
||||
Serializer
|
||||
startMultiSigningData(STObject const& obj)
|
||||
startMultiSigningData(STObject const& obj, HashPrefix prefix)
|
||||
{
|
||||
Serializer s;
|
||||
s.add32(HashPrefix::TxMultiSign);
|
||||
s.add32(prefix);
|
||||
obj.addWithoutSigningFields(s);
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -108,6 +108,8 @@ transResults()
|
||||
MAKE_ERROR(tecPRECISION_LOSS, "The amounts used by the transaction cannot interact."),
|
||||
MAKE_ERROR(tecBAD_PROOF, "Proof cannot be verified"),
|
||||
MAKE_ERROR(tecNO_SPONSOR_PERMISSION, "Sponsor has not authorized this transaction."),
|
||||
MAKE_ERROR(tecOUT_OF_GAS, "The WASM code ran out of gas during execution."),
|
||||
MAKE_ERROR(tecBYTECODE_REJECTED, "The custom WASM code that was run rejected your transaction."),
|
||||
|
||||
MAKE_ERROR(tefALREADY, "The exact transaction was already in this ledger."),
|
||||
MAKE_ERROR(tefBAD_ADD_AUTH, "Not authorized to add account."),
|
||||
@@ -133,6 +135,8 @@ transResults()
|
||||
MAKE_ERROR(tefINVALID_LEDGER_FIX_TYPE, "The LedgerFixType field has an invalid value."),
|
||||
MAKE_ERROR(tefNO_DST_PARTIAL, "Partial payment to create account not allowed."),
|
||||
MAKE_ERROR(tefBAD_PATH_COUNT, "Malformed: Too many paths."),
|
||||
MAKE_ERROR(tefNO_BYTECODE, "There is no WASM code to run, but a WASM-specific field was included."),
|
||||
MAKE_ERROR(tefBYTECODE_NOT_INCLUDED, "WASM code requires a field that was not included."),
|
||||
|
||||
MAKE_ERROR(telLOCAL_ERROR, "Local failure."),
|
||||
MAKE_ERROR(telBAD_DOMAIN, "Domain too long."),
|
||||
@@ -204,6 +208,8 @@ transResults()
|
||||
MAKE_ERROR(temBAD_TRANSFER_FEE, "Malformed: Transfer fee is outside valid range."),
|
||||
MAKE_ERROR(temINVALID_INNER_BATCH, "Malformed: Invalid inner batch transaction."),
|
||||
MAKE_ERROR(temBAD_CIPHERTEXT, "Malformed: Invalid ciphertext."),
|
||||
MAKE_ERROR(temINVALID_BYTECODE, "Malformed: Provided byte code is invalid."),
|
||||
MAKE_ERROR(temTEMP_DISABLED, "The transaction requires logic that is currently temporarily disabled."),
|
||||
|
||||
MAKE_ERROR(terRETRY, "Retry transaction."),
|
||||
MAKE_ERROR(terFUNDS_SPENT, "DEPRECATED."),
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <xrpl/protocol/Serializer.h>
|
||||
#include <xrpl/shamap/SHAMap.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
@@ -40,11 +41,41 @@ depthMask(unsigned int depth)
|
||||
return kMasks.entry[depth];
|
||||
}
|
||||
|
||||
// The prefix of `key` at `depth`: the leading nibbles naming the subtree a node at that depth
|
||||
// identifies, with the remainder of the key masked off.
|
||||
static uint256
|
||||
maskedToDepth(uint256 const& key, unsigned int depth)
|
||||
{
|
||||
return key & depthMask(depth);
|
||||
}
|
||||
|
||||
// Whether `id` at `depth` is what `key` looks like once masked down to that depth, i.e.
|
||||
// whether an ID with this depth and id names a subtree that `key` falls under.
|
||||
static bool
|
||||
isPrefixOfAtDepth(uint256 const& id, unsigned int depth, uint256 const& key)
|
||||
{
|
||||
return maskedToDepth(key, depth) == id;
|
||||
}
|
||||
|
||||
// canonicalize the hash to a node ID for this depth
|
||||
SHAMapNodeID::SHAMapNodeID(unsigned int depth, uint256 const& hash) : id_(hash), depth_(depth)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
depth <= SHAMap::kLeafDepth, "xrpl::SHAMapNodeID::SHAMapNodeID : maximum depth input");
|
||||
// Every SHAMapNodeID's depth is stored here, so this is the one place that can stop an
|
||||
// out-of-range one from being kept: a depth past kLeafDepth would go on to index depthMask
|
||||
// out of bounds, and getRawString would narrow it to a byte, silently renaming the node.
|
||||
// Clamp rather than throw, since node IDs are built from peer-supplied depths on the ledger
|
||||
// data path, where no caller catches an exception before it reaches a thread boundary.
|
||||
if (depth_ > SHAMap::kLeafDepth)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
UNREACHABLE("xrpl::SHAMapNodeID::SHAMapNodeID : depth within tree");
|
||||
depth_ = SHAMap::kLeafDepth;
|
||||
id_ = maskedToDepth(id_, depth_);
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
// Reads the clamped member rather than the depth argument, so it cannot index depthMask past
|
||||
// its last entry even once the clamp above has reported the bad input and carried on.
|
||||
XRPL_ASSERT(
|
||||
isPrefixOf(id_), "xrpl::SHAMapNodeID::SHAMapNodeID : hash and depth inputs do match");
|
||||
}
|
||||
@@ -89,7 +120,7 @@ SHAMapNodeID::getChildNodeID(unsigned int branch) const
|
||||
bool
|
||||
SHAMapNodeID::isPrefixOf(uint256 const& key) const
|
||||
{
|
||||
return (key & depthMask(depth_)) == id_;
|
||||
return isPrefixOfAtDepth(id_, depth_, key);
|
||||
}
|
||||
|
||||
[[nodiscard]] std::optional<SHAMapNodeID>
|
||||
@@ -102,9 +133,9 @@ deserializeSHAMapNodeID(void const* data, std::size_t size)
|
||||
unsigned int const depth = *(static_cast<unsigned char const*>(data) + 32);
|
||||
if (depth <= SHAMap::kLeafDepth)
|
||||
{
|
||||
auto const id = uint256::fromVoid(data);
|
||||
|
||||
if (id == (id & depthMask(depth)))
|
||||
// Reject a serialized ID carrying bits below its own depth. Checked before
|
||||
// constructing, since the constructor asserts that same property.
|
||||
if (auto const id = uint256::fromVoid(data); isPrefixOfAtDepth(id, depth, id))
|
||||
ret.emplace(depth, id);
|
||||
}
|
||||
}
|
||||
@@ -115,7 +146,11 @@ deserializeSHAMapNodeID(void const* data, std::size_t size)
|
||||
[[nodiscard]] unsigned int
|
||||
selectBranch(SHAMapNodeID const& id, uint256 const& hash)
|
||||
{
|
||||
auto const depth = id.getDepth();
|
||||
XRPL_ASSERT(id.getDepth() < 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)));
|
||||
|
||||
if ((depth & 1) != 0u)
|
||||
@@ -134,8 +169,18 @@ selectBranch(SHAMapNodeID const& id, uint256 const& hash)
|
||||
SHAMapNodeID
|
||||
SHAMapNodeID::createID(unsigned int depth, uint256 const& key)
|
||||
{
|
||||
XRPL_ASSERT(depth <= SHAMap::kLeafDepth, "xrpl::SHAMapNodeID::createID : valid depth");
|
||||
return SHAMapNodeID(depth, key & depthMask(depth));
|
||||
// The mask is chosen here, before the constructor runs, so the clamp there cannot cover this
|
||||
// call: an out-of-range depth would index depthMask's table while still evaluating this
|
||||
// argument. A public factory has to hold its own bound.
|
||||
if (depth > SHAMap::kLeafDepth)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
UNREACHABLE("xrpl::SHAMapNodeID::createID : depth within tree");
|
||||
depth = SHAMap::kLeafDepth;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
return SHAMapNodeID(depth, maskedToDepth(key, depth));
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -1246,7 +1246,7 @@ removeExpiredNFTokenOffers(
|
||||
}
|
||||
|
||||
static void
|
||||
removeDeletedCredentials(ApplyView& view, std::vector<uint256> const& creds, beast::Journal viewJ)
|
||||
removeExpiredCredentials(ApplyView& view, std::vector<uint256> const& creds, beast::Journal viewJ)
|
||||
{
|
||||
for (auto const& index : creds)
|
||||
{
|
||||
@@ -1255,7 +1255,7 @@ removeDeletedCredentials(ApplyView& view, std::vector<uint256> const& creds, bea
|
||||
if (auto const ter = credentials::deleteSLE(view, sle, viewJ); !isTesSuccess(ter))
|
||||
{
|
||||
JLOG(viewJ.error())
|
||||
<< "removeDeletedCredentials: failed to delete credential. Err: "
|
||||
<< "removeExpiredCredentials: failed to delete expired credential. Err: "
|
||||
<< transToken(ter);
|
||||
}
|
||||
}
|
||||
@@ -1437,8 +1437,7 @@ Transactor::processPersistentChanges(TER result, XRPAmount fee)
|
||||
// should be used, making it possible to do more useful work
|
||||
// when transactions fail with a `tec` code.
|
||||
|
||||
auto typesForResult = [credentialCleanup =
|
||||
view().rules().enabled(fixCleanup3_4_0)](TER const ter) {
|
||||
auto typesForResult = [](TER const ter) {
|
||||
std::unordered_set<LedgerEntryType> types;
|
||||
if ((ter == tecOVERSIZE) || (ter == tecKILLED))
|
||||
{
|
||||
@@ -1447,11 +1446,6 @@ Transactor::processPersistentChanges(TER result, XRPAmount fee)
|
||||
else if (ter == tecINCOMPLETE)
|
||||
{
|
||||
types.insert(ltRIPPLE_STATE);
|
||||
// A bounded pseudo-account credential cleanup (VaultDelete /
|
||||
// LoanBrokerDelete) persists its partial credential deletions so a
|
||||
// later transaction can resume.
|
||||
if (credentialCleanup)
|
||||
types.insert(ltCREDENTIAL);
|
||||
}
|
||||
else if (ter == tecEXPIRED)
|
||||
{
|
||||
@@ -1529,7 +1523,7 @@ Transactor::processPersistentChanges(TER result, XRPAmount fee)
|
||||
removeDeletedTrustLines(view(), ids, viewJ);
|
||||
break;
|
||||
case ltCREDENTIAL:
|
||||
removeDeletedCredentials(view(), ids, viewJ);
|
||||
removeExpiredCredentials(view(), ids, viewJ);
|
||||
break;
|
||||
// LCOV_EXCL_START
|
||||
default:
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <xrpl/core/ServiceRegistry.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/OpenView.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Rules.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STObject.h>
|
||||
@@ -23,13 +24,38 @@
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
// These are the same flags defined as HashRouterFlags::PRIVATE1-4 in
|
||||
// HashRouter.h
|
||||
// This file owns HashRouterFlags::PRIVATE1-4 and PRIVATE7-8 in HashRouter.h.
|
||||
// These are the first four; the other two are below.
|
||||
constexpr HashRouterFlags kSfSigbad = HashRouterFlags::PRIVATE1; // Signature is bad
|
||||
constexpr HashRouterFlags kSfSiggood = HashRouterFlags::PRIVATE2; // Signature is good
|
||||
constexpr HashRouterFlags kSfLocalbad = HashRouterFlags::PRIVATE3; // Local checks failed
|
||||
constexpr HashRouterFlags kSfLocalgood = HashRouterFlags::PRIVATE4; // Local checks passed
|
||||
|
||||
// Before fixCleanup3_4_0, a signature in an alternate role field, such as
|
||||
// sfSponsorSignature, covered the same bytes as the top level signature. Which
|
||||
// bytes a role signature must cover therefore depends on whether the fix is
|
||||
// enabled, but the four flags above record only the verdict, not the rules that
|
||||
// produced it. A verdict reached under one prefix would otherwise be reused
|
||||
// under the other.
|
||||
//
|
||||
// The two flags below hold the verdict for the pre-fix prefixes, so the pre-fix
|
||||
// and post-fix verdicts occupy separate slots and neither is ever read in the
|
||||
// other's era. Nothing is cleared when the amendment activates: setFlags only
|
||||
// sets bits, so a stale pre-fix verdict simply stops being read and ages out
|
||||
// with the rest of the routing table.
|
||||
//
|
||||
// This is not one switchover at a single instant. The era is chosen per call
|
||||
// from the rules passed in, and callers do not agree on the rules: relay and
|
||||
// submit verify against the validated rules, which lag the open ledger rules
|
||||
// that preflight2 verifies against. At the amendment's flag ledger the same
|
||||
// transaction can therefore be checked under both prefixes, on the same node,
|
||||
// at the same time.
|
||||
//
|
||||
// Remove these two flags, and oldPrefixSig below, when Cleanup3_4_0 is retired
|
||||
// in features.macro.
|
||||
constexpr HashRouterFlags kSfSigbadOldPrefix = HashRouterFlags::PRIVATE7;
|
||||
constexpr HashRouterFlags kSfSiggoodOldPrefix = HashRouterFlags::PRIVATE8;
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
std::pair<Validity, std::string>
|
||||
@@ -48,21 +74,41 @@ checkValidity(HashRouter& router, STTx const& tx, Rules const& rules)
|
||||
return {Validity::SigBad, "Batch inner transactions are never considered validly signed."};
|
||||
}
|
||||
|
||||
if (any(flags & kSfSigbad))
|
||||
// Pick the cache slot for this call's era; see kSfSiggoodOldPrefix above.
|
||||
// Only a transaction that carries a role signature, and only while the fix
|
||||
// is disabled, uses the separate slot. Every other transaction, and every
|
||||
// transaction once the fix is enabled, uses the ordinary flags and verifies
|
||||
// exactly once, so there is no steady state cost.
|
||||
//
|
||||
// Both directions matter. A good verdict from before the fix must not let a
|
||||
// signature moved between roles survive the amendment, and a bad verdict
|
||||
// from before the fix must not condemn a transaction that the new prefixes
|
||||
// accept.
|
||||
//
|
||||
// Whether a transaction carries a role signature is fixed for its ID: the
|
||||
// fields are kNotSigning, so they are excluded from the signed bytes, but
|
||||
// they are still covered by the transaction ID. Repeat calls for one ID
|
||||
// therefore always agree on which slot pair to use.
|
||||
bool const oldPrefixSig = !rules.enabled(fixCleanup3_4_0) &&
|
||||
(tx.isFieldPresent(sfSponsorSignature) || tx.isFieldPresent(sfCounterpartySignature));
|
||||
auto const sigbadFlag = oldPrefixSig ? kSfSigbadOldPrefix : kSfSigbad;
|
||||
auto const siggoodFlag = oldPrefixSig ? kSfSiggoodOldPrefix : kSfSiggood;
|
||||
|
||||
if (any(flags & sigbadFlag))
|
||||
{
|
||||
// Signature is known bad
|
||||
return {Validity::SigBad, "Transaction has bad signature."};
|
||||
}
|
||||
|
||||
if (!any(flags & kSfSiggood))
|
||||
if (!any(flags & siggoodFlag))
|
||||
{
|
||||
auto const sigVerify = tx.checkSign(rules);
|
||||
if (!sigVerify)
|
||||
{
|
||||
router.setFlags(id, kSfSigbad);
|
||||
router.setFlags(id, sigbadFlag);
|
||||
return {Validity::SigBad, sigVerify.error()};
|
||||
}
|
||||
router.setFlags(id, kSfSiggood);
|
||||
router.setFlags(id, siggoodFlag);
|
||||
}
|
||||
|
||||
// Signature is now known good
|
||||
@@ -94,6 +140,19 @@ checkValidity(HashRouter& router, STTx const& tx, Rules const& rules)
|
||||
void
|
||||
forceValidity(HashRouter& router, uint256 const& txid, Validity validity)
|
||||
{
|
||||
// Callers reach here when they deliberately skip signature verification,
|
||||
// such as a cluster peer that trusts its neighbor's checks, or a
|
||||
// configuration that turns signature checks off. Nothing was verified, so
|
||||
// there is no prefix era to record. Mark both of checkValidity's signature
|
||||
// slots good: otherwise the forced verdict is ignored for a role-signature
|
||||
// transaction until fixCleanup3_4_0 is enabled, and the signature the
|
||||
// caller meant to skip gets verified after all. Marking both cannot leak a
|
||||
// verdict across eras, because no verdict was reached, and this is the only
|
||||
// place the distinction can be recorded: kSfSiggood alone does not say
|
||||
// whether checkValidity verified a post-fix signature or a caller forced
|
||||
// the result. An already cached bad verdict still wins, since checkValidity
|
||||
// tests its bad flag first. Drop kSfSiggoodOldPrefix when Cleanup3_4_0 is
|
||||
// retired.
|
||||
HashRouterFlags flags = HashRouterFlags::UNDEFINED;
|
||||
switch (validity)
|
||||
{
|
||||
@@ -101,7 +160,7 @@ forceValidity(HashRouter& router, uint256 const& txid, Validity validity)
|
||||
flags |= kSfLocalgood;
|
||||
[[fallthrough]];
|
||||
case Validity::SigGoodOnly:
|
||||
flags |= kSfSiggood;
|
||||
flags |= kSfSiggood | kSfSiggoodOldPrefix;
|
||||
[[fallthrough]];
|
||||
case Validity::SigBad:
|
||||
// would be silly to call directly
|
||||
|
||||
@@ -148,7 +148,8 @@ XRPNotCreated::visitEntry(bool isDelete, SLE::const_ref before, SLE::const_ref a
|
||||
drops_ -= (*before)[sfBalance].xrp().drops();
|
||||
break;
|
||||
case ltPAYCHAN:
|
||||
drops_ -= ((*before)[sfAmount] - (*before)[sfBalance]).xrp().drops();
|
||||
if (isXRP((*before)[sfAmount]))
|
||||
drops_ -= ((*before)[sfAmount] - (*before)[sfBalance]).xrp().drops();
|
||||
break;
|
||||
case ltESCROW:
|
||||
if (isXRP((*before)[sfAmount]))
|
||||
@@ -181,7 +182,7 @@ XRPNotCreated::visitEntry(bool isDelete, SLE::const_ref before, SLE::const_ref a
|
||||
drops_ += (*after)[sfBalance].xrp().drops();
|
||||
break;
|
||||
case ltPAYCHAN:
|
||||
if (!isDelete)
|
||||
if (!isDelete && isXRP((*after)[sfAmount]))
|
||||
drops_ += ((*after)[sfAmount] - (*after)[sfBalance]).xrp().drops();
|
||||
break;
|
||||
case ltESCROW:
|
||||
|
||||
@@ -234,14 +234,6 @@ ValidMPTIssuance::finalize(
|
||||
|
||||
if (hasPrivilege(tx, Privilege::DestroyMptIssuance))
|
||||
{
|
||||
// A VaultDelete that is still cleaning up credentials pinned to its
|
||||
// pseudo-account returns tecINCOMPLETE and has not yet reached the
|
||||
// share issuance. Don't require the issuance to be removed until
|
||||
// the deletion completes (a later transaction).
|
||||
if (rules.enabled(fixCleanup3_4_0) && txnType == ttVAULT_DELETE &&
|
||||
result == tecINCOMPLETE)
|
||||
return mptIssuancesDeleted_ == 0 && mptIssuancesCreated_ == 0;
|
||||
|
||||
if (mptIssuancesDeleted_ == 0)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: MPT issuance deletion "
|
||||
@@ -388,6 +380,12 @@ ValidMPTIssuance::finalize(
|
||||
return true;
|
||||
}
|
||||
|
||||
// A token paychan claim may auto-create the destination's MPToken.
|
||||
if (rules.enabled(featureTokenPaychan) && txnType == ttPAYCHAN_CLAIM &&
|
||||
mptIssuancesCreated_ == 0 && mptIssuancesDeleted_ == 0 && mptokensDeleted_ == 0 &&
|
||||
mptokensCreated_ <= 1)
|
||||
return true;
|
||||
|
||||
if (hasPrivilege(tx, Privilege::MayDeleteMpt) &&
|
||||
((txnType == ttAMM_DELETE && mptokensDeleted_ <= 2) || mptokensDeleted_ == 1) &&
|
||||
mptokensCreated_ == 0 && mptIssuancesCreated_ == 0 && mptIssuancesDeleted_ == 0)
|
||||
|
||||
@@ -1129,9 +1129,7 @@ ValidVault::finalize(
|
||||
// only. If the receiver's trust line sits at a coarser scale, the inflow
|
||||
// may safely round down to zero.
|
||||
//
|
||||
// XRP and MPT remain strict. Because they are integer-exact, a zero
|
||||
// destination delta indicates a true accounting bug, not a rounding
|
||||
// artifact.
|
||||
// XRP and MPT remain strict for rounding artifacts.
|
||||
bool const tolerateZeroDelta =
|
||||
view.rules().enabled(fixCleanup3_2_0) && !vaultAsset.integral();
|
||||
auto const invalidBalanceChange = tolerateZeroDelta
|
||||
|
||||
@@ -237,6 +237,7 @@ AMMClawback::applyGuts(Sandbox& sb)
|
||||
0,
|
||||
FreezeHandling::IgnoreFreeze,
|
||||
AuthHandling::IgnoreAuth,
|
||||
ReserveHandling::IgnoreReserve,
|
||||
WithdrawAll::Yes,
|
||||
preFeeBalance_,
|
||||
ctx_.journal);
|
||||
@@ -345,6 +346,7 @@ AMMClawback::equalWithdrawMatchingOneAmount(
|
||||
0,
|
||||
FreezeHandling::IgnoreFreeze,
|
||||
AuthHandling::IgnoreAuth,
|
||||
ReserveHandling::IgnoreReserve,
|
||||
WithdrawAll::Yes,
|
||||
preFeeBalance_,
|
||||
ctx_.journal);
|
||||
@@ -385,6 +387,7 @@ AMMClawback::equalWithdrawMatchingOneAmount(
|
||||
0,
|
||||
FreezeHandling::IgnoreFreeze,
|
||||
AuthHandling::IgnoreAuth,
|
||||
ReserveHandling::IgnoreReserve,
|
||||
WithdrawAll::No,
|
||||
preFeeBalance_,
|
||||
ctx_.journal);
|
||||
@@ -406,6 +409,7 @@ AMMClawback::equalWithdrawMatchingOneAmount(
|
||||
0,
|
||||
FreezeHandling::IgnoreFreeze,
|
||||
AuthHandling::IgnoreAuth,
|
||||
ReserveHandling::IgnoreReserve,
|
||||
WithdrawAll::No,
|
||||
preFeeBalance_,
|
||||
ctx_.journal);
|
||||
|
||||
@@ -527,6 +527,7 @@ AMMWithdraw::withdraw(
|
||||
tfee,
|
||||
issuerFreezeHandling(),
|
||||
AuthHandling::ZeroIfUnauthorized,
|
||||
ReserveHandling::EnforceReserve,
|
||||
isWithdrawAll(ctx_.tx),
|
||||
preFeeBalance_,
|
||||
j_);
|
||||
@@ -548,6 +549,7 @@ AMMWithdraw::withdraw(
|
||||
std::uint16_t tfee,
|
||||
FreezeHandling freezeHandling,
|
||||
AuthHandling authHandling,
|
||||
ReserveHandling reserveHandling,
|
||||
WithdrawAll withdrawAll,
|
||||
XRPAmount const& priorBalance,
|
||||
beast::Journal const& journal)
|
||||
@@ -681,6 +683,14 @@ AMMWithdraw::withdraw(
|
||||
});
|
||||
if (assetNotExists)
|
||||
{
|
||||
// Intentionally ignore the reserve check for AMMClawback, so the
|
||||
// holder can not avoid clawback by deleting the trustline/MPToken
|
||||
// and keeping a low spendable balance. AMMClawback has a higher
|
||||
// priority than the reserve check.
|
||||
if (view.rules().enabled(fixCleanup3_4_0) &&
|
||||
reserveHandling == ReserveHandling::IgnoreReserve)
|
||||
return tesSUCCESS;
|
||||
|
||||
auto sleAccount = view.peek(keylet::account(account));
|
||||
if (!sleAccount)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
@@ -850,6 +860,7 @@ AMMWithdraw::equalWithdrawTokens(
|
||||
tfee,
|
||||
issuerFreezeHandling(),
|
||||
AuthHandling::ZeroIfUnauthorized,
|
||||
ReserveHandling::EnforceReserve,
|
||||
isWithdrawAll(ctx_.tx),
|
||||
preFeeBalance_,
|
||||
ctx_.journal);
|
||||
@@ -903,6 +914,7 @@ AMMWithdraw::equalWithdrawTokens(
|
||||
std::uint16_t tfee,
|
||||
FreezeHandling freezeHandling,
|
||||
AuthHandling authHandling,
|
||||
ReserveHandling reserveHandling,
|
||||
WithdrawAll withdrawAll,
|
||||
XRPAmount const& priorBalance,
|
||||
beast::Journal const& journal)
|
||||
@@ -926,6 +938,7 @@ AMMWithdraw::equalWithdrawTokens(
|
||||
tfee,
|
||||
freezeHandling,
|
||||
authHandling,
|
||||
reserveHandling,
|
||||
WithdrawAll::Yes,
|
||||
priorBalance,
|
||||
journal);
|
||||
@@ -962,6 +975,7 @@ AMMWithdraw::equalWithdrawTokens(
|
||||
tfee,
|
||||
freezeHandling,
|
||||
authHandling,
|
||||
reserveHandling,
|
||||
withdrawAll,
|
||||
priorBalance,
|
||||
journal);
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <xrpl/ledger/Sandbox.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/CredentialHelpers.h>
|
||||
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
|
||||
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
|
||||
#include <xrpl/ledger/helpers/OfferHelpers.h>
|
||||
@@ -242,8 +243,31 @@ OfferCreate::preclaim(PreclaimContext const& ctx)
|
||||
// is part of the domain
|
||||
if (ctx.tx.isFieldPresent(sfDomainID))
|
||||
{
|
||||
if (!permissioned_dex::accountInDomain(ctx.view, id, ctx.tx[sfDomainID]))
|
||||
return tecNO_PERMISSION;
|
||||
if (ctx.view.rules().enabled(fixCleanup3_4_0))
|
||||
{
|
||||
auto const domainID = ctx.tx[sfDomainID];
|
||||
auto const sleDomain = ctx.view.read(keylet::permissionedDomain(domainID));
|
||||
if (!sleDomain)
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
// Domain owner is always considered in the domain, no credential check
|
||||
// needed. For all other accounts, use validDomain which detects expired
|
||||
// credentials. Suppress tecEXPIRED here so doApply can run and delete
|
||||
// the expired credential SLEs from the ledger.
|
||||
if (sleDomain->getAccountID(sfOwner) != id)
|
||||
{
|
||||
// validDomain returns tecNO_AUTH when no matching credential is
|
||||
// found. Map it to tecNO_PERMISSION to preserve existing behavior.
|
||||
if (auto const err = credentials::validDomain(ctx.view, domainID, id);
|
||||
!isTesSuccess(err) && err != tecEXPIRED)
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!permissioned_dex::accountInDomain(ctx.view, id, ctx.tx[sfDomainID]))
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
}
|
||||
|
||||
if (auto const ter = canTrade(ctx.view, saTakerPays.asset()); !isTesSuccess(ter))
|
||||
@@ -1000,6 +1024,27 @@ OfferCreate::applyGuts(Sandbox& sb, Sandbox& sbCancel)
|
||||
TER
|
||||
OfferCreate::doApply()
|
||||
{
|
||||
// If a DomainID is present, verify the account is still in the domain and
|
||||
// delete any expired credential SLEs. This must happen before the Sandboxes
|
||||
// are created: if we return a tec error, the engine applies sbCancel (not
|
||||
// sb) to rawView, so deletions made inside sb would be lost. Deletions made
|
||||
// directly to ctx_.view() here are preserved regardless of which branch
|
||||
// applyGuts takes.
|
||||
if (ctx_.tx.isFieldPresent(sfDomainID) && ctx_.view().rules().enabled(fixCleanup3_4_0))
|
||||
{
|
||||
auto const domainID = ctx_.tx[sfDomainID];
|
||||
auto const sleDomain = ctx_.view().read(keylet::permissionedDomain(domainID));
|
||||
if (!sleDomain)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
if (sleDomain->getAccountID(sfOwner) != accountID_)
|
||||
{
|
||||
if (auto const err = verifyValidDomain(ctx_.view(), accountID_, domainID, j_);
|
||||
!isTesSuccess(err))
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
// This is the ledger view that we work against. Transactions are applied
|
||||
// as we go on processing transactions.
|
||||
Sandbox sb(&ctx_.view());
|
||||
|
||||
@@ -5,15 +5,10 @@
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/EscrowHelpers.h>
|
||||
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
|
||||
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Concepts.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/Issue.h>
|
||||
#include <xrpl/protocol/MPTIssue.h>
|
||||
#include <xrpl/protocol/Rate.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
@@ -34,60 +29,6 @@ EscrowCancel::preflight(PreflightContext const& ctx)
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
template <ValidIssueType T>
|
||||
static TER
|
||||
escrowCancelPreclaimHelper(
|
||||
PreclaimContext const& ctx,
|
||||
AccountID const& account,
|
||||
STAmount const& amount);
|
||||
|
||||
template <>
|
||||
TER
|
||||
escrowCancelPreclaimHelper<Issue>(
|
||||
PreclaimContext const& ctx,
|
||||
AccountID const& account,
|
||||
STAmount const& amount)
|
||||
{
|
||||
AccountID const& issuer = amount.getIssuer();
|
||||
// If the issuer is the same as the account, return tecINTERNAL
|
||||
if (issuer == account)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
// If the issuer has requireAuth set, check if the account is authorized
|
||||
if (auto const ter = requireAuth(ctx.view, amount.get<Issue>(), account); !isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
template <>
|
||||
TER
|
||||
escrowCancelPreclaimHelper<MPTIssue>(
|
||||
PreclaimContext const& ctx,
|
||||
AccountID const& account,
|
||||
STAmount const& amount)
|
||||
{
|
||||
AccountID const issuer = amount.getIssuer();
|
||||
// If the issuer is the same as the account, return tecINTERNAL
|
||||
if (issuer == account)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
// If the mpt does not exist, return tecOBJECT_NOT_FOUND
|
||||
auto const issuanceKey = keylet::mptokenIssuance(amount.get<MPTIssue>().getMptID());
|
||||
auto const sleIssuance = ctx.view.read(issuanceKey);
|
||||
if (!sleIssuance)
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
|
||||
// If the issuer has requireAuth set, check if the account is
|
||||
// authorized
|
||||
auto const& mptIssue = amount.get<MPTIssue>();
|
||||
if (auto const ter = requireAuth(ctx.view, mptIssue, account, AuthType::WeakAuth);
|
||||
!isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
EscrowCancel::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
@@ -106,7 +47,7 @@ EscrowCancel::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
if (auto const ret = std::visit(
|
||||
[&]<typename T>(T const&) {
|
||||
return escrowCancelPreclaimHelper<T>(ctx, account, amount);
|
||||
return escrowUnlockPreclaimHelper<T>(ctx.view, account, amount, false);
|
||||
},
|
||||
amount.asset().value());
|
||||
!isTesSuccess(ret))
|
||||
@@ -169,6 +110,12 @@ EscrowCancel::doApply()
|
||||
auto const sle = ctx_.view().peek(keylet::account(account));
|
||||
STAmount const amount = slep->getFieldAmount(sfAmount);
|
||||
|
||||
// The return can re-create a holding the owner deleted while the escrow
|
||||
// was pending; the removed escrow must not be counted against its reserve.
|
||||
bool const recycleReserve = ctx_.view().rules().enabled(fixCleanup3_4_0);
|
||||
if (recycleReserve)
|
||||
decreaseOwnerCountForObject(ctx_.view(), sle, slep, 1, ctx_.journal);
|
||||
|
||||
// Transfer amount back to the owner
|
||||
if (isXRP(amount))
|
||||
{
|
||||
@@ -212,7 +159,8 @@ EscrowCancel::doApply()
|
||||
}
|
||||
}
|
||||
|
||||
decreaseOwnerCountForObject(ctx_.view(), sle, slep, 1, ctx_.journal);
|
||||
if (!recycleReserve)
|
||||
decreaseOwnerCountForObject(ctx_.view(), sle, slep, 1, ctx_.journal);
|
||||
|
||||
// Remove escrow from ledger
|
||||
ctx_.view().erase(slep);
|
||||
|
||||
@@ -9,8 +9,7 @@
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
|
||||
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
|
||||
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
|
||||
#include <xrpl/ledger/helpers/EscrowHelpers.h>
|
||||
#include <xrpl/ledger/helpers/SponsorHelpers.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
@@ -18,6 +17,7 @@
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/Issue.h>
|
||||
#include <xrpl/protocol/Keylet.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
#include <xrpl/protocol/MPTAmount.h>
|
||||
#include <xrpl/protocol/MPTIssue.h>
|
||||
@@ -88,8 +88,13 @@ EscrowCreate::checkExtraFeatures(PreflightContext const& ctx)
|
||||
// Only require featureMPTokensV1 when the escrow amount is an MPT and
|
||||
// fixCleanup3_2_0 is active; XRP/IOU escrows are unaffected by this gate.
|
||||
if (ctx.rules.enabled(fixCleanup3_2_0) && ctx.tx[sfAmount].holds<MPTIssue>())
|
||||
return ctx.rules.enabled(featureMPTokensV1);
|
||||
return true;
|
||||
{
|
||||
if (!ctx.rules.enabled(featureMPTokensV1))
|
||||
return false;
|
||||
}
|
||||
|
||||
return (!ctx.tx.isFieldPresent(sfBytecode) && !ctx.tx.isFieldPresent(sfData)) ||
|
||||
ctx.rules.enabled(featureSmartEscrow);
|
||||
}
|
||||
|
||||
template <ValidIssueType T>
|
||||
@@ -179,162 +184,6 @@ EscrowCreate::preflight(PreflightContext const& ctx)
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
template <ValidIssueType T>
|
||||
static TER
|
||||
escrowCreatePreclaimHelper(
|
||||
PreclaimContext const& ctx,
|
||||
AccountID const& account,
|
||||
AccountID const& dest,
|
||||
STAmount const& amount);
|
||||
|
||||
template <>
|
||||
TER
|
||||
escrowCreatePreclaimHelper<Issue>(
|
||||
PreclaimContext const& ctx,
|
||||
AccountID const& account,
|
||||
AccountID const& dest,
|
||||
STAmount const& amount)
|
||||
{
|
||||
auto const& issue = amount.get<Issue>();
|
||||
AccountID const& issuer = amount.getIssuer();
|
||||
// If the issuer is the same as the account, return tecNO_PERMISSION
|
||||
if (issuer == account)
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
// If the lsfAllowTrustLineLocking is not enabled, return tecNO_PERMISSION
|
||||
auto const sleIssuer = ctx.view.read(keylet::account(issuer));
|
||||
if (!sleIssuer)
|
||||
return tecNO_ISSUER;
|
||||
if (!sleIssuer->isFlag(lsfAllowTrustLineLocking))
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
// If the account does not have a trustline to the issuer, return tecNO_LINE
|
||||
auto const sleRippleState = ctx.view.read(keylet::trustLine(account, issuer, issue.currency));
|
||||
if (!sleRippleState)
|
||||
return tecNO_LINE;
|
||||
|
||||
STAmount const balance = (*sleRippleState)[sfBalance];
|
||||
|
||||
// If balance is positive, issuer must have higher address than account
|
||||
if (balance > beast::kZero && issuer < account)
|
||||
return tecNO_PERMISSION; // LCOV_EXCL_LINE
|
||||
|
||||
// If balance is negative, issuer must have lower address than account
|
||||
if (balance < beast::kZero && issuer > account)
|
||||
return tecNO_PERMISSION; // LCOV_EXCL_LINE
|
||||
|
||||
// If the issuer has requireAuth set, check if the account is authorized
|
||||
if (auto const ter = requireAuth(ctx.view, issue, account); !isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
// If the issuer has requireAuth set, check if the destination is authorized
|
||||
if (auto const ter = requireAuth(ctx.view, issue, dest); !isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
// If the issuer has frozen the account, return tecFROZEN
|
||||
if (isFrozen(ctx.view, account, issue))
|
||||
return tecFROZEN;
|
||||
|
||||
// If the issuer has frozen the destination, return tecFROZEN
|
||||
if (isFrozen(ctx.view, dest, issue))
|
||||
return tecFROZEN;
|
||||
|
||||
STAmount const spendableAmount = accountHolds(
|
||||
ctx.view, account, issue.currency, issuer, FreezeHandling::IgnoreFreeze, ctx.j);
|
||||
|
||||
// If the balance is less than or equal to 0, return tecINSUFFICIENT_FUNDS
|
||||
if (spendableAmount <= beast::kZero)
|
||||
return tecINSUFFICIENT_FUNDS;
|
||||
|
||||
// If the spendable amount is less than the amount, return
|
||||
// tecINSUFFICIENT_FUNDS
|
||||
if (spendableAmount < amount)
|
||||
return tecINSUFFICIENT_FUNDS;
|
||||
|
||||
// If the amount is not addable to the balance, return tecPRECISION_LOSS
|
||||
if (!canAdd(spendableAmount, amount))
|
||||
return tecPRECISION_LOSS;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
template <>
|
||||
TER
|
||||
escrowCreatePreclaimHelper<MPTIssue>(
|
||||
PreclaimContext const& ctx,
|
||||
AccountID const& account,
|
||||
AccountID const& dest,
|
||||
STAmount const& amount)
|
||||
{
|
||||
AccountID const issuer = amount.getIssuer();
|
||||
// If the issuer is the same as the account, return tecNO_PERMISSION
|
||||
if (issuer == account)
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
// If the mpt does not exist, return tecOBJECT_NOT_FOUND
|
||||
auto const issuanceKey = keylet::mptokenIssuance(amount.get<MPTIssue>().getMptID());
|
||||
auto const sleIssuance = ctx.view.read(issuanceKey);
|
||||
if (!sleIssuance)
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
|
||||
// If the lsfMPTCanEscrow is not enabled, return tecNO_PERMISSION
|
||||
if (!sleIssuance->isFlag(lsfMPTCanEscrow))
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
// If the issuer is not the same as the issuer of the mpt, return
|
||||
// tecNO_PERMISSION
|
||||
if (sleIssuance->getAccountID(sfIssuer) != issuer)
|
||||
return tecNO_PERMISSION; // LCOV_EXCL_LINE
|
||||
|
||||
// If the account does not have the mpt, return tecOBJECT_NOT_FOUND
|
||||
if (!ctx.view.exists(keylet::mptoken(issuanceKey.key, account)))
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
|
||||
// If the issuer has requireAuth set, check if the account is
|
||||
// authorized
|
||||
auto const& mptIssue = amount.get<MPTIssue>();
|
||||
if (auto const ter = requireAuth(ctx.view, mptIssue, account, AuthType::WeakAuth);
|
||||
!isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
// If the issuer has requireAuth set, check if the destination is
|
||||
// authorized
|
||||
if (auto const ter = requireAuth(ctx.view, mptIssue, dest, AuthType::WeakAuth);
|
||||
!isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
// If the issuer has frozen the account, return tecLOCKED
|
||||
if (isFrozen(ctx.view, account, *sleIssuance))
|
||||
return tecLOCKED;
|
||||
|
||||
// If the issuer has frozen the destination, return tecLOCKED
|
||||
if (isFrozen(ctx.view, dest, *sleIssuance))
|
||||
return tecLOCKED;
|
||||
|
||||
// If the mpt cannot be transferred, return tecNO_AUTH
|
||||
if (auto const ter = canTransfer(ctx.view, mptIssue, account, dest); !isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
STAmount const spendableAmount = accountHolds(
|
||||
ctx.view,
|
||||
account,
|
||||
amount.get<MPTIssue>(),
|
||||
FreezeHandling::IgnoreFreeze,
|
||||
AuthHandling::IgnoreAuth,
|
||||
ctx.j);
|
||||
|
||||
// If the balance is less than or equal to 0, return tecINSUFFICIENT_FUNDS
|
||||
if (spendableAmount <= beast::kZero)
|
||||
return tecINSUFFICIENT_FUNDS;
|
||||
|
||||
// If the spendable amount is less than the amount, return
|
||||
// tecINSUFFICIENT_FUNDS
|
||||
if (spendableAmount < amount)
|
||||
return tecINSUFFICIENT_FUNDS;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
EscrowCreate::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
@@ -360,7 +209,7 @@ EscrowCreate::preclaim(PreclaimContext const& ctx)
|
||||
|
||||
if (auto const ret = std::visit(
|
||||
[&]<typename T>(T const&) {
|
||||
return escrowCreatePreclaimHelper<T>(ctx, account, dest, amount);
|
||||
return escrowLockPreclaimHelper<T>(ctx.view, account, dest, amount, ctx.j);
|
||||
},
|
||||
amount.asset().value());
|
||||
!isTesSuccess(ret))
|
||||
@@ -369,54 +218,6 @@ EscrowCreate::preclaim(PreclaimContext const& ctx)
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
template <ValidIssueType T>
|
||||
static TER
|
||||
escrowLockApplyHelper(
|
||||
ApplyView& view,
|
||||
AccountID const& issuer,
|
||||
AccountID const& sender,
|
||||
STAmount const& amount,
|
||||
beast::Journal journal);
|
||||
|
||||
template <>
|
||||
TER
|
||||
escrowLockApplyHelper<Issue>(
|
||||
ApplyView& view,
|
||||
AccountID const& issuer,
|
||||
AccountID const& sender,
|
||||
STAmount const& amount,
|
||||
beast::Journal journal)
|
||||
{
|
||||
// Defensive: Issuer cannot create an escrow
|
||||
if (issuer == sender)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const ter =
|
||||
directSendNoFee(view, sender, issuer, amount, !amount.holds<MPTIssue>(), journal);
|
||||
if (!isTesSuccess(ter))
|
||||
return ter; // LCOV_EXCL_LINE
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
template <>
|
||||
TER
|
||||
escrowLockApplyHelper<MPTIssue>(
|
||||
ApplyView& view,
|
||||
AccountID const& issuer,
|
||||
AccountID const& sender,
|
||||
STAmount const& amount,
|
||||
beast::Journal journal)
|
||||
{
|
||||
// Defensive: Issuer cannot create an escrow
|
||||
if (issuer == sender)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const ter = lockEscrowMPT(view, sender, amount, journal);
|
||||
if (!isTesSuccess(ter))
|
||||
return ter; // LCOV_EXCL_LINE
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
EscrowCreate::doApply()
|
||||
{
|
||||
|
||||
@@ -12,15 +12,10 @@
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/CredentialHelpers.h>
|
||||
#include <xrpl/ledger/helpers/EscrowHelpers.h>
|
||||
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
|
||||
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Concepts.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/Issue.h>
|
||||
#include <xrpl/protocol/MPTIssue.h>
|
||||
#include <xrpl/protocol/Rate.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
@@ -65,7 +60,13 @@ checkCondition(Slice f, Slice c)
|
||||
bool
|
||||
EscrowFinish::checkExtraFeatures(PreflightContext const& ctx)
|
||||
{
|
||||
return !ctx.tx.isFieldPresent(sfCredentialIDs) || ctx.rules.enabled(featureCredentials);
|
||||
if (ctx.tx.isFieldPresent(sfCredentialIDs) && !ctx.rules.enabled(featureCredentials))
|
||||
return false;
|
||||
|
||||
if (ctx.tx.isFieldPresent(sfGas) && !ctx.rules.enabled(featureSmartEscrow))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
NotTEC
|
||||
@@ -130,68 +131,6 @@ EscrowFinish::calculateBaseFee(ReadView const& view, STTx const& tx)
|
||||
return Transactor::calculateBaseFee(view, tx) + extraFee;
|
||||
}
|
||||
|
||||
template <ValidIssueType T>
|
||||
static TER
|
||||
escrowFinishPreclaimHelper(
|
||||
PreclaimContext const& ctx,
|
||||
AccountID const& dest,
|
||||
STAmount const& amount);
|
||||
|
||||
template <>
|
||||
TER
|
||||
escrowFinishPreclaimHelper<Issue>(
|
||||
PreclaimContext const& ctx,
|
||||
AccountID const& dest,
|
||||
STAmount const& amount)
|
||||
{
|
||||
AccountID const& issuer = amount.getIssuer();
|
||||
// If the issuer is the same as the account, return tesSUCCESS
|
||||
if (issuer == dest)
|
||||
return tesSUCCESS;
|
||||
|
||||
// If the issuer has requireAuth set, check if the destination is authorized
|
||||
if (auto const ter = requireAuth(ctx.view, amount.get<Issue>(), dest); !isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
// If the issuer has deep frozen the destination, return tecFROZEN
|
||||
if (isDeepFrozen(ctx.view, dest, amount.get<Issue>().currency, amount.getIssuer()))
|
||||
return tecFROZEN;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
template <>
|
||||
TER
|
||||
escrowFinishPreclaimHelper<MPTIssue>(
|
||||
PreclaimContext const& ctx,
|
||||
AccountID const& dest,
|
||||
STAmount const& amount)
|
||||
{
|
||||
AccountID const& issuer = amount.getIssuer();
|
||||
// If the issuer is the same as the dest, return tesSUCCESS
|
||||
if (issuer == dest)
|
||||
return tesSUCCESS;
|
||||
|
||||
// If the mpt does not exist, return tecOBJECT_NOT_FOUND
|
||||
auto const issuanceKey = keylet::mptokenIssuance(amount.get<MPTIssue>().getMptID());
|
||||
auto const sleIssuance = ctx.view.read(issuanceKey);
|
||||
if (!sleIssuance)
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
|
||||
// If the issuer has requireAuth set, check if the destination is
|
||||
// authorized
|
||||
auto const& mptIssue = amount.get<MPTIssue>();
|
||||
if (auto const ter = requireAuth(ctx.view, mptIssue, dest, AuthType::WeakAuth);
|
||||
!isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
// If the issuer has frozen the destination, return tecLOCKED
|
||||
if (isFrozen(ctx.view, dest, *sleIssuance))
|
||||
return tecLOCKED;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
EscrowFinish::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
@@ -217,7 +156,7 @@ EscrowFinish::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
if (auto const ret = std::visit(
|
||||
[&]<typename T>(T const&) {
|
||||
return escrowFinishPreclaimHelper<T>(ctx, dest, amount);
|
||||
return escrowUnlockPreclaimHelper<T>(ctx.view, dest, amount);
|
||||
},
|
||||
amount.asset().value());
|
||||
!isTesSuccess(ret))
|
||||
@@ -343,14 +282,12 @@ EscrowFinish::doApply()
|
||||
}
|
||||
}
|
||||
|
||||
// With the Sponsor amendment, release the escrow reserve before delivery.
|
||||
// Token delivery can auto-create a destination holding, and the same
|
||||
// sponsor (or the same account, for a self-escrow) may cover both the
|
||||
// escrow being removed and the holding being created. Without the
|
||||
// amendment, keep the legacy order: releasing early changes the reserve
|
||||
// arithmetic for self-escrows and would break consensus if not gated.
|
||||
bool const sponsorEnabled = ctx_.view().rules().enabled(featureSponsor);
|
||||
if (sponsorEnabled)
|
||||
// Delivery can auto-create the destination's holding; the removed escrow
|
||||
// must not be counted against its reserve. The two share a reserve payer
|
||||
// for a self-escrow, or when one sponsor covers both.
|
||||
bool const recycleReserve =
|
||||
ctx_.view().rules().enabled(featureSponsor) || ctx_.view().rules().enabled(fixCleanup3_4_0);
|
||||
if (recycleReserve)
|
||||
decreaseOwnerCountForObject(ctx_.view(), account, slep, 1, ctx_.journal);
|
||||
|
||||
STAmount const amount = slep->getFieldAmount(sfAmount);
|
||||
@@ -402,8 +339,7 @@ EscrowFinish::doApply()
|
||||
|
||||
ctx_.view().update(sled);
|
||||
|
||||
// Adjust source owner count (legacy position, pre-Sponsor)
|
||||
if (!sponsorEnabled)
|
||||
if (!recycleReserve)
|
||||
decreaseOwnerCountForObject(ctx_.view(), account, slep, 1, ctx_.journal);
|
||||
|
||||
// Remove escrow from ledger
|
||||
|
||||
@@ -4,13 +4,11 @@
|
||||
#include <xrpl/basics/Number.h>
|
||||
#include <xrpl/beast/utility/Zero.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/CredentialHelpers.h>
|
||||
#include <xrpl/ledger/helpers/LendingHelpers.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/protocol/Asset.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
@@ -142,19 +140,6 @@ LoanBrokerDelete::doApply()
|
||||
|
||||
auto const brokerPseudoID = broker->at(sfAccount);
|
||||
|
||||
// Remove any credentials pinned to the broker pseudo-account before anything
|
||||
// else. They would otherwise keep its owner directory alive and block
|
||||
// deletion with tecHAS_OBLIGATIONS. Doing it first means a bounded,
|
||||
// tecINCOMPLETE cleanup can be resumed by a later transaction without having
|
||||
// already torn down the broker.
|
||||
if (view().rules().enabled(fixCleanup3_4_0))
|
||||
{
|
||||
if (auto const ter = credentials::deletePseudoAccountCredentials(
|
||||
view(), brokerPseudoID, kMaxDeletablePseudoAccountCredentials, j_);
|
||||
!isTesSuccess(ter))
|
||||
return ter;
|
||||
}
|
||||
|
||||
if (!view().dirRemove(
|
||||
keylet::ownerDir(accountID_), broker->at(sfOwnerNode), broker->key(), false))
|
||||
{
|
||||
|
||||
@@ -458,11 +458,41 @@ Payment::preclaim(PreclaimContext const& ctx)
|
||||
|
||||
if (ctx.tx.isFieldPresent(sfDomainID))
|
||||
{
|
||||
if (!permissioned_dex::accountInDomain(ctx.view, ctx.tx[sfAccount], ctx.tx[sfDomainID]))
|
||||
return tecNO_PERMISSION;
|
||||
if (ctx.view.rules().enabled(fixCleanup3_4_0))
|
||||
{
|
||||
auto const domainID = ctx.tx[sfDomainID];
|
||||
auto const sleDomain = ctx.view.read(keylet::permissionedDomain(domainID));
|
||||
if (!sleDomain)
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
if (!permissioned_dex::accountInDomain(ctx.view, ctx.tx[sfDestination], ctx.tx[sfDomainID]))
|
||||
return tecNO_PERMISSION;
|
||||
// Domain owner is always considered in the domain. For other accounts,
|
||||
// suppress tecEXPIRED so doApply can run and delete expired credential
|
||||
// SLEs from the ledger.
|
||||
auto const checkAccount = [&](AccountID const& acct) -> TER {
|
||||
if (sleDomain->getAccountID(sfOwner) == acct)
|
||||
return tesSUCCESS;
|
||||
// validDomain returns tecNO_AUTH when no matching credential is
|
||||
// found. Map it to tecNO_PERMISSION to preserve existing behavior.
|
||||
if (auto const err = credentials::validDomain(ctx.view, domainID, acct);
|
||||
!isTesSuccess(err) && err != tecEXPIRED)
|
||||
return tecNO_PERMISSION;
|
||||
return tesSUCCESS;
|
||||
};
|
||||
|
||||
if (auto const err = checkAccount(ctx.tx[sfAccount]); !isTesSuccess(err))
|
||||
return err;
|
||||
if (auto const err = checkAccount(ctx.tx[sfDestination]); !isTesSuccess(err))
|
||||
return err;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!permissioned_dex::accountInDomain(ctx.view, ctx.tx[sfAccount], ctx.tx[sfDomainID]))
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
if (!permissioned_dex::accountInDomain(
|
||||
ctx.view, ctx.tx[sfDestination], ctx.tx[sfDomainID]))
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
@@ -471,6 +501,31 @@ Payment::preclaim(PreclaimContext const& ctx)
|
||||
TER
|
||||
Payment::doApply()
|
||||
{
|
||||
// If a DomainID is present, verify both sender and destination are still in
|
||||
// the domain and delete any expired credential SLEs from the ledger.
|
||||
if (ctx_.tx.isFieldPresent(sfDomainID) && ctx_.view().rules().enabled(fixCleanup3_4_0))
|
||||
{
|
||||
auto const domainID = ctx_.tx[sfDomainID];
|
||||
auto const sleDomain = ctx_.view().read(keylet::permissionedDomain(domainID));
|
||||
if (!sleDomain)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const cleanupFor = [&](AccountID const& acct) -> TER {
|
||||
if (sleDomain->getAccountID(sfOwner) == acct)
|
||||
return tesSUCCESS;
|
||||
return verifyValidDomain(ctx_.view(), acct, domainID, j_);
|
||||
};
|
||||
|
||||
auto const destination = ctx_.tx[sfDestination];
|
||||
auto const senderErr = cleanupFor(accountID_);
|
||||
auto const destinationErr = accountID_ == destination ? senderErr : cleanupFor(destination);
|
||||
|
||||
if (!isTesSuccess(senderErr))
|
||||
return senderErr;
|
||||
if (!isTesSuccess(destinationErr))
|
||||
return destinationErr;
|
||||
}
|
||||
|
||||
auto const deliverMin = ctx_.tx[~sfDeliverMin];
|
||||
|
||||
// Ripple if source or destination is non-native or if there are paths.
|
||||
|
||||
@@ -4,14 +4,17 @@
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/helpers/CredentialHelpers.h>
|
||||
#include <xrpl/ledger/helpers/EscrowHelpers.h>
|
||||
#include <xrpl/ledger/helpers/PaymentChannelHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Concepts.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/Keylet.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
#include <xrpl/protocol/PayChan.h>
|
||||
#include <xrpl/protocol/PublicKey.h>
|
||||
#include <xrpl/protocol/Rate.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
@@ -24,6 +27,7 @@
|
||||
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <variant>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
@@ -46,11 +50,19 @@ PaymentChannelClaim::preflight(PreflightContext const& ctx)
|
||||
return temMALFORMED;
|
||||
|
||||
auto const bal = ctx.tx[~sfBalance];
|
||||
if (bal && (!isXRP(*bal) || *bal <= beast::kZero))
|
||||
if (bal && *bal <= beast::kZero)
|
||||
return temBAD_AMOUNT;
|
||||
|
||||
auto const amt = ctx.tx[~sfAmount];
|
||||
if (amt && (!isXRP(*amt) || *amt <= beast::kZero))
|
||||
if (amt && *amt <= beast::kZero)
|
||||
return temBAD_AMOUNT;
|
||||
|
||||
if (((bal && !isXRP(*bal)) || (amt && !isXRP(*amt))) && !ctx.rules.enabled(featureTokenPaychan))
|
||||
return temBAD_AMOUNT;
|
||||
|
||||
// Both bal and amt must reference the same asset before comparing,
|
||||
// otherwise STAmount comparison throws.
|
||||
if (bal && amt && bal->asset() != amt->asset())
|
||||
return temBAD_AMOUNT;
|
||||
|
||||
if (bal && amt && *bal > *amt)
|
||||
@@ -70,8 +82,8 @@ PaymentChannelClaim::preflight(PreflightContext const& ctx)
|
||||
// The signature isn't needed if txAccount == src, but if it's
|
||||
// present, check it
|
||||
|
||||
auto const reqBalance = bal->xrp();
|
||||
auto const authAmt = amt ? amt->xrp() : reqBalance;
|
||||
auto const reqBalance = *bal;
|
||||
auto const authAmt = amt ? *amt : reqBalance;
|
||||
|
||||
if (reqBalance > authAmt)
|
||||
return temBAD_AMOUNT;
|
||||
@@ -96,12 +108,45 @@ PaymentChannelClaim::preflight(PreflightContext const& ctx)
|
||||
TER
|
||||
PaymentChannelClaim::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
if (!ctx.view.rules().enabled(featureCredentials))
|
||||
return Transactor::preclaim(ctx);
|
||||
if (ctx.view.rules().enabled(featureCredentials))
|
||||
{
|
||||
if (auto const err = credentials::valid(ctx.tx, ctx.view, ctx.tx[sfAccount], ctx.j);
|
||||
!isTesSuccess(err))
|
||||
return err;
|
||||
}
|
||||
|
||||
if (auto const err = credentials::valid(ctx.tx, ctx.view, ctx.tx[sfAccount], ctx.j);
|
||||
!isTesSuccess(err))
|
||||
return err;
|
||||
if (ctx.view.rules().enabled(featureTokenPaychan))
|
||||
{
|
||||
Keylet const k{ltPAYCHAN, ctx.tx[sfChannel]};
|
||||
auto const slep = ctx.view.read(k);
|
||||
if (!slep)
|
||||
return tecNO_TARGET;
|
||||
|
||||
AccountID const dest = (*slep)[sfDestination];
|
||||
auto const& chanFunds = slep->getFieldAmount(sfAmount);
|
||||
|
||||
if (auto const bal = ctx.tx[~sfBalance])
|
||||
{
|
||||
// The requested balance (and optional amount) must match the
|
||||
// channel's asset; otherwise STAmount comparisons/subtractions
|
||||
// in doApply would throw on mismatched issues.
|
||||
if (bal->asset() != chanFunds.asset())
|
||||
return tecWRONG_ASSET;
|
||||
if (auto const amt = ctx.tx[~sfAmount]; amt && amt->asset() != chanFunds.asset())
|
||||
return tecWRONG_ASSET;
|
||||
}
|
||||
|
||||
if (!isXRP(chanFunds) && ctx.tx.isFieldPresent(sfBalance))
|
||||
{
|
||||
if (auto const ret = std::visit(
|
||||
[&]<typename T>(T const&) {
|
||||
return escrowUnlockPreclaimHelper<T>(ctx.view, dest, chanFunds);
|
||||
},
|
||||
chanFunds.asset().value());
|
||||
!isTesSuccess(ret))
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
@@ -116,25 +161,35 @@ PaymentChannelClaim::doApply()
|
||||
|
||||
AccountID const src = (*slep)[sfAccount];
|
||||
AccountID const dst = (*slep)[sfDestination];
|
||||
AccountID const txAccount = ctx_.tx[sfAccount];
|
||||
|
||||
auto const curExpiration = (*slep)[~sfExpiration];
|
||||
if (isChannelExpired(ctx_.view(), (*slep)[~sfCancelAfter]) ||
|
||||
isChannelExpired(ctx_.view(), curExpiration))
|
||||
{
|
||||
return closeChannel(slep, ctx_.view(), k.key, ctx_.registry.get().getJournal("View"));
|
||||
return closeChannel(
|
||||
slep,
|
||||
ctx_.getApplyViewContext(),
|
||||
k.key,
|
||||
accountID_,
|
||||
ctx_.registry.get().getJournal("View"));
|
||||
}
|
||||
|
||||
if (txAccount != src && txAccount != dst)
|
||||
if (accountID_ != src && accountID_ != dst)
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
if (ctx_.tx[~sfBalance])
|
||||
{
|
||||
auto const chanBalance = slep->getFieldAmount(sfBalance).xrp();
|
||||
auto const chanFunds = slep->getFieldAmount(sfAmount).xrp();
|
||||
auto const reqBalance = ctx_.tx[sfBalance].xrp();
|
||||
auto const chanBalance = slep->getFieldAmount(sfBalance);
|
||||
auto const chanFunds = slep->getFieldAmount(sfAmount);
|
||||
auto const reqBalance = ctx_.tx[sfBalance];
|
||||
|
||||
if (txAccount == dst && !ctx_.tx[~sfSignature])
|
||||
// Asset consistency between the requested balance/amount and the
|
||||
// channel was validated in preclaim.
|
||||
XRPL_ASSERT(
|
||||
reqBalance.asset() == chanFunds.asset(),
|
||||
"xrpl::PaymentChannelClaim::doApply : balance matches channel asset");
|
||||
|
||||
if (accountID_ == dst && !ctx_.tx[~sfSignature])
|
||||
{
|
||||
return ctx_.view().rules().enabled(fixCleanup3_2_0) ? TER{tecNO_PERMISSION}
|
||||
: TER{temBAD_SIGNATURE};
|
||||
@@ -164,22 +219,56 @@ PaymentChannelClaim::doApply()
|
||||
return tecNO_DST;
|
||||
|
||||
if (auto err =
|
||||
verifyDepositPreauth(ctx_.tx, ctx_.view(), txAccount, dst, sled, ctx_.journal);
|
||||
verifyDepositPreauth(ctx_.tx, ctx_.view(), accountID_, dst, sled, ctx_.journal);
|
||||
!isTesSuccess(err))
|
||||
return err;
|
||||
|
||||
(*slep)[sfBalance] = ctx_.tx[sfBalance];
|
||||
XRPAmount const reqDelta = reqBalance - chanBalance;
|
||||
STAmount const reqDelta = reqBalance - chanBalance;
|
||||
XRPL_ASSERT(
|
||||
reqDelta >= beast::kZero, "xrpl::PaymentChannelClaim::doApply : minimum balance delta");
|
||||
(*sled)[sfBalance] = (*sled)[sfBalance] + reqDelta;
|
||||
|
||||
// Transfer amount to destination
|
||||
if (isXRP(reqDelta))
|
||||
{
|
||||
(*sled)[sfBalance] = (*sled)[sfBalance] + reqDelta;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!ctx_.view().rules().enabled(featureTokenPaychan))
|
||||
return temDISABLED;
|
||||
|
||||
Rate lockedRate = slep->isFieldPresent(sfTransferRate)
|
||||
? xrpl::Rate(slep->getFieldU32(sfTransferRate))
|
||||
: kParityRate;
|
||||
auto const& issuer = reqDelta.getIssuer();
|
||||
bool const createAsset = dst == accountID_;
|
||||
if (auto const ret = std::visit(
|
||||
[&]<typename T>(T const&) {
|
||||
return escrowUnlockApplyHelper<T>(
|
||||
ctx_.getApplyViewContext(),
|
||||
lockedRate,
|
||||
sled,
|
||||
preFeeBalance_,
|
||||
reqDelta,
|
||||
issuer,
|
||||
src,
|
||||
dst,
|
||||
createAsset,
|
||||
j_);
|
||||
},
|
||||
reqDelta.asset().value());
|
||||
!isTesSuccess(ret))
|
||||
return ret;
|
||||
}
|
||||
|
||||
ctx_.view().update(sled);
|
||||
ctx_.view().update(slep);
|
||||
}
|
||||
|
||||
if (ctx_.tx.isFlag(tfRenew))
|
||||
{
|
||||
if (src != txAccount)
|
||||
if (src != accountID_)
|
||||
return tecNO_PERMISSION;
|
||||
(*slep)[~sfExpiration] = std::nullopt;
|
||||
ctx_.view().update(slep);
|
||||
@@ -188,8 +277,15 @@ PaymentChannelClaim::doApply()
|
||||
if (ctx_.tx.isFlag(tfClose))
|
||||
{
|
||||
// Channel will close immediately if dry or the receiver closes
|
||||
if (dst == txAccount || (*slep)[sfBalance] == (*slep)[sfAmount])
|
||||
return closeChannel(slep, ctx_.view(), k.key, ctx_.registry.get().getJournal("View"));
|
||||
if (dst == accountID_ || (*slep)[sfBalance] == (*slep)[sfAmount])
|
||||
{
|
||||
return closeChannel(
|
||||
slep,
|
||||
ctx_.getApplyViewContext(),
|
||||
k.key,
|
||||
accountID_,
|
||||
ctx_.registry.get().getJournal("View"));
|
||||
}
|
||||
|
||||
auto const settleExpiration = saturatingAdd(
|
||||
ctx_.view().rules(),
|
||||
|
||||
@@ -1,18 +1,25 @@
|
||||
#include <xrpl/tx/transactors/payment_channel/PaymentChannelCreate.h>
|
||||
|
||||
#include <xrpl/basics/chrono.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/beast/utility/Zero.h>
|
||||
#include <xrpl/core/ServiceRegistry.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
|
||||
#include <xrpl/ledger/helpers/EscrowHelpers.h>
|
||||
#include <xrpl/ledger/helpers/PaymentChannelHelpers.h>
|
||||
#include <xrpl/ledger/helpers/SponsorHelpers.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/protocol/Concepts.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/Keylet.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
#include <xrpl/protocol/MPTIssue.h>
|
||||
#include <xrpl/protocol/PublicKey.h>
|
||||
#include <xrpl/protocol/Rate.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
@@ -23,6 +30,7 @@
|
||||
#include <xrpl/tx/applySteps.h>
|
||||
|
||||
#include <memory>
|
||||
#include <variant>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
@@ -51,14 +59,40 @@ namespace xrpl {
|
||||
TxConsequences
|
||||
PaymentChannelCreate::makeTxConsequences(PreflightContext const& ctx)
|
||||
{
|
||||
return TxConsequences{ctx.tx, ctx.tx[sfAmount].xrp()};
|
||||
return TxConsequences{ctx.tx, isXRP(ctx.tx[sfAmount]) ? ctx.tx[sfAmount].xrp() : beast::kZero};
|
||||
}
|
||||
|
||||
bool
|
||||
PaymentChannelCreate::checkExtraFeatures(PreflightContext const& ctx)
|
||||
{
|
||||
// Only require featureMPTokensV1 when the channel amount is an MPT and
|
||||
// fixCleanup3_2_0 is active; XRP/IOU channels are unaffected by this gate.
|
||||
if (ctx.rules.enabled(fixCleanup3_2_0) && ctx.tx[sfAmount].holds<MPTIssue>())
|
||||
return ctx.rules.enabled(featureMPTokensV1);
|
||||
return true;
|
||||
}
|
||||
|
||||
NotTEC
|
||||
PaymentChannelCreate::preflight(PreflightContext const& ctx)
|
||||
{
|
||||
if (!isXRP(ctx.tx[sfAmount]) || (ctx.tx[sfAmount] <= beast::kZero))
|
||||
auto const amount = ctx.tx[sfAmount];
|
||||
if (!isXRP(amount))
|
||||
{
|
||||
if (!ctx.rules.enabled(featureTokenPaychan))
|
||||
return temBAD_AMOUNT;
|
||||
|
||||
if (auto const ret = std::visit(
|
||||
[&]<typename T>(T const&) {
|
||||
return payChanAmountPreflightHelper<T>(ctx.rules, amount);
|
||||
},
|
||||
amount.asset().value());
|
||||
!isTesSuccess(ret))
|
||||
return ret;
|
||||
}
|
||||
else if (amount <= beast::kZero)
|
||||
{
|
||||
return temBAD_AMOUNT;
|
||||
}
|
||||
|
||||
if (ctx.tx[sfAccount] == ctx.tx[sfDestination])
|
||||
return temDST_IS_SRC;
|
||||
@@ -77,6 +111,8 @@ PaymentChannelCreate::preclaim(PreclaimContext const& ctx)
|
||||
if (!sle)
|
||||
return terNO_ACCOUNT;
|
||||
|
||||
auto const amount = ctx.tx[sfAmount];
|
||||
|
||||
// Check reserve and funds availability
|
||||
if (!ctx.view.rules().enabled(featureSponsor))
|
||||
{
|
||||
@@ -86,15 +122,15 @@ PaymentChannelCreate::preclaim(PreclaimContext const& ctx)
|
||||
if (balance < reserve)
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
|
||||
if (balance < reserve + ctx.tx[sfAmount])
|
||||
if (isXRP(amount) && balance < reserve + ctx.tx[sfAmount])
|
||||
return tecUNFUNDED;
|
||||
}
|
||||
|
||||
auto const dst = ctx.tx[sfDestination];
|
||||
auto const dest = ctx.tx[sfDestination];
|
||||
|
||||
{
|
||||
// Check destination account
|
||||
auto const sled = ctx.view.read(keylet::account(dst));
|
||||
auto const sled = ctx.view.read(keylet::account(dest));
|
||||
if (!sled)
|
||||
return tecNO_DST;
|
||||
|
||||
@@ -115,6 +151,17 @@ PaymentChannelCreate::preclaim(PreclaimContext const& ctx)
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
|
||||
if (!isXRP(amount))
|
||||
{
|
||||
if (auto const ret = std::visit(
|
||||
[&]<typename T>(T const&) {
|
||||
return escrowLockPreclaimHelper<T>(ctx.view, account, dest, amount, ctx.j);
|
||||
},
|
||||
amount.asset().value());
|
||||
!isTesSuccess(ret))
|
||||
return ret;
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
@@ -122,6 +169,7 @@ TER
|
||||
PaymentChannelCreate::doApply()
|
||||
{
|
||||
auto const account = ctx_.tx[sfAccount];
|
||||
auto const amount = ctx_.tx[sfAmount];
|
||||
auto const sle = ctx_.view().peek(keylet::account(account));
|
||||
if (!sle)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
@@ -159,25 +207,28 @@ PaymentChannelCreate::doApply()
|
||||
// - unsponsored: 1 — source owes reserve including the new increment.
|
||||
auto const sourceReserve = accountReserve(
|
||||
ctx_.view(), sle, j_, {.ownerCountDelta = getTxReserveSponsorID(ctx_.tx) ? 0 : 1});
|
||||
if (preFeeBalance_ - ctx_.tx[sfAmount].xrp() < sourceReserve)
|
||||
// Token channels lock no XRP, so only a native amount reduces the
|
||||
// source's spendable balance.
|
||||
auto const lockedXrp = isXRP(amount) ? amount.xrp() : XRPAmount{0};
|
||||
if (preFeeBalance_ - lockedXrp < sourceReserve)
|
||||
return tecUNFUNDED;
|
||||
}
|
||||
|
||||
auto const dst = ctx_.tx[sfDestination];
|
||||
auto const dest = ctx_.tx[sfDestination];
|
||||
|
||||
// Create PayChan in ledger.
|
||||
//
|
||||
// Note that we use the value from the sequence or ticket as the
|
||||
// payChan sequence. For more explanation see comments in SeqProxy.h.
|
||||
Keylet const payChanKeylet = keylet::payChannel(account, dst, ctx_.tx.getSeqProxy());
|
||||
Keylet const payChanKeylet = keylet::payChannel(account, dest, ctx_.tx.getSeqProxy());
|
||||
auto const slep = std::make_shared<SLE>(payChanKeylet);
|
||||
|
||||
// Funds held in this channel
|
||||
(*slep)[sfAmount] = ctx_.tx[sfAmount];
|
||||
(*slep)[sfAmount] = amount;
|
||||
// Amount channel has already paid
|
||||
(*slep)[sfBalance] = ctx_.tx[sfAmount].zeroed();
|
||||
(*slep)[sfBalance] = amount.zeroed();
|
||||
(*slep)[sfAccount] = account;
|
||||
(*slep)[sfDestination] = dst;
|
||||
(*slep)[sfDestination] = dest;
|
||||
(*slep)[sfSettleDelay] = ctx_.tx[sfSettleDelay];
|
||||
(*slep)[sfPublicKey] = ctx_.tx[sfPublicKey];
|
||||
(*slep)[~sfCancelAfter] = ctx_.tx[~sfCancelAfter];
|
||||
@@ -188,6 +239,13 @@ PaymentChannelCreate::doApply()
|
||||
(*slep)[sfSequence] = ctx_.tx.getSeqProxy().value();
|
||||
}
|
||||
|
||||
if (ctx_.view().rules().enabled(featureTokenPaychan) && !isXRP(amount))
|
||||
{
|
||||
auto const xferRate = transferRate(ctx_.view(), amount);
|
||||
if (xferRate != kParityRate)
|
||||
(*slep)[sfTransferRate] = xferRate.value;
|
||||
}
|
||||
|
||||
ctx_.view().insert(slep);
|
||||
|
||||
// Add PayChan to owner directory
|
||||
@@ -202,14 +260,39 @@ PaymentChannelCreate::doApply()
|
||||
// Add PayChan to the recipient's owner directory
|
||||
{
|
||||
auto const page =
|
||||
ctx_.view().dirInsert(keylet::ownerDir(dst), payChanKeylet, describeOwnerDir(dst));
|
||||
ctx_.view().dirInsert(keylet::ownerDir(dest), payChanKeylet, describeOwnerDir(dest));
|
||||
if (!page)
|
||||
return tecDIR_FULL; // LCOV_EXCL_LINE
|
||||
(*slep)[sfDestinationNode] = *page;
|
||||
}
|
||||
|
||||
// Add PayChan to the issuer's owner directory, if applicable
|
||||
auto const& issuer = amount.getIssuer();
|
||||
if (!isXRP(amount) && issuer != accountID_ && issuer != dest && !amount.holds<MPTIssue>())
|
||||
{
|
||||
auto page = ctx_.view().dirInsert(
|
||||
keylet::ownerDir(issuer), payChanKeylet, describeOwnerDir(issuer));
|
||||
if (!page)
|
||||
return tecDIR_FULL; // LCOV_EXCL_LINE
|
||||
(*slep)[sfIssuerNode] = *page;
|
||||
}
|
||||
|
||||
// Deduct owner's balance, increment owner count
|
||||
(*sle)[sfBalance] = (*sle)[sfBalance] - ctx_.tx[sfAmount];
|
||||
if (isXRP(amount))
|
||||
{
|
||||
(*sle)[sfBalance] = (*sle)[sfBalance] - amount;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (auto const ret = std::visit(
|
||||
[&]<typename T>(T const&) {
|
||||
return escrowLockApplyHelper<T>(ctx_.view(), issuer, accountID_, amount, j_);
|
||||
},
|
||||
amount.asset().value());
|
||||
!isTesSuccess(ret))
|
||||
return ret;
|
||||
}
|
||||
|
||||
increaseOwnerCount(ctx_.getApplyViewContext(), sle, 1, ctx_.journal);
|
||||
addSponsorToLedgerEntry(ctx_.getApplyViewContext(), slep);
|
||||
ctx_.view().update(sle);
|
||||
|
||||
@@ -2,15 +2,19 @@
|
||||
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/beast/utility/Zero.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/EscrowHelpers.h>
|
||||
#include <xrpl/ledger/helpers/PaymentChannelHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Concepts.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/Keylet.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
#include <xrpl/protocol/MPTIssue.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
@@ -20,12 +24,25 @@
|
||||
#include <xrpl/tx/Transactor.h>
|
||||
#include <xrpl/tx/applySteps.h>
|
||||
|
||||
#include <memory>
|
||||
#include <variant>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
TxConsequences
|
||||
PaymentChannelFund::makeTxConsequences(PreflightContext const& ctx)
|
||||
{
|
||||
return TxConsequences{ctx.tx, ctx.tx[sfAmount].xrp()};
|
||||
return TxConsequences{ctx.tx, isXRP(ctx.tx[sfAmount]) ? ctx.tx[sfAmount].xrp() : beast::kZero};
|
||||
}
|
||||
|
||||
bool
|
||||
PaymentChannelFund::checkExtraFeatures(PreflightContext const& ctx)
|
||||
{
|
||||
// Only require featureMPTokensV1 when the funding amount is an MPT and
|
||||
// fixCleanup3_2_0 is active; XRP/IOU channels are unaffected by this gate.
|
||||
if (ctx.rules.enabled(fixCleanup3_2_0) && ctx.tx[sfAmount].holds<MPTIssue>())
|
||||
return ctx.rules.enabled(featureMPTokensV1);
|
||||
return true;
|
||||
}
|
||||
|
||||
NotTEC
|
||||
@@ -34,8 +51,43 @@ PaymentChannelFund::preflight(PreflightContext const& ctx)
|
||||
if (ctx.rules.enabled(fixCleanup3_2_0) && ctx.tx[sfChannel] == beast::kZero)
|
||||
return temMALFORMED;
|
||||
|
||||
if (!isXRP(ctx.tx[sfAmount]) || (ctx.tx[sfAmount] <= beast::kZero))
|
||||
auto const amount = ctx.tx[sfAmount];
|
||||
if (!isXRP(amount))
|
||||
{
|
||||
if (!ctx.rules.enabled(featureTokenPaychan))
|
||||
return temBAD_AMOUNT;
|
||||
|
||||
if (auto const ret = std::visit(
|
||||
[&]<typename T>(T const&) {
|
||||
return payChanAmountPreflightHelper<T>(ctx.rules, amount);
|
||||
},
|
||||
amount.asset().value());
|
||||
!isTesSuccess(ret))
|
||||
return ret;
|
||||
}
|
||||
else if (amount <= beast::kZero)
|
||||
{
|
||||
return temBAD_AMOUNT;
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
PaymentChannelFund::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
if (ctx.view.rules().enabled(featureTokenPaychan))
|
||||
{
|
||||
Keylet const k{ltPAYCHAN, ctx.tx[sfChannel]};
|
||||
auto const slep = ctx.view.read(k);
|
||||
if (!slep)
|
||||
return tecNO_ENTRY;
|
||||
|
||||
// The funded asset must match the channel's asset; otherwise the
|
||||
// STAmount accumulation in doApply would throw on mismatched issues.
|
||||
if (ctx.tx[sfAmount].asset() != (*slep)[sfAmount].asset())
|
||||
return tecWRONG_ASSET;
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
@@ -49,16 +101,21 @@ PaymentChannelFund::doApply()
|
||||
return tecNO_ENTRY;
|
||||
|
||||
AccountID const src = (*slep)[sfAccount];
|
||||
auto const txAccount = ctx_.tx[sfAccount];
|
||||
AccountID const dst = (*slep)[sfDestination];
|
||||
auto const curExpiration = (*slep)[~sfExpiration];
|
||||
|
||||
if (isChannelExpired(ctx_.view(), (*slep)[~sfCancelAfter]) ||
|
||||
isChannelExpired(ctx_.view(), curExpiration))
|
||||
{
|
||||
return closeChannel(slep, ctx_.view(), k.key, ctx_.registry.get().getJournal("View"));
|
||||
return closeChannel(
|
||||
slep,
|
||||
ctx_.getApplyViewContext(),
|
||||
k.key,
|
||||
accountID_,
|
||||
ctx_.registry.get().getJournal("View"));
|
||||
}
|
||||
|
||||
if (src != txAccount)
|
||||
if (src != accountID_)
|
||||
{
|
||||
// only the owner can add funds or extend
|
||||
return tecNO_PERMISSION;
|
||||
@@ -82,10 +139,19 @@ PaymentChannelFund::doApply()
|
||||
ctx_.view().update(slep);
|
||||
}
|
||||
|
||||
auto const sle = ctx_.view().peek(keylet::account(txAccount));
|
||||
auto const sle = ctx_.view().peek(keylet::account(accountID_));
|
||||
if (!sle)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const amount = ctx_.tx[sfAmount];
|
||||
auto const& chanAmt = slep->getFieldAmount(sfAmount);
|
||||
|
||||
// Asset consistency between the funded amount and the channel was
|
||||
// validated in preclaim.
|
||||
XRPL_ASSERT(
|
||||
amount.asset() == chanAmt.asset(),
|
||||
"xrpl::PaymentChannelFund::doApply : amount matches channel asset");
|
||||
|
||||
{
|
||||
// Check reserve and funds availability
|
||||
STAmount const balance = (*sle)[sfBalance];
|
||||
@@ -99,20 +165,56 @@ PaymentChannelFund::doApply()
|
||||
// when a sponsor is present and would ignore the source's post-lock
|
||||
// balance entirely. Funding an existing channel adds no owned object,
|
||||
// so there is no owner-count delta.
|
||||
if (balance < accountReserve(ctx_.view(), sle, j_) + ctx_.tx[sfAmount])
|
||||
if (isXRP(amount) && balance < accountReserve(ctx_.view(), sle, j_) + amount)
|
||||
return tecUNFUNDED;
|
||||
}
|
||||
|
||||
// do not allow adding funds if dst does not exist
|
||||
if (AccountID const dst = (*slep)[sfDestination]; !ctx_.view().read(keylet::account(dst)))
|
||||
if (!ctx_.view().read(keylet::account(dst)))
|
||||
{
|
||||
return tecNO_DST;
|
||||
}
|
||||
|
||||
(*slep)[sfAmount] = (*slep)[sfAmount] + ctx_.tx[sfAmount];
|
||||
ctx_.view().update(slep);
|
||||
if (!isXRP(amount))
|
||||
{
|
||||
// Funding is subject to the same issuer controls (locking opt-in,
|
||||
// authorization, freeze/lock, transferability, spendable balance)
|
||||
// as channel creation.
|
||||
if (auto const ret = std::visit(
|
||||
[&]<typename T>(T const&) {
|
||||
return escrowLockPreclaimHelper<T>(ctx_.view(), accountID_, dst, amount, j_);
|
||||
},
|
||||
amount.asset().value());
|
||||
!isTesSuccess(ret))
|
||||
return ret;
|
||||
|
||||
(*sle)[sfBalance] = (*sle)[sfBalance] - ctx_.tx[sfAmount];
|
||||
// Guard the channel-amount accumulation itself: a small IOU amount
|
||||
// added to a much larger channel amount would be rounded away below
|
||||
// after the source had already been debited. MPT amounts are exact
|
||||
// integers bounded by the outstanding supply, so only IOUs can lose
|
||||
// precision here.
|
||||
if (amount.holds<Issue>() && !canAdd(chanAmt, amount))
|
||||
return tecPRECISION_LOSS;
|
||||
}
|
||||
|
||||
if (isXRP(amount))
|
||||
{
|
||||
(*sle)[sfBalance] = (*sle)[sfBalance] - amount;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto const& issuer = amount.getIssuer();
|
||||
if (auto const ret = std::visit(
|
||||
[&]<typename T>(T const&) {
|
||||
return escrowLockApplyHelper<T>(ctx_.view(), issuer, accountID_, amount, j_);
|
||||
},
|
||||
amount.asset().value());
|
||||
!isTesSuccess(ret))
|
||||
return ret;
|
||||
}
|
||||
|
||||
(*slep)[sfAmount] = chanAmt + amount;
|
||||
ctx_.view().update(slep);
|
||||
ctx_.view().update(sle);
|
||||
|
||||
return tesSUCCESS;
|
||||
|
||||
@@ -123,6 +123,12 @@ Change::preclaim(PreclaimContext const& ctx)
|
||||
ctx.tx.isFieldPresent(sfReserveIncrementDrops))
|
||||
return temDISABLED;
|
||||
}
|
||||
// The ttFEE transaction format defines these fields as optional,
|
||||
// but they are unconditionally forbidden until FeeVoteImpl is
|
||||
// updated to populate them (SmartEscrow behavioral port).
|
||||
if (ctx.tx.isFieldPresent(sfGasLimit) || ctx.tx.isFieldPresent(sfBytecodeSizeLimit) ||
|
||||
ctx.tx.isFieldPresent(sfGasPrice))
|
||||
return temDISABLED;
|
||||
return tesSUCCESS;
|
||||
case ttAMENDMENT:
|
||||
case ttUNL_MODIFY:
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user