mirror of
https://github.com/XRPLF/rippled.git
synced 2026-10-22 11:37:07 +00:00
Compare commits
26
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b707bac74f | ||
|
|
da2b5d5eaf | ||
|
|
4de4902200 | ||
|
|
2ea1f09dcb | ||
|
|
68b4b894de | ||
|
|
defd83e5a6 | ||
|
|
452e31a05f | ||
|
|
1798262d7a | ||
|
|
cd633b9ac9 | ||
|
|
5c5f7d315a | ||
|
|
0f7493ce50 | ||
|
|
40f61f828a | ||
|
|
108277f4b7 | ||
|
|
651bb207b4 | ||
|
|
c45363fd8b | ||
|
|
f1744cb76e | ||
|
|
bcbaa4df07 | ||
|
|
3cd357949c | ||
|
|
a9027bb997 | ||
|
|
0a6da4de74 | ||
|
|
6a05339c6c | ||
|
|
0c41a87604 | ||
|
|
f5938e4097 | ||
|
|
c7d10c1b60 | ||
|
|
84e2a155b9 | ||
|
|
cbade49976 |
No files matched your search
@@ -255,6 +255,7 @@ words:
|
||||
- queuable
|
||||
- Raphson
|
||||
- rcflags
|
||||
- reencrypted
|
||||
- replayer
|
||||
- repodata
|
||||
- repomd
|
||||
|
||||
@@ -8,3 +8,7 @@ watch_file rust-toolchain.toml
|
||||
watch_dir conan
|
||||
|
||||
use flake
|
||||
|
||||
# Optional, untracked local overrides. To use a different shell, put e.g.
|
||||
# `use flake .#formal-verification` in .envrc.local.
|
||||
source_env_if_exists .envrc.local
|
||||
@@ -5,6 +5,18 @@
|
||||
# This file is sorted in reverse chronological order, with the most recent commits at the top.
|
||||
# The commits listed here are ignored by git blame, which is useful for formatting-only commits that would otherwise obscure the history of changes to a file.
|
||||
|
||||
# chore: CamelCase for typedef/using in `clang-tidy` (#8177)
|
||||
97a1824537d20d82d25bf151a37a7a4532ebf085
|
||||
# chore: Rename CamelCase namespaces to snake_case (#7933)
|
||||
06488c1318d96f56d0536251bee08ac85fa7fdd3
|
||||
# style: Unify style for all Doxygen comments (#7776)
|
||||
73b6852a122854140336e6e6bc30a3a4b41aa5fd
|
||||
# style: More clang-tidy identifier renaming (#7290)
|
||||
a830ab10efed8d3e59ef2fc15d66efdf9c6bb0d8
|
||||
# refactor: Rename static constants (#7120)
|
||||
5b6e8b6f93b19c1e3f6a3467a25639031d9d9a53
|
||||
# chore: More fixes for bad renames (#7092)
|
||||
7afdd71a54d562b32a50b29a5aa00bb997dc9053
|
||||
# refactor: Enable clang-tidy `readability-identifier-naming` check (#6571)
|
||||
8995564ed6b9e453e144bb663303072a3c1ba305
|
||||
# refactor: Enable remaining clang-tidy `cppcoreguidelines` checks (#6538)
|
||||
|
||||
@@ -36,4 +36,4 @@ runs:
|
||||
|
||||
- name: Determine release channel and package release
|
||||
id: release_info
|
||||
uses: XRPLF/actions/release-info@ebcf6cea14eee258697308a51fea55cad777581b
|
||||
uses: XRPLF/actions/release-info@a9f2eeca6fb3980ba3a84cf68566f1c69ad30674
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Exit the script as soon as an error occurs.
|
||||
set -euo pipefail
|
||||
|
||||
# This script fails if <head> merges a release back into <base>,
|
||||
# but also adds commits that aren't on a release or staging branch, see RELEASING.md.
|
||||
# Merge commits are allowed.
|
||||
# Usage: .github/scripts/releasing/check-merge-back-commits.sh <base> <head>
|
||||
|
||||
if [ "$#" -ne 2 ]; then
|
||||
echo "Usage: $0 <base> <head>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
BASE=$1
|
||||
HEAD=$2
|
||||
|
||||
SCRIPT_DIR=$(dirname "${BASH_SOURCE[0]}")
|
||||
# shellcheck source=.github/scripts/releasing/common.sh
|
||||
source "${SCRIPT_DIR}/common.sh"
|
||||
load_release_branches
|
||||
|
||||
# A PR is a merge-back if some of its commits are on a release or staging branch.
|
||||
PR_COUNT=$(git rev-list --no-merges --count "${BASE}..${HEAD}")
|
||||
NEW_COUNT=$(git rev-list --no-merges --count "${BASE}..${HEAD}" --not "${BRANCHES[@]}")
|
||||
if ((NEW_COUNT == PR_COUNT)); then
|
||||
echo "This PR doesn't merge a release back."
|
||||
exit 0
|
||||
fi
|
||||
if ((NEW_COUNT == 0)); then
|
||||
echo "This merge-back adds no commits of its own."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "This PR merges a release back, but also adds commits that aren't on a release or staging branch:"
|
||||
git log --no-merges --format=' %h %s' "${BASE}..${HEAD}" --not "${BRANCHES[@]}"
|
||||
echo
|
||||
echo "Make these changes in a separate PR, see RELEASING.md."
|
||||
exit 1
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Exit the script as soon as an error occurs.
|
||||
set -euo pipefail
|
||||
@@ -21,11 +21,10 @@ patch_ids() {
|
||||
git log --no-merges --patch --no-color --no-ext-diff "$@" | git patch-id --stable | sort
|
||||
}
|
||||
|
||||
mapfile -t BRANCHES < <(git for-each-ref --format='%(refname)' 'refs/remotes/*/release/*' 'refs/remotes/*/staging/*')
|
||||
if [ "${#BRANCHES[@]}" -eq 0 ]; then
|
||||
echo "Error: No release or staging branches found."
|
||||
exit 1
|
||||
fi
|
||||
SCRIPT_DIR=$(dirname "${BASH_SOURCE[0]}")
|
||||
# shellcheck source=.github/scripts/releasing/common.sh
|
||||
source "${SCRIPT_DIR}/common.sh"
|
||||
load_release_branches
|
||||
|
||||
# Each line is "<patch-id> <commit>".
|
||||
RELEASE_PATCHES=$(patch_ids "${BRANCHES[@]}" --not "${HEAD}")
|
||||
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Exit the script as soon as an error occurs.
|
||||
set -euo pipefail
|
||||
|
||||
# This script fails if <head> contains release or staging commits that <base> does not,
|
||||
# i.e. if <head> merges a release back into <base>.
|
||||
# Used in the merge queue, which squashes PRs and would drop the merge commit, see RELEASING.md.
|
||||
# Usage: .github/scripts/releasing/check-no-merge-back.sh <base> <head>
|
||||
|
||||
if [ "$#" -ne 2 ]; then
|
||||
echo "Usage: $0 <base> <head>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
BASE=$1
|
||||
HEAD=$2
|
||||
|
||||
SCRIPT_DIR=$(dirname "${BASH_SOURCE[0]}")
|
||||
# shellcheck source=.github/scripts/releasing/common.sh
|
||||
source "${SCRIPT_DIR}/common.sh"
|
||||
load_release_branches
|
||||
|
||||
RELEASE_COMMITS=$(git rev-list "${BRANCHES[@]}" --not "${BASE}")
|
||||
HEAD_COMMITS=$(git rev-list "${BASE}..${HEAD}")
|
||||
# The release commits in <head>, newest first.
|
||||
MERGED=$(grep -xF -f <(echo "${RELEASE_COMMITS}") <<<"${HEAD_COMMITS}" || true)
|
||||
if [ -z "${MERGED}" ]; then
|
||||
echo "No release commits are merged back."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "This PR merges $(wc -l <<<"${MERGED}" | tr -d ' ') release commits back, e.g.:"
|
||||
head -5 <<<"${MERGED}" | xargs git log --no-walk --format=' %h %s'
|
||||
echo
|
||||
echo "Merge-backs must not go through the merge queue, which squashes them."
|
||||
echo "Fast-forward develop to the PR branch instead, see RELEASING.md."
|
||||
exit 1
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Exit the script as soon as an error occurs.
|
||||
set -euo pipefail
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
# shellcheck shell=bash
|
||||
|
||||
# Helpers shared by the release checks in this directory, see RELEASING.md.
|
||||
|
||||
# Sets BRANCHES to all release and staging branches, and fails if there are none.
|
||||
load_release_branches() {
|
||||
mapfile -t BRANCHES < <(git for-each-ref --format='%(refname)' 'refs/remotes/*/release/*' 'refs/remotes/*/staging/*')
|
||||
if [ "${#BRANCHES[@]}" -eq 0 ]; then
|
||||
echo "Error: No release or staging branches found."
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
@@ -166,10 +166,31 @@ jobs:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
- name: Check for copied release commits
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
env:
|
||||
BASE: ${{ github.event.pull_request.base.sha || github.event.merge_group.base_sha }}
|
||||
HEAD: ${{ github.event.pull_request.head.sha || github.event.merge_group.head_sha }}
|
||||
BASE: ${{ github.event.pull_request.base.sha }}
|
||||
HEAD: ${{ github.event.pull_request.head.sha }}
|
||||
run: .github/scripts/releasing/check-no-copied-release-commits.sh "${BASE}" "${HEAD}"
|
||||
# Runs even if the previous check fails, so that both problems are reported at once.
|
||||
- name: Check merge-back has no new commits
|
||||
if: ${{ !cancelled() && github.event_name == 'pull_request' }}
|
||||
env:
|
||||
BASE: ${{ github.event.pull_request.base.sha }}
|
||||
HEAD: ${{ github.event.pull_request.head.sha }}
|
||||
run: .github/scripts/releasing/check-merge-back-commits.sh "${BASE}" "${HEAD}"
|
||||
# The queue squashes PRs, so check the PR's own branch, named in the queue branch.
|
||||
- name: Check the merge queue doesn't merge a release back
|
||||
if: ${{ github.event_name == 'merge_group' }}
|
||||
env:
|
||||
BASE: ${{ github.event.merge_group.base_sha }}
|
||||
HEAD_REF: ${{ github.event.merge_group.head_ref }}
|
||||
run: |
|
||||
if ! [[ "${HEAD_REF}" =~ /pr-([0-9]+)-[0-9a-f]+$ ]]; then
|
||||
echo "Error: Can't find the PR number in '${HEAD_REF}'."
|
||||
exit 1
|
||||
fi
|
||||
git fetch --no-tags origin "refs/pull/${BASH_REMATCH[1]}/head"
|
||||
.github/scripts/releasing/check-no-merge-back.sh "${BASE}" FETCH_HEAD
|
||||
|
||||
clang-tidy:
|
||||
needs: should-run
|
||||
|
||||
@@ -269,14 +269,23 @@ jobs:
|
||||
env:
|
||||
CONTEXT: image-context
|
||||
IMAGE: ${{ matrix.target == 'voidstar' && 'xrpld-voidstar' || 'xrplf/xrpld' }}:${{ github.ref_type == 'tag' && github.ref_name || 'develop' }}
|
||||
# Docker Hub is public, so a private build never reaches it;
|
||||
# the Antithesis registry is not.
|
||||
PUSH: ${{ inputs.publish && (matrix.target == 'voidstar' || github.event.repository.visibility == 'public') }}
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Determine release info
|
||||
id: release_info
|
||||
uses: ./.github/actions/release-info
|
||||
|
||||
# Docker Hub is public, so it only gets builds whose packages are public:
|
||||
# those of a public codebase, and stable releases.
|
||||
# The Antithesis registry is private, so it gets every build.
|
||||
- name: Decide whether to push
|
||||
env:
|
||||
PUSH: ${{ inputs.publish && (matrix.target == 'voidstar' || github.event.repository.visibility == 'public' || steps.release_info.outputs.channel == 'stable') }}
|
||||
run: echo "PUSH=${PUSH}" | tee -a "${GITHUB_ENV}"
|
||||
|
||||
- name: Download package artifacts
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
|
||||
@@ -92,6 +92,9 @@ target/
|
||||
# Direnv's directory
|
||||
/.direnv
|
||||
|
||||
# Direnv's local, per-developer overrides
|
||||
/.envrc.local
|
||||
|
||||
# clangd cache
|
||||
/.cache
|
||||
|
||||
|
||||
@@ -22,6 +22,19 @@ 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.
|
||||
|
||||
## XRP Ledger server version 3.5.0
|
||||
|
||||
Version 3.5.0 is not yet released.
|
||||
|
||||
### Additions in 3.5.0
|
||||
|
||||
- `subscribe`, `unsubscribe`: Added an optional `mpt_issuances` request field, an array of MPT issuance IDs (hex strings). Subscribers receive a message with `type` `mptTransaction` for each validated transaction whose metadata affects a subscribed issuance; the message has the same fields as the `transactions` stream. MPT issuance subscriptions count toward the per-connection subscription limit. An empty array, a non-array value, or an invalid ID returns `invalidParams`. ([#5671](https://github.com/XRPLF/rippled/pull/5671))
|
||||
|
||||
### Bugfixes in 3.5.0
|
||||
|
||||
- `channel_authorize`: The `channel_id` field now returns an `invalidParams` error if the value is not a string. [#7582](https://github.com/XRPLF/rippled/pull/7582)
|
||||
- `channel_verify`: The `channel_id` and `signature` fields now return an `invalidParams` error if the value is not a string. [#7582](https://github.com/XRPLF/rippled/pull/7582)
|
||||
|
||||
## XRP Ledger server version 3.4.0
|
||||
|
||||
Version 3.4.0 is not yet released. These changes are available in the 3.4.0 beta releases.
|
||||
|
||||
@@ -41,15 +41,13 @@ branch.
|
||||
git checkout develop
|
||||
```
|
||||
|
||||
For a release candidate, choose the relevant release branch, e.g.
|
||||
`release/3.2.x`.
|
||||
For a release or release candidate, check out its [tag](https://github.com/XRPLF/rippled/releases), e.g.:
|
||||
|
||||
```bash
|
||||
git checkout release/3.2.x
|
||||
git checkout 3.4.0
|
||||
```
|
||||
|
||||
For a stable release, choose one of the [tagged
|
||||
releases](https://github.com/XRPLF/rippled/releases).
|
||||
See [RELEASING.md](./RELEASING.md) for how branches and releases are organized.
|
||||
|
||||
A build reports `0.0.0-dev` with its short commit hash as build metadata, e.g.
|
||||
`0.0.0-dev+0123abc`. Only builds of a release, from a tag in CI or a versioned
|
||||
|
||||
+20
-586
@@ -14,9 +14,13 @@ The following branches exist in the main project repository:
|
||||
|
||||
- `develop`: The latest set of unreleased features, and the most common
|
||||
starting point for contributions.
|
||||
- `release/*` (e.g. `release/3.2.x`): Release branches, one per release line,
|
||||
holding the latest release candidate, or stable release for that line.
|
||||
Stable releases are published as [tagged releases](https://github.com/XRPLF/rippled/releases).
|
||||
- `staging/*` (e.g. `staging/3.4.x`): Staging branches, one per release line,
|
||||
where fixes for that line are developed.
|
||||
- `release/*` (e.g. `release/3.4.x`): Release branches, one per release line,
|
||||
holding the latest public release candidate or release for that line.
|
||||
Releases are published as [tagged releases](https://github.com/XRPLF/rippled/releases).
|
||||
|
||||
See [RELEASING.md](./RELEASING.md) for how these branches are used.
|
||||
|
||||
The tip of each branch must be signed. In order for GitHub to sign a
|
||||
squashed commit that it builds from your pull request, GitHub must know
|
||||
@@ -145,8 +149,8 @@ tl;dr
|
||||
|
||||
In general, pull requests use `develop` as the base branch.
|
||||
|
||||
The exceptions are fixes, improvements, and hotfixes for an existing release,
|
||||
which use that release's branch (e.g. `release/3.2.x`) as the base.
|
||||
The exceptions are fixes for an existing release line,
|
||||
which use that line's staging branch (e.g. `staging/3.4.x`) as the base.
|
||||
|
||||
If your changes are not quite ready, but you want to make it easily available
|
||||
for preliminary examination or review, you can create a "Draft" pull request.
|
||||
@@ -591,15 +595,16 @@ the suggested commit message, or modify it as needed.
|
||||
|
||||
#### Slightly more complicated pull requests
|
||||
|
||||
Some pull requests need to be pushed to `develop` as more than one
|
||||
commit. A PR author may _request_ to merge as separate commits. They
|
||||
Some pull requests need to be pushed to their base branch (usually `develop`)
|
||||
as more than one commit.
|
||||
A PR author may _request_ to merge as separate commits. They
|
||||
must _justify_ why separate commits are needed, and _specify_ how they
|
||||
would like the commits to be merged. If you disagree with the author,
|
||||
discuss it with them directly.
|
||||
|
||||
If the process is reasonable, follow it. The simplest option is to do a
|
||||
fast forward only merge (`--ff-only`) on the command line and push to
|
||||
`develop`.
|
||||
fast forward only merge (`--ff-only`) on the command line
|
||||
and push to the base branch.
|
||||
|
||||
Some examples of when separate commits are worthwhile are:
|
||||
|
||||
@@ -612,9 +617,10 @@ Some examples of when separate commits are worthwhile are:
|
||||
|
||||
Either way, check that:
|
||||
|
||||
- The commits are based on the current tip of `develop`.
|
||||
- The commits are clean: No merge commits (except when reverse
|
||||
merging), no "[FOLD]" or "fixup!" messages.
|
||||
- The commits are based on the current tip of the base branch.
|
||||
- The commits are clean:
|
||||
No merge commits (except when merging a release, see [RELEASING.md](./RELEASING.md)),
|
||||
no "[FOLD]" or "fixup!" messages.
|
||||
- All commits are signed. If the commits are not signed by the author, use
|
||||
`git commit --amend -S` to sign them yourself.
|
||||
- At least one (but preferably all) of the commits has the PR number
|
||||
@@ -626,578 +632,8 @@ use them!**
|
||||
|
||||
### Releases
|
||||
|
||||
All releases, including release candidates and betas, are handled
|
||||
differently from typical PRs. Most importantly, never use
|
||||
the Github UI to merge a release.
|
||||
|
||||
Xrpld uses a linear workflow model that can be summarized as:
|
||||
|
||||
1. In between releases, developers work against the `develop` branch.
|
||||
2. Periodically, a maintainer will build and tag a beta version from
|
||||
`develop`, which is pushed to `release`.
|
||||
- Betas are usually released every two to three weeks, though that
|
||||
schedule can vary depending on progress, availability, and other
|
||||
factors.
|
||||
3. When the changes in `develop` are considered stable and mature enough
|
||||
to be ready to release, a release candidate (RC) is built and tagged
|
||||
from `develop`, and merged to `release`.
|
||||
- Further development for that release (primarily fixes) then
|
||||
continues against `release`, while other development continues on
|
||||
`develop`. Effectively, `release` is forked from `develop`. Changes
|
||||
to `release` must be reverse merged to `develop`.
|
||||
4. When the candidate has passed testing and is ready for release, the
|
||||
final release is merged to `master`.
|
||||
5. If any issues are found post-release, a hotfix / point release may be
|
||||
created, which is merged to `master`, and then reverse merged to
|
||||
`develop`.
|
||||
|
||||
#### Betas, and the first release candidate
|
||||
|
||||
##### Preparing the `develop` branch
|
||||
|
||||
1. Optimally, the `develop` branch will be ready to go, with all
|
||||
relevant PRs already merged.
|
||||
2. If there are any PRs pending, merge them **BEFORE** preparing the beta.
|
||||
1. If only one or two PRs need to be merged, merge those PRs [as
|
||||
normal](#when-and-how-to-merge-pull-requests), updating the second
|
||||
one, and waiting for CI to finish in between.
|
||||
2. If there are several pending PRs, do not use the Github UI,
|
||||
because the delays waiting for CI in between each merge will be
|
||||
unnecessarily onerous. (Incidentally, this process can also be
|
||||
used to merge if the Github UI has issues.) Merge each PR branch
|
||||
directly to a `release-next` on your local machine and create a single
|
||||
PR, then push your branch to `develop`.
|
||||
1. Squash the changes from each PR, one commit each (unless more
|
||||
are needed), being sure to sign each commit and update the
|
||||
commit message to include the PR number. You may be able to use
|
||||
a fast-forward merge for the first PR.
|
||||
2. Push your branch.
|
||||
3. Continue to [Making the release](#making-the-release) to update
|
||||
the version number, etc.
|
||||
|
||||
The workflow may look something like:
|
||||
|
||||
```
|
||||
git fetch --multiple upstreams user1 user2 user3 [...]
|
||||
git checkout -B release-next --no-track upstream/develop
|
||||
|
||||
# Only do an ff-only merge if pr-branch1 is either already
|
||||
# squashed, or needs to be merged with separate commits,
|
||||
# and has no merge commits.
|
||||
# Use -S on the ff-only merge if pr-branch1 isn't signed.
|
||||
git merge [-S] --ff-only user1/pr-branch1
|
||||
|
||||
git merge --squash user2/pr-branch2
|
||||
git commit -S # Use the commit message provided on the PR
|
||||
|
||||
git merge --squash user3/pr-branch3
|
||||
git commit -S # Use the commit message provided on the PR
|
||||
|
||||
[...]
|
||||
|
||||
# Make sure the commits look right
|
||||
git log --show-signature "upstream/develop..HEAD"
|
||||
|
||||
git push --set-upstream origin
|
||||
|
||||
# Continue to "Making the release" to update the version number, so
|
||||
# everything can be done in one PR.
|
||||
```
|
||||
|
||||
You can also use the [squash-branches] script.
|
||||
|
||||
You may also need to manually close the open PRs after the changes are
|
||||
merged to `develop`. Be sure to include the commit ID.
|
||||
|
||||
##### Making the release
|
||||
|
||||
This includes, betas, and the first release candidate (RC).
|
||||
|
||||
1. If you didn't create one [preparing the `develop`
|
||||
branch](#preparing-the-develop-branch), Ensure there is no old
|
||||
`release-next` branch hanging around. Then make a `release-next`
|
||||
branch that only changes the version number. e.g.
|
||||
|
||||
```
|
||||
git fetch upstreams
|
||||
|
||||
git checkout --no-track -B release-next upstream/develop
|
||||
|
||||
v="A.B.C-bD"
|
||||
build=$( find -name BuildInfo.cpp )
|
||||
sed 's/\(^.*versionString =\).*$/\1 "'${v}'"/' ${build} > version.cpp && mv -vi version.cpp ${build}
|
||||
|
||||
git diff
|
||||
|
||||
git add ${build}
|
||||
|
||||
git commit -S -m "Set version to ${v}"
|
||||
|
||||
# You could use your "origin" repo, but some CI tests work better on upstream.
|
||||
git push upstream-push
|
||||
git fetch upstreams
|
||||
git branch --set-upstream-to=upstream/release-next
|
||||
```
|
||||
|
||||
You can also use the [update-version] script. 2. Create a Pull Request for `release-next` with **`develop`** as
|
||||
the base branch.
|
||||
|
||||
1. Use the title "[TRIVIAL] Set version to X.X.X-bX".
|
||||
2. Instead of the default description template, use the following:
|
||||
|
||||
```
|
||||
## High Level Overview of Change
|
||||
|
||||
This PR only changes the version number. It will be merged as
|
||||
soon as Github CI actions successfully complete.
|
||||
```
|
||||
|
||||
3. Wait for CI to successfully complete, and get someone to approve
|
||||
the PR. (It is safe to ignore known CI issues.)
|
||||
4. Push the updated `develop` branch using your `release-next`
|
||||
branch. **Do not use the Github UI. It's important to preserve
|
||||
commit IDs.**
|
||||
|
||||
```
|
||||
git push upstream-push release-next:develop
|
||||
```
|
||||
|
||||
5. In the unlikely event that the push fails because someone has merged
|
||||
something else in the meantime, rebase your branch onto the updated
|
||||
`develop` branch, push again, and go back to step 3.
|
||||
6. Ensure that your PR against `develop` is closed. Github should do it
|
||||
automatically.
|
||||
7. Once this is done, forward progress on `develop` can continue
|
||||
(other PRs may be merged).
|
||||
8. Now create a Pull Request for `release-next` with **`release`** as
|
||||
the base branch. Instead of the default template, reuse and update
|
||||
the message from the previous release. Include the following verbiage
|
||||
somewhere in the description:
|
||||
|
||||
```
|
||||
The base branch is `release`. [All releases (including
|
||||
betas)](https://github.com/XRPLF/rippled/blob/develop/CONTRIBUTING.md#before-you-start)
|
||||
go in `release`. This PR branch will be pushed directly to `release` (not
|
||||
squashed or rebased, and not using the GitHub UI).
|
||||
```
|
||||
|
||||
7. Sign-offs for the three platforms (Linux, Mac, Windows) usually occur
|
||||
offline, but at least one approval will be needed on the PR.
|
||||
- If issues are discovered during testing, simply abandon the
|
||||
release. It's easy to start a new release, it should be easy to
|
||||
abandon one. **DO NOT REUSE THE VERSION NUMBER.** e.g. If you
|
||||
abandon 2.4.0-b1, the next attempt will be 2.4.0-b2.
|
||||
8. Once everything is ready to go, push to `release`.
|
||||
|
||||
```
|
||||
git fetch upstreams
|
||||
|
||||
# Just to be safe, do a dry run first:
|
||||
git push --dry-run upstream-push release-next:release
|
||||
|
||||
# If everything looks right, push the branch
|
||||
git push upstream-push release-next:release
|
||||
|
||||
# Check that all of the branches are updated
|
||||
git fetch upstreams
|
||||
git log -1 --oneline
|
||||
# The output should look like:
|
||||
# 0123456789 (HEAD -> upstream/release-next, upstream/release,
|
||||
# upstream/develop) Set version to 2.4.0-b1
|
||||
# Note that upstream/develop may not be on this commit, but
|
||||
# upstream/release must be.
|
||||
# Other branches, including some from upstream-push, may also be
|
||||
# present.
|
||||
```
|
||||
|
||||
9. Tag the release, too.
|
||||
|
||||
```
|
||||
git tag <version number>
|
||||
git push upstream-push <version number>
|
||||
```
|
||||
|
||||
10. Delete the `release-next` branch on the repo. Use the Github UI or:
|
||||
|
||||
```
|
||||
git push --delete upstream-push release-next
|
||||
```
|
||||
|
||||
11. Finally [create a new release on
|
||||
Github](https://github.com/XRPLF/rippled/releases).
|
||||
|
||||
#### Release candidates after the first
|
||||
|
||||
Once the first release candidate is [merged into
|
||||
release](#making-the-release), then `release` and `develop` _are allowed
|
||||
to diverge_.
|
||||
|
||||
If a bug or issue is discovered in a version that has a release
|
||||
candidate being tested, any fix and new version will need to be applied
|
||||
against `release`, then reverse-merged to `develop`. This helps keep git
|
||||
history as linear as possible.
|
||||
|
||||
A `release-next` branch will be created from `release`, and any further
|
||||
work for that release must be based on `release-next`. Specifically,
|
||||
PRs must use `release-next` as the base, and those PRs will be merged
|
||||
directly to `release-next` when approved. Changes should be restricted
|
||||
to bug fixes, but other changes may be necessary from time to time.
|
||||
|
||||
1. Open any PRs for the pending release using `release-next` as the base,
|
||||
so they can be merged directly in to it. Unlike `develop`, though,
|
||||
`release-next` can be thrown away and recreated if necessary.
|
||||
2. Once a new release candidate is ready, create a version commit as in
|
||||
step 1 [above](#making-the-release) on `release-next`. You can use
|
||||
the [update-version] script for this, too.
|
||||
3. Jump to step 8 ("Now create a Pull Request for `release-next` with
|
||||
**`release`** as the base") from the process
|
||||
[above](#making-the-release) to merge `release-next` into `release`.
|
||||
|
||||
##### Follow up: reverse merge
|
||||
|
||||
Once the RC is merged and tagged, it needs to be reverse merged into
|
||||
`develop` as soon as possible.
|
||||
|
||||
1. Create a branch, based on `upstream/develop`.
|
||||
The branch name is not important, but could include "mergeNNNrcN".
|
||||
E.g. For release A.B.C-rcD, use `mergeABCrcD`.
|
||||
|
||||
```
|
||||
git fetch upstreams
|
||||
|
||||
git checkout --no-track -b mergeABCrcD upstream/develop
|
||||
```
|
||||
|
||||
2. Merge `release` into your branch.
|
||||
|
||||
```
|
||||
# I like the "--edit --log --verbose" parameters, but they are
|
||||
# not required.
|
||||
git merge upstream/release
|
||||
```
|
||||
|
||||
3. `BuildInfo.cpp` will have a conflict with the version number.
|
||||
Resolve it with the version from `develop` - the higher version.
|
||||
4. Push your branch to your repo (or `upstream` if you have permission),
|
||||
and open a normal PR against `develop`. The "High level overview" can
|
||||
simply indicate that this is a merge of the RC. The "Context" should
|
||||
summarize the changes from the RC. Include the following text
|
||||
prominently:
|
||||
|
||||
```
|
||||
This PR must be merged manually using a push. Do not use the Github UI.
|
||||
```
|
||||
|
||||
5. Depending on the complexity of the changes, and/or merge conflicts,
|
||||
the PR may need a thorough review, or just a sign-off that the
|
||||
merge was done correctly.
|
||||
6. If `develop` is updated before this PR is merged, do not merge
|
||||
`develop` back into your branch. Instead rebase preserving merges,
|
||||
or do the merge again. (See also the `rerere` git config setting.)
|
||||
|
||||
```
|
||||
git rebase --rebase-merges upstream/develop
|
||||
# OR
|
||||
git reset --hard upstream/develop
|
||||
git merge upstream/release
|
||||
```
|
||||
|
||||
7. When the PR is ready, push it to `develop`.
|
||||
|
||||
```
|
||||
git fetch upstreams
|
||||
|
||||
# Make sure the commits look right
|
||||
git log --show-signature "upstream/develop^..HEAD"
|
||||
|
||||
git push upstream-push mergeABCrcD:develop
|
||||
|
||||
git fetch upstreams
|
||||
```
|
||||
|
||||
Development on `develop` can proceed as normal.
|
||||
|
||||
#### Final releases
|
||||
|
||||
A final release is any release that is not a beta or RC, such as 2.2.0.
|
||||
|
||||
Only code that has already been tested and vetted across all three
|
||||
platforms should be included in a final release. Most of the time, that
|
||||
means that the commit immediately preceding the commit setting the
|
||||
version number will be an RC. Occasionally, there may be last-minute bug
|
||||
fixes included as well. If so, those bug fixes must have been tested
|
||||
internally as if they were RCs (at minimum, ensuring unit tests pass,
|
||||
and the app starts, syncs, and stops cleanly across all three
|
||||
platforms.)
|
||||
|
||||
_If in doubt, make an RC first._
|
||||
|
||||
The process for building a final release is very similar to [the process
|
||||
for building a beta](#making-the-release), except the code will be
|
||||
moving from `release` to `master` instead of from `develop` to
|
||||
`release`, and both branches will be pushed at the same time.
|
||||
|
||||
1. Ensure there is no old `master-next` branch hanging around.
|
||||
Then make a `master-next` branch that only changes the version
|
||||
number. As above, or using the
|
||||
[update-version] script.
|
||||
2. Create a Pull Request for `master-next` with **`master`** as
|
||||
the base branch. Instead of the default template, reuse and update
|
||||
the message from the previous final release. Include the following verbiage
|
||||
somewhere in the description:
|
||||
|
||||
```
|
||||
The base branch is `master`. This PR branch will be pushed directly to
|
||||
`release` and `master` (not squashed or rebased, and not using the
|
||||
GitHub UI).
|
||||
```
|
||||
|
||||
7. Sign-offs for the three platforms (Linux, Mac, Windows) usually occur
|
||||
offline, but at least one approval will be needed on the PR.
|
||||
- If issues are discovered during testing, close the PR, delete
|
||||
`master-next`, and move development back to `release`, [issuing
|
||||
more RCs as necessary](#release-candidates-after-the-first)
|
||||
8. Once everything is ready to go, push to `release` and `master`.
|
||||
|
||||
```
|
||||
git fetch upstreams
|
||||
|
||||
# Just to be safe, do dry runs first:
|
||||
git push --dry-run upstream-push master-next:release
|
||||
git push --dry-run upstream-push master-next:master
|
||||
|
||||
# If everything looks right, push the branch
|
||||
git push upstream-push master-next:release
|
||||
git push upstream-push master-next:master
|
||||
|
||||
# Check that all of the branches are updated
|
||||
git fetch upstreams
|
||||
git log -1 --oneline
|
||||
# The output should look like:
|
||||
# 0123456789 (HEAD -> upstream/master-next, upstream/master,
|
||||
# upstream/release) Set version to A.B.0
|
||||
# Note that both upstream/release and upstream/master must be on this
|
||||
# commit.
|
||||
# Other branches, including some from upstream-push, may also be
|
||||
# present.
|
||||
```
|
||||
|
||||
9. Tag the release, too.
|
||||
|
||||
```
|
||||
git tag <version number>
|
||||
git push upstream-push <version number>
|
||||
```
|
||||
|
||||
10. Delete the `master-next` branch on the repo. Use the Github UI or:
|
||||
|
||||
```
|
||||
git push --delete upstream-push master-next
|
||||
```
|
||||
|
||||
11. [Create a new release on
|
||||
Github](https://github.com/XRPLF/rippled/releases). Be sure that
|
||||
"Set as the latest release" is checked.
|
||||
12. Open a PR to update the [API-CHANGELOG](API-CHANGELOG.md) and `API-VERSION-[n].md` with the changes for this release (if any are missing).
|
||||
13. Finally, [reverse merge the release into `develop`](#follow-up-reverse-merge).
|
||||
|
||||
#### Special cases: point releases, hotfixes, etc.
|
||||
|
||||
On occasion, a bug or issue is discovered in a version that already
|
||||
had a final release. Most of the time, development will have started
|
||||
on the next version, and will usually have changes in `develop`
|
||||
and often in `release`.
|
||||
|
||||
Because git history is kept as linear as possible, any fix and new
|
||||
version will need to be applied against `master`.
|
||||
|
||||
The process for building a hotfix release is very similar to [the
|
||||
process for building release candidates after the
|
||||
first](#release-candidates-after-the-first) and [for building a final
|
||||
release](#final-releases), except the changes will be done against
|
||||
`master` instead of `release`.
|
||||
|
||||
If there is only a single issue for the hotfix, the work can be done in
|
||||
any branch. When it's ready to merge, jump to step 3 using your branch
|
||||
instead of `master-next`.
|
||||
|
||||
1. Create a `master-next` branch from `master`.
|
||||
|
||||
```
|
||||
git checkout --no-track -b master-next upstream/master
|
||||
git push upstream-push
|
||||
git fetch upstreams
|
||||
```
|
||||
|
||||
2. Open any PRs for the pending hotfix using `master-next` as the base,
|
||||
so they can be merged directly in to it. Unlike `develop`, though,
|
||||
`master-next` can be thrown away and recreated if necessary.
|
||||
3. Once the hotfix is ready, create a version commit using the same
|
||||
steps as above, or use the
|
||||
[update-version] script.
|
||||
4. Create a Pull Request for `master-next` with **`master`** as
|
||||
the base branch. Instead of the default template, reuse and update
|
||||
the message from the previous final release. Include the following verbiage
|
||||
somewhere in the description:
|
||||
|
||||
```
|
||||
The base branch is `master`. This PR branch will be pushed directly to
|
||||
`master` (not squashed or rebased, and not using the GitHub UI).
|
||||
```
|
||||
|
||||
7. Sign-offs for the three platforms (Linux, Mac, Windows) usually occur
|
||||
offline, but at least one approval will be needed on the PR.
|
||||
- If issues are discovered during testing, update `master-next` as
|
||||
needed, but ensure that the changes are properly squashed, and the
|
||||
version setting commit remains last
|
||||
8. Once everything is ready to go, push to `master` **only**.
|
||||
|
||||
```
|
||||
git fetch upstreams
|
||||
|
||||
# Just to be safe, do a dry run first:
|
||||
git push --dry-run upstream-push master-next:master
|
||||
|
||||
# If everything looks right, push the branch
|
||||
git push upstream-push master-next:master
|
||||
|
||||
# Check that all of the branches are updated
|
||||
git fetch upstreams
|
||||
git log -1 --oneline
|
||||
# The output should look like:
|
||||
# 0123456789 (HEAD -> upstream/master-next, upstream/master) Set version
|
||||
# to 2.4.1
|
||||
# Note that upstream/master must be on this commit. upstream/release and
|
||||
# upstream/develop should not.
|
||||
# Other branches, including some from upstream-push, may also be
|
||||
# present.
|
||||
```
|
||||
|
||||
9. Tag the release, too.
|
||||
|
||||
```
|
||||
git tag <version number>
|
||||
git push upstream-push <version number>
|
||||
```
|
||||
|
||||
9. Delete the `master-next` branch on the repo.
|
||||
|
||||
```
|
||||
git push --delete upstream-push master-next
|
||||
```
|
||||
|
||||
10. [Create a new release on
|
||||
Github](https://github.com/XRPLF/rippled/releases). Be sure that
|
||||
"Set as the latest release" is checked.
|
||||
|
||||
Once the hotfix is released, it needs to be reverse merged into
|
||||
`develop` as soon as possible. It may also need to be merged into
|
||||
`release` if a release candidate is under development.
|
||||
|
||||
1. Create a branch in your own repo, based on `upstream/develop`.
|
||||
The branch name is not important, but could include "mergeNNN".
|
||||
E.g. For release 2.2.3, use `merge223`.
|
||||
|
||||
```
|
||||
git fetch upstreams
|
||||
|
||||
git checkout --no-track -b merge223 upstream/develop
|
||||
```
|
||||
|
||||
2. Merge master into your branch.
|
||||
|
||||
```
|
||||
# I like the "--edit --log --verbose" parameters, but they are
|
||||
# not required.
|
||||
git merge upstream/master
|
||||
```
|
||||
|
||||
3. `BuildInfo.cpp` will have a conflict with the version number.
|
||||
Resolve it with the version from `develop` - the higher version.
|
||||
4. Push your branch to your repo, and open a normal PR against
|
||||
`develop`. The "High level overview" can simply indicate that this
|
||||
is a merge of the hotfix version. The "Context" should summarize
|
||||
the changes from the hotfix. Include the following text
|
||||
prominently:
|
||||
|
||||
```
|
||||
This PR must be merged manually using a --ff-only merge. Do not use the Github UI.
|
||||
```
|
||||
|
||||
5. Depending on the complexity of the hotfix, and/or merge conflicts,
|
||||
the PR may need a thorough review, or just a sign-off that the
|
||||
merge was done correctly.
|
||||
6. If `develop` is updated before this PR is merged, do not merge
|
||||
`develop` back into your branch. Instead rebase preserving merges,
|
||||
or do the merge again. (See also the `rerere` git config setting.)
|
||||
|
||||
```
|
||||
git rebase --rebase-merges upstream/develop
|
||||
# OR
|
||||
git reset --hard upstream/develop
|
||||
git merge upstream/master
|
||||
```
|
||||
|
||||
7. When the PR is ready, push it to `develop`.
|
||||
|
||||
```
|
||||
git fetch upstreams
|
||||
|
||||
# Make sure the commits look right
|
||||
git log --show-signature "upstream/develop..HEAD"
|
||||
|
||||
git push upstream-push HEAD:develop
|
||||
```
|
||||
|
||||
Development on `develop` can proceed as normal. It is recommended to
|
||||
create a beta (or RC) immediately to ensure that everything worked as
|
||||
expected.
|
||||
|
||||
##### An even rarer scenario: A hotfix on an old release
|
||||
|
||||
Historically, once a final release is tagged and packages are released,
|
||||
versions older than the latest final release are no longer supported.
|
||||
However, there is a possibility that a very high severity bug may occur
|
||||
in a non-amendment blocked version that is still being run by
|
||||
a significant fraction of users, which would necessitate a hotfix / point
|
||||
release to that version as well as any later versions.
|
||||
|
||||
This scenario would follow the same basic procedure as above,
|
||||
except that _none_ of `develop`, `release`, or `master`
|
||||
would be touched during the release process.
|
||||
|
||||
In this example, consider if version 2.1.1 needed to be patched.
|
||||
|
||||
1. Create two branches in the main (`upstream`) repo.
|
||||
|
||||
```
|
||||
git fetch upstreams
|
||||
|
||||
# Create a base branch off the tag
|
||||
git checkout --no-track -b master-2.1.2 2.1.1
|
||||
git push upstream-push
|
||||
|
||||
# Create a working branch
|
||||
git checkout --no-track -b master212-next master-2.1.2
|
||||
git push upstream-push
|
||||
|
||||
git fetch upstreams
|
||||
```
|
||||
|
||||
2. Work continues as above, except using `master-2.1.2`as
|
||||
the base branch for any merging, packaging, etc.
|
||||
3. After the release is tagged and packages are built, you could
|
||||
potentially delete both branches, e.g. `master-2.1.2` and
|
||||
`master212-next`. However, it may be useful to keep `master-2.1.2`
|
||||
around indefinitely for reference.
|
||||
4. Assuming that a hotfix is also released for the latest
|
||||
version in parallel with this one, or if the issue is
|
||||
already fixed in the latest version, do no do any
|
||||
reverse merges. However, if it is not, it probably makes
|
||||
sense to reverse merge `master-2.1.2` into `master`,
|
||||
release a hotfix for _that_ version, then reverse merge
|
||||
from `master` to `develop`. (Please don't do this unless absolutely
|
||||
necessary.)
|
||||
Releases, release branches, and merging releases back into `develop`
|
||||
are described in [RELEASING.md](./RELEASING.md).
|
||||
|
||||
[contrib]: https://docs.github.com/en/get-started/quickstart/contributing-to-projects
|
||||
[squash]: https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/incorporating-changes-from-a-pull-request/about-pull-request-merges#squash-and-merge-your-commits
|
||||
@@ -1205,5 +641,3 @@ git fetch upstreams
|
||||
[xrpld]: https://github.com/XRPLF/rippled
|
||||
[signing]: https://docs.github.com/en/authentication/managing-commit-signature-verification/about-commit-signature-verification
|
||||
[setup-upstreams]: ./bin/git/setup-upstreams.sh
|
||||
[squash-branches]: ./bin/git/squash-branches.sh
|
||||
[update-version]: ./bin/git/update-version.sh
|
||||
+152
@@ -0,0 +1,152 @@
|
||||
# Branching and Release Management
|
||||
|
||||
This document describes how we branch, release, and merge releases back into `develop`.
|
||||
It does not define version naming
|
||||
(e.g., what constitutes a major-minor, patch, or beta release).
|
||||
|
||||
Examples use `X.Y` for a release line:
|
||||
`X` and `Y` are placeholders, while the trailing `x` is literal,
|
||||
e.g., `release/X.Y.x` is `release/3.4.x` for the `3.4` line.
|
||||
|
||||
## Branches
|
||||
|
||||
| Branch | Purpose |
|
||||
| :-------------- | :---------------------------------------------------------------------------------------- |
|
||||
| `develop` | Main development branch. Betas and the first RC of a major-minor release are tagged here. |
|
||||
| `staging/X.Y.x` | Where fixes for the `X.Y` line are developed and RCs are prepared. |
|
||||
| `release/X.Y.x` | The last public RC or release of the `X.Y` line. |
|
||||
|
||||
A release line is named `X.Y.x`
|
||||
because one branch serves every patch release of that line (`X.Y.0`, `X.Y.1`, `X.Y.2`, ...).
|
||||
The `/` groups branches hierarchically,
|
||||
so tools can filter them and protection rules can target `release/*` and `staging/*`.
|
||||
|
||||
## Principles
|
||||
|
||||
- **Releases are merged back into `develop`, never cherry-picked or rebased onto it.**
|
||||
Cherry-picked and rebased commits get new hashes,
|
||||
so Git can't tell that `develop` already has them.
|
||||
Future merges then replay them and produce artificial conflicts,
|
||||
and it's hard to verify that every fix actually reached `develop`.
|
||||
Merging keeps a single history:
|
||||
Git knows exactly which release commits `develop` contains,
|
||||
and no fix is left behind.
|
||||
- **Branches only move forward.**
|
||||
`develop`, `staging/X.Y.x`, and `release/X.Y.x` are never rewritten.
|
||||
- **Cherry-picking only goes from `develop` to a staging branch**,
|
||||
for fixes that must get into a release after the code freeze
|
||||
(see [Emergency Fixes From `develop`](#emergency-fixes-from-develop)).
|
||||
- **Security fixes are prepared privately and published with the release that contains them**,
|
||||
so vulnerabilities are not disclosed prematurely.
|
||||
|
||||
## Release Lifecycle
|
||||
|
||||
This diagram shows a major-minor release and its first patch release.
|
||||
The staging and release branches are drawn as one line.
|
||||
|
||||
```text
|
||||
develop staging/X.Y.x & release/X.Y.x
|
||||
│
|
||||
├── Tag: X.Y.0-b1
|
||||
├── Tag: X.Y.0-bN
|
||||
├── Tag: X.Y.0-rc1 ───────────────┐ (branches created)
|
||||
│ │
|
||||
│ (development continues) ├── Fixes
|
||||
│ ├── Tag: X.Y.0-rcN
|
||||
│ ├── Tag: X.Y.0 (final)
|
||||
◀──── (merge) ────────────────────┤
|
||||
│ ├── Fixes
|
||||
│ ├── Tag: X.Y.1-rcN
|
||||
│ ├── Tag: X.Y.1 (final)
|
||||
◀──── (merge) ────────────────────┤
|
||||
│
|
||||
▼
|
||||
```
|
||||
|
||||
### Betas, First RC & Branching
|
||||
|
||||
> [!NOTE]
|
||||
> This phase applies only to a new major-minor release (e.g., `X.Y.0`).
|
||||
> Patch releases work on the existing branches of the line.
|
||||
|
||||
1. **Betas:** All beta versions (e.g., `X.Y.0-b1`) are built and tagged directly on `develop`.
|
||||
2. **First RC:** We release the first RC (`X.Y.0-rc1`)
|
||||
once everything that should be included in the release has been merged.
|
||||
3. **Branches:** `staging/X.Y.x` and `release/X.Y.x` are created from `develop`
|
||||
at the commit tagged `X.Y.0-rc1`.
|
||||
The first RC also kicks off the QE process.
|
||||
4. **Code freeze:** No new features or unrelated changes are pulled from `develop`
|
||||
into `staging/X.Y.x` or `release/X.Y.x`.
|
||||
Only critical stabilization fixes go into the line.
|
||||
5. **No large changes on `develop`:** Until `X.Y.0` is [merged back](#merging-back-into-develop),
|
||||
large changes (e.g., big refactors, moving or renaming many files, mass reformatting)
|
||||
are not merged into `develop`,
|
||||
so that fixes on the line and the merge back don't run into conflicts.
|
||||
|
||||
### Release Candidates
|
||||
|
||||
1. Fixes are developed against `staging/X.Y.x`.
|
||||
2. When ready, a new RC is created on `staging/X.Y.x`,
|
||||
and `release/X.Y.x` is fast-forwarded to it.
|
||||
|
||||
RCs that contain unpublished security fixes are not published,
|
||||
and don't touch the public branches.
|
||||
|
||||
RCs are not merged back into `develop`:
|
||||
`X.Y.0-rc1` is tagged on `develop` itself,
|
||||
and all later changes reach `develop` with the [final release](#final-release).
|
||||
|
||||
### Final Release
|
||||
|
||||
Security fixes become public as soon as they reach the public repo,
|
||||
so these steps happen only once the release is ready to be published,
|
||||
one right after the other.
|
||||
|
||||
1. Unpublished security fixes, if any, are merged into `staging/X.Y.x`.
|
||||
2. `release/X.Y.x` is fast-forwarded to `staging/X.Y.x`,
|
||||
and the release is tagged on it.
|
||||
3. The release is immediately [merged back into `develop`](#merging-back-into-develop).
|
||||
For a major-minor release, this lifts the freeze on large changes in `develop`.
|
||||
|
||||
### Merging Back Into `develop`
|
||||
|
||||
The merge back is a regular PR into `develop`
|
||||
whose branch contains a real merge commit of the release tag:
|
||||
|
||||
1. Create a branch from `develop`, run `git merge --no-ff <tag>`, and resolve any conflicts.
|
||||
2. Once the PR is approved, `develop` is fast-forwarded to the PR branch,
|
||||
so the merge commit lands as it is.
|
||||
Never squash or rebase it.
|
||||
Never add it to the merge queue either: the queue squashes PRs, which would drop the merge commit.
|
||||
3. If `develop` moves while the PR is open, redo the merge on top of the new `develop`.
|
||||
|
||||
After the merge, `git log develop..<tag>` must be empty.
|
||||
|
||||
## Special Cases
|
||||
|
||||
### Emergency Fixes From `develop`
|
||||
|
||||
If a commit was merged to `develop`
|
||||
and needs to be included in a release after the code freeze:
|
||||
|
||||
1. Create a PR that cherry-picks the commit onto `staging/X.Y.x`.
|
||||
2. Leave `develop` as it is, with no reverts.
|
||||
3. Follow the [release candidates](#release-candidates) process as usual.
|
||||
|
||||
When the release is later merged back into `develop`,
|
||||
both sides already contain the same change,
|
||||
so the merge usually resolves it cleanly or with a trivial conflict.
|
||||
From Git's perspective, the cherry-picked commit then becomes part of `develop` too.
|
||||
|
||||
### Several Supported Lines
|
||||
|
||||
When a fix must ship in more than one supported line (e.g., `X.Y` and `X.(Y+1)`),
|
||||
the lines are merged upwards rather than cherry-picked between:
|
||||
|
||||
1. The fix goes into the oldest line first.
|
||||
2. After that line's release,
|
||||
its `release/X.Y.x` is merged into the staging branch of the next newer line,
|
||||
and so on up to the newest line.
|
||||
3. The newest line is merged back into `develop` as usual.
|
||||
|
||||
This way every newer line, and eventually `develop`, contains the history of the older lines.
|
||||
@@ -24,9 +24,13 @@
|
||||
# `g++-15`, ...) are probed under both names: a suffixed name can break while
|
||||
# the plain one still works (see mkVersionedToolLinks in nix/packages.nix).
|
||||
#
|
||||
# Tools scoped to a single dev shell rather than to commonPackages are checked
|
||||
# only in that shell, keyed off XRPL_DEVSHELL.
|
||||
#
|
||||
# Environment variables:
|
||||
# CI if set, skip the tools above when on macOS.
|
||||
# CHECK_TOOLS_SKIP_CLONE if set, skip the git-over-HTTPS connectivity check.
|
||||
# XRPL_DEVSHELL active dev shell; selects shell-specific tools.
|
||||
|
||||
set -uo pipefail
|
||||
|
||||
@@ -163,6 +167,14 @@ if [ "${os}" = "linux" ] || [ "${os}" = "macos" ]; then
|
||||
check rustfmt
|
||||
fi
|
||||
|
||||
# Lean4 is in the formal-verification shell only, not in commonPackages.
|
||||
if [ "${XRPL_DEVSHELL:-}" = "formal-verification" ]; then
|
||||
echo
|
||||
echo "Formal verification toolchain:"
|
||||
check lean
|
||||
check lake
|
||||
fi
|
||||
|
||||
# GCC is the default compiler on Linux. macOS uses the system Apple Clang
|
||||
# instead, so GCC/g++/gcov are not expected there.
|
||||
if [ "${os}" = "linux" ]; then
|
||||
|
||||
Generated
+6
-6
@@ -2,11 +2,11 @@
|
||||
"nodes": {
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1781173989,
|
||||
"narHash": "sha256-fnzKKPvS+oieI/pTzotA5tkoM47EB1NpaBcgk4R97hE=",
|
||||
"lastModified": 1791130267,
|
||||
"narHash": "sha256-1sjwcQMcgAbmiip/VivBSiK8p2bTkQGOvCuO7vnwfx4=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "8c91a71d13451abc40eb9dae8910f972f979852f",
|
||||
"rev": "9013764fcc0ea99fa16cf7aa7decf8a5c3889dd2",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -47,11 +47,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1784611586,
|
||||
"narHash": "sha256-OfqgY+0hp/zseZB7uyH0U8kIDPS4scZZCyAurEplvG0=",
|
||||
"lastModified": 1791189933,
|
||||
"narHash": "sha256-Y8eN7ki0Urx4Ojf4w1xSpWg8uk/3lJqK+E0Oth77rH0=",
|
||||
"owner": "oxalica",
|
||||
"repo": "rust-overlay",
|
||||
"rev": "14f58845249f3552a89b07772626b8d3c632fa86",
|
||||
"rev": "e60029353d0c48d216bc4b065168ccd4079c166f",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/beast/type_name.h>
|
||||
#include <boost/core/type_name.hpp>
|
||||
|
||||
#include <atomic>
|
||||
#include <string>
|
||||
@@ -112,7 +112,7 @@ private:
|
||||
static auto&
|
||||
getCounter() noexcept
|
||||
{
|
||||
static CountedObjects::Counter kC{beast::typeName<Object>()};
|
||||
static CountedObjects::Counter kC{boost::core::type_name<Object>()};
|
||||
return kC;
|
||||
}
|
||||
|
||||
|
||||
@@ -110,10 +110,10 @@ static_assert(
|
||||
*
|
||||
* However, it does not have sufficient precision to represent the full integer
|
||||
* range of int64_t values (-2^63 to 2^63-1), which are needed for XRP and MPT
|
||||
* values. The implementation of SingleAssetVault, and LendingProtocol need to
|
||||
* represent those integer values accurately and precisely, both for the
|
||||
* STNumber field type, and for internal calculations. That necessitated the
|
||||
* "large" scale.
|
||||
* values. The implementation of SingleAssetVault, LendingProtocol, and
|
||||
* MPTokensV2 need to represent those integer values accurately and precisely,
|
||||
* both for the STNumber field type, and for internal calculations. That
|
||||
* necessitated the "large" scale.
|
||||
*
|
||||
* The "Large" scales are intended to represent all values that can be represented
|
||||
* by an STAmount - IOUs, XRP, and MPTs. It has a min value of 10^18, and a max
|
||||
@@ -134,8 +134,8 @@ struct MantissaRange final
|
||||
// NOLINTBEGIN(readability-enum-initial-value)
|
||||
// The values don't matter, except for Large
|
||||
enum class MantissaScale {
|
||||
// Small can be removed when either featureSingleAssetVault or featureLendingProtocol are
|
||||
// retired
|
||||
// Small can be removed when any of featureSingleAssetVault, featureLendingProtocol, or
|
||||
// featureMPTokensV2 are retired
|
||||
Small,
|
||||
// LargeLegacy can be removed when fixCleanup3_2_0 is retired
|
||||
LargeLegacy,
|
||||
@@ -311,10 +311,10 @@ concept Integral64 = std::is_same_v<T, std::int64_t> || std::is_same_v<T, std::u
|
||||
*
|
||||
* The mantissa range may be changed at runtime via setMantissaScale(). The
|
||||
* default mantissa range is "large". The range is updated whenever transaction
|
||||
* processing begins, based on whether SingleAssetVault or LendingProtocol are
|
||||
* enabled. If either is enabled, the mantissa range is set to "large". If not,
|
||||
* it is set to "small", preserving backward compatibility and correct
|
||||
* "amendment-gating".
|
||||
* processing begins, based on whether SingleAssetVault, LendingProtocol, or
|
||||
* MPTokensV2 are enabled. If any is enabled, the mantissa range is set to
|
||||
* "large". If not, it is set to "small", preserving backward compatibility and
|
||||
* correct "amendment-gating".
|
||||
*
|
||||
* It is extremely unlikely that any more calls to setMantissaScale() will be
|
||||
* needed outside of unit tests.
|
||||
@@ -344,8 +344,8 @@ concept Integral64 = std::is_same_v<T, std::int64_t> || std::is_same_v<T, std::u
|
||||
* set/getMantissaScale() functions may be most appropriate. However, if the
|
||||
* test has anything to do with transaction processing, it should enable or
|
||||
* disable the amendments that control the mantissa range choice
|
||||
* (SingleAssetVault and LendingProtocol), and/or check if either of those
|
||||
* amendments are enabled to determine which result to expect.
|
||||
* (SingleAssetVault, LendingProtocol, and MPTokensV2), and/or check if any of
|
||||
* those amendments are enabled to determine which result to expect.
|
||||
*/
|
||||
class Number final
|
||||
{
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/basics/ByteUtilities.h>
|
||||
#include <xrpl/beast/type_name.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
|
||||
#include <boost/align.hpp>
|
||||
#include <boost/container/static_vector.hpp>
|
||||
#include <boost/core/type_name.hpp>
|
||||
#include <boost/predef.h>
|
||||
|
||||
#include <algorithm>
|
||||
@@ -16,6 +16,7 @@
|
||||
#include <cstring>
|
||||
#include <mutex>
|
||||
#include <stdexcept>
|
||||
#include <typeinfo>
|
||||
#include <vector>
|
||||
|
||||
#if BOOST_OS_LINUX
|
||||
@@ -335,7 +336,7 @@ public:
|
||||
}) != cfg.end())
|
||||
{
|
||||
throw std::runtime_error(
|
||||
"SlabAllocatorSet<" + beast::typeName<Type>() + ">: duplicate slab size");
|
||||
"SlabAllocatorSet<" + boost::core::type_name<Type>() + ">: duplicate slab size");
|
||||
}
|
||||
|
||||
for (auto const& c : cfg)
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/basics/sanitizers.h>
|
||||
#include <xrpl/beast/type_name.h>
|
||||
|
||||
#include <boost/core/type_name.hpp>
|
||||
|
||||
#include <exception>
|
||||
#include <string>
|
||||
@@ -55,7 +56,7 @@ Throw(Args&&... args)
|
||||
std::is_convertible_v<E*, std::exception*>, "Exception must derive from std::exception.");
|
||||
|
||||
E e(std::forward<Args>(args)...);
|
||||
logThrow(std::string("Throwing exception of type " + beast::typeName<E>() + ": ") + e.what());
|
||||
logThrow("Throwing exception of type " + boost::core::type_name<E>() + ": " + e.what());
|
||||
throw std::move(e);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,200 @@
|
||||
#pragma once
|
||||
|
||||
#include <concepts>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
/**
|
||||
* Opt-in bitwise operators for scoped enumerations.
|
||||
*
|
||||
* Provides `&`, `|`, `^`, `~` and the corresponding compound assignment forms
|
||||
* for any scoped enumeration with an unsigned underlying type that opts in by
|
||||
* specializing @ref OptIn:
|
||||
*
|
||||
* @code
|
||||
* namespace xrpl {
|
||||
*
|
||||
* enum class MyFlags : std::uint32_t { a = 1, b = 2, c = 4 };
|
||||
*
|
||||
* template <>
|
||||
* struct enum_bitops::OptIn<MyFlags> : std::true_type {};
|
||||
*
|
||||
* } // namespace xrpl
|
||||
* @endcode
|
||||
*
|
||||
* The operators are constexpr and noexcept, and return the enumeration type.
|
||||
*
|
||||
* @par Where the specialization may be declared
|
||||
* In namespace xrpl, or at global scope with full qualification, and after
|
||||
* the enumeration is defined but before the operators are first used. This
|
||||
* cannot be done in a nested namespace or at class scope; for enumerations
|
||||
* nested in a class this means after the class definition.
|
||||
*
|
||||
* @par Where the operators are found
|
||||
* The operators are declared in namespace xrpl, so argument-dependent lookup
|
||||
* will find them only for enumerations declared in xrpl or nested in a class
|
||||
* declared directly in xrpl. Enumerations in a nested namespace will only be
|
||||
* found from code inside xrpl, and only if no enclosing scope declares an
|
||||
* operator of the same name; elsewhere they require using-declarations.
|
||||
*/
|
||||
namespace enum_bitops {
|
||||
|
||||
/**
|
||||
* Types that may be opted in to the bitwise operators.
|
||||
*
|
||||
* Satisfied by a scoped enumeration whose underlying type is an unsigned
|
||||
* integer type.
|
||||
*
|
||||
* @tparam T The type to test.
|
||||
*/
|
||||
template <typename T>
|
||||
concept Eligible = std::is_scoped_enum_v<T> && std::unsigned_integral<std::underlying_type_t<T>>;
|
||||
|
||||
/**
|
||||
* Opt-in switch for the bitwise operators.
|
||||
*
|
||||
* The primary template derives from std::false_type. Specialize it to derive
|
||||
* from std::true_type to enable the operators for an @ref Eligible enumeration.
|
||||
*
|
||||
* @tparam T The scoped enumeration to opt in.
|
||||
*/
|
||||
template <Eligible T>
|
||||
struct OptIn : std::false_type
|
||||
{
|
||||
};
|
||||
|
||||
/**
|
||||
* Enumerations for which the bitwise operators are enabled.
|
||||
*
|
||||
* Satisfied when @p T satisfies @ref Eligible and @ref OptIn has been
|
||||
* specialized for it to derive from std::true_type.
|
||||
*
|
||||
* @tparam T The type to test.
|
||||
*/
|
||||
template <typename T>
|
||||
concept Candidate = Eligible<T> && OptIn<T>::value;
|
||||
|
||||
} // namespace enum_bitops
|
||||
|
||||
// These are declared in xrpl, not in enum_bitops, so that argument-dependent
|
||||
// lookup finds them for enumerations whose associated namespace is xrpl.
|
||||
|
||||
/**
|
||||
* @name Bitwise operators for opted-in scoped enumerations
|
||||
*
|
||||
* Each operator applies the corresponding built-in operator to the
|
||||
* underlying values and converts the result back to the enumeration type.
|
||||
* Both operands must have the same enumeration type; there is no implicit
|
||||
* conversion to or from the underlying type.
|
||||
*/
|
||||
/** @{ */
|
||||
|
||||
/**
|
||||
* Bitwise AND.
|
||||
*
|
||||
* @param lhs The left operand.
|
||||
* @param rhs The right operand.
|
||||
* @return The bits set in both @p lhs and @p rhs.
|
||||
*/
|
||||
template <enum_bitops::Candidate T>
|
||||
constexpr T
|
||||
operator&(T lhs, T rhs) noexcept
|
||||
{
|
||||
return static_cast<T>(std::to_underlying(lhs) & std::to_underlying(rhs));
|
||||
}
|
||||
|
||||
/**
|
||||
* Bitwise OR.
|
||||
*
|
||||
* @param lhs The left operand.
|
||||
* @param rhs The right operand.
|
||||
* @return The bits set in @p lhs, in @p rhs, or in both.
|
||||
*/
|
||||
template <enum_bitops::Candidate T>
|
||||
constexpr T
|
||||
operator|(T lhs, T rhs) noexcept
|
||||
{
|
||||
return static_cast<T>(std::to_underlying(lhs) | std::to_underlying(rhs));
|
||||
}
|
||||
|
||||
/**
|
||||
* Bitwise exclusive OR.
|
||||
*
|
||||
* @param lhs The left operand.
|
||||
* @param rhs The right operand.
|
||||
* @return The bits set in exactly one of @p lhs and @p rhs.
|
||||
*/
|
||||
template <enum_bitops::Candidate T>
|
||||
constexpr T
|
||||
operator^(T lhs, T rhs) noexcept
|
||||
{
|
||||
return static_cast<T>(std::to_underlying(lhs) ^ std::to_underlying(rhs));
|
||||
}
|
||||
|
||||
/**
|
||||
* Bitwise complement.
|
||||
*
|
||||
* The result has every bit of the underlying type that is clear in
|
||||
* @p val, including bits that no enumerator names. It is intended for
|
||||
* clearing flags, as in `flags & ~flag`.
|
||||
*
|
||||
* @param val The operand.
|
||||
* @return The complement of @p val.
|
||||
*/
|
||||
template <enum_bitops::Candidate T>
|
||||
constexpr T
|
||||
operator~(T val) noexcept
|
||||
{
|
||||
return static_cast<T>(~std::to_underlying(val));
|
||||
}
|
||||
|
||||
/**
|
||||
* Bitwise AND assignment.
|
||||
*
|
||||
* @param lhs The value to modify.
|
||||
* @param rhs The right operand.
|
||||
* @return A reference to @p lhs.
|
||||
*/
|
||||
template <enum_bitops::Candidate T>
|
||||
constexpr T&
|
||||
operator&=(T& lhs, T rhs) noexcept
|
||||
{
|
||||
lhs = lhs & rhs;
|
||||
return lhs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bitwise OR assignment.
|
||||
*
|
||||
* @param lhs The value to modify.
|
||||
* @param rhs The right operand.
|
||||
* @return A reference to @p lhs.
|
||||
*/
|
||||
template <enum_bitops::Candidate T>
|
||||
constexpr T&
|
||||
operator|=(T& lhs, T rhs) noexcept
|
||||
{
|
||||
lhs = lhs | rhs;
|
||||
return lhs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bitwise exclusive OR assignment.
|
||||
*
|
||||
* @param lhs The value to modify.
|
||||
* @param rhs The right operand.
|
||||
* @return A reference to @p lhs.
|
||||
*/
|
||||
template <enum_bitops::Candidate T>
|
||||
constexpr T&
|
||||
operator^=(T& lhs, T rhs) noexcept
|
||||
{
|
||||
lhs = lhs ^ rhs;
|
||||
return lhs;
|
||||
}
|
||||
|
||||
/** @} */
|
||||
|
||||
} // namespace xrpl
|
||||
+152
-47
@@ -2,107 +2,212 @@
|
||||
|
||||
#include <xrpl/beast/utility/instrumentation.h> // IWYU pragma: keep
|
||||
|
||||
#include <concepts>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
// safe_cast adds compile-time checks to a static_cast to ensure that
|
||||
// the destination can hold all values of the source. This is particularly
|
||||
// handy when the source or destination is an enumeration type.
|
||||
/** Every value of @p Src can be represented by @p Dest.
|
||||
|
||||
template <class Src, class Dest>
|
||||
concept SafeToCast = (std::is_integral_v<Src> && std::is_integral_v<Dest>) &&
|
||||
(std::is_signed_v<Src> || std::is_unsigned_v<Dest>) &&
|
||||
(std::is_signed_v<Src> != std::is_signed_v<Dest> ? sizeof(Dest) > sizeof(Src)
|
||||
: sizeof(Dest) >= sizeof(Src));
|
||||
Given two integral types, the cast is safe when the destination can
|
||||
hold every possible value of the source.
|
||||
|
||||
template <class Dest, class Src>
|
||||
Comparing the bounds requires care: we use @c std::cmp_less_equal and
|
||||
@c std::cmp_greater_equal; the plain relational operators would apply
|
||||
arithmetic conversions, resulting in incorrect results when comparing
|
||||
across signedness.
|
||||
|
||||
Because @c std::cmp_* requires standard signed or unsigned integer
|
||||
arguments, which excludes character types and bool, we first widen
|
||||
all type bounds to the maximum-width integer type while preserving
|
||||
signedness.
|
||||
|
||||
@note Extended integer types, like __int128 on gcc, cannot be safely
|
||||
widened to a standard integer type, so the concept will reject
|
||||
them.
|
||||
*/
|
||||
template <typename Src, typename Dest>
|
||||
concept SafeToCast = std::is_integral_v<Src> && std::is_integral_v<Dest> && []() consteval {
|
||||
using WideSrc = std::conditional_t<std::is_signed_v<Src>, std::intmax_t, std::uintmax_t>;
|
||||
using WideDest = std::conditional_t<std::is_signed_v<Dest>, std::intmax_t, std::uintmax_t>;
|
||||
|
||||
// Note: this guard must be an evaluated branch, and not a
|
||||
// static_assert. The lambda body is outside the immediate
|
||||
// context, so a substitution-time failure here would be a
|
||||
// hard error rather than leaving the concept unsatisfied.
|
||||
if constexpr (sizeof(Src) > sizeof(WideSrc) || sizeof(Dest) > sizeof(WideDest))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
return std::cmp_less_equal(
|
||||
static_cast<WideDest>(std::numeric_limits<Dest>::min()),
|
||||
static_cast<WideSrc>(std::numeric_limits<Src>::min())) &&
|
||||
std::cmp_greater_equal(
|
||||
static_cast<WideDest>(std::numeric_limits<Dest>::max()),
|
||||
static_cast<WideSrc>(std::numeric_limits<Src>::max()));
|
||||
}
|
||||
}();
|
||||
|
||||
/** Compile-time-checked static_cast that rejects non-value preserving casts.
|
||||
|
||||
@note There is deliberately no enum-to-enum overload, and no overload
|
||||
returning the underlying type of an enum. For the latter, use
|
||||
@c std::to_underlying.
|
||||
*/
|
||||
/** @{ */
|
||||
template <typename Dest, typename Src>
|
||||
requires(std::is_integral_v<Dest> && std::is_integral_v<Src>)
|
||||
constexpr Dest
|
||||
safeCast(Src s) noexcept
|
||||
requires(std::is_integral_v<Dest> && std::is_integral_v<Src>)
|
||||
{
|
||||
static_assert(
|
||||
std::is_signed_v<Dest> || std::is_unsigned_v<Src>, "Cannot cast signed to unsigned");
|
||||
constexpr unsigned kNotSame = std::is_signed_v<Dest> != std::is_signed_v<Src>;
|
||||
static_assert(
|
||||
sizeof(Dest) >= sizeof(Src) + kNotSame,
|
||||
"Destination is too small to hold all values of source");
|
||||
SafeToCast<Src, Dest>, "This cast is not value-preserving. Please use unsafeCast instead.");
|
||||
return static_cast<Dest>(s);
|
||||
}
|
||||
|
||||
template <class Dest, class Src>
|
||||
template <typename Dest, typename Src>
|
||||
requires(std::is_enum_v<Dest> && std::is_integral_v<Src>)
|
||||
constexpr Dest
|
||||
safeCast(Src s) noexcept
|
||||
requires(std::is_enum_v<Dest> && std::is_integral_v<Src>)
|
||||
{
|
||||
return static_cast<Dest>(safeCast<std::underlying_type_t<Dest>>(s));
|
||||
}
|
||||
|
||||
template <class Dest, class Src>
|
||||
template <typename Dest, typename Src>
|
||||
requires(std::is_integral_v<Dest> && std::is_enum_v<Src>)
|
||||
constexpr Dest
|
||||
safeCast(Src s) noexcept
|
||||
requires(std::is_integral_v<Dest> && std::is_enum_v<Src>)
|
||||
{
|
||||
return safeCast<Dest>(static_cast<std::underlying_type_t<Src>>(s));
|
||||
return safeCast<Dest>(std::to_underlying(s));
|
||||
}
|
||||
/** @} */
|
||||
|
||||
// unsafe_cast explicitly flags a static_cast as not necessarily able to hold
|
||||
// all values of the source. It includes a compile-time check so that if
|
||||
// underlying types become safe, it can be converted to a safe_cast.
|
||||
/** Integral-to-integral cast that is known to be narrowing or sign-erasing.
|
||||
|
||||
template <class Dest, class Src>
|
||||
This explicitly flags a conversion that can lose information for some
|
||||
values of the source type, where the call site accepts that loss (or
|
||||
truncation is the intended behavior).
|
||||
|
||||
The compile-time check ensures the cast remains "unsafe": if the types
|
||||
involved later change such that the conversion becomes inherently
|
||||
value-preserving, the static assertion fires with instructions to
|
||||
migrate the call site to @ref safeCast.
|
||||
|
||||
If the conversion's safety depends on a runtime precondition rather
|
||||
than on the types, or varies across instantiations of generic code,
|
||||
use @ref checkedCast instead.
|
||||
*/
|
||||
/** @{ */
|
||||
template <typename Dest, typename Src>
|
||||
requires(std::is_integral_v<Dest> && std::is_integral_v<Src>)
|
||||
constexpr Dest
|
||||
unsafeCast(Src s) noexcept
|
||||
requires(std::is_integral_v<Dest> && std::is_integral_v<Src>)
|
||||
{
|
||||
static_assert(
|
||||
!SafeToCast<Src, Dest>,
|
||||
"Only unsafe if casting signed to unsigned or "
|
||||
"destination is too small");
|
||||
!SafeToCast<Src, Dest>, "This cast is value-preserving. Please use safeCast instead.");
|
||||
return static_cast<Dest>(s);
|
||||
}
|
||||
|
||||
template <class Dest, class Src>
|
||||
template <typename Dest, typename Src>
|
||||
requires(std::is_enum_v<Dest> && std::is_integral_v<Src>)
|
||||
constexpr Dest
|
||||
unsafeCast(Src s) noexcept
|
||||
requires(std::is_enum_v<Dest> && std::is_integral_v<Src>)
|
||||
{
|
||||
return static_cast<Dest>(unsafeCast<std::underlying_type_t<Dest>>(s));
|
||||
}
|
||||
|
||||
template <class Dest, class Src>
|
||||
template <typename Dest, typename Src>
|
||||
requires(std::is_integral_v<Dest> && std::is_enum_v<Src>)
|
||||
constexpr Dest
|
||||
unsafeCast(Src s) noexcept
|
||||
requires(std::is_integral_v<Dest> && std::is_enum_v<Src>)
|
||||
{
|
||||
return unsafeCast<Dest>(static_cast<std::underlying_type_t<Src>>(s));
|
||||
return unsafeCast<Dest>(std::to_underlying(s));
|
||||
}
|
||||
/** @} */
|
||||
|
||||
/** Integral-to-integral cast when the caller has performed a bounds check.
|
||||
|
||||
This documents that a runtime precondition or external invariant, which
|
||||
is not necessarily visible to the compiler, guarantees that the requested
|
||||
conversion is value-preserving for the values that can actually occur.
|
||||
|
||||
This is primarily meant for generic code, where the same expression may
|
||||
be value-preserving for one instantiation but not for another, making
|
||||
both @ref safeCast and @ref unsafeCast unusable.
|
||||
|
||||
Unlike @ref safeCast and @ref unsafeCast, this imposes no static check
|
||||
on the type relationship: the caller's claim is about runtime values,
|
||||
not about types.
|
||||
*/
|
||||
/** @{ */
|
||||
template <typename Dest, typename Src>
|
||||
requires(std::is_integral_v<Dest> && std::is_integral_v<Src>)
|
||||
constexpr Dest
|
||||
checkedCast(Src s) noexcept
|
||||
{
|
||||
return static_cast<Dest>(s);
|
||||
}
|
||||
|
||||
template <typename Dest, typename Src>
|
||||
requires(std::is_enum_v<Dest> && std::is_integral_v<Src>)
|
||||
constexpr Dest
|
||||
checkedCast(Src s) noexcept
|
||||
{
|
||||
return static_cast<Dest>(checkedCast<std::underlying_type_t<Dest>>(s));
|
||||
}
|
||||
|
||||
template <typename Dest, typename Src>
|
||||
requires(std::is_integral_v<Dest> && std::is_enum_v<Src>)
|
||||
constexpr Dest
|
||||
checkedCast(Src s) noexcept
|
||||
{
|
||||
return checkedCast<Dest>(std::to_underlying(s));
|
||||
}
|
||||
/** @} */
|
||||
|
||||
/** Downcast within a class hierarchy, verified in debug builds.
|
||||
|
||||
Performs a static_cast down a hierarchy, but in debug builds verifies
|
||||
via dynamic_cast that the object's dynamic type actually permits the
|
||||
downcast. Both build modes execute the same conversion; debug builds
|
||||
merely add the check.
|
||||
|
||||
The pointer form passes null through unchanged, as dynamic_cast does.
|
||||
|
||||
@note The check requires @p Src to be polymorphic; in release builds
|
||||
an invalid downcast is undefined behavior on use, exactly as
|
||||
with a bare static_cast.
|
||||
*/
|
||||
/** @{ */
|
||||
template <class Dest, class Src>
|
||||
requires std::is_pointer_v<Dest>
|
||||
requires(
|
||||
std::is_pointer_v<Dest> && std::is_polymorphic_v<Src> &&
|
||||
std::derived_from<std::remove_pointer_t<Dest>, Src> &&
|
||||
(std::is_const_v<std::remove_pointer_t<Dest>> || !std::is_const_v<Src>) &&
|
||||
(std::is_volatile_v<std::remove_pointer_t<Dest>> || !std::is_volatile_v<Src>))
|
||||
inline Dest
|
||||
safeDowncast(Src* s) noexcept
|
||||
{
|
||||
#ifdef NDEBUG
|
||||
XRPL_ASSERT(
|
||||
s == nullptr || dynamic_cast<Dest>(s) != nullptr, "xrpl::safeDowncast : valid downcast");
|
||||
return static_cast<Dest>(s); // NOLINT(cppcoreguidelines-pro-type-static-cast-downcast)
|
||||
#else
|
||||
auto* result = dynamic_cast<Dest>(s);
|
||||
XRPL_ASSERT(result != nullptr, "xrpl::safeDowncast : pointer downcast is valid");
|
||||
return result;
|
||||
#endif
|
||||
}
|
||||
|
||||
template <class Dest, class Src>
|
||||
requires std::is_lvalue_reference_v<Dest>
|
||||
requires(
|
||||
std::is_lvalue_reference_v<Dest> && std::is_polymorphic_v<Src> &&
|
||||
std::derived_from<std::remove_reference_t<Dest>, Src>)
|
||||
inline Dest
|
||||
safeDowncast(Src& s) noexcept
|
||||
{
|
||||
#ifndef NDEBUG
|
||||
XRPL_ASSERT(
|
||||
dynamic_cast<std::add_pointer_t<std::remove_reference_t<Dest>>>(&s) != nullptr,
|
||||
"xrpl::safeDowncast : reference downcast is valid");
|
||||
#endif
|
||||
return static_cast<Dest>(s); // NOLINT(cppcoreguidelines-pro-type-static-cast-downcast)
|
||||
return *safeDowncast<std::add_pointer_t<std::remove_reference_t<Dest>>>(std::addressof(s));
|
||||
}
|
||||
/** @} */
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -1,47 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdlib>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <typeinfo>
|
||||
|
||||
#ifndef _MSC_VER
|
||||
#include <cxxabi.h>
|
||||
#endif
|
||||
|
||||
namespace beast {
|
||||
|
||||
template <typename T>
|
||||
std::string
|
||||
typeName()
|
||||
{
|
||||
using TR = std::remove_reference_t<T>;
|
||||
|
||||
std::string name = typeid(TR).name();
|
||||
|
||||
#ifndef _MSC_VER
|
||||
if (auto s = abi::__cxa_demangle(name.c_str(), nullptr, nullptr, nullptr))
|
||||
{
|
||||
name = s;
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-no-malloc)
|
||||
std::free(s);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (std::is_const_v<TR>)
|
||||
name += " const";
|
||||
if (std::is_volatile_v<TR>)
|
||||
name += " volatile";
|
||||
if (std::is_lvalue_reference_v<T>)
|
||||
{
|
||||
name += "&";
|
||||
}
|
||||
else if (std::is_rvalue_reference_v<T>)
|
||||
{
|
||||
name += "&&";
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
} // namespace beast
|
||||
@@ -2,26 +2,24 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <compare>
|
||||
#include <concepts>
|
||||
|
||||
namespace beast {
|
||||
|
||||
/**
|
||||
* Zero allows classes to offer efficient comparisons to zero.
|
||||
*
|
||||
* Zero is a struct to allow classes to efficiently compare with zero without
|
||||
* requiring an rvalue construction.
|
||||
*
|
||||
* It's often the case that we have classes which combine a number and a unit.
|
||||
* In such cases, comparisons like t > 0 or t != 0 make sense, but comparisons
|
||||
* like t > 1 or t != 1 do not.
|
||||
* like t > 1 or t != 1 do not. Comparing against kZero expresses exactly that,
|
||||
* without constructing a T.
|
||||
*
|
||||
* The class Zero allows such comparisons to be easily made.
|
||||
*
|
||||
* The comparing class T either needs to have a method called signum() which
|
||||
* returns a positive number, 0, or a negative; or there needs to be a signum
|
||||
* function which resolves in the namespace which takes an instance of T and
|
||||
* returns a positive, zero or negative number.
|
||||
* A type T participates if either `t.signum()` or an unqualified `signum(t)`
|
||||
* found by argument-dependent lookup returns an integer that is negative,
|
||||
* zero, or positive according to the sign of t. Both `t == kZero` and
|
||||
* `kZero == t` work, as do all six relational operators in either order.
|
||||
*/
|
||||
|
||||
struct Zero
|
||||
{
|
||||
explicit Zero() = default;
|
||||
@@ -30,115 +28,44 @@ struct Zero
|
||||
inline constexpr Zero kZero{};
|
||||
|
||||
/**
|
||||
* Default implementation of signum calls the method on the class.
|
||||
* Default implementation of signum: call the member function.
|
||||
*/
|
||||
template <typename T>
|
||||
auto
|
||||
signum(T const& t)
|
||||
template <class T>
|
||||
requires requires(T const& t) {
|
||||
{ t.signum() } -> std::integral;
|
||||
}
|
||||
[[nodiscard]] constexpr auto
|
||||
signum(T const& t) noexcept(noexcept(t.signum()))
|
||||
{
|
||||
return t.signum();
|
||||
}
|
||||
|
||||
namespace detail::zero_helper {
|
||||
namespace detail {
|
||||
|
||||
// For argument dependent lookup to function properly, calls to signum must
|
||||
// be made from a namespace that does not include overloads of the function..
|
||||
/**
|
||||
* A type with a usable signum: either the member-based default above, or a
|
||||
* `signum(t)` overload in T's own namespace, found by ADL. A user overload
|
||||
* that is a better match than the template wins, as usual.
|
||||
*/
|
||||
template <class T>
|
||||
auto
|
||||
callSignum(T const& t)
|
||||
concept HasSignum = requires(T const& t) {
|
||||
{ signum(t) } -> std::integral;
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
template <detail::HasSignum T>
|
||||
[[nodiscard]] constexpr bool
|
||||
operator==(T const& t, Zero) noexcept(noexcept(signum(t)))
|
||||
{
|
||||
return signum(t);
|
||||
return signum(t) == 0;
|
||||
}
|
||||
|
||||
} // namespace detail::zero_helper
|
||||
|
||||
// Handle operators where T is on the left side using signum.
|
||||
|
||||
template <typename T>
|
||||
bool
|
||||
operator==(T const& t, Zero)
|
||||
template <detail::HasSignum T>
|
||||
[[nodiscard]] constexpr std::strong_ordering
|
||||
operator<=>(T const& t, Zero) noexcept(noexcept(signum(t)))
|
||||
{
|
||||
return detail::zero_helper::callSignum(t) == 0;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool
|
||||
operator!=(T const& t, Zero)
|
||||
{
|
||||
return detail::zero_helper::callSignum(t) != 0;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool
|
||||
operator<(T const& t, Zero)
|
||||
{
|
||||
return detail::zero_helper::callSignum(t) < 0;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool
|
||||
operator>(T const& t, Zero)
|
||||
{
|
||||
return detail::zero_helper::callSignum(t) > 0;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool
|
||||
operator>=(T const& t, Zero)
|
||||
{
|
||||
return detail::zero_helper::callSignum(t) >= 0;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool
|
||||
operator<=(T const& t, Zero)
|
||||
{
|
||||
return detail::zero_helper::callSignum(t) <= 0;
|
||||
}
|
||||
|
||||
// Handle operators where T is on the right side by
|
||||
// reversing the operation, so that T is on the left side.
|
||||
|
||||
template <typename T>
|
||||
bool
|
||||
operator==(Zero, T const& t)
|
||||
{
|
||||
return t == kZero;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool
|
||||
operator!=(Zero, T const& t)
|
||||
{
|
||||
return t != kZero;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool
|
||||
operator<(Zero, T const& t)
|
||||
{
|
||||
return t > kZero;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool
|
||||
operator>(Zero, T const& t)
|
||||
{
|
||||
return t < kZero;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool
|
||||
operator>=(Zero, T const& t)
|
||||
{
|
||||
return t <= kZero;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool
|
||||
operator<=(Zero, T const& t)
|
||||
{
|
||||
return t >= kZero;
|
||||
return signum(t) <=> 0;
|
||||
}
|
||||
|
||||
} // namespace beast
|
||||
@@ -1,7 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/basics/safe_cast.h>
|
||||
#include <xrpl/basics/enum_bitops.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/ledger/OwnerCounts.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
@@ -22,80 +22,39 @@
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
// Bitwise flag enum with existing operator overloads
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-use-enum-class)
|
||||
enum ApplyFlags : std::uint32_t {
|
||||
TapNone = 0x00,
|
||||
enum class ApplyFlags : std::uint32_t {
|
||||
None = 0x00,
|
||||
|
||||
// This is a local transaction with the
|
||||
// fail_hard flag set.
|
||||
TapFailHard = 0x10,
|
||||
FailHard = 0x10,
|
||||
|
||||
// This is not the transaction's last pass
|
||||
// Transaction can be retried, soft failures allowed
|
||||
TapRetry = 0x20,
|
||||
Retry = 0x20,
|
||||
|
||||
// Transaction came from a privileged source
|
||||
TapUnlimited = 0x400,
|
||||
Unlimited = 0x400,
|
||||
|
||||
// Transaction is executing as part of a batch
|
||||
TapBatch = 0x800,
|
||||
Batch = 0x800,
|
||||
|
||||
// Transaction shouldn't be applied
|
||||
// Signatures shouldn't be checked
|
||||
TapDryRun = 0x1000,
|
||||
DryRun = 0x1000,
|
||||
|
||||
// Transaction is being preflighted as the payload of a
|
||||
// TransactionProposalCreate. Its signatures are collected on-ledger
|
||||
// afterward, so signature-presence checks (e.g. Batch signer matching)
|
||||
// are skipped at proposal-creation time (On-Chain Cosigner spec
|
||||
// §5.3.1.2).
|
||||
TapProposal = 0x2000
|
||||
Proposal = 0x2000
|
||||
};
|
||||
|
||||
constexpr ApplyFlags
|
||||
operator|(ApplyFlags const& lhs, ApplyFlags const& rhs)
|
||||
template <>
|
||||
struct enum_bitops::OptIn<ApplyFlags> : std::true_type
|
||||
{
|
||||
return safeCast<ApplyFlags>(
|
||||
safeCast<std::underlying_type_t<ApplyFlags>>(lhs) |
|
||||
safeCast<std::underlying_type_t<ApplyFlags>>(rhs));
|
||||
}
|
||||
|
||||
static_assert((TapFailHard | TapRetry) == safeCast<ApplyFlags>(0x30u), "ApplyFlags operator |");
|
||||
static_assert((TapRetry | TapFailHard) == safeCast<ApplyFlags>(0x30u), "ApplyFlags operator |");
|
||||
|
||||
constexpr ApplyFlags
|
||||
operator&(ApplyFlags const& lhs, ApplyFlags const& rhs)
|
||||
{
|
||||
return safeCast<ApplyFlags>(
|
||||
safeCast<std::underlying_type_t<ApplyFlags>>(lhs) &
|
||||
safeCast<std::underlying_type_t<ApplyFlags>>(rhs));
|
||||
}
|
||||
|
||||
static_assert((TapFailHard & TapRetry) == TapNone, "ApplyFlags operator &");
|
||||
static_assert((TapRetry & TapFailHard) == TapNone, "ApplyFlags operator &");
|
||||
|
||||
constexpr ApplyFlags
|
||||
operator~(ApplyFlags const& flags)
|
||||
{
|
||||
return safeCast<ApplyFlags>(~safeCast<std::underlying_type_t<ApplyFlags>>(flags));
|
||||
}
|
||||
|
||||
static_assert(~TapRetry == safeCast<ApplyFlags>(0xFFFFFFDFu), "ApplyFlags operator ~");
|
||||
|
||||
inline ApplyFlags
|
||||
operator|=(ApplyFlags& lhs, ApplyFlags const& rhs)
|
||||
{
|
||||
lhs = lhs | rhs;
|
||||
return lhs;
|
||||
}
|
||||
|
||||
inline ApplyFlags
|
||||
operator&=(ApplyFlags& lhs, ApplyFlags const& rhs)
|
||||
{
|
||||
lhs = lhs & rhs;
|
||||
return lhs;
|
||||
}
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/entries/SLEBase.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STVector256.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <optional>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
@@ -25,6 +31,31 @@ public:
|
||||
: Base(keylet::skip(), view, j)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks up a hash `diff` slots back from the most recent entry in the
|
||||
* sfHashes vector (diff == 0 is the most recent entry).
|
||||
*
|
||||
* Callers decide which skip list entry to read and how to translate a
|
||||
* target ledger sequence into `diff`; this only does the bounds check
|
||||
* and vector indexing shared by both the recent (stride 1) and distant
|
||||
* (stride 256) skip lists.
|
||||
*
|
||||
* @param diff how many slots back from the most recent hash to look up;
|
||||
* 0 is the most recent hash.
|
||||
* @return the hash at that slot, or std::nullopt if the entry does not
|
||||
* exist or `diff` is out of range.
|
||||
*/
|
||||
[[nodiscard]] std::optional<UInt256>
|
||||
hashAt(std::size_t diff) const
|
||||
{
|
||||
if (!this->exists())
|
||||
return std::nullopt;
|
||||
STVector256 const vec = (*this)->getFieldV256(sfHashes);
|
||||
if (vec.size() > diff)
|
||||
return vec[vec.size() - diff - 1];
|
||||
return std::nullopt;
|
||||
}
|
||||
};
|
||||
|
||||
using LedgerHashesEntryR = LedgerHashesEntry<ReadView>;
|
||||
|
||||
@@ -101,22 +101,6 @@ operator<<(std::ostream& os, AccountID const& x)
|
||||
return os;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the global cache used to map AccountID to base58 conversions.
|
||||
*
|
||||
* The cache is optional and need not be initialized. But because conversion
|
||||
* is expensive (it requires a SHA-256 operation) in most cases the overhead
|
||||
* of the cache is worth the benefit.
|
||||
*
|
||||
* @param count The number of entries the cache should accommodate. Zero will
|
||||
* disable the cache, releasing any memory associated with it.
|
||||
*
|
||||
* @note The function will only initialize the cache the first time it is
|
||||
* invoked. Subsequent invocations do nothing.
|
||||
*/
|
||||
void
|
||||
initAccountIdCache(std::size_t count);
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/basics/contract.h>
|
||||
#include <xrpl/beast/type_name.h>
|
||||
#include <xrpl/protocol/SOTemplate.h>
|
||||
|
||||
#include <boost/container/flat_map.hpp>
|
||||
#include <boost/core/type_name.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
@@ -84,7 +84,7 @@ public:
|
||||
* Derived classes will load the object with all the known formats.
|
||||
*/
|
||||
private:
|
||||
KnownFormats() : name_(beast::typeName<Derived>())
|
||||
KnownFormats() : name_(boost::core::type_name<Derived>())
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <xrpl/basics/CountedObject.h>
|
||||
#include <xrpl/basics/UnorderedContainers.h>
|
||||
#include <xrpl/basics/enum_bitops.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
@@ -15,6 +16,7 @@
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
@@ -22,18 +24,8 @@ namespace xrpl {
|
||||
|
||||
class STPathElement final : public CountedObject<STPathElement>
|
||||
{
|
||||
unsigned int type_;
|
||||
AccountID accountID_;
|
||||
PathAsset assetID_;
|
||||
AccountID issuerID_;
|
||||
|
||||
bool isOffer_;
|
||||
std::size_t hashValue_;
|
||||
|
||||
public:
|
||||
// Bitwise values (typeCurrency | typeMPT)
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-use-enum-class)
|
||||
enum Type {
|
||||
enum class Type : std::uint8_t {
|
||||
TypeNone = 0x00,
|
||||
TypeAccount = 0x01, // Rippling through an account (vs taking an offer).
|
||||
TypeCurrency = 0x10, // Currency follows.
|
||||
@@ -45,6 +37,8 @@ public:
|
||||
// Combination of all types.
|
||||
};
|
||||
|
||||
using enum Type;
|
||||
|
||||
STPathElement();
|
||||
STPathElement(STPathElement const&) = default;
|
||||
STPathElement&
|
||||
@@ -62,42 +56,42 @@ public:
|
||||
bool forceAsset = false);
|
||||
|
||||
STPathElement(
|
||||
unsigned int uType,
|
||||
Type type,
|
||||
AccountID const& account,
|
||||
PathAsset const& asset,
|
||||
AccountID const& issuer);
|
||||
|
||||
[[nodiscard]] std::uint32_t
|
||||
getNodeType() const;
|
||||
[[nodiscard]] Type
|
||||
getNodeType() const noexcept;
|
||||
|
||||
[[nodiscard]] bool
|
||||
isOffer() const;
|
||||
isOffer() const noexcept;
|
||||
|
||||
[[nodiscard]] bool
|
||||
isAccount() const;
|
||||
isAccount() const noexcept;
|
||||
|
||||
[[nodiscard]] bool
|
||||
hasIssuer() const;
|
||||
hasIssuer() const noexcept;
|
||||
|
||||
[[nodiscard]] bool
|
||||
hasCurrency() const;
|
||||
hasCurrency() const noexcept;
|
||||
|
||||
[[nodiscard]] bool
|
||||
hasMPT() const;
|
||||
hasMPT() const noexcept;
|
||||
|
||||
[[nodiscard]] bool
|
||||
hasAsset() const;
|
||||
hasAsset() const noexcept;
|
||||
|
||||
[[nodiscard]] bool
|
||||
isNone() const;
|
||||
isNone() const noexcept;
|
||||
|
||||
// Nodes are either an account ID or a offer prefix. Offer prefixs denote a
|
||||
// class of offers.
|
||||
[[nodiscard]] AccountID const&
|
||||
getAccountID() const;
|
||||
getAccountID() const noexcept;
|
||||
|
||||
[[nodiscard]] PathAsset const&
|
||||
getPathAsset() const;
|
||||
getPathAsset() const noexcept;
|
||||
|
||||
[[nodiscard]] Currency const&
|
||||
getCurrency() const;
|
||||
@@ -106,17 +100,35 @@ public:
|
||||
getMPTID() const;
|
||||
|
||||
[[nodiscard]] AccountID const&
|
||||
getIssuerID() const;
|
||||
getIssuerID() const noexcept;
|
||||
|
||||
[[nodiscard]] bool
|
||||
isType(Type const& pe) const;
|
||||
isType(Type pe) const noexcept;
|
||||
|
||||
bool
|
||||
operator==(STPathElement const& t) const;
|
||||
friend bool
|
||||
operator==(STPathElement const& lhs, STPathElement const& rhs) noexcept
|
||||
{
|
||||
return lhs.isType(TypeAccount) == rhs.isType(TypeAccount) &&
|
||||
lhs.hashValue_ == rhs.hashValue_ && lhs.accountID_ == rhs.accountID_ &&
|
||||
lhs.assetID_ == rhs.assetID_ && lhs.issuerID_ == rhs.issuerID_;
|
||||
}
|
||||
|
||||
private:
|
||||
static std::size_t
|
||||
getHash(STPathElement const& element);
|
||||
|
||||
Type type_;
|
||||
AccountID accountID_;
|
||||
PathAsset assetID_;
|
||||
AccountID issuerID_;
|
||||
|
||||
bool isOffer_;
|
||||
std::size_t hashValue_;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct enum_bitops::OptIn<STPathElement::Type> : std::true_type
|
||||
{
|
||||
};
|
||||
|
||||
template <class Hasher>
|
||||
@@ -124,7 +136,7 @@ void
|
||||
hash_append(Hasher& h, STPathElement const& e) noexcept
|
||||
{
|
||||
using beast::hash_append;
|
||||
hash_append(h, (e.getNodeType() & STPathElement::TypeAccount) != 0u);
|
||||
hash_append(h, e.isType(STPathElement::TypeAccount));
|
||||
hash_append(h, e.getAccountID());
|
||||
hash_append(h, e.getPathAsset());
|
||||
hash_append(h, e.getIssuerID());
|
||||
@@ -404,11 +416,11 @@ inline STPathElement::STPathElement(
|
||||
}
|
||||
|
||||
inline STPathElement::STPathElement(
|
||||
unsigned int uType,
|
||||
Type type,
|
||||
AccountID const& account,
|
||||
PathAsset const& asset,
|
||||
AccountID const& issuer)
|
||||
: type_(uType)
|
||||
: type_(type)
|
||||
, accountID_(account)
|
||||
, assetID_(asset)
|
||||
, issuerID_(issuer)
|
||||
@@ -423,56 +435,56 @@ inline STPathElement::STPathElement(
|
||||
hashValue_ = getHash(*this);
|
||||
}
|
||||
|
||||
inline std::uint32_t
|
||||
STPathElement::getNodeType() const
|
||||
inline STPathElement::Type
|
||||
STPathElement::getNodeType() const noexcept
|
||||
{
|
||||
return type_;
|
||||
}
|
||||
|
||||
inline bool
|
||||
STPathElement::isOffer() const
|
||||
STPathElement::isOffer() const noexcept
|
||||
{
|
||||
return isOffer_;
|
||||
}
|
||||
|
||||
inline bool
|
||||
STPathElement::isAccount() const
|
||||
STPathElement::isAccount() const noexcept
|
||||
{
|
||||
return !isOffer();
|
||||
}
|
||||
|
||||
inline bool
|
||||
STPathElement::isType(Type const& pe) const
|
||||
STPathElement::isType(Type pe) const noexcept
|
||||
{
|
||||
return (type_ & pe) != 0u;
|
||||
return (type_ & pe) != STPathElement::TypeNone;
|
||||
}
|
||||
|
||||
inline bool
|
||||
STPathElement::hasIssuer() const
|
||||
STPathElement::hasIssuer() const noexcept
|
||||
{
|
||||
return isType(STPathElement::TypeIssuer);
|
||||
}
|
||||
|
||||
inline bool
|
||||
STPathElement::hasCurrency() const
|
||||
STPathElement::hasCurrency() const noexcept
|
||||
{
|
||||
return isType(STPathElement::TypeCurrency);
|
||||
}
|
||||
|
||||
inline bool
|
||||
STPathElement::hasMPT() const
|
||||
STPathElement::hasMPT() const noexcept
|
||||
{
|
||||
return isType(STPathElement::TypeMpt);
|
||||
}
|
||||
|
||||
inline bool
|
||||
STPathElement::hasAsset() const
|
||||
STPathElement::hasAsset() const noexcept
|
||||
{
|
||||
return isType(STPathElement::TypeAsset);
|
||||
}
|
||||
|
||||
inline bool
|
||||
STPathElement::isNone() const
|
||||
STPathElement::isNone() const noexcept
|
||||
{
|
||||
return getNodeType() == STPathElement::TypeNone;
|
||||
}
|
||||
@@ -480,13 +492,13 @@ STPathElement::isNone() const
|
||||
// Nodes are either an account ID or a offer prefix. Offer prefixs denote a
|
||||
// class of offers.
|
||||
inline AccountID const&
|
||||
STPathElement::getAccountID() const
|
||||
STPathElement::getAccountID() const noexcept
|
||||
{
|
||||
return accountID_;
|
||||
}
|
||||
|
||||
inline PathAsset const&
|
||||
STPathElement::getPathAsset() const
|
||||
STPathElement::getPathAsset() const noexcept
|
||||
{
|
||||
return assetID_;
|
||||
}
|
||||
@@ -504,18 +516,11 @@ STPathElement::getMPTID() const
|
||||
}
|
||||
|
||||
inline AccountID const&
|
||||
STPathElement::getIssuerID() const
|
||||
STPathElement::getIssuerID() const noexcept
|
||||
{
|
||||
return issuerID_;
|
||||
}
|
||||
|
||||
inline bool
|
||||
STPathElement::operator==(STPathElement const& t) const
|
||||
{
|
||||
return (type_ & TypeAccount) == (t.type_ & TypeAccount) && hashValue_ == t.hashValue_ &&
|
||||
accountID_ == t.accountID_ && assetID_ == t.assetID_ && issuerID_ == t.issuerID_;
|
||||
}
|
||||
|
||||
// ------------ STPath ------------
|
||||
|
||||
inline STPath::STPath(std::vector<STPathElement> p) : path_(std::move(p))
|
||||
|
||||
@@ -238,6 +238,12 @@ inline constexpr FlagValue tfUniversalMask = ~tfUniversal;
|
||||
TF_FLAG(tfSponsorshipEnd, 0x00010000) \
|
||||
TF_FLAG(tfSponsorshipCreate, 0x00020000) \
|
||||
TF_FLAG(tfSponsorshipReassign, 0x00040000), \
|
||||
MASK_ADJ(0)) \
|
||||
\
|
||||
TRANSACTION(ConfidentialMPTHolderKeyUpdate, \
|
||||
TF_FLAG(tfHolderKeyRotation, 0x00010000) \
|
||||
TF_FLAG(tfHolderKeyRecovery, 0x00020000) \
|
||||
TF_FLAG(tfCancelRecovery, 0x00040000), \
|
||||
MASK_ADJ(0))
|
||||
|
||||
// clang-format on
|
||||
|
||||
@@ -12,10 +12,12 @@
|
||||
#include <xrpl/protocol/STObject.h>
|
||||
#include <xrpl/protocol/Serializer.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/UintTypes.h>
|
||||
|
||||
#include <boost/container/flat_set.hpp>
|
||||
|
||||
#include <cstdint>
|
||||
#include <flat_set>
|
||||
#include <optional>
|
||||
|
||||
namespace xrpl {
|
||||
@@ -115,6 +117,9 @@ public:
|
||||
parentBatchID_ = id;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::flat_set<MPTID>
|
||||
getAffectedMPTs() const;
|
||||
|
||||
private:
|
||||
UInt256 transactionID_;
|
||||
std::uint32_t ledgerSeq_;
|
||||
|
||||
@@ -439,6 +439,7 @@ LEDGER_ENTRY(ltMPTOKEN, 0x007f, MPToken, mptoken, ({
|
||||
{sfIssuerKeyMirrorEpoch, SoeOptional},
|
||||
{sfAuditorKeyMirrorEpoch, SoeOptional},
|
||||
{sfHolderEncryptionKey, SoeOptional},
|
||||
{sfRecoveryKey, SoeOptional},
|
||||
}))
|
||||
|
||||
/** A ledger object which tracks Oracle
|
||||
|
||||
@@ -330,6 +330,7 @@ TYPED_SFIELD(sfAuditorEncryptionKey, VL, 44)
|
||||
TYPED_SFIELD(sfAmountCommitment, VL, 45)
|
||||
TYPED_SFIELD(sfBalanceCommitment, VL, 46)
|
||||
TYPED_SFIELD(sfBytecode, VL, 47)
|
||||
TYPED_SFIELD(sfRecoveryKey, VL, 48)
|
||||
|
||||
// account (common)
|
||||
TYPED_SFIELD(sfAccount, ACCOUNT, 1)
|
||||
|
||||
@@ -1147,12 +1147,27 @@ TRANSACTION(ttCONFIDENTIAL_MPT_MIRROR_UPDATE, 92, ConfidentialMPTMirrorUpdate,
|
||||
{sfZKProof, SoeRequired},
|
||||
}))
|
||||
|
||||
/** This transaction rotates or recovers a confidential MPT holder's ElGamal encryption key,
|
||||
or cancels a recovery. */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpl/tx/transactors/token/ConfidentialMPTHolderKeyUpdate.h>
|
||||
#endif
|
||||
TRANSACTION(ttCONFIDENTIAL_MPT_HOLDER_KEY_UPDATE, 93, ConfidentialMPTHolderKeyUpdate,
|
||||
({.amendment = featureConfidentialMPTKeyRotation}),
|
||||
({
|
||||
{sfMPTokenIssuanceID, SoeRequired},
|
||||
{sfHolderEncryptionKey, SoeOptional},
|
||||
{sfConfidentialBalanceSpending, SoeOptional},
|
||||
{sfConfidentialBalanceInbox, SoeOptional},
|
||||
{sfZKProof, SoeOptional},
|
||||
}))
|
||||
|
||||
/** This transaction posts an unsigned transaction on-ledger as a
|
||||
TransactionProposal, pending multi-signature collection. */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpl/tx/transactors/proposal/TransactionProposalCreate.h>
|
||||
#endif
|
||||
TRANSACTION(ttTRANSACTION_PROPOSAL_CREATE, 93, TransactionProposalCreate,
|
||||
TRANSACTION(ttTRANSACTION_PROPOSAL_CREATE, 95, TransactionProposalCreate,
|
||||
({.amendment = featureCosign}),
|
||||
({
|
||||
{sfProposedTransaction, SoeRequired},
|
||||
|
||||
@@ -411,6 +411,7 @@ JSS(minimum_level); // out: TxQ
|
||||
JSS(missingCommand); // error
|
||||
JSS(mpt_amount); // out: mpt_holders
|
||||
JSS(mpt_issuance_id); // in: Payment, mpt_holders
|
||||
JSS(mpt_issuances); // in: Subscribe, Unsubscribe
|
||||
JSS(mptoken_index); // out: mpt_holders
|
||||
JSS(mpt_issuance_id_a); // out: BookChanges
|
||||
JSS(mpt_issuance_id_b); // out: BookChanges
|
||||
|
||||
@@ -339,6 +339,30 @@ public:
|
||||
{
|
||||
return this->sle_->isFieldPresent(sfHolderEncryptionKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfRecoveryKey (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_VL::type::value_type>
|
||||
getRecoveryKey() const
|
||||
{
|
||||
if (hasRecoveryKey())
|
||||
return this->sle_->at(sfRecoveryKey);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfRecoveryKey is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasRecoveryKey() const
|
||||
{
|
||||
return this->sle_->isFieldPresent(sfRecoveryKey);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -552,6 +576,17 @@ public:
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfRecoveryKey (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
MPTokenBuilder&
|
||||
setRecoveryKey(std::decay_t<typename SF_VL::type::value_type> const& value)
|
||||
{
|
||||
object_[sfRecoveryKey] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Build and return the completed MPToken wrapper.
|
||||
* @param index The ledger entry index.
|
||||
|
||||
@@ -0,0 +1,279 @@
|
||||
// This file is auto-generated. Do not edit.
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/STParsedJSON.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
#include <xrpl/protocol_autogen/TransactionBase.h>
|
||||
#include <xrpl/protocol_autogen/TransactionBuilderBase.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
|
||||
#include <stdexcept>
|
||||
#include <optional>
|
||||
|
||||
namespace xrpl::transactions {
|
||||
|
||||
class ConfidentialMPTHolderKeyUpdateBuilder;
|
||||
|
||||
/**
|
||||
* @brief Transaction: ConfidentialMPTHolderKeyUpdate
|
||||
*
|
||||
* Type: ttCONFIDENTIAL_MPT_HOLDER_KEY_UPDATE (93)
|
||||
* Delegable: Delegation::NotDelegable
|
||||
* Amendment: featureConfidentialMPTKeyRotation
|
||||
* Privileges: Privilege::NoPriv
|
||||
*
|
||||
* Immutable wrapper around STTx providing type-safe field access.
|
||||
* Use ConfidentialMPTHolderKeyUpdateBuilder to construct new transactions.
|
||||
*/
|
||||
class ConfidentialMPTHolderKeyUpdate : public TransactionBase
|
||||
{
|
||||
public:
|
||||
static constexpr xrpl::TxType txType = ttCONFIDENTIAL_MPT_HOLDER_KEY_UPDATE;
|
||||
|
||||
/**
|
||||
* @brief Construct a ConfidentialMPTHolderKeyUpdate transaction wrapper from an existing STTx object.
|
||||
* @throws std::runtime_error if the transaction type doesn't match.
|
||||
*/
|
||||
explicit ConfidentialMPTHolderKeyUpdate(std::shared_ptr<STTx const> tx)
|
||||
: TransactionBase(std::move(tx))
|
||||
{
|
||||
// Verify transaction type
|
||||
if (tx_->getTxnType() != txType)
|
||||
{
|
||||
throw std::runtime_error("Invalid transaction type for ConfidentialMPTHolderKeyUpdate");
|
||||
}
|
||||
}
|
||||
|
||||
// Transaction-specific field getters
|
||||
|
||||
/**
|
||||
* @brief Get sfMPTokenIssuanceID (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_UINT192::type::value_type
|
||||
getMPTokenIssuanceID() const
|
||||
{
|
||||
return this->tx_->at(sfMPTokenIssuanceID);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfHolderEncryptionKey (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_VL::type::value_type>
|
||||
getHolderEncryptionKey() const
|
||||
{
|
||||
if (hasHolderEncryptionKey())
|
||||
{
|
||||
return this->tx_->at(sfHolderEncryptionKey);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfHolderEncryptionKey is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasHolderEncryptionKey() const
|
||||
{
|
||||
return this->tx_->isFieldPresent(sfHolderEncryptionKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfConfidentialBalanceSpending (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_VL::type::value_type>
|
||||
getConfidentialBalanceSpending() const
|
||||
{
|
||||
if (hasConfidentialBalanceSpending())
|
||||
{
|
||||
return this->tx_->at(sfConfidentialBalanceSpending);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfConfidentialBalanceSpending is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasConfidentialBalanceSpending() const
|
||||
{
|
||||
return this->tx_->isFieldPresent(sfConfidentialBalanceSpending);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfConfidentialBalanceInbox (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_VL::type::value_type>
|
||||
getConfidentialBalanceInbox() const
|
||||
{
|
||||
if (hasConfidentialBalanceInbox())
|
||||
{
|
||||
return this->tx_->at(sfConfidentialBalanceInbox);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfConfidentialBalanceInbox is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasConfidentialBalanceInbox() const
|
||||
{
|
||||
return this->tx_->isFieldPresent(sfConfidentialBalanceInbox);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfZKProof (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_VL::type::value_type>
|
||||
getZKProof() const
|
||||
{
|
||||
if (hasZKProof())
|
||||
{
|
||||
return this->tx_->at(sfZKProof);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfZKProof is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasZKProof() const
|
||||
{
|
||||
return this->tx_->isFieldPresent(sfZKProof);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Builder for ConfidentialMPTHolderKeyUpdate transactions.
|
||||
*
|
||||
* Provides a fluent interface for constructing transactions with method chaining.
|
||||
* Uses STObject internally for flexible transaction construction.
|
||||
* Inherits common field setters from TransactionBuilderBase.
|
||||
*/
|
||||
class ConfidentialMPTHolderKeyUpdateBuilder : public TransactionBuilderBase<ConfidentialMPTHolderKeyUpdateBuilder>
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Construct a new ConfidentialMPTHolderKeyUpdateBuilder with required fields.
|
||||
* @param account The account initiating the transaction.
|
||||
* @param mPTokenIssuanceID The sfMPTokenIssuanceID field value.
|
||||
* @param sequence Optional sequence number for the transaction.
|
||||
* @param fee Optional fee for the transaction.
|
||||
*/
|
||||
ConfidentialMPTHolderKeyUpdateBuilder(SF_ACCOUNT::type::value_type account,
|
||||
std::decay_t<typename SF_UINT192::type::value_type> const& mPTokenIssuanceID, std::optional<SF_UINT32::type::value_type> sequence = std::nullopt,
|
||||
std::optional<SF_AMOUNT::type::value_type> fee = std::nullopt
|
||||
)
|
||||
: TransactionBuilderBase<ConfidentialMPTHolderKeyUpdateBuilder>(ttCONFIDENTIAL_MPT_HOLDER_KEY_UPDATE, account, sequence, fee)
|
||||
{
|
||||
setMPTokenIssuanceID(mPTokenIssuanceID);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Construct a ConfidentialMPTHolderKeyUpdateBuilder from an existing STTx object.
|
||||
* @param tx The existing transaction to copy from.
|
||||
* @throws std::runtime_error if the transaction type doesn't match.
|
||||
*/
|
||||
ConfidentialMPTHolderKeyUpdateBuilder(std::shared_ptr<STTx const> tx)
|
||||
{
|
||||
if (tx->getTxnType() != ttCONFIDENTIAL_MPT_HOLDER_KEY_UPDATE)
|
||||
{
|
||||
throw std::runtime_error("Invalid transaction type for ConfidentialMPTHolderKeyUpdateBuilder");
|
||||
}
|
||||
object_ = *tx;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Transaction-specific field setters
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Set sfMPTokenIssuanceID (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
ConfidentialMPTHolderKeyUpdateBuilder&
|
||||
setMPTokenIssuanceID(std::decay_t<typename SF_UINT192::type::value_type> const& value)
|
||||
{
|
||||
object_[sfMPTokenIssuanceID] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfHolderEncryptionKey (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
ConfidentialMPTHolderKeyUpdateBuilder&
|
||||
setHolderEncryptionKey(std::decay_t<typename SF_VL::type::value_type> const& value)
|
||||
{
|
||||
object_[sfHolderEncryptionKey] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfConfidentialBalanceSpending (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
ConfidentialMPTHolderKeyUpdateBuilder&
|
||||
setConfidentialBalanceSpending(std::decay_t<typename SF_VL::type::value_type> const& value)
|
||||
{
|
||||
object_[sfConfidentialBalanceSpending] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfConfidentialBalanceInbox (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
ConfidentialMPTHolderKeyUpdateBuilder&
|
||||
setConfidentialBalanceInbox(std::decay_t<typename SF_VL::type::value_type> const& value)
|
||||
{
|
||||
object_[sfConfidentialBalanceInbox] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfZKProof (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
ConfidentialMPTHolderKeyUpdateBuilder&
|
||||
setZKProof(std::decay_t<typename SF_VL::type::value_type> const& value)
|
||||
{
|
||||
object_[sfZKProof] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Build and return the ConfidentialMPTHolderKeyUpdate wrapper.
|
||||
* @param publicKey The public key for signing.
|
||||
* @param secretKey The secret key for signing.
|
||||
* @return The constructed transaction wrapper.
|
||||
*/
|
||||
ConfidentialMPTHolderKeyUpdate
|
||||
build(PublicKey const& publicKey, SecretKey const& secretKey)
|
||||
{
|
||||
sign(publicKey, secretKey);
|
||||
return ConfidentialMPTHolderKeyUpdate{std::make_shared<STTx>(std::move(object_))};
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace xrpl::transactions
|
||||
@@ -18,7 +18,7 @@ class TransactionProposalCreateBuilder;
|
||||
/**
|
||||
* @brief Transaction: TransactionProposalCreate
|
||||
*
|
||||
* Type: ttTRANSACTION_PROPOSAL_CREATE (93)
|
||||
* Type: ttTRANSACTION_PROPOSAL_CREATE (95)
|
||||
* Delegable: Delegation::NotDelegable
|
||||
* Amendment: featureCosign
|
||||
* Privileges: Privilege::NoPriv
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Book.h>
|
||||
#include <xrpl/protocol/ErrorCodes.h>
|
||||
#include <xrpl/protocol/UintTypes.h>
|
||||
#include <xrpl/resource/Consumer.h>
|
||||
#include <xrpl/server/Manifest.h>
|
||||
|
||||
@@ -26,11 +27,11 @@ namespace xrpl {
|
||||
/**
|
||||
* Maximum number of subscriptions a single client connection may hold at once.
|
||||
*
|
||||
* Applies to the account, real-time account, and account-history subscriptions
|
||||
* tracked on one InfoSub (the sets counted by totalSubscriptionCount), bounding
|
||||
* the disconnect-time cleanup of those sets. Book subscriptions are tracked
|
||||
* separately (OrderBookDB) and are not counted here. Generous enough for
|
||||
* legitimate power users such as block explorers.
|
||||
* Applies to the account, real-time account, account-history, and MPT issuance
|
||||
* subscriptions tracked on one InfoSub (the sets counted by
|
||||
* totalSubscriptionCount), bounding the disconnect-time cleanup of those sets.
|
||||
* Book subscriptions are tracked separately (OrderBookDB) and are not counted
|
||||
* here. Generous enough for legitimate power users such as block explorers.
|
||||
*/
|
||||
constexpr std::size_t kMaxSubscriptionsPerConnection = 100'000;
|
||||
|
||||
@@ -151,6 +152,21 @@ public:
|
||||
AccountID const& account,
|
||||
bool historyOnly) = 0;
|
||||
|
||||
/**
|
||||
* Remove an MPT issuance subscription during InfoSub teardown.
|
||||
*
|
||||
* Removes only the server-side entry from subMPT_. Does NOT touch
|
||||
* InfoSub::mptSubscriptions_ because the InfoSub is being destroyed.
|
||||
* Called by ~InfoSub() for each issuance in mptSubscriptions_.
|
||||
*
|
||||
* @param uListener The sequence number of the subscriber being torn down.
|
||||
* @param mptID The MPT issuance to remove.
|
||||
*
|
||||
* @note Thread-safety: acquires mptLock_ internally.
|
||||
*/
|
||||
virtual void
|
||||
unsubMPTInternal(std::uint64_t uListener, MPTID const& mptID) = 0;
|
||||
|
||||
/**
|
||||
* Schedule the server-side teardown of a disconnecting connection's
|
||||
* account subscriptions off the destructor thread.
|
||||
@@ -270,6 +286,11 @@ public:
|
||||
virtual bool
|
||||
unsubConsensus(std::uint64_t uListener) = 0;
|
||||
|
||||
virtual void
|
||||
subMPT(InfoSub::Ref ispListener, HashSet<MPTID> const& mptIDs) = 0;
|
||||
virtual void
|
||||
unsubMPT(InfoSub::Ref ispListener, HashSet<MPTID> const& mptIDs) = 0;
|
||||
|
||||
// VFALCO TODO Remove
|
||||
// This was added for one particular partner, it
|
||||
// "pushes" subscription data to a particular URL.
|
||||
@@ -309,9 +330,9 @@ public:
|
||||
* Return the number of subscriptions currently tracked on this
|
||||
* connection.
|
||||
*
|
||||
* The combined size of the per-connection account, real-time account, and
|
||||
* account-history subscription sets. `doSubscribe` reads this to enforce
|
||||
* the per-connection subscription cap before admitting more.
|
||||
* The combined size of the per-connection account, real-time account,
|
||||
* account-history, and MPT issuance subscription sets. `doSubscribe` reads
|
||||
* this to enforce the per-connection subscription cap before admitting more.
|
||||
*
|
||||
* @return The total tracked subscription count for this connection.
|
||||
*
|
||||
@@ -342,6 +363,25 @@ public:
|
||||
HashSet<AccountID> const& normalAccounts,
|
||||
std::size_t cap);
|
||||
|
||||
/**
|
||||
* Enforce the cap and reserve a request's net-new MPT issuances, atomically.
|
||||
*
|
||||
* The MPT analogue of tryReserveAccountSubscriptions: under one hold of
|
||||
* `lock_`, count the net-new issuances, check the total against @p cap, and
|
||||
* insert them only if it fits. All-or-nothing, so a rejected request records
|
||||
* nothing. Doing check and insert together stops two concurrent requests
|
||||
* sharing an InfoSub (the admin subscribe-by-url path) from both passing the
|
||||
* check before either records its issuances. The server-side map is
|
||||
* populated afterwards by subMPT, whose re-insert is a no-op.
|
||||
*
|
||||
* @param mptIDs The MPT issuance ids to reserve.
|
||||
* @param cap The effective per-connection cap.
|
||||
* @return true if reserved; false if the request must be rejected.
|
||||
* @note Thread-safe: takes `lock_`.
|
||||
*/
|
||||
[[nodiscard]] bool
|
||||
tryReserveMPTSubscriptions(HashSet<MPTID> const& mptIDs, std::size_t cap);
|
||||
|
||||
/**
|
||||
* Whether this connection already tracks an account-history for @p account.
|
||||
*
|
||||
@@ -413,12 +453,29 @@ public:
|
||||
[[nodiscard]] unsigned int
|
||||
getApiVersion() const noexcept;
|
||||
|
||||
void
|
||||
insertSubMPTInfo(MPTID const& mptID);
|
||||
|
||||
void
|
||||
deleteSubMPTInfo(MPTID const& mptID);
|
||||
|
||||
protected:
|
||||
// Mutable so the read-only totalSubscriptionCount() accessor can lock it
|
||||
// from a const method; locking semantics are otherwise unchanged.
|
||||
mutable std::mutex lock_;
|
||||
|
||||
private:
|
||||
// The lock type guarding this instance's subscription sets.
|
||||
using ScopedLock = std::scoped_lock<decltype(lock_)>;
|
||||
|
||||
/**
|
||||
* The combined tally the per-connection cap is enforced against.
|
||||
*
|
||||
* @param lock Proof that `lock_` is held; unused otherwise.
|
||||
*/
|
||||
[[nodiscard]] std::size_t
|
||||
subscriptionCount(ScopedLock const& lock) const;
|
||||
|
||||
Consumer consumer_;
|
||||
Source& source_;
|
||||
HashSet<AccountID> realTimeSubscriptions_;
|
||||
@@ -427,6 +484,7 @@ private:
|
||||
std::uint64_t seq_;
|
||||
HashSet<AccountID> accountHistorySubscriptions_;
|
||||
HashSet<Book> bookSubscriptions_;
|
||||
HashSet<MPTID> mptSubscriptions_;
|
||||
unsigned int apiVersion_ = 0;
|
||||
|
||||
static int
|
||||
|
||||
@@ -51,7 +51,8 @@ public:
|
||||
beast::Journal journal = beast::Journal{beast::Journal::getNullSink()})
|
||||
: ApplyContext(registry, base, std::nullopt, tx, preclaimResult, baseFee, flags, journal)
|
||||
{
|
||||
XRPL_ASSERT((flags & TapBatch) == 0, "Batch apply flag should not be set");
|
||||
XRPL_ASSERT(
|
||||
(flags & ApplyFlags::Batch) == ApplyFlags::None, "Batch apply flag should not be set");
|
||||
}
|
||||
|
||||
std::reference_wrapper<ServiceRegistry> registry;
|
||||
|
||||
@@ -59,10 +59,11 @@ public:
|
||||
, parentBatchId(parentBatchId)
|
||||
, j(j)
|
||||
{
|
||||
XRPL_ASSERT((flags & TapBatch) == TapBatch, "Batch apply flag should be set");
|
||||
XRPL_ASSERT(
|
||||
(flags & ApplyFlags::Batch) == ApplyFlags::Batch, "Batch apply flag should be set");
|
||||
XRPL_ASSERT_IF(
|
||||
(flags & TapProposal) != TapNone,
|
||||
(flags & TapDryRun) != TapNone,
|
||||
(flags & ApplyFlags::Proposal) != ApplyFlags::None,
|
||||
(flags & ApplyFlags::DryRun) != ApplyFlags::None,
|
||||
"xrpl::PreflightContext : proposal preflight implies dry run");
|
||||
}
|
||||
|
||||
@@ -74,10 +75,11 @@ public:
|
||||
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
|
||||
: registry(registry), tx(tx), rules(std::move(rules)), flags(flags), j(j)
|
||||
{
|
||||
XRPL_ASSERT((flags & TapBatch) == 0, "Batch apply flag should not be set");
|
||||
XRPL_ASSERT(
|
||||
(flags & ApplyFlags::Batch) == ApplyFlags::None, "Batch apply flag should not be set");
|
||||
XRPL_ASSERT_IF(
|
||||
(flags & TapProposal) != TapNone,
|
||||
(flags & TapDryRun) != TapNone,
|
||||
(flags & ApplyFlags::Proposal) != ApplyFlags::None,
|
||||
(flags & ApplyFlags::DryRun) != ApplyFlags::None,
|
||||
"xrpl::PreflightContext : proposal preflight implies dry run");
|
||||
}
|
||||
|
||||
@@ -116,7 +118,7 @@ public:
|
||||
, j(j)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
parentBatchId.has_value() == ((flags & TapBatch) == TapBatch),
|
||||
parentBatchId.has_value() == ((flags & ApplyFlags::Batch) == ApplyFlags::Batch),
|
||||
"Parent Batch ID should be set if batch apply flag is set");
|
||||
}
|
||||
|
||||
@@ -129,7 +131,8 @@ public:
|
||||
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
|
||||
: PreclaimContext(registry, view, preflightResult, tx, flags, std::nullopt, j)
|
||||
{
|
||||
XRPL_ASSERT((flags & TapBatch) == 0, "Batch apply flag should not be set");
|
||||
XRPL_ASSERT(
|
||||
(flags & ApplyFlags::Batch) == ApplyFlags::None, "Batch apply flag should not be set");
|
||||
}
|
||||
|
||||
PreclaimContext&
|
||||
|
||||
@@ -41,7 +41,7 @@ struct ApplyResult
|
||||
inline bool
|
||||
isTecClaimHardFail(TER ter, ApplyFlags flags)
|
||||
{
|
||||
return isTecClaim(ter) && ((flags & TapRetry) == 0u);
|
||||
return isTecClaim(ter) && ((flags & ApplyFlags::Retry) == ApplyFlags::None);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -46,6 +46,9 @@ class ValidMPTIssuance
|
||||
* MPTokens and RippleStates deleted during apply. finalize() checks each
|
||||
* holder's AccountRoot to detect vault pseudo-account holdings deleted
|
||||
* outside VaultDelete. All these checks are gated on fixCleanup3_2_0.
|
||||
*
|
||||
* Under fixCleanup3_5_0, finalize() also rejects any MPToken erased with
|
||||
* a non-zero sfMPTAmount.
|
||||
*/
|
||||
std::vector<std::shared_ptr<SLE const>> deletedHoldings_;
|
||||
|
||||
@@ -138,8 +141,9 @@ public:
|
||||
* - Convert/ConvertBack symmetry:
|
||||
* Regular MPToken balance change (±X) == COA (Confidential Outstanding Amount) change (∓X)
|
||||
* - Cannot delete MPToken with non-zero confidential state:
|
||||
* Cannot delete if sfIssuerEncryptedBalance exists
|
||||
* Cannot delete if sfConfidentialBalanceInbox and sfConfidentialBalanceSpending exist
|
||||
* Cannot delete if any of sfConfidentialBalanceSpending, sfConfidentialBalanceInbox,
|
||||
* sfIssuerEncryptedBalance or sfAuditorEncryptedBalance is present, and the issuance's
|
||||
* sfConfidentialOutstandingAmount is non-zero. Mirrors MPTokenAuthorize::preclaim.
|
||||
* - Privacy flag consistency:
|
||||
* MPToken confidential balance fields can only be created or changed if
|
||||
* lsfMPTCanHoldConfidentialBalance is set on the issuance.
|
||||
@@ -162,6 +166,8 @@ class ValidConfidentialMPToken
|
||||
std::int64_t outstandingDelta = 0;
|
||||
SLE::const_pointer issuance;
|
||||
bool deletedWithEncrypted = false;
|
||||
// True when an erased MPToken had a non-zero pre-tx public balance.
|
||||
bool deletedWithBalanceBefore = false;
|
||||
bool badConsistency = false;
|
||||
bool badCOA = false;
|
||||
bool changesConfidentialFields = false;
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/core/ServiceRegistry.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
#include <xrpl/tx/ApplyContext.h>
|
||||
#include <xrpl/tx/Transactor.h>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
/**
|
||||
* @brief Allows a confidential MPT holder to rotate or recover their ElGamal
|
||||
* encryption key, or to cancel a pending recovery.
|
||||
*
|
||||
* Submitted by the holder. Exactly one of three modes must be selected via
|
||||
* the transaction flags:
|
||||
*
|
||||
* - Rotation (tfHolderKeyRotation): the holder still has their current
|
||||
* ElGamal private key. They provide a new public key along with their
|
||||
* current spending/inbox balances re-encrypted under that new key. The
|
||||
* holder's encryption key and confidential balances are updated
|
||||
* immediately.
|
||||
*
|
||||
* - Recovery (tfHolderKeyRecovery): the holder has lost their current
|
||||
* private key. They provide a new public key but cannot provide
|
||||
* re-encrypted balances. The new key is recorded as a pending
|
||||
* sfRecoveryKey; the confidential balances are left untouched. Completing
|
||||
* the recovery (rewriting the balances) is done separately by the issuer
|
||||
* via ConfidentialMPTRecoverBalance.
|
||||
*
|
||||
* - Cancel (tfCancelRecovery): the holder revokes a pending recovery
|
||||
* authorization created by a prior Recovery-mode transaction. No key,
|
||||
* balances, or proof are carried; authorization is via the holder's
|
||||
* ordinary XRPL signature. sfRecoveryKey is removed and everything else
|
||||
* is left untouched. Fails if no recovery is pending.
|
||||
*
|
||||
* @note Zero-knowledge proof verification for Rotation and Recovery is
|
||||
* deferred to a follow-up once the mpt-crypto constructions are finalized.
|
||||
*/
|
||||
class ConfidentialMPTHolderKeyUpdate : public Transactor
|
||||
{
|
||||
public:
|
||||
static constexpr auto kConsequencesFactory = ConsequencesFactoryType::Normal;
|
||||
|
||||
explicit ConfidentialMPTHolderKeyUpdate(ApplyContext& ctx) : Transactor(ctx)
|
||||
{
|
||||
}
|
||||
|
||||
static bool
|
||||
checkExtraFeatures(PreflightContext const& ctx);
|
||||
|
||||
static std::uint32_t
|
||||
getFlagsMask(PreflightContext const& ctx);
|
||||
|
||||
static NotTEC
|
||||
preflight(PreflightContext const& ctx);
|
||||
|
||||
static XRPAmount
|
||||
calculateBaseFee(ReadView const& view, STTx const& tx);
|
||||
|
||||
static TER
|
||||
preclaim(PreclaimContext const& ctx);
|
||||
|
||||
TER
|
||||
doApply() override;
|
||||
|
||||
void
|
||||
visitInvariantEntry(bool isDelete, SLE::ConstRef before, SLE::ConstRef after) override;
|
||||
|
||||
[[nodiscard]] bool
|
||||
finalizeInvariants(
|
||||
STTx const& tx,
|
||||
TER result,
|
||||
XRPAmount fee,
|
||||
ReadView const& view,
|
||||
beast::Journal const& j) override;
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -26,6 +26,10 @@ The store paths carry their derivation hash, so they change whenever a tool is
|
||||
rebuilt — a `flake.lock` update generally rewrites most of them even when no
|
||||
version moves. That is deliberate: it makes tooling changes visible in review.
|
||||
|
||||
Tools scoped to a single dev shell rather than to `commonPackages` do not appear
|
||||
here: `check-tools.sh` keys those off `XRPL_DEVSHELL`, and none of the three
|
||||
environments above is such a shell. Adding to one needs no snapshot update.
|
||||
|
||||
## Regenerating
|
||||
|
||||
The two Linux snapshots come from the `nix-ubuntu` image (Docker or a compatible
|
||||
|
||||
+73
-73
@@ -2,144 +2,144 @@ Detected OS: macos (Darwin arm64)
|
||||
|
||||
Core build tools:
|
||||
✅ cmake
|
||||
cmake version 4.1.2
|
||||
/nix/store/gvabsb4yqb5xsqzqph54rijnn4zpihnp-cmake-4.1.2/bin/cmake
|
||||
cmake version 4.4.3
|
||||
/nix/store/q85csxf4s4shx89zif097h1ql4ax1rrz-cmake-4.4.3/bin/cmake
|
||||
✅ conan
|
||||
Conan version 2.28.1
|
||||
/nix/store/9jiyxmkpwmn6dcqs0765s83riw3l5ail-conan-2.28.1/bin/conan
|
||||
Conan version 2.32.0
|
||||
/nix/store/921jqsgbilixmr3xchj95si0jl8pi13z-conan-2.32.0/bin/conan
|
||||
✅ git
|
||||
git version 2.54.0
|
||||
/nix/store/a14yxcqvv9x2l9mllgpirzhvz93pgprg-git-2.54.0/bin/git
|
||||
git version 2.55.0
|
||||
/nix/store/gw7c7m0dwca5lg4152x9lpmxpj171bzw-git-2.55.0/bin/git
|
||||
✅ python3
|
||||
Python 3.13.13
|
||||
/nix/store/ygxqin6ydzjfawywqpp5pal8wv6sf5bh-python3-3.13.13/bin/python3.13
|
||||
Python 3.14.7
|
||||
/nix/store/0kcws5blwbdyxqxjd1w8hjipz61xny3k-python3-3.14.7/bin/python3.14
|
||||
|
||||
Development tooling:
|
||||
✅ ccache
|
||||
ccache version 4.13.6
|
||||
/nix/store/57davyvs6p6dkrl3svzwg1ph18wsy4cz-ccache-4.13.6/bin/ccache
|
||||
/nix/store/ydr5nlzxp1djb5256y0zzqa87vz55gsk-ccache-4.13.6/bin/ccache
|
||||
✅ clang
|
||||
clang version 22.1.7
|
||||
/nix/store/192glrb2cldvziyf3378mzjqbzx3ih4g-clang-wrapper-22.1.7/bin/clang
|
||||
clang version 22.1.8
|
||||
/nix/store/dakcxgwz4ixkk6ps50b7vn3dvblyzsnf-clang-wrapper-22.1.8/bin/clang
|
||||
✅ clang-22
|
||||
clang version 22.1.7
|
||||
/nix/store/rbap7zqq7mw00fyqa02p5rj7gqjp4w5i-clang-22/bin/clang-22
|
||||
clang version 22.1.8
|
||||
/nix/store/w826sqb98nnym6kzackg829664iqdlxj-clang-22/bin/clang-22
|
||||
✅ clang++
|
||||
clang version 22.1.7
|
||||
/nix/store/192glrb2cldvziyf3378mzjqbzx3ih4g-clang-wrapper-22.1.7/bin/clang++
|
||||
clang version 22.1.8
|
||||
/nix/store/dakcxgwz4ixkk6ps50b7vn3dvblyzsnf-clang-wrapper-22.1.8/bin/clang++
|
||||
✅ clang++-22
|
||||
clang version 22.1.7
|
||||
/nix/store/v9haf787f7bcz0mq1sad4bpyx21pj6li-clang++-22/bin/clang++-22
|
||||
clang version 22.1.8
|
||||
/nix/store/wnwyw3yfqrhpxij65bl3pa1x65q2zg3m-clang++-22/bin/clang++-22
|
||||
✅ ClangBuildAnalyzer
|
||||
ClangBuildAnalyzer 1.6.0
|
||||
/nix/store/4l50ds9fa2mkvh7wg8qzrlbmjs12sb8l-clangbuildanalyzer-1.6.0/bin/ClangBuildAnalyzer
|
||||
/nix/store/c7jjnw29ra78jdlzjww2m374izhy05n8-clangbuildanalyzer-1.6.0/bin/ClangBuildAnalyzer
|
||||
✅ curl
|
||||
curl 8.20.0 (aarch64-apple-darwin25.3.0) libcurl/8.20.0 OpenSSL/3.6.2 zlib/1.3.2 libssh2/1.11.1 nghttp2/1.69.0 mit-krb5/1.22.1
|
||||
/nix/store/kclq0czaxvsgh4ym9ld7b6iwy50l1snk-curl-8.20.0-bin/bin/curl
|
||||
curl 8.22.0 (aarch64-apple-darwin25.6.0) libcurl/8.22.0 OpenSSL/3.5.8 zlib/1.3.2 libssh2/1.11.1 nghttp2/1.70.0 mit-krb5/1.22.2
|
||||
/nix/store/pq1h17abqy6q5y6k5dn9qj0fwb89flba-curl-8.22.0-bin/bin/curl
|
||||
✅ file
|
||||
file-5.47
|
||||
/nix/store/dax63li7wwcbqxxkkgzc4g2rx7d4w86x-file-5.47/bin/file
|
||||
file-5.48
|
||||
/nix/store/y44vpziwbzfm8020dzlqqbybyyy7lb6g-file-5.48/bin/file
|
||||
✅ less
|
||||
less 692 (PCRE2 regular expressions)
|
||||
/nix/store/lvr16y75r1pdxpdv0aph5ak2yd0hkvqm-less-692/bin/less
|
||||
less 710 (PCRE2 regular expressions)
|
||||
/nix/store/3yn3dx5fwg5i0inqqzwnxq7f6lwhpjqa-less-710/bin/less
|
||||
✅ make
|
||||
GNU Make 4.4.1
|
||||
/nix/store/8wwiw8pwyhrkzyq28hqzxfl4z84lks81-gnumake-4.4.1/bin/make
|
||||
/nix/store/8l4wgxmhdxfvc9g18y76bnxl1ghgcp7n-gnumake-4.4.1/bin/make
|
||||
✅ netstat
|
||||
present
|
||||
/nix/store/qsd1kzqb0ahrk433vmyl245gp623j19s-network_cmds-730.80.3/bin/netstat
|
||||
/nix/store/57fqvgdav8lrmzndkwm7qrgksl01yj23-network_cmds-741.100.2/bin/netstat
|
||||
✅ ninja
|
||||
1.13.2
|
||||
/nix/store/bqykhrblarkj4fl0hz2mf8ngwfv6x6bz-ninja-1.13.2/bin/ninja
|
||||
/nix/store/infm4vlx71q83bqz0k559w1gn4b0rq4i-ninja-1.13.2/bin/ninja
|
||||
✅ perl
|
||||
v5.42.0
|
||||
/nix/store/js13ri9fvm0ajk1fpd3acigys2a9whdv-perl-5.42.0/bin/perl
|
||||
v5.42.3
|
||||
/nix/store/7rvr6qjfzk2ybzn1imjw6c6qq1n1a9cr-perl-5.42.3/bin/perl
|
||||
✅ pkg-config
|
||||
0.29.2
|
||||
/nix/store/lzrwr375jqhhbca116kja96xf1md83l8-pkg-config-wrapper-0.29.2/bin/pkg-config
|
||||
/nix/store/nhgylvj98klqggy54h7gbc3b2ia4a00y-pkg-config-wrapper-0.29.2/bin/pkg-config
|
||||
✅ vim
|
||||
VIM - Vi IMproved 9.2 (2026 Feb 14, compiled Jan 01 1980 00:00:00)
|
||||
/nix/store/6vbkykg92w603c0sw3mkk7p7mfaawbns-vim-9.2.0389/bin/vim
|
||||
/nix/store/akaa9kqm3b8rlkv61kvsqr2l2j70rvxb-vim-9.2.1001/bin/vim
|
||||
✅ zip
|
||||
Zip 3.0
|
||||
/nix/store/z6ph729vcakbvz3wh8ln1wk6mi06w487-zip-3.0/bin/zip
|
||||
/nix/store/dvawr36npnad4xa6dxi2ss0fw44aa82r-zip-3.0/bin/zip
|
||||
✅ clang-apply-replacements
|
||||
clang-apply-replacements version 22.1.7
|
||||
/nix/store/vzyyjf3cm1hbj9wcr2qcb66x6j98zpy7-clang-tools-22.1.7/bin/clang-apply-replacements
|
||||
clang-apply-replacements version 22.1.8
|
||||
/nix/store/5bij4zn161lagwyndmrr21vmxjg054nv-clang-tools-22.1.8/bin/clang-apply-replacements
|
||||
✅ clang-apply-replacements-22
|
||||
clang-apply-replacements version 22.1.7
|
||||
/nix/store/m3ii69rca4077lf4wlk7m3jcag1fs577-clang-apply-replacements-22/bin/clang-apply-replacements-22
|
||||
clang-apply-replacements version 22.1.8
|
||||
/nix/store/w4bz034k4l0w719rrmbwnqcgyjbswy81-clang-apply-replacements-22/bin/clang-apply-replacements-22
|
||||
✅ clang-format
|
||||
clang-format version 22.1.7
|
||||
/nix/store/vzyyjf3cm1hbj9wcr2qcb66x6j98zpy7-clang-tools-22.1.7/bin/clang-format
|
||||
clang-format version 22.1.8
|
||||
/nix/store/5bij4zn161lagwyndmrr21vmxjg054nv-clang-tools-22.1.8/bin/clang-format
|
||||
✅ clang-format-22
|
||||
clang-format version 22.1.7
|
||||
/nix/store/4fawqy6ngqcsqd2ygyyzm93q0xy3f5gs-clang-format-22/bin/clang-format-22
|
||||
clang-format version 22.1.8
|
||||
/nix/store/w7z346l0r8y36b6lc5i7jzj3w0581l2y-clang-format-22/bin/clang-format-22
|
||||
✅ clang-tidy
|
||||
LLVM version 22.1.7
|
||||
/nix/store/vzyyjf3cm1hbj9wcr2qcb66x6j98zpy7-clang-tools-22.1.7/bin/clang-tidy
|
||||
LLVM version 22.1.8
|
||||
/nix/store/5bij4zn161lagwyndmrr21vmxjg054nv-clang-tools-22.1.8/bin/clang-tidy
|
||||
✅ clang-tidy-22
|
||||
LLVM version 22.1.7
|
||||
/nix/store/jqw4280saixaxxihwdba9ldm2fsm6dr3-clang-tidy-22/bin/clang-tidy-22
|
||||
LLVM version 22.1.8
|
||||
/nix/store/0lv25qd3ddgjm3ndn70q61jfr6krlkl7-clang-tidy-22/bin/clang-tidy-22
|
||||
✅ dot
|
||||
dot - graphviz version 12.2.1 (0)
|
||||
/nix/store/ijb4fbnqa6wzlpqnhb6q9knqpf7qqn5z-graphviz-12.2.1/bin/dot
|
||||
dot - graphviz version 15.1.1 (0)
|
||||
/nix/store/mc99a6bpbk2waym2cnw6ifmxli4ndb4x-graphviz-15.1.1/bin/dot
|
||||
✅ doxygen
|
||||
1.16.1
|
||||
/nix/store/kbryjdpq9jizjb0ws0nzbf2h2ymbdiwm-doxygen-1.16.1/bin/doxygen
|
||||
1.17.0
|
||||
/nix/store/kz43yaj0wg6fx6cbrcmi2442z1ramy0c-doxygen-1.17.0/bin/doxygen
|
||||
✅ gcovr
|
||||
gcovr 8.4
|
||||
/nix/store/wn8jiyh9p0bybs96s4163qp3k8vfmczx-python3.13-gcovr-8.4/bin/gcovr
|
||||
/nix/store/czj1vxsizrbba280gg22cys40qxkqjgr-python3.14-gcovr-8.4/bin/gcovr
|
||||
✅ gh
|
||||
gh version 2.94.0 (nixpkgs)
|
||||
/nix/store/fhnpw0hs0gjms1ha6ap02jq7rx13gkbp-gh-2.94.0/bin/gh
|
||||
gh version 2.102.0 (2026-09-30)
|
||||
/nix/store/s8bahl6zssvry93ihivfmk1l6c58byrr-gh-2.102.0/bin/gh
|
||||
✅ git-cliff
|
||||
git-cliff 2.13.1
|
||||
/nix/store/cy0wwhgxa7yvrz97zydbq6sqmixc90fq-git-cliff-2.13.1/bin/git-cliff
|
||||
git-cliff 2.14.2
|
||||
/nix/store/k4m5yk0qb7hfslwsq1zjajz1d94j12bj-git-cliff-2.14.2/bin/git-cliff
|
||||
✅ git-lfs
|
||||
git-lfs/3.7.1 (3.7.1; darwin arm64; go 1.26.3)
|
||||
/nix/store/k9r7zjfjplqa4d5s71cqvf2iv73jd9mc-git-lfs-3.7.1/bin/git-lfs
|
||||
git-lfs/3.8.0 (3.8.0; darwin arm64; go 1.26.8)
|
||||
/nix/store/wzixjr3yb40a13m2q654hm3bdjn6sal3-git-lfs-3.8.0/bin/git-lfs
|
||||
✅ gpg
|
||||
gpg (GnuPG) 2.4.9
|
||||
/nix/store/cgh6iwzz5jgx9z5whka4vgj210i6npc6-gnupg-2.4.9/bin/gpg
|
||||
/nix/store/0ldfwnwx385i5kh7cyvxx4mkvgjhs5rh-gnupg-2.4.9/bin/gpg
|
||||
✅ pre-commit
|
||||
pre-commit 4.5.1
|
||||
/nix/store/z3cca68620w0w10f090szgzdnmh1waf2-pre-commit-4.5.1/bin/pre-commit
|
||||
pre-commit 4.6.2
|
||||
/nix/store/ivh54xypg9qvd3ha9lis0i8p4nkf8n7a-pre-commit-4.6.2/bin/pre-commit
|
||||
✅ run-clang-tidy
|
||||
usage: run-clang-tidy [-h] [-allow-enabling-alpha-checkers]
|
||||
/nix/store/4x28x911z2f9y7adqlh3qspp4a16dig7-run-clang-tidy/bin/run-clang-tidy
|
||||
/nix/store/3xss1mm3mh1hzcmg182na6ki1p5rnxf1-run-clang-tidy/bin/run-clang-tidy
|
||||
✅ run-clang-tidy-22
|
||||
usage: run-clang-tidy [-h] [-allow-enabling-alpha-checkers]
|
||||
/nix/store/x8iymrh76sk5q91ryg5pa7i32s6gfh34-run-clang-tidy-22/bin/run-clang-tidy-22
|
||||
/nix/store/w68r9z07hcq4fwbfyq3yvc7qxm8aqfbl-run-clang-tidy-22/bin/run-clang-tidy-22
|
||||
|
||||
Rust toolchain:
|
||||
✅ cargo
|
||||
cargo 1.97.1 (c980f4866 2026-06-30)
|
||||
/nix/store/bnfk1sl4s9angb0vj1cj9a5y5zvqinwy-rust-minimal-1.97.1/bin/cargo
|
||||
/nix/store/gklji8yq8yrx1k039c10kwz60vqf2dxj-rust-minimal-1.97.1/bin/cargo
|
||||
✅ cargo-audit
|
||||
cargo-audit-audit 0.22.1
|
||||
/nix/store/snwkga2f5gyf404h7mmp9wriwxb8v65f-cargo-audit-0.22.1/bin/cargo-audit
|
||||
cargo-audit-audit 0.22.2
|
||||
/nix/store/7jgrwj557l91940aj0ag4i924ima1cqn-cargo-audit-0.22.2/bin/cargo-audit
|
||||
✅ cargo-llvm-cov
|
||||
cargo-llvm-cov 0.8.5
|
||||
/nix/store/fpiqdh91gwyxalqp409ynm0s0g086w7w-cargo-llvm-cov-0.8.5/bin/cargo-llvm-cov
|
||||
cargo-llvm-cov 0.9.0
|
||||
/nix/store/5q1h285z8zzkw2qxig9a0b1avcsk1cf0-cargo-llvm-cov-0.9.0/bin/cargo-llvm-cov
|
||||
✅ cargo-nextest
|
||||
cargo-nextest 0.9.137
|
||||
/nix/store/ylz7m947mhkgsp6i7611id3s3gcd58nq-cargo-nextest-0.9.137/bin/cargo-nextest
|
||||
cargo-nextest 0.9.146
|
||||
/nix/store/69v3hx3k597hcxd2zy7yad7fg0aw5yxd-cargo-nextest-0.9.146/bin/cargo-nextest
|
||||
✅ clippy-driver
|
||||
clippy 0.1.97 (8bab26f4f6 2026-07-14)
|
||||
/nix/store/bnfk1sl4s9angb0vj1cj9a5y5zvqinwy-rust-minimal-1.97.1/bin/clippy-driver
|
||||
/nix/store/gklji8yq8yrx1k039c10kwz60vqf2dxj-rust-minimal-1.97.1/bin/clippy-driver
|
||||
✅ 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
|
||||
/nix/store/30rbyk8gx7v53fxqhs4pp0fvxfjycspr-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 1.101.0-nightly (282215592 2026-10-04)
|
||||
/nix/store/a9552rf3pylcnkijc1z0hf1lcnv50djq-rust-nightly/bin/rust-nightly
|
||||
✅ rustc
|
||||
rustc 1.97.1 (8bab26f4f 2026-07-14)
|
||||
/nix/store/bnfk1sl4s9angb0vj1cj9a5y5zvqinwy-rust-minimal-1.97.1/bin/rustc
|
||||
/nix/store/gklji8yq8yrx1k039c10kwz60vqf2dxj-rust-minimal-1.97.1/bin/rustc
|
||||
✅ rustfmt
|
||||
rustfmt 1.9.0-stable (8bab26f4f6 2026-07-14)
|
||||
/nix/store/5ymwgr9jqjz7zzbmj0j5vqbwcd3kp0vm-rustfmt-preview-1.97.1-aarch64-apple-darwin/bin/rustfmt
|
||||
/nix/store/0jmdipyn8266j9kqx2xxm2qkxlrlrjfc-rustfmt-preview-1.97.1-aarch64-apple-darwin/bin/rustfmt
|
||||
|
||||
Skipping git-over-HTTPS check (CHECK_TOOLS_SKIP_CLONE is set).
|
||||
|
||||
|
||||
+2
-1
@@ -21,7 +21,8 @@ let
|
||||
)
|
||||
);
|
||||
|
||||
toolchain = if pkgs.stdenv.isLinux then linux.toolchain else (darwin.toolchain ++ [ darwinEnv ]);
|
||||
toolchain =
|
||||
if pkgs.stdenv.hostPlatform.isLinux then linux.toolchain else (darwin.toolchain ++ [ darwinEnv ]);
|
||||
in
|
||||
{
|
||||
default = pkgs.buildEnv {
|
||||
|
||||
+20
-6
@@ -20,15 +20,16 @@ let
|
||||
|
||||
# Custom-glibc stdenvs, matching the CI environment. darwin has no custom
|
||||
# glibc, so there they fall back to the plain nixpkgs stdenvs.
|
||||
customGccStdenv = if pkgs.stdenv.isLinux then linux.gccStdenv else plainGccStdenv;
|
||||
customClangStdenv = if pkgs.stdenv.isLinux then linux.clangStdenv else plainClangStdenv;
|
||||
customGccStdenv = if pkgs.stdenv.hostPlatform.isLinux then linux.gccStdenv else plainGccStdenv;
|
||||
customClangStdenv =
|
||||
if pkgs.stdenv.hostPlatform.isLinux then linux.clangStdenv else plainClangStdenv;
|
||||
|
||||
# gcov matching each gcc shell, so `-Dcoverage=ON` builds work in the shell.
|
||||
plainGcov = mkGcov {
|
||||
name = "plain";
|
||||
cc = gccPackage.cc;
|
||||
};
|
||||
customGccGcov = if pkgs.stdenv.isLinux then linux.gcov else plainGcov;
|
||||
customGccGcov = if pkgs.stdenv.hostPlatform.isLinux then linux.gcov else plainGcov;
|
||||
|
||||
# Whole directory: init.sh locates the profiles relative to itself.
|
||||
conanDir = ../conan;
|
||||
@@ -51,7 +52,7 @@ let
|
||||
|
||||
# Not sdkEnv: a shell's stdenv already sets that up. Prepended so the stub
|
||||
# beats the nixpkgs libresolv this shell's tooling drags in.
|
||||
darwinLibresolvHook = pkgs.lib.optionalString pkgs.stdenv.isDarwin (
|
||||
darwinLibresolvHook = pkgs.lib.optionalString pkgs.stdenv.hostPlatform.isDarwin (
|
||||
pkgs.lib.concatLines (
|
||||
pkgs.lib.mapAttrsToList (
|
||||
name: value: ''export ${name}="${value} ''${${name}:-}"''
|
||||
@@ -126,7 +127,7 @@ let
|
||||
in
|
||||
rec {
|
||||
# macOS: Nix Clang. Linux: Nix GCC.
|
||||
default = if pkgs.stdenv.isDarwin then clang else gcc;
|
||||
default = if pkgs.stdenv.hostPlatform.isDarwin then clang else gcc;
|
||||
|
||||
# gcc/clang use the custom-glibc toolchain, matching CI. On darwin there is no
|
||||
# custom glibc, so they fall back to the plain nixpkgs toolchain.
|
||||
@@ -147,6 +148,19 @@ rec {
|
||||
versionedTools = clangVersionedTools;
|
||||
};
|
||||
|
||||
# The gcc shell plus the Lean4 formal verification toolchain
|
||||
formal-verification = makeShell {
|
||||
shellName = "formal-verification";
|
||||
stdenv = customGccStdenv;
|
||||
compilerName = "gcc";
|
||||
version = gccVersion;
|
||||
versionedTools = gccVersionedTools;
|
||||
extraPackages = [
|
||||
customGccGcov
|
||||
pkgs.lean4
|
||||
];
|
||||
};
|
||||
|
||||
# Nix provides no compiler; use the one from your system (e.g. Apple Clang).
|
||||
no-compiler = makeShell {
|
||||
shellName = "no-compiler";
|
||||
@@ -158,7 +172,7 @@ rec {
|
||||
# The *-plain shells (stock nixpkgs toolchain) exist only on Linux: on darwin
|
||||
# gcc/clang are already plain, so these would be redundant and are omitted, which
|
||||
# makes `nix develop .#gcc-plain` fail there rather than silently aliasing gcc.
|
||||
// pkgs.lib.optionalAttrs pkgs.stdenv.isLinux {
|
||||
// pkgs.lib.optionalAttrs pkgs.stdenv.hostPlatform.isLinux {
|
||||
gcc-plain = makeShell {
|
||||
shellName = "gcc-plain";
|
||||
stdenv = plainGccStdenv;
|
||||
|
||||
+6
-4
@@ -217,12 +217,13 @@ The `release-info` action decides the channel from the event, and
|
||||
| tag | `X.Y.Z-bN` | `beta` | `deb-beta` | `rpm-beta-hosted` |
|
||||
| tag, any other | `xrpld --version` | `custom` | `deb-custom` | `rpm-custom-hosted` |
|
||||
| push to `develop` | `0.0.0-dev+<hash>` | `develop` | `deb-develop` | `rpm-develop-hosted` |
|
||||
| tag, non-public codebase | _any_ | `private` | `deb-private` | `rpm-private-hosted` |
|
||||
| tag, non-public codebase | _any but `X.Y.Z`_ | `private` | `deb-private` | `rpm-private-hosted` |
|
||||
|
||||
A variant is published to the same channel under its own name, so
|
||||
`xrpld-assert` never overwrites `xrpld`.
|
||||
|
||||
Only a tag picks a release channel. Versions sort in row order, so moving to a
|
||||
Only a tag picks a release channel. A final release, `X.Y.Z`, goes to `stable`
|
||||
even from a non-public codebase. Versions sort in row order, so moving to a
|
||||
more mature channel never downgrades. A tag matching none of the release
|
||||
patterns, such as `X.Y.Z-hotfix1`, publishes to `custom`, which sits outside
|
||||
that order.
|
||||
@@ -284,8 +285,9 @@ checks that the server starts in each image. A tag's images are tagged with the
|
||||
tag name, a develop image as `develop`. With `publish: true`:
|
||||
|
||||
- `xrpld` is pushed to `xrplf/xrpld` on Docker Hub using the `DOCKERHUB_TOKEN`
|
||||
secret, an organization access token for `xrplf`. Private builds are never
|
||||
pushed there.
|
||||
secret, an organization access token for `xrplf`. Builds of a non-public
|
||||
codebase are pushed there only for `stable` releases, whose packages are
|
||||
public too.
|
||||
- `voidstar` replaces `/usr/bin/xrpld` with the binary of the `voidstar` build
|
||||
config, adds `libvoidstar.so` and links the binary into `/symbols`, as
|
||||
Antithesis expects. It is pushed as `xrpld-voidstar` to the Antithesis
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <cerrno>
|
||||
#include <cstddef>
|
||||
#include <filesystem>
|
||||
#include <format>
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
#include <ios>
|
||||
@@ -99,12 +100,13 @@ uniqueRandomPath(
|
||||
if (ec)
|
||||
{
|
||||
Throw<std::runtime_error>(
|
||||
"Unable to check path '" + candidate.string() + "': " + ec.message());
|
||||
std::format("Unable to check path '{}': {}", candidate.string(), ec.message()));
|
||||
}
|
||||
if (!exists)
|
||||
return candidate;
|
||||
}
|
||||
Throw<std::runtime_error>("Unable to generate a unique path under '" + base.string() + "'");
|
||||
Throw<std::runtime_error>(
|
||||
std::format("Unable to generate a unique path under '{}'", base.string()));
|
||||
}
|
||||
|
||||
TempDir::TempDir() : path_(uniqueRandomPath(std::filesystem::temp_directory_path()))
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/ledger/LedgerTiming.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/entries/FeeSettingsEntry.h>
|
||||
#include <xrpl/nodestore/NodeObject.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Fees.h>
|
||||
@@ -560,7 +561,7 @@ Ledger::setup()
|
||||
|
||||
try
|
||||
{
|
||||
if (auto const sle = read(keylet::feeSettings()))
|
||||
if (auto const sle = FeeSettingsEntryR(*this))
|
||||
{
|
||||
bool oldFees = false;
|
||||
bool newFees = false;
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include <boost/container/pmr/monotonic_buffer_resource.hpp>
|
||||
|
||||
#include <cstddef>
|
||||
#include <format>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
@@ -264,7 +265,7 @@ OpenView::rawTxInsert(
|
||||
auto const result = txs_.emplace(
|
||||
std::piecewise_construct, std::forward_as_tuple(key), std::forward_as_tuple(txn, metaData));
|
||||
if (!result.second)
|
||||
Throw<std::logic_error>("rawTxInsert: duplicate TX id: " + to_string(key));
|
||||
Throw<std::logic_error>(std::format("rawTxInsert: duplicate TX id: {}", to_string(key)));
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/entries/LedgerHashesEntry.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/CredentialHelpers.h>
|
||||
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
|
||||
@@ -361,17 +362,16 @@ hashOfSeq(ReadView const& ledger, LedgerIndex seq, beast::Journal journal)
|
||||
if (int const diff = ledger.seq() - seq; diff <= 256)
|
||||
{
|
||||
// Within 256...
|
||||
auto const hashIndex = ledger.read(keylet::skip());
|
||||
LedgerHashesEntryR const hashIndex(ledger, journal);
|
||||
if (hashIndex)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
hashIndex->getFieldU32(sfLastLedgerSequence) == (ledger.seq() - 1),
|
||||
"xrpl::hashOfSeq : matching ledger sequence");
|
||||
STVector256 vec = hashIndex->getFieldV256(sfHashes);
|
||||
if (vec.size() >= diff)
|
||||
return vec[vec.size() - diff];
|
||||
if (auto const hash = hashIndex.hashAt(diff - 1))
|
||||
return hash;
|
||||
JLOG(journal.warn()) << "Ledger " << ledger.seq() << " missing hash for " << seq << " ("
|
||||
<< vec.size() << "," << diff << ")";
|
||||
<< hashIndex->getFieldV256(sfHashes).size() << "," << diff << ")";
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -387,16 +387,14 @@ hashOfSeq(ReadView const& ledger, LedgerIndex seq, beast::Journal journal)
|
||||
}
|
||||
|
||||
// in skiplist
|
||||
auto const hashIndex = ledger.read(keylet::skip(seq));
|
||||
LedgerHashesEntryR const hashIndex(keylet::skip(seq), ledger, journal);
|
||||
if (hashIndex)
|
||||
{
|
||||
auto const lastSeq = hashIndex->getFieldU32(sfLastLedgerSequence);
|
||||
XRPL_ASSERT(lastSeq >= seq, "xrpl::hashOfSeq : minimum last ledger");
|
||||
XRPL_ASSERT((lastSeq & 0xff) == 0, "xrpl::hashOfSeq : valid last ledger");
|
||||
auto const diff = (lastSeq - seq) >> 8;
|
||||
STVector256 vec = hashIndex->getFieldV256(sfHashes);
|
||||
if (vec.size() > diff)
|
||||
return vec[vec.size() - diff - 1];
|
||||
if (auto const hash = hashIndex.hashAt((lastSeq - seq) >> 8))
|
||||
return hash;
|
||||
}
|
||||
JLOG(journal.warn()) << "Can't get seq " << seq << " from " << ledger.seq() << " error";
|
||||
return std::nullopt;
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <format>
|
||||
#include <functional>
|
||||
#include <iterator>
|
||||
#include <memory>
|
||||
@@ -394,9 +395,11 @@ removeToken(ApplyView& view, AccountID const& owner, UInt256 const& nftokenID, S
|
||||
|
||||
if (!page2)
|
||||
{
|
||||
Throw<std::runtime_error>(
|
||||
"page " + to_string(page1->key()) + " has a broken " + field.getName() +
|
||||
" field pointing to " + to_string(*id));
|
||||
Throw<std::runtime_error>(std::format(
|
||||
"page {} has a broken {} field pointing to {}",
|
||||
to_string(page1->key()),
|
||||
field.getName(),
|
||||
to_string(*id)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -597,7 +600,7 @@ removeTokenOffersWithLimit(ApplyView& view, Keylet const& directory, std::size_t
|
||||
else
|
||||
{
|
||||
Throw<std::runtime_error>(
|
||||
"Offer " + to_string(offerIndexes[i]) + " cannot be deleted!");
|
||||
std::format("Offer {} cannot be deleted!", to_string(offerIndexes[i])));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -734,9 +737,9 @@ repairNFTokenDirectoryLinks(ApplyView& view, AccountID const& owner)
|
||||
if (!newPrev)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
Throw<std::runtime_error>(
|
||||
"NFTokenPage directory for " + to_string(owner) +
|
||||
" cannot be repaired. Unexpected link problem.");
|
||||
Throw<std::runtime_error>(std::format(
|
||||
"NFTokenPage directory for {} cannot be repaired. Unexpected link problem.",
|
||||
to_string(owner)));
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
newPrev->at(sfNextPageMin) = nextPage->key();
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
#include <atomic>
|
||||
#include <cstddef>
|
||||
#include <filesystem>
|
||||
#include <format>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
@@ -193,7 +194,7 @@ public:
|
||||
if (!s.ok())
|
||||
{
|
||||
Throw<std::runtime_error>(
|
||||
std::string("Unable to set RocksDB bbt_options: ") + s.ToString());
|
||||
std::format("Unable to set RocksDB bbt_options: {}", s.ToString()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -206,7 +207,7 @@ public:
|
||||
if (!s.ok())
|
||||
{
|
||||
Throw<std::runtime_error>(
|
||||
std::string("Unable to set RocksDB options: ") + s.ToString());
|
||||
std::format("Unable to set RocksDB options: {}", s.ToString()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -241,7 +242,7 @@ public:
|
||||
if (!status.ok() || (localDb == nullptr))
|
||||
{
|
||||
Throw<std::runtime_error>(
|
||||
std::string("Unable to open/create RocksDB: ") + status.ToString());
|
||||
std::format("Unable to open/create RocksDB: {}", status.ToString()));
|
||||
}
|
||||
db.reset(localDb);
|
||||
}
|
||||
@@ -316,8 +317,8 @@ public:
|
||||
}
|
||||
else
|
||||
{
|
||||
status = static_cast<Status>(
|
||||
static_cast<int>(Status::CustomCode) + unsafeCast<int>(getStatus.code()));
|
||||
status = checkedCast<Status>(
|
||||
safeCast<int>(Status::CustomCode) + safeCast<int>(getStatus.code()));
|
||||
|
||||
JLOG(journal.error()) << getStatus.ToString();
|
||||
}
|
||||
@@ -356,7 +357,7 @@ public:
|
||||
auto ret = db->Write(options, &wb);
|
||||
|
||||
if (!ret.ok())
|
||||
Throw<std::runtime_error>("storeBatch failed: " + ret.ToString());
|
||||
Throw<std::runtime_error>(std::format("storeBatch failed: {}", ret.ToString()));
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
#include <xrpl/protocol/digest.h>
|
||||
#include <xrpl/protocol/tokens.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
@@ -19,85 +21,136 @@
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
namespace detail {
|
||||
namespace {
|
||||
|
||||
/**
|
||||
* Caches the base58 representations of AccountIDs
|
||||
/** If true, we implement a cache for account ID lookups.
|
||||
|
||||
This is generally a good idea and can improve performance, especially
|
||||
when serving client requests.
|
||||
|
||||
The cache may generate warnings under some sanitizers; if you want to
|
||||
disable caching, set this variable to false.
|
||||
*/
|
||||
class AccountIdCache
|
||||
constexpr bool cacheAccountIDs = true;
|
||||
|
||||
/** Represents a cached entry for the given AccountID.
|
||||
|
||||
We keep the size and alignment of the structure at 64 bytes to
|
||||
make sure it fits within a cache line and avoid false sharing.
|
||||
*/
|
||||
struct alignas(64) CachedAccountID
|
||||
{
|
||||
private:
|
||||
struct CachedAccountID
|
||||
{
|
||||
AccountID id;
|
||||
char encoding[40] = {0};
|
||||
};
|
||||
/** The sequence lock for the entry.
|
||||
|
||||
// The actual cache
|
||||
std::vector<CachedAccountID> cache_;
|
||||
An even value means that the data is stable, whereas an odd
|
||||
value indicates the data is being actively modified.
|
||||
|
||||
// We use a hash function designed to resist algorithmic complexity attacks
|
||||
HardenedHash<> hasher_;
|
||||
We use a 32-bit value to ensure that the counter will never
|
||||
return to an observed value during a read window.
|
||||
*/
|
||||
std::atomic<std::uint32_t> seq{0};
|
||||
|
||||
// 64 spinlocks, packed into a single 64-bit value
|
||||
std::atomic<std::uint64_t> locks_ = 0;
|
||||
/** The account id. */
|
||||
AccountID id{};
|
||||
|
||||
public:
|
||||
AccountIdCache(std::size_t count) : cache_(count)
|
||||
{
|
||||
// This is non-binding, but we try to avoid wasting memory that
|
||||
// is caused by overallocation.
|
||||
cache_.shrink_to_fit();
|
||||
}
|
||||
/** The length of the cached encoding.
|
||||
|
||||
std::string
|
||||
toBase58(AccountID const& id)
|
||||
{
|
||||
auto const index = hasher_(id) % cache_.size();
|
||||
This field disambiguates collisions between ACCOUNT_ZERO and
|
||||
never-written-to slots, since they would otherwise share the
|
||||
same encoded bits.
|
||||
*/
|
||||
std::uint8_t len = 0;
|
||||
|
||||
PackedSpinlock sl(locks_, index % 64);
|
||||
|
||||
{
|
||||
std::scoped_lock const lock(sl);
|
||||
|
||||
// The check against the first character of the encoding ensures
|
||||
// that we don't mishandle the case of the all-zero account:
|
||||
if (cache_[index].encoding[0] != 0 && cache_[index].id == id)
|
||||
return cache_[index].encoding;
|
||||
}
|
||||
|
||||
auto ret = encodeBase58Token(TokenType::AccountID, id.data(), id.size());
|
||||
|
||||
XRPL_ASSERT(ret.size() <= 38, "xrpl::detail::AccountIdCache : maximum result size");
|
||||
|
||||
{
|
||||
std::scoped_lock const lock(sl);
|
||||
cache_[index].id = id;
|
||||
std::strcpy(cache_[index].encoding, ret.c_str());
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
/** The actual string encoding. */
|
||||
char encoding[39] = {0};
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
static_assert(sizeof(CachedAccountID) == 64 && alignof(CachedAccountID) == 64);
|
||||
|
||||
static std::unique_ptr<detail::AccountIdCache> gAccountIdCache;
|
||||
// The actual cache to use: a simple, direct-mapped, best-effort construct.
|
||||
//
|
||||
// We do not use a hardened hash for this because an AccountID is already
|
||||
// just cryptographically generated (i.e. uniformly distributed) bits and
|
||||
// there is no amplification, since collisions do not carry an additional
|
||||
// cost.
|
||||
//
|
||||
// The size is a "nice" power of 2 because it makes the calculation of an
|
||||
// index easier.
|
||||
constinit std::array<CachedAccountID, 65536> gAccountIdCache{};
|
||||
|
||||
void
|
||||
initAccountIdCache(std::size_t count)
|
||||
{
|
||||
if (!gAccountIdCache && count != 0)
|
||||
gAccountIdCache = std::make_unique<detail::AccountIdCache>(count);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
std::string
|
||||
toBase58(AccountID const& v)
|
||||
{
|
||||
if (gAccountIdCache)
|
||||
return gAccountIdCache->toBase58(v);
|
||||
// Since the input bits are generated by a cryptographically secure
|
||||
// hash construct, we can just pick any subset of them, no muss, no
|
||||
// fuss.
|
||||
auto const index = [&v]() -> std::uint16_t {
|
||||
auto const data = v.data();
|
||||
|
||||
return encodeBase58Token(TokenType::AccountID, v.data(), v.size());
|
||||
return static_cast<std::uint16_t>(data[0]) | (static_cast<std::uint16_t>(data[1]) << 8);
|
||||
}();
|
||||
|
||||
if constexpr (cacheAccountIDs)
|
||||
{
|
||||
auto& slot = gAccountIdCache[index];
|
||||
|
||||
// This is the optimistic, store-free read path: a collision with a
|
||||
// concurrent writer will be detected because the seq recheck fails
|
||||
// and we fall through to the slow path.
|
||||
//
|
||||
// These unsynchronized reads of the slot fields are, formally, data
|
||||
// races and, technically speaking, result in undefined behavior and
|
||||
// a tool like TSAN might flag them. These should not have an impact
|
||||
// on the correctness of the returned data: the sequence lock checks
|
||||
// ensure we discard any data that was in flux.
|
||||
if (auto const s0 = slot.seq.load(std::memory_order::acquire);
|
||||
(s0 & 1) == 0 && slot.len != 0 && slot.id == v)
|
||||
{
|
||||
char buf[38];
|
||||
auto const len = std::min<std::size_t>(slot.len, sizeof(buf));
|
||||
std::memcpy(buf, slot.encoding, len);
|
||||
|
||||
// Prevents the loads above from sinking below the recheck; the
|
||||
// acquire on s0 constrains later operations, not earlier ones.
|
||||
std::atomic_thread_fence(std::memory_order::acquire);
|
||||
|
||||
// Everything read from the slot — the gates above and the bytes in
|
||||
// buf — precedes this recheck and is validated by it. No slot access
|
||||
// may follow it.
|
||||
if (slot.seq.load(std::memory_order::relaxed) == s0)
|
||||
return std::string(buf, len);
|
||||
}
|
||||
}
|
||||
|
||||
auto ret = encodeBase58Token(TokenType::AccountID, v.data(), v.size());
|
||||
|
||||
XRPL_ASSERT(!ret.empty() && ret.size() <= 38, "xrpl::toBase58 : maximum result size");
|
||||
|
||||
if constexpr (cacheAccountIDs)
|
||||
{
|
||||
auto& slot = gAccountIdCache[index];
|
||||
|
||||
// The CAS to an odd value excludes other writers; if it fails, another
|
||||
// thread wrote or is writing into this slot. That is not a problem: we
|
||||
// just don't store our result into the cache this time.
|
||||
if (auto s0 = slot.seq.load(std::memory_order::relaxed); (s0 & 1) == 0 &&
|
||||
slot.seq.compare_exchange_strong(s0, s0 + 1, std::memory_order::acquire))
|
||||
{
|
||||
slot.id = v;
|
||||
slot.len = static_cast<std::uint8_t>(ret.size());
|
||||
std::copy_n(ret.data(), ret.size(), slot.encoding);
|
||||
|
||||
// The acquire on the CAS above keeps the writes from becoming visible
|
||||
// before the seqlock transitions to an odd value, which means readers
|
||||
// can detect an in-progress write. This release publishes them before
|
||||
// the seqlock transitions to an even value.
|
||||
slot.seq.store(s0 + 2, std::memory_order::release);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
template <>
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <format>
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <optional>
|
||||
@@ -105,8 +106,8 @@ Permission::Permission()
|
||||
if (type <= UINT16_MAX)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
Throw<std::logic_error>(
|
||||
"Granular permission value must exceed the maximum uint16_t value: " + name);
|
||||
Throw<std::logic_error>(std::format(
|
||||
"Granular permission value must exceed the maximum uint16_t value: {}", name));
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,8 +43,9 @@ setCurrentTransactionRules(std::optional<Rules> r)
|
||||
auto const range = [&r]() {
|
||||
// If any new conditions with new amendments are added to "enableLargeNumbers", those
|
||||
// amendments must also be added to useRulesGuards.
|
||||
bool const enableLargeNumbers =
|
||||
!r || (r->enabled(featureSingleAssetVault) || r->enabled(featureLendingProtocol));
|
||||
bool const enableLargeNumbers = !r ||
|
||||
(r->enabled(featureSingleAssetVault) || r->enabled(featureLendingProtocol) ||
|
||||
r->enabled(featureMPTokensV2));
|
||||
// If enableLargeNumbers is true, then useRulesGuards must also return true.
|
||||
// However, the reverse is not true. Other amendments can cause the rules guard to be used,
|
||||
// even though large numbers are _not_ used.
|
||||
@@ -84,7 +85,8 @@ useRulesGuards(Rules const& rules)
|
||||
// with createGuards, and any other callers, and the first set of guards can be created directly
|
||||
// at the call site, without using optional.
|
||||
return rules.enabled(featureSingleAssetVault) || rules.enabled(featureLendingProtocol) ||
|
||||
rules.enabled(fixCleanup3_2_0) || rules.enabled(fixCleanup3_3_0);
|
||||
rules.enabled(fixCleanup3_2_0) || rules.enabled(fixCleanup3_3_0) ||
|
||||
rules.enabled(featureMPTokensV2);
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <exception>
|
||||
#include <format>
|
||||
#include <iterator>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
@@ -1193,8 +1194,7 @@ muldiv(std::uint64_t multiplier, std::uint64_t multiplicand, std::uint64_t divis
|
||||
if (ret > std::numeric_limits<std::uint64_t>::max())
|
||||
{
|
||||
Throw<std::overflow_error>(
|
||||
"overflow: (" + std::to_string(multiplier) + " * " + std::to_string(multiplicand) +
|
||||
") / " + std::to_string(divisor));
|
||||
std::format("overflow: ({} * {}) / {}", multiplier, multiplicand, divisor));
|
||||
}
|
||||
|
||||
return static_cast<uint64_t>(ret);
|
||||
@@ -1215,9 +1215,8 @@ muldivRound(
|
||||
|
||||
if (ret > std::numeric_limits<std::uint64_t>::max())
|
||||
{
|
||||
Throw<std::overflow_error>(
|
||||
"overflow: ((" + std::to_string(multiplier) + " * " + std::to_string(multiplicand) +
|
||||
") + " + std::to_string(rounding) + ") / " + std::to_string(divisor));
|
||||
Throw<std::overflow_error>(std::format(
|
||||
"overflow: (({} * {}) + {}) / {}", multiplier, multiplicand, rounding, divisor));
|
||||
}
|
||||
|
||||
return static_cast<uint64_t>(ret);
|
||||
|
||||
@@ -35,9 +35,8 @@ STLedgerEntry::STLedgerEntry(Keylet const& k) : STObject(sfLedgerEntry), key_(k.
|
||||
|
||||
if (format == nullptr)
|
||||
{
|
||||
Throw<std::runtime_error>(
|
||||
"Attempt to create a SLE of unknown type " +
|
||||
std::to_string(safeCast<std::uint16_t>(k.type)));
|
||||
Throw<std::runtime_error>(std::format(
|
||||
"Attempt to create a SLE of unknown type {}", safeCast<std::uint16_t>(k.type)));
|
||||
}
|
||||
|
||||
set(format->getSOTemplate());
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <format>
|
||||
#include <limits>
|
||||
#include <ostream>
|
||||
#include <stdexcept>
|
||||
@@ -170,7 +171,7 @@ partsFromString(std::string const& number)
|
||||
boost::smatch match;
|
||||
|
||||
if (!boost::regex_match(number, match, kReNumber))
|
||||
Throw<std::runtime_error>("'" + number + "' is not a number");
|
||||
Throw<std::runtime_error>(std::format("'{}' is not a number", number));
|
||||
|
||||
// Match fields:
|
||||
// 0 = whole input
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <xrpl/basics/UnorderedContainers.h>
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/basics/contract.h>
|
||||
#include <xrpl/basics/safe_cast.h>
|
||||
#include <xrpl/beast/hash/uhash.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
@@ -17,9 +18,13 @@
|
||||
#include <xrpl/protocol/jss.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <concepts>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
@@ -36,10 +41,10 @@ STPathElement::getHash(STPathElement const& element)
|
||||
// important. We don't even really need to fully hash the whole
|
||||
// base_uint here, as a few bytes would do for our use.
|
||||
//
|
||||
// The note above is only true because the result of this function reaches
|
||||
// nothing but STPathElement::operator==, where it is a fast-reject
|
||||
// prefilter ahead of the field comparisons that decide the answer. Do not
|
||||
// use it to key a container.
|
||||
// The note above is only true because the result of this function is only
|
||||
// used by the STPathElement equality operator as a fast-reject prefilter,
|
||||
// ahead of the field comparisons that then decide the answer. Do not use
|
||||
// it to key a container.
|
||||
|
||||
for (auto const x : element.getAccountID())
|
||||
hashAccount += (hashAccount * 257) ^ x;
|
||||
@@ -106,12 +111,38 @@ STPathSet::operator=(STPathSet const& other)
|
||||
|
||||
STPathSet::STPathSet(SerialIter& sit, SField const& name) : STBase(name)
|
||||
{
|
||||
std::vector<STPathElement> path;
|
||||
for (;;)
|
||||
{
|
||||
int const iType = sit.get8();
|
||||
using enum STPathElement::Type;
|
||||
|
||||
if (iType == STPathElement::TypeNone || iType == STPathElement::TypeBoundary)
|
||||
auto parsePathElementType = [](std::underlying_type_t<STPathElement::Type> byte) noexcept
|
||||
-> std::optional<STPathElement::Type> {
|
||||
if (byte == std::to_underlying(TypeBoundary))
|
||||
return TypeBoundary;
|
||||
|
||||
constexpr auto knownBits = static_cast<std::underlying_type_t<STPathElement::Type>>(
|
||||
std::to_underlying(TypeAccount) | std::to_underlying(TypeCurrency) |
|
||||
std::to_underlying(TypeIssuer) | std::to_underlying(TypeMpt));
|
||||
|
||||
if ((byte & ~knownBits) != 0)
|
||||
return std::nullopt;
|
||||
|
||||
return static_cast<STPathElement::Type>(byte);
|
||||
};
|
||||
|
||||
std::vector<STPathElement> path;
|
||||
|
||||
std::optional<STPathElement::Type> type;
|
||||
|
||||
do
|
||||
{
|
||||
type = parsePathElementType(sit.get8());
|
||||
|
||||
if (!type)
|
||||
{
|
||||
JLOG(debugLog().error()) << "Bad path element in pathset";
|
||||
Throw<std::runtime_error>("bad path element");
|
||||
}
|
||||
|
||||
if (type == TypeNone || type == TypeBoundary)
|
||||
{
|
||||
if (path.empty())
|
||||
{
|
||||
@@ -122,28 +153,11 @@ STPathSet::STPathSet(SerialIter& sit, SField const& name) : STBase(name)
|
||||
// Move rather than converting the vector to an STPath by copy.
|
||||
value_.emplace_back(std::move(path));
|
||||
path.clear();
|
||||
|
||||
if (iType == STPathElement::TypeNone)
|
||||
return;
|
||||
}
|
||||
else if ((iType & ~STPathElement::TypeAll) != 0)
|
||||
{
|
||||
JLOG(debugLog().error()) << "Bad path element " << iType << " in pathset";
|
||||
Throw<std::runtime_error>("bad path element");
|
||||
}
|
||||
else
|
||||
{
|
||||
auto const hasAccount = (iType & STPathElement::TypeAccount) != 0u;
|
||||
auto const hasCurrency = (iType & STPathElement::TypeCurrency) != 0u;
|
||||
auto const hasIssuer = (iType & STPathElement::TypeIssuer) != 0u;
|
||||
auto const hasMPT = (iType & STPathElement::TypeMpt) != 0u;
|
||||
|
||||
AccountID account;
|
||||
PathAsset asset;
|
||||
AccountID issuer;
|
||||
|
||||
if (hasAccount)
|
||||
account = sit.get160();
|
||||
bool const hasCurrency = ((*type & TypeCurrency) == TypeCurrency);
|
||||
bool const hasMPT = ((*type & TypeMpt) == TypeMpt);
|
||||
|
||||
if (hasCurrency && hasMPT)
|
||||
{
|
||||
@@ -151,18 +165,26 @@ STPathSet::STPathSet(SerialIter& sit, SField const& name) : STBase(name)
|
||||
Throw<std::runtime_error>("bad path element: MPT and Currency");
|
||||
}
|
||||
|
||||
AccountID account;
|
||||
AccountID issuer;
|
||||
|
||||
if ((*type & TypeAccount) == TypeAccount)
|
||||
account = sit.get160();
|
||||
|
||||
PathAsset asset;
|
||||
|
||||
if (hasCurrency)
|
||||
asset = Currency::fromRaw(sit.get160());
|
||||
|
||||
if (hasMPT)
|
||||
asset = sit.get192();
|
||||
|
||||
if (hasIssuer)
|
||||
if ((*type & TypeIssuer) == TypeIssuer)
|
||||
issuer = sit.get160();
|
||||
|
||||
path.emplace_back(account, asset, issuer, hasCurrency || hasMPT);
|
||||
}
|
||||
}
|
||||
} while (type != TypeNone);
|
||||
}
|
||||
|
||||
STBase*
|
||||
@@ -216,24 +238,22 @@ STPath::getJson(JsonOptions) const
|
||||
for (auto const& it : path_)
|
||||
{
|
||||
json::Value elem(json::ValueType::Object);
|
||||
auto const iType = it.getNodeType();
|
||||
|
||||
elem[jss::type] = iType;
|
||||
elem[jss::type] = safeCast<std::uint32_t>(it.getNodeType());
|
||||
|
||||
if ((iType & STPathElement::TypeAccount) != 0u)
|
||||
if (it.isType(STPathElement::TypeAccount))
|
||||
elem[jss::account] = to_string(it.getAccountID());
|
||||
|
||||
XRPL_ASSERT(
|
||||
((iType & STPathElement::TypeCurrency) == 0u) ||
|
||||
((iType & STPathElement::TypeMpt) == 0u),
|
||||
!(it.hasCurrency() && it.hasMPT()),
|
||||
"xrpl::STPath::getJson : not type Currency and MPT");
|
||||
if ((iType & STPathElement::TypeCurrency) != 0u)
|
||||
if (it.hasCurrency())
|
||||
elem[jss::currency] = to_string(it.getCurrency());
|
||||
|
||||
if ((iType & STPathElement::TypeMpt) != 0u)
|
||||
if (it.hasMPT())
|
||||
elem[jss::mpt_issuance_id] = to_string(it.getMPTID());
|
||||
|
||||
if ((iType & STPathElement::TypeIssuer) != 0u)
|
||||
if (it.hasIssuer())
|
||||
elem[jss::issuer] = to_string(it.getIssuerID());
|
||||
|
||||
ret.append(elem);
|
||||
@@ -265,34 +285,42 @@ STPathSet::add(Serializer& s) const
|
||||
XRPL_ASSERT(getFName().fieldType == STI_PATHSET, "xrpl::STPathSet::add : valid field type");
|
||||
bool first = true;
|
||||
|
||||
// Workaround, since `Serializer` does not accept strongly-typed enums
|
||||
// yet. When support is added, the static_assert will trigger, and the
|
||||
// lambda can be removed entirely.
|
||||
auto const toByte = [](std::same_as<STPathElement::Type> auto t) noexcept {
|
||||
static_assert(
|
||||
!requires(Serializer& ser) { ser.add8(t); },
|
||||
"Serializer::add8 now accepts STPathElement::Type directly; remove toByte.");
|
||||
return std::to_underlying(t);
|
||||
};
|
||||
|
||||
for (auto const& spPath : value_)
|
||||
{
|
||||
if (!first)
|
||||
s.add8(STPathElement::TypeBoundary);
|
||||
s.add8(toByte(STPathElement::TypeBoundary));
|
||||
|
||||
for (auto const& speElement : spPath)
|
||||
{
|
||||
int const iType = speElement.getNodeType();
|
||||
s.add8(toByte(speElement.getNodeType()));
|
||||
|
||||
s.add8(iType);
|
||||
|
||||
if ((iType & STPathElement::TypeAccount) != 0u)
|
||||
if (speElement.isType(STPathElement::TypeAccount))
|
||||
s.addBitString(speElement.getAccountID());
|
||||
|
||||
if ((iType & STPathElement::TypeMpt) != 0u)
|
||||
if (speElement.hasMPT())
|
||||
s.addBitString(speElement.getMPTID());
|
||||
|
||||
if ((iType & STPathElement::TypeCurrency) != 0u)
|
||||
if (speElement.hasCurrency())
|
||||
s.addBitString(speElement.getCurrency());
|
||||
|
||||
if ((iType & STPathElement::TypeIssuer) != 0u)
|
||||
if (speElement.hasIssuer())
|
||||
s.addBitString(speElement.getIssuerID());
|
||||
}
|
||||
|
||||
first = false;
|
||||
}
|
||||
|
||||
s.add8(STPathElement::TypeNone);
|
||||
s.add8(toByte(STPathElement::TypeNone));
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -59,9 +59,8 @@ getTxFormat(TxType type)
|
||||
|
||||
if (format == nullptr)
|
||||
{
|
||||
Throw<std::runtime_error>(
|
||||
"Invalid transaction type " +
|
||||
std::to_string(safeCast<std::underlying_type_t<TxType>>(type)));
|
||||
Throw<std::runtime_error>(std::format(
|
||||
"Invalid transaction type {}", safeCast<std::underlying_type_t<TxType>>(type)));
|
||||
}
|
||||
|
||||
return format;
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
#include <xrpl/protocol/Serializer.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <format>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
namespace xrpl {
|
||||
@@ -22,7 +22,7 @@ STVector256::STVector256(SerialIter& sit, SField const& name) : STBase(name)
|
||||
if (slice.size() % UInt256::size() != 0)
|
||||
{
|
||||
Throw<std::runtime_error>(
|
||||
"Bad serialization for STVector256: " + std::to_string(slice.size()));
|
||||
std::format("Bad serialization for STVector256: {}", slice.size()));
|
||||
}
|
||||
|
||||
auto const cnt = slice.size() / UInt256::size();
|
||||
|
||||
@@ -70,7 +70,8 @@ STXChainBridge::STXChainBridge(SField const& name, json::Value const& v) : STBas
|
||||
std::string const name = it.memberName();
|
||||
if (!kBridgeJson.isMember(name))
|
||||
{
|
||||
Throw<std::runtime_error>("STXChainBridge extra field detected: " + name);
|
||||
Throw<std::runtime_error>(
|
||||
std::format("STXChainBridge extra field detected: {}", name));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <format>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
|
||||
namespace xrpl {
|
||||
@@ -456,7 +456,7 @@ SerialIter::getFieldID(int& type, int& name)
|
||||
// uncommon type
|
||||
type = get8();
|
||||
if (type < 16)
|
||||
Throw<std::runtime_error>("gFID: uncommon type out of range " + std::to_string(type));
|
||||
Throw<std::runtime_error>(std::format("gFID: uncommon type out of range {}", type));
|
||||
}
|
||||
|
||||
if (name == 0)
|
||||
@@ -464,7 +464,7 @@ SerialIter::getFieldID(int& type, int& name)
|
||||
// uncommon name
|
||||
name = get8();
|
||||
if (name < 16)
|
||||
Throw<std::runtime_error>("gFID: uncommon name out of range " + std::to_string(name));
|
||||
Throw<std::runtime_error>(std::format("gFID: uncommon name out of range {}", name));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
#include <xrpl/basics/contract.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STAccount.h>
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
@@ -13,10 +15,12 @@
|
||||
#include <xrpl/protocol/STObject.h>
|
||||
#include <xrpl/protocol/Serializer.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/UintTypes.h>
|
||||
|
||||
#include <boost/container/flat_set.hpp>
|
||||
|
||||
#include <cstdint>
|
||||
#include <flat_set>
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
|
||||
@@ -145,6 +149,52 @@ TxMeta::getAffectedAccounts() const
|
||||
return list;
|
||||
}
|
||||
|
||||
std::flat_set<MPTID>
|
||||
TxMeta::getAffectedMPTs() const
|
||||
{
|
||||
std::flat_set<MPTID> list;
|
||||
|
||||
for (auto const& it : nodes_)
|
||||
{
|
||||
int const index =
|
||||
it.getFieldIndex((it.getFName() == sfCreatedNode) ? sfNewFields : sfFinalFields);
|
||||
|
||||
if (index != -1)
|
||||
{
|
||||
auto inner = dynamic_cast<STObject const*>(&it.peekAtIndex(index));
|
||||
XRPL_ASSERT(inner, "xrpl::getAffectedMPTs : STObject type cast succeeded");
|
||||
if (inner != nullptr)
|
||||
{
|
||||
// An MPTokenIssuance entry does not store its own issuance id;
|
||||
// the id is derived from the issuer and the sequence that
|
||||
// created the issuance.
|
||||
if (it.getFieldU16(sfLedgerEntryType) == ltMPTOKEN_ISSUANCE)
|
||||
{
|
||||
list.insert(
|
||||
makeMptID(inner->getFieldU32(sfSequence), inner->getAccountID(sfIssuer)));
|
||||
}
|
||||
|
||||
for (auto const& field : *inner)
|
||||
{
|
||||
if (auto mptID = dynamic_cast<STBitString<192> const*>(&field);
|
||||
field.getFName() == sfMPTokenIssuanceID && (mptID != nullptr))
|
||||
{
|
||||
list.insert(mptID->value());
|
||||
}
|
||||
else if (
|
||||
auto amount = dynamic_cast<STAmount const*>(&field);
|
||||
(amount != nullptr) && amount->holds<MPTIssue>())
|
||||
{
|
||||
list.insert(amount->get<MPTIssue>().getMptID());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
STObject&
|
||||
TxMeta::getAffectedNode(SLE::Ref node, SField const& type)
|
||||
{
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
|
||||
#include <soci/sqlite3/soci-sqlite3.h> // IWYU pragma: keep
|
||||
|
||||
#include <format>
|
||||
#include <memory>
|
||||
|
||||
namespace xrpl {
|
||||
@@ -40,8 +41,8 @@ getSociSqliteInit(std::string const& name, std::string const& dir, std::string c
|
||||
{
|
||||
if (name.empty())
|
||||
{
|
||||
Throw<std::runtime_error>(
|
||||
"Sqlite databases must specify a dir and a name. Name: " + name + " Dir: " + dir);
|
||||
Throw<std::runtime_error>(std::format(
|
||||
"Sqlite databases must specify a dir and a name. Name: {} Dir: {}", name, dir));
|
||||
}
|
||||
std::filesystem::path file(dir);
|
||||
if (std::filesystem::is_directory(file))
|
||||
@@ -56,7 +57,7 @@ getSociInit(BasicConfig const& config, std::string const& dbName)
|
||||
auto const backendName = get(section, Keys::kBackend, "sqlite");
|
||||
|
||||
if (backendName != "sqlite")
|
||||
Throw<std::runtime_error>("Unsupported soci backend: " + backendName);
|
||||
Throw<std::runtime_error>(std::format("Unsupported soci backend: {}", backendName));
|
||||
|
||||
auto const path = config.legacy(Sections::kDatabasePath);
|
||||
auto const ext = dbName == "validators" || dbName == "peerfinder" ? ".sqlite" : ".db";
|
||||
@@ -101,7 +102,7 @@ open(soci::session& s, std::string const& beName, std::string const& connectionS
|
||||
}
|
||||
else
|
||||
{
|
||||
Throw<std::runtime_error>("Unsupported soci backend: " + beName);
|
||||
Throw<std::runtime_error>(std::format("Unsupported soci backend: {}", beName));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
#include <xrpl/server/InfoSub.h>
|
||||
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/basics/UnorderedContainers.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Book.h>
|
||||
#include <xrpl/protocol/UintTypes.h>
|
||||
#include <xrpl/resource/Consumer.h>
|
||||
|
||||
#include <cstddef>
|
||||
@@ -42,6 +44,22 @@ safeUnsub(std::uint64_t seq, F&& f, beast::Journal j) noexcept
|
||||
}
|
||||
}
|
||||
|
||||
// Number of requested entries not already tracked in `existing`. Only these are
|
||||
// charged against the cap, so re-subscribing entries a connection already holds
|
||||
// is free.
|
||||
template <typename T>
|
||||
[[nodiscard]] std::size_t
|
||||
countNew(HashSet<T> const& requested, HashSet<T> const& existing)
|
||||
{
|
||||
std::size_t fresh = 0;
|
||||
for (auto const& entry : requested)
|
||||
{
|
||||
if (!existing.contains(entry))
|
||||
++fresh;
|
||||
}
|
||||
return fresh;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// This is the primary interface into the "client" portion of the program.
|
||||
@@ -84,6 +102,16 @@ InfoSub::~InfoSub()
|
||||
safeUnsub(seq_, [&] { source_.unsubPeerStatus(seq_); }, j);
|
||||
safeUnsub(seq_, [&] { source_.unsubConsensus(seq_); }, j);
|
||||
|
||||
// MPT subscriptions are torn down inline here, keyed on seq_, like books
|
||||
// below. The set is capped; each unsubMPTInternal takes mptLock_ for a
|
||||
// single O(1) erase and releases it, so a competing MPT publish can
|
||||
// interleave between erases. Use the internal variant so it does not write
|
||||
// back to mptSubscriptions_ on this partially-destroyed object.
|
||||
for (auto const& mptID : mptSubscriptions_)
|
||||
{
|
||||
safeUnsub(seq_, [&] { source_.unsubMPTInternal(seq_, mptID); }, j);
|
||||
}
|
||||
|
||||
// Book subscriptions are torn down inline here, keyed on seq_, rather than
|
||||
// through the chunked account cleanup below. The book set is not capped, so
|
||||
// it can be large; but each unsubBookInternal takes bookLock_ for a single
|
||||
@@ -145,16 +173,21 @@ InfoSub::onSendEmpty()
|
||||
{
|
||||
}
|
||||
|
||||
std::size_t
|
||||
InfoSub::subscriptionCount(ScopedLock const&) const
|
||||
{
|
||||
return normalSubscriptions_.size() + realTimeSubscriptions_.size() +
|
||||
accountHistorySubscriptions_.size() + mptSubscriptions_.size();
|
||||
}
|
||||
|
||||
std::size_t
|
||||
InfoSub::totalSubscriptionCount() const
|
||||
{
|
||||
// Hold lock_ for the whole read so the three sets cannot be mutated
|
||||
// Hold lock_ for the whole read so the counted sets cannot be mutated
|
||||
// mid-count by a concurrent (un)subscribe on this connection.
|
||||
std::scoped_lock const sl(lock_);
|
||||
|
||||
// Combined tally the per-connection cap is enforced against.
|
||||
return normalSubscriptions_.size() + realTimeSubscriptions_.size() +
|
||||
accountHistorySubscriptions_.size();
|
||||
return subscriptionCount(sl);
|
||||
}
|
||||
|
||||
bool
|
||||
@@ -166,25 +199,10 @@ InfoSub::tryReserveAccountSubscriptions(
|
||||
// One lock hold covers the count, the check and the insert.
|
||||
std::scoped_lock const sl(lock_);
|
||||
|
||||
// Entries not already tracked; re-subscribing held accounts is not charged.
|
||||
auto const countNew = [](HashSet<AccountID> const& requested,
|
||||
HashSet<AccountID> const& existing) {
|
||||
std::size_t fresh = 0;
|
||||
for (auto const& account : requested)
|
||||
{
|
||||
if (!existing.contains(account))
|
||||
++fresh;
|
||||
}
|
||||
return fresh;
|
||||
};
|
||||
|
||||
std::size_t const additional = countNew(proposedAccounts, realTimeSubscriptions_) +
|
||||
countNew(normalAccounts, normalSubscriptions_);
|
||||
|
||||
std::size_t const current = normalSubscriptions_.size() + realTimeSubscriptions_.size() +
|
||||
accountHistorySubscriptions_.size();
|
||||
|
||||
if (exceedsSubscriptionCap(current, additional, cap))
|
||||
if (exceedsSubscriptionCap(subscriptionCount(sl), additional, cap))
|
||||
return false;
|
||||
|
||||
realTimeSubscriptions_.insert(proposedAccounts.begin(), proposedAccounts.end());
|
||||
@@ -192,6 +210,19 @@ InfoSub::tryReserveAccountSubscriptions(
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
InfoSub::tryReserveMPTSubscriptions(HashSet<MPTID> const& mptIDs, std::size_t cap)
|
||||
{
|
||||
// One lock hold covers the count, the check and the insert.
|
||||
std::scoped_lock const sl(lock_);
|
||||
|
||||
if (exceedsSubscriptionCap(subscriptionCount(sl), countNew(mptIDs, mptSubscriptions_), cap))
|
||||
return false;
|
||||
|
||||
mptSubscriptions_.insert(mptIDs.begin(), mptIDs.end());
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
InfoSub::insertSubAccountInfo(AccountID const& account, bool rt)
|
||||
{
|
||||
@@ -288,4 +319,20 @@ InfoSub::getApiVersion() const noexcept
|
||||
return apiVersion_;
|
||||
}
|
||||
|
||||
void
|
||||
InfoSub::insertSubMPTInfo(MPTID const& mptID)
|
||||
{
|
||||
std::scoped_lock const sl(lock_);
|
||||
|
||||
mptSubscriptions_.insert(mptID);
|
||||
}
|
||||
|
||||
void
|
||||
InfoSub::deleteSubMPTInfo(MPTID const& mptID)
|
||||
{
|
||||
std::scoped_lock const sl(lock_);
|
||||
|
||||
mptSubscriptions_.erase(mptID);
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -26,6 +26,7 @@
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <exception>
|
||||
#include <format>
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <mutex>
|
||||
@@ -48,7 +49,7 @@ to_string(Manifest const& m)
|
||||
return "Revocation Manifest " + mk;
|
||||
|
||||
if (!m.signingKey)
|
||||
Throw<std::runtime_error>("No SigningKey in manifest " + mk);
|
||||
Throw<std::runtime_error>(std::format("No SigningKey in manifest {}", mk));
|
||||
|
||||
return "Manifest " + mk + " (" + std::to_string(m.sequence) + ": " +
|
||||
toBase58(TokenType::NodePublic, *m.signingKey) + ")";
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <format>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
@@ -107,10 +108,10 @@ SHAMapNodeID::getChildNodeID(unsigned int branch) const
|
||||
depth_ <= SHAMap::kLeafDepth, "xrpl::SHAMapNodeID::getChildNodeID : maximum leaf depth");
|
||||
|
||||
if (depth_ >= SHAMap::kLeafDepth)
|
||||
Throw<std::logic_error>("Request for child node ID of " + to_string(*this));
|
||||
Throw<std::logic_error>(std::format("Request for child node ID of {}", to_string(*this)));
|
||||
|
||||
if (!isPrefixOf(id_))
|
||||
Throw<std::logic_error>("Incorrect mask for " + to_string(*this));
|
||||
Throw<std::logic_error>(std::format("Incorrect mask for {}", to_string(*this)));
|
||||
|
||||
SHAMapNodeID node{depth_ + 1, id_};
|
||||
node.id_.begin()[depth_ / 2] |= ((depth_ & 1) != 0u) ? branch : (branch << 4);
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include <xrpl/shamap/SHAMapTxPlusMetaLeafNode.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <format>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
@@ -30,9 +31,8 @@ SHAMapTreeNode::makeTransaction(Slice data, SHAMapHash const& hash, bool hashVal
|
||||
{
|
||||
if (data.size() < kMinShaMapItemBytes)
|
||||
{
|
||||
Throw<std::runtime_error>(
|
||||
"Short TXN node: " + std::to_string(data.size()) + " bytes (minimum " +
|
||||
std::to_string(kMinShaMapItemBytes) + " required)");
|
||||
Throw<std::runtime_error>(std::format(
|
||||
"Short TXN node: {} bytes (minimum {} required)", data.size(), kMinShaMapItemBytes));
|
||||
}
|
||||
|
||||
auto item = makeShamapitem(sha512Half(HashPrefix::TransactionId, data), data);
|
||||
@@ -52,27 +52,25 @@ SHAMapTreeNode::makeTransactionWithMeta(Slice data, SHAMapHash const& hash, bool
|
||||
|
||||
if (s.size() < tag.kBytes)
|
||||
{
|
||||
Throw<std::runtime_error>(
|
||||
"Short TXN+MD node: " + std::to_string(s.size()) + " bytes (minimum " +
|
||||
std::to_string(tag.kBytes) + " required for tag)");
|
||||
Throw<std::runtime_error>(std::format(
|
||||
"Short TXN+MD node: {} bytes (minimum {} required for tag)", s.size(), tag.kBytes));
|
||||
}
|
||||
|
||||
// FIXME: improve this interface so that the above check isn't needed
|
||||
if (!s.getBitString(tag, s.size() - tag.kBytes))
|
||||
{
|
||||
Throw<std::out_of_range>(
|
||||
"Short TXN+MD node: failed to read tag at offset " +
|
||||
std::to_string(s.size() - tag.kBytes));
|
||||
Throw<std::out_of_range>(std::format(
|
||||
"Short TXN+MD node: failed to read tag at offset {}", s.size() - tag.kBytes));
|
||||
}
|
||||
|
||||
s.chop(tag.kBytes);
|
||||
|
||||
if (s.size() < kMinShaMapItemBytes)
|
||||
{
|
||||
Throw<std::runtime_error>(
|
||||
"Short TXN+MD node: " + std::to_string(s.size()) +
|
||||
" bytes after tag removal (minimum " + std::to_string(kMinShaMapItemBytes) +
|
||||
" required)");
|
||||
Throw<std::runtime_error>(std::format(
|
||||
"Short TXN+MD node: {} bytes after tag removal (minimum {} required)",
|
||||
s.size(),
|
||||
kMinShaMapItemBytes));
|
||||
}
|
||||
|
||||
auto item = makeShamapitem(tag, s.slice());
|
||||
@@ -92,16 +90,15 @@ SHAMapTreeNode::makeAccountState(Slice data, SHAMapHash const& hash, bool hashVa
|
||||
|
||||
if (s.size() < tag.kBytes)
|
||||
{
|
||||
Throw<std::runtime_error>(
|
||||
"Short AS node: " + std::to_string(s.size()) + " bytes (minimum " +
|
||||
std::to_string(tag.kBytes) + " required for tag)");
|
||||
Throw<std::runtime_error>(std::format(
|
||||
"Short AS node: {} bytes (minimum {} required for tag)", s.size(), tag.kBytes));
|
||||
}
|
||||
|
||||
// FIXME: improve this interface so that the above check isn't needed
|
||||
if (!s.getBitString(tag, s.size() - tag.kBytes))
|
||||
{
|
||||
Throw<std::out_of_range>(
|
||||
"Short AS node: failed to read tag at offset " + std::to_string(s.size() - tag.kBytes));
|
||||
std::format("Short AS node: failed to read tag at offset {}", s.size() - tag.kBytes));
|
||||
}
|
||||
|
||||
s.chop(tag.kBytes);
|
||||
@@ -111,9 +108,10 @@ SHAMapTreeNode::makeAccountState(Slice data, SHAMapHash const& hash, bool hashVa
|
||||
|
||||
if (s.size() < kMinShaMapItemBytes)
|
||||
{
|
||||
Throw<std::runtime_error>(
|
||||
"Short AS node: " + std::to_string(s.size()) + " bytes after tag removal (minimum " +
|
||||
std::to_string(kMinShaMapItemBytes) + " required)");
|
||||
Throw<std::runtime_error>(std::format(
|
||||
"Short AS node: {} bytes after tag removal (minimum {} required)",
|
||||
s.size(),
|
||||
kMinShaMapItemBytes));
|
||||
}
|
||||
|
||||
auto item = makeShamapitem(tag, s.slice());
|
||||
@@ -152,7 +150,7 @@ SHAMapTreeNode::makeFromWire(Slice rawNode)
|
||||
if (type == kWireTypeTransactionWithMeta)
|
||||
return makeTransactionWithMeta(rawNode, hash, hashValid);
|
||||
|
||||
Throw<std::runtime_error>("wire: Unknown type (" + std::to_string(type) + ")");
|
||||
Throw<std::runtime_error>(std::format("wire: Unknown type ({})", type));
|
||||
}
|
||||
|
||||
SHAMapTreeNodePtr
|
||||
@@ -183,9 +181,8 @@ SHAMapTreeNode::makeFromPrefix(Slice rawNode, SHAMapHash const& hash)
|
||||
if (type == HashPrefix::TxNode)
|
||||
return makeTransactionWithMeta(rawNode, hash, hashValid);
|
||||
|
||||
Throw<std::runtime_error>(
|
||||
"prefix: unknown type (" +
|
||||
std::to_string(safeCast<std::underlying_type_t<HashPrefix>>(type)) + ")");
|
||||
Throw<std::runtime_error>(std::format(
|
||||
"prefix: unknown type ({})", safeCast<std::underlying_type_t<HashPrefix>>(type)));
|
||||
}
|
||||
|
||||
std::string
|
||||
|
||||
@@ -38,7 +38,7 @@ ApplyContext::ApplyContext(
|
||||
, parentBatchId_(parentBatchId)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
parentBatchId.has_value() == ((flags_ & TapBatch) == TapBatch),
|
||||
parentBatchId.has_value() == ((flags_ & ApplyFlags::Batch) == ApplyFlags::Batch),
|
||||
"Parent Batch ID should be set if batch apply flag is set");
|
||||
view_.emplace(&base_, flags_);
|
||||
}
|
||||
@@ -53,13 +53,14 @@ std::optional<TxMeta>
|
||||
ApplyContext::apply(TER ter)
|
||||
{
|
||||
// NOLINTNEXTLINE(bugprone-unchecked-optional-access) view_ emplaced in constructor
|
||||
return view_->apply(base_, tx, ter, parentBatchId_, (flags_ & TapDryRun) != 0u, journal);
|
||||
return view_->apply(
|
||||
base_, tx, ter, parentBatchId_, (flags_ & ApplyFlags::DryRun) != ApplyFlags::None, journal);
|
||||
}
|
||||
|
||||
void
|
||||
ApplyContext::addOrderBook(Book const& book)
|
||||
{
|
||||
if ((flags_ & TapDryRun) == TapNone)
|
||||
if ((flags_ & ApplyFlags::DryRun) == ApplyFlags::None)
|
||||
registry.get().getOrderBookDB().addOrderBook(book);
|
||||
}
|
||||
|
||||
|
||||
@@ -134,7 +134,7 @@ preflightCheckSigningKey(STObject const& sigObject, beast::Journal j)
|
||||
std::optional<NotTEC>
|
||||
preflightCheckSimulateKeys(ApplyFlags flags, STObject const& sigObject, beast::Journal j)
|
||||
{
|
||||
if ((flags & TapDryRun) != 0u) // simulation
|
||||
if ((flags & ApplyFlags::DryRun) != ApplyFlags::None) // simulation
|
||||
{
|
||||
std::optional<Slice> const signature = sigObject[~sfTxnSignature];
|
||||
if (signature && !signature->empty())
|
||||
@@ -510,7 +510,8 @@ Transactor::minimumFee(
|
||||
Fees const& fees,
|
||||
ApplyFlags flags)
|
||||
{
|
||||
return scaleFeeLoad(baseFee, registry.getFeeTrack(), fees, (flags & TapUnlimited) != 0u);
|
||||
return scaleFeeLoad(
|
||||
baseFee, registry.getFeeTrack(), fees, (flags & ApplyFlags::Unlimited) != ApplyFlags::None);
|
||||
}
|
||||
|
||||
TER
|
||||
@@ -521,7 +522,7 @@ Transactor::checkFee(PreclaimContext const& ctx, XRPAmount baseFee)
|
||||
|
||||
auto const feePaid = ctx.tx[sfFee].xrp();
|
||||
|
||||
if ((ctx.flags & TapBatch) != 0u)
|
||||
if ((ctx.flags & ApplyFlags::Batch) != ApplyFlags::None)
|
||||
{
|
||||
if (feePaid == beast::kZero)
|
||||
return tesSUCCESS;
|
||||
@@ -951,7 +952,8 @@ Transactor::checkSign(
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
if (((flags & TapDryRun) != 0u) && pkSigner.empty() && !sigObject.isFieldPresent(sfSigners))
|
||||
if (((flags & ApplyFlags::DryRun) != ApplyFlags::None) && pkSigner.empty() &&
|
||||
!sigObject.isFieldPresent(sfSigners))
|
||||
{
|
||||
// simulate: skip signature validation when neither SigningPubKey nor
|
||||
// Signers are provided
|
||||
@@ -1122,7 +1124,7 @@ Transactor::checkMultiSign(
|
||||
}
|
||||
|
||||
XRPL_ASSERT(
|
||||
(flags & TapDryRun) || !spk.empty(),
|
||||
(flags & ApplyFlags::DryRun) != ApplyFlags::None || !spk.empty(),
|
||||
"xrpl::Transactor::checkMultiSign : non-empty signer or "
|
||||
"simulation");
|
||||
AccountID const signingAcctIDFromPubKey =
|
||||
@@ -1604,7 +1606,7 @@ Transactor::operator()()
|
||||
if (ctx_.size() > kOversizeMetaDataCap)
|
||||
result = tecOVERSIZE;
|
||||
|
||||
if (isTecClaim(result) && ((view().flags() & TapFailHard) != 0u))
|
||||
if (isTecClaim(result) && ((view().flags() & ApplyFlags::FailHard) != ApplyFlags::None))
|
||||
{
|
||||
// If the TapFailHard flag is set, a tec result
|
||||
// must not do anything
|
||||
@@ -1684,7 +1686,7 @@ Transactor::operator()()
|
||||
// Once we call apply, we will no longer be able to look at view()
|
||||
metadata = ctx_.apply(result);
|
||||
|
||||
if ((ctx_.flags() & TapDryRun) != 0u)
|
||||
if ((ctx_.flags() & ApplyFlags::DryRun) != ApplyFlags::None)
|
||||
return logger(result, false, std::move(metadata));
|
||||
|
||||
return logger(result, canApply, std::move(metadata));
|
||||
|
||||
@@ -215,7 +215,7 @@ applyBatchTransactions(
|
||||
auto applyOneTransaction = [®istry, &j, &parentBatchId, &batchView](STTx const& tx) {
|
||||
OpenView perTxBatchView(kBatchView, batchView);
|
||||
|
||||
auto const ret = apply(registry, perTxBatchView, parentBatchId, tx, TapBatch, j);
|
||||
auto const ret = apply(registry, perTxBatchView, parentBatchId, tx, ApplyFlags::Batch, j);
|
||||
XRPL_ASSERT(
|
||||
ret.applied == (isTesSuccess(ret.ter) || isTecClaim(ret.ter)),
|
||||
"Inner transaction should not be applied");
|
||||
@@ -274,7 +274,7 @@ applyTransaction(
|
||||
{
|
||||
// Returns false if the transaction has need not be retried.
|
||||
if (retryAssured)
|
||||
flags = flags | TapRetry;
|
||||
flags = flags | ApplyFlags::Retry;
|
||||
|
||||
JLOG(j.debug()) << "TXN " << txn.getTransactionID() << (retryAssured ? "/retry" : "/final");
|
||||
|
||||
|
||||
@@ -346,7 +346,8 @@ preflight(
|
||||
// builds; re-check the same invariant here so a release build can't
|
||||
// silently skip a proposed transaction's signature-presence checks
|
||||
// outside of a dry run.
|
||||
if ((flags & TapProposal) != TapNone && (flags & TapDryRun) == TapNone)
|
||||
if ((flags & ApplyFlags::Proposal) != ApplyFlags::None &&
|
||||
(flags & ApplyFlags::DryRun) == ApplyFlags::None)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j.fatal()) << "apply (preflight): TapProposal set without TapDryRun.";
|
||||
@@ -377,7 +378,8 @@ preflight(
|
||||
PreflightContext const pfCtx(registry, tx, parentBatchId, rules, flags, j);
|
||||
|
||||
// See the comment in the other preflight() overload above.
|
||||
if ((flags & TapProposal) != TapNone && (flags & TapDryRun) == TapNone)
|
||||
if ((flags & ApplyFlags::Proposal) != ApplyFlags::None &&
|
||||
(flags & ApplyFlags::DryRun) == ApplyFlags::None)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j.fatal()) << "apply (preflight): TapProposal set without TapDryRun.";
|
||||
|
||||
@@ -41,6 +41,7 @@ constexpr auto kConfidentialMptTxTypes = std::to_array<TxType>({
|
||||
ttCONFIDENTIAL_MPT_MERGE_INBOX,
|
||||
ttCONFIDENTIAL_MPT_CLAWBACK,
|
||||
ttCONFIDENTIAL_MPT_MIRROR_UPDATE,
|
||||
ttCONFIDENTIAL_MPT_HOLDER_KEY_UPDATE,
|
||||
});
|
||||
|
||||
// Clamp to the cap (== INT64_MAX) before the signed conversion. Invariant
|
||||
@@ -73,7 +74,7 @@ ValidMPTIssuance::visitEntry(bool isDelete, SLE::ConstRef before, SLE::ConstRef
|
||||
// only meaningful post-fixCleanup3_2_0 (the field is never set
|
||||
// pre-amendment, and the holding-deletion rule does not apply).
|
||||
// Skip both blocks when the amendment is off so we avoid wasted work
|
||||
// on the hot path.
|
||||
// on the hot path, except where noted for fixCleanup3_5_0 below.
|
||||
bool const fix320Enabled = isFeatureEnabled(fixCleanup3_2_0);
|
||||
|
||||
if (after && after->getType() == ltMPTOKEN_ISSUANCE)
|
||||
@@ -108,7 +109,10 @@ ValidMPTIssuance::visitEntry(bool isDelete, SLE::ConstRef before, SLE::ConstRef
|
||||
if (isDelete)
|
||||
{
|
||||
mptokensDeleted_++;
|
||||
if (fix320Enabled)
|
||||
// deletedHoldings_ also feeds finalize()'s erase-time public
|
||||
// balance check, gated on fixCleanup3_5_0 independently of
|
||||
// fixCleanup3_2_0.
|
||||
if (fix320Enabled || isFeatureEnabled(fixCleanup3_5_0))
|
||||
deletedHoldings_.push_back(after);
|
||||
}
|
||||
else if (!before)
|
||||
@@ -195,6 +199,19 @@ ValidMPTIssuance::finalize(
|
||||
return false;
|
||||
}
|
||||
|
||||
// Deleting an MPToken with a non-zero MPTAmount is rejected.
|
||||
if (rules.enabled(fixCleanup3_5_0))
|
||||
{
|
||||
for (auto const& sleHolding : deletedHoldings_)
|
||||
{
|
||||
if (sleHolding->getType() == ltMPTOKEN && sleHolding->getFieldU64(sfMPTAmount) > 0)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: MPToken deleted with non-zero balance";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isTesSuccess(result) || (mptV2Enabled && result == tecINCOMPLETE))
|
||||
{
|
||||
[[maybe_unused]]
|
||||
@@ -579,16 +596,27 @@ ValidConfidentialMPToken::visitEntry(
|
||||
change.mptAmountDelta =
|
||||
subtractMPTAmountDelta(change.mptAmountDelta, before->getFieldU64(sfMPTAmount));
|
||||
|
||||
// Cannot delete MPToken with non-zero confidential state or non-zero public amount
|
||||
// Cannot delete MPToken with non-zero confidential state.
|
||||
if (isDelete)
|
||||
{
|
||||
bool const hasPublicBalance = before->getFieldU64(sfMPTAmount) > 0;
|
||||
bool const hasEncryptedFields = before->isFieldPresent(sfConfidentialBalanceSpending) ||
|
||||
// changes_ is keyed by issuance, so sibling holders erased by the
|
||||
// same transaction share this entry. Only ever set these flags,
|
||||
// never clear them, or an empty sibling visited later would mask
|
||||
// a funded MPToken.
|
||||
|
||||
// Retired by fixCleanup3_5_0, which moved the public balance
|
||||
// check to ValidMPTIssuance::finalize. Kept pre-amendment for
|
||||
// consensus safety: a non-zero public balance used to feed the
|
||||
// confidential gate below, rejecting the erase whenever the
|
||||
// issuance's COA was non-zero, and already-validated ledgers
|
||||
// depend on that.
|
||||
if (!isFeatureEnabled(fixCleanup3_5_0) && before->getFieldU64(sfMPTAmount) > 0)
|
||||
changes_[id].deletedWithBalanceBefore = true;
|
||||
|
||||
if (before->isFieldPresent(sfConfidentialBalanceSpending) ||
|
||||
before->isFieldPresent(sfConfidentialBalanceInbox) ||
|
||||
before->isFieldPresent(sfIssuerEncryptedBalance) ||
|
||||
before->isFieldPresent(sfAuditorEncryptedBalance);
|
||||
|
||||
if (hasPublicBalance || hasEncryptedFields)
|
||||
before->isFieldPresent(sfAuditorEncryptedBalance))
|
||||
changes_[id].deletedWithEncrypted = true;
|
||||
}
|
||||
}
|
||||
@@ -694,6 +722,8 @@ ValidConfidentialMPToken::finalize(
|
||||
if (result != tesSUCCESS)
|
||||
return true;
|
||||
|
||||
bool const fix350Enabled = view.rules().enabled(fixCleanup3_5_0);
|
||||
|
||||
for (auto const& [id, checks] : changes_)
|
||||
{
|
||||
// Find the MPTokenIssuance
|
||||
@@ -708,8 +738,20 @@ ValidConfidentialMPToken::finalize(
|
||||
if (!issuance)
|
||||
continue;
|
||||
|
||||
// Cannot delete MPToken with non-zero confidential state
|
||||
if (checks.deletedWithEncrypted)
|
||||
// Cannot delete MPToken with non-zero confidential state.
|
||||
//
|
||||
// Before fixCleanup3_5_0 this gate also absorbed the pre-transaction
|
||||
// public balance, so any drain-then-erase of an MPToken -- an
|
||||
// AMMWithdraw of the whole pool, a LoanBrokerDelete returning cover --
|
||||
// was rejected whenever some unrelated holder of the same issuance
|
||||
// held a confidential balance. The COA gate itself is correct for
|
||||
// ciphertext and mirrors MPTokenAuthorize::preclaim; only the public
|
||||
// balance leg was misplaced.
|
||||
bool const deletedWithEncrypted = fix350Enabled
|
||||
? checks.deletedWithEncrypted
|
||||
: (checks.deletedWithEncrypted || checks.deletedWithBalanceBefore);
|
||||
|
||||
if (deletedWithEncrypted)
|
||||
{
|
||||
if ((*issuance)[~sfConfidentialOutstandingAmount].value_or(0) > 0)
|
||||
{
|
||||
@@ -784,10 +826,10 @@ ValidConfidentialMPToken::finalize(
|
||||
return false;
|
||||
}
|
||||
|
||||
// Among confidential MPT transactions, only ConfidentialMPTSend and
|
||||
// ConfidentialMPTMergeInbox leave coaDelta unmodified. Therefore, if a confidential MPT
|
||||
// transaction reaches here, it must be one of these two types, neither of which will
|
||||
// modify sfOutstandingAmount
|
||||
// Reaching here means this confidential MPT transaction left coaDelta
|
||||
// unmodified (e.g. ConfidentialMPTSend, ConfidentialMPTMergeInbox, or
|
||||
// ConfidentialMPTHolderKeyUpdate/ConfidentialMPTMirrorUpdate, none of which touch
|
||||
// sfConfidentialOutstandingAmount), so it must not modify sfOutstandingAmount either.
|
||||
if (checks.outstandingDelta != 0)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: OutstandingAmount changed "
|
||||
|
||||
@@ -985,7 +985,7 @@ std::optional<std::variant<Quality, AMMOffer<TIn, TOut>>>
|
||||
BookStep<TIn, TOut, TDerived>::tip(ReadView const& view) const
|
||||
{
|
||||
// This can be simplified (and sped up) if directories are never empty.
|
||||
Sandbox sb(&view, TapNone);
|
||||
Sandbox sb(&view, ApplyFlags::None);
|
||||
BookTip bt(sb, book_);
|
||||
auto const lobQuality = bt.step(j_) ? std::optional<Quality>(bt.quality()) : std::nullopt;
|
||||
// Multi-path offer generates an offer with the quality
|
||||
|
||||
@@ -57,10 +57,8 @@ checkNear(IOUAmount const& expected, IOUAmount const& actual)
|
||||
static bool
|
||||
isXRPAccount(STPathElement const& pe)
|
||||
{
|
||||
if (pe.getNodeType() != STPathElement::TypeAccount)
|
||||
return false;
|
||||
return isXRP(pe.getAccountID());
|
||||
};
|
||||
return pe.getNodeType() == STPathElement::TypeAccount && isXRP(pe.getAccountID());
|
||||
}
|
||||
|
||||
static std::pair<TER, std::unique_ptr<Step>>
|
||||
toStep(
|
||||
@@ -71,11 +69,8 @@ toStep(
|
||||
{
|
||||
auto& j = ctx.j;
|
||||
|
||||
if (ctx.isFirst && e1->isAccount() &&
|
||||
((e1->getNodeType() & STPathElement::TypeCurrency) != 0u) && e1->getPathAsset().isXRP())
|
||||
{
|
||||
if (ctx.isFirst && e1->isAccount() && e1->hasCurrency() && e1->getPathAsset().isXRP())
|
||||
return makeXrpEndpointStep(ctx, e1->getAccountID());
|
||||
}
|
||||
|
||||
if (ctx.isLast && isXRPAccount(*e1) && e2->isAccount())
|
||||
return makeXrpEndpointStep(ctx, e2->getAccountID());
|
||||
@@ -117,15 +112,9 @@ toStep(
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
XRPL_ASSERT(
|
||||
(e2->getNodeType() & STPathElement::TypeAsset) ||
|
||||
(e2->getNodeType() & STPathElement::TypeIssuer),
|
||||
"xrpl::toStep : currency or issuer");
|
||||
PathAsset const outAsset =
|
||||
((e2->getNodeType() & STPathElement::TypeAsset) != 0u) ? e2->getPathAsset() : curAsset;
|
||||
auto const outIssuer = ((e2->getNodeType() & STPathElement::TypeIssuer) != 0u)
|
||||
? e2->getIssuerID()
|
||||
: curAsset.getIssuer();
|
||||
XRPL_ASSERT(e2->hasAsset() || e2->hasIssuer(), "xrpl::toStep : currency or issuer");
|
||||
PathAsset const outAsset = e2->hasAsset() ? e2->getPathAsset() : curAsset;
|
||||
auto const outIssuer = e2->hasIssuer() ? e2->getIssuerID() : curAsset.getIssuer();
|
||||
|
||||
if (isXRP(curAsset) && outAsset.isXRP())
|
||||
{
|
||||
@@ -197,16 +186,15 @@ toStrand(
|
||||
for (std::size_t i = 0; i < path.size(); ++i)
|
||||
{
|
||||
auto const& pe = path[i];
|
||||
auto const t = pe.getNodeType();
|
||||
|
||||
if (((t & ~STPathElement::TypeAll) != 0u) || (t == 0u))
|
||||
if ((pe.getNodeType() & ~STPathElement::TypeAll) != STPathElement::TypeNone || pe.isNone())
|
||||
return {temBAD_PATH, Strand{}};
|
||||
|
||||
bool const hasAccount = (t & STPathElement::TypeAccount) != 0u;
|
||||
bool const hasIssuer = (t & STPathElement::TypeIssuer) != 0u;
|
||||
bool const hasCurrency = (t & STPathElement::TypeCurrency) != 0u;
|
||||
bool const hasMPT = (t & STPathElement::TypeMpt) != 0u;
|
||||
bool const hasAsset = (t & STPathElement::TypeAsset) != 0u;
|
||||
bool const hasAccount = pe.isType(STPathElement::TypeAccount);
|
||||
bool const hasIssuer = pe.hasIssuer();
|
||||
bool const hasCurrency = pe.hasCurrency();
|
||||
bool const hasMPT = pe.hasMPT();
|
||||
bool const hasAsset = pe.hasAsset();
|
||||
|
||||
if (hasAccount && (hasIssuer || hasCurrency))
|
||||
return {temBAD_PATH, Strand{}};
|
||||
@@ -250,9 +238,7 @@ toStrand(
|
||||
}();
|
||||
|
||||
// Currency or MPT
|
||||
auto hasAsset = [](STPathElement const pe) {
|
||||
return pe.getNodeType() & STPathElement::TypeAsset;
|
||||
};
|
||||
auto hasAsset = [](STPathElement const& pe) { return pe.hasAsset(); };
|
||||
|
||||
std::vector<STPathElement> normPath;
|
||||
// reserve enough for the path, the implied source, destination,
|
||||
|
||||
@@ -187,7 +187,8 @@ AccountSet::preclaim(PreclaimContext const& ctx)
|
||||
if (!dirIsEmpty(ctx.view, keylet::ownerDir(id)))
|
||||
{
|
||||
JLOG(ctx.j.trace()) << "Retry: Owner directory not empty.";
|
||||
return ((ctx.flags & TapRetry) != 0u) ? TER{terOWNERS} : TER{tecOWNERS};
|
||||
return ((ctx.flags & ApplyFlags::Retry) != ApplyFlags::None) ? TER{terOWNERS}
|
||||
: TER{tecOWNERS};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -295,7 +295,8 @@ OfferCreate::checkAcceptAsset(
|
||||
JLOG(j.debug()) << "delay: can't receive IOUs from non-existent issuer: "
|
||||
<< to_string(asset.getIssuer());
|
||||
|
||||
return ((flags & TapRetry) != 0u) ? TER{terNO_ACCOUNT} : TER{tecNO_ISSUER};
|
||||
return ((flags & ApplyFlags::Retry) != ApplyFlags::None) ? TER{terNO_ACCOUNT}
|
||||
: TER{tecNO_ISSUER};
|
||||
}
|
||||
|
||||
// An account cannot create a trustline to itself, so no line can exist
|
||||
@@ -318,7 +319,8 @@ OfferCreate::checkAcceptAsset(
|
||||
{
|
||||
JLOG(j.debug()) << "delay: can't receive IOUs from issuer with "
|
||||
"DisallowIncomingTrustline set";
|
||||
return ((flags & TapRetry) != 0u) ? TER{terNO_LINE} : TER{tecNO_LINE};
|
||||
return ((flags & ApplyFlags::Retry) != ApplyFlags::None) ? TER{terNO_LINE}
|
||||
: TER{tecNO_LINE};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -326,7 +328,8 @@ OfferCreate::checkAcceptAsset(
|
||||
{
|
||||
if (!trustLine)
|
||||
{
|
||||
return ((flags & TapRetry) != 0u) ? TER{terNO_LINE} : TER{tecNO_LINE};
|
||||
return ((flags & ApplyFlags::Retry) != ApplyFlags::None) ? TER{terNO_LINE}
|
||||
: TER{tecNO_LINE};
|
||||
}
|
||||
|
||||
// Entries have a canonical representation, determined by a
|
||||
@@ -342,7 +345,8 @@ OfferCreate::checkAcceptAsset(
|
||||
JLOG(j.debug()) << "delay: can't receive IOUs from "
|
||||
"issuer without auth.";
|
||||
|
||||
return ((flags & TapRetry) != 0u) ? TER{terNO_AUTH} : TER{tecNO_AUTH};
|
||||
return ((flags & ApplyFlags::Retry) != ApplyFlags::None) ? TER{terNO_AUTH}
|
||||
: TER{tecNO_AUTH};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/entries/EscrowEntry.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/EscrowHelpers.h>
|
||||
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
|
||||
@@ -94,13 +95,12 @@ EscrowCancel::preclaim(PreclaimContext const& ctx)
|
||||
if (ctx.view.rules().enabled(featureTokenEscrow))
|
||||
{
|
||||
auto const seqProxy = SeqProxy::rawSequence(ctx.tx[sfOfferSequence]);
|
||||
auto const k = keylet::escrow(ctx.tx[sfOwner], seqProxy);
|
||||
auto const slep = ctx.view.read(k);
|
||||
if (!slep)
|
||||
EscrowEntryR const escrow(ctx.tx[sfOwner], seqProxy, ctx.view);
|
||||
if (!escrow)
|
||||
return tecNO_TARGET;
|
||||
|
||||
AccountID const account = (*slep)[sfAccount];
|
||||
STAmount const amount = (*slep)[sfAmount];
|
||||
AccountID const account = (*escrow)[sfAccount];
|
||||
STAmount const amount = (*escrow)[sfAmount];
|
||||
|
||||
if (!isXRP(amount))
|
||||
{
|
||||
@@ -120,9 +120,8 @@ TER
|
||||
EscrowCancel::doApply()
|
||||
{
|
||||
auto const seqProxy = SeqProxy::rawSequence(ctx_.tx[sfOfferSequence]);
|
||||
auto const k = keylet::escrow(ctx_.tx[sfOwner], seqProxy);
|
||||
auto const slep = ctx_.view().peek(k);
|
||||
if (!slep)
|
||||
EscrowEntryW escrow(ctx_.tx[sfOwner], seqProxy, ctx_.view(), j_);
|
||||
if (!escrow)
|
||||
{
|
||||
if (ctx_.view().rules().enabled(featureTokenEscrow))
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
@@ -133,19 +132,19 @@ EscrowCancel::doApply()
|
||||
auto const now = ctx_.view().header().parentCloseTime;
|
||||
|
||||
// No cancel time specified: can't execute at all.
|
||||
if (!(*slep)[~sfCancelAfter])
|
||||
if (!(*escrow)[~sfCancelAfter])
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
// Too soon: can't execute before the cancel time.
|
||||
if (!after(now, (*slep)[sfCancelAfter]))
|
||||
if (!after(now, (*escrow)[sfCancelAfter]))
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
AccountID const account = (*slep)[sfAccount];
|
||||
AccountID const account = (*escrow)[sfAccount];
|
||||
|
||||
// Remove escrow from owner directory
|
||||
{
|
||||
auto const page = (*slep)[sfOwnerNode];
|
||||
if (!ctx_.view().dirRemove(keylet::ownerDir(account), page, k.key, true))
|
||||
auto const page = (*escrow)[sfOwnerNode];
|
||||
if (!ctx_.view().dirRemove(keylet::ownerDir(account), page, escrow.key(), true))
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j_.fatal()) << "Unable to delete Escrow from owner.";
|
||||
@@ -155,9 +154,10 @@ EscrowCancel::doApply()
|
||||
}
|
||||
|
||||
// Remove escrow from recipient's owner directory, if present.
|
||||
if (auto const optPage = (*slep)[~sfDestinationNode]; optPage)
|
||||
if (auto const optPage = (*escrow)[~sfDestinationNode]; optPage)
|
||||
{
|
||||
if (!ctx_.view().dirRemove(keylet::ownerDir((*slep)[sfDestination]), *optPage, k.key, true))
|
||||
if (!ctx_.view().dirRemove(
|
||||
keylet::ownerDir((*escrow)[sfDestination]), *optPage, escrow.key(), true))
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j_.fatal()) << "Unable to delete Escrow from recipient.";
|
||||
@@ -167,13 +167,13 @@ EscrowCancel::doApply()
|
||||
}
|
||||
|
||||
auto const sle = ctx_.view().peek(keylet::account(account));
|
||||
STAmount const amount = slep->getFieldAmount(sfAmount);
|
||||
STAmount const amount = escrow->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);
|
||||
decreaseOwnerCountForObject(ctx_.view(), sle, escrow.mutableRawSle(), 1, ctx_.journal);
|
||||
|
||||
// Transfer amount back to the owner
|
||||
if (isXRP(amount))
|
||||
@@ -192,7 +192,7 @@ EscrowCancel::doApply()
|
||||
return escrowUnlockApplyHelper<T>(
|
||||
ctx_.getApplyViewContext(),
|
||||
kParityRate,
|
||||
ctx_.view().rules().enabled(fixCleanup3_2_0) ? sle : slep,
|
||||
ctx_.view().rules().enabled(fixCleanup3_2_0) ? sle : escrow.mutableRawSle(),
|
||||
preFeeBalance_,
|
||||
amount,
|
||||
issuer,
|
||||
@@ -206,9 +206,9 @@ EscrowCancel::doApply()
|
||||
return ret; // LCOV_EXCL_LINE
|
||||
|
||||
// Remove escrow from issuers owner directory, if present.
|
||||
if (auto const optPage = (*slep)[~sfIssuerNode]; optPage)
|
||||
if (auto const optPage = (*escrow)[~sfIssuerNode]; optPage)
|
||||
{
|
||||
if (!ctx_.view().dirRemove(keylet::ownerDir(issuer), *optPage, k.key, true))
|
||||
if (!ctx_.view().dirRemove(keylet::ownerDir(issuer), *optPage, escrow.key(), true))
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j_.fatal()) << "Unable to delete Escrow from recipient.";
|
||||
@@ -219,10 +219,10 @@ EscrowCancel::doApply()
|
||||
}
|
||||
|
||||
if (!recycleReserve)
|
||||
decreaseOwnerCountForObject(ctx_.view(), sle, slep, 1, ctx_.journal);
|
||||
decreaseOwnerCountForObject(ctx_.view(), sle, escrow.mutableRawSle(), 1, ctx_.journal);
|
||||
|
||||
// Remove escrow from ledger
|
||||
ctx_.view().erase(slep);
|
||||
escrow.erase();
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <xrpl/core/ServiceRegistry.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/entries/EscrowEntry.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
|
||||
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
|
||||
@@ -481,30 +482,31 @@ EscrowCreate::doApply()
|
||||
|
||||
// Create escrow in ledger. Note that we use the value from the
|
||||
// sequence or ticket. For more explanation see comments in SeqProxy.h.
|
||||
Keylet const escrowKeylet = keylet::escrow(accountID_, ctx_.tx.getSeqProxy());
|
||||
auto const slep = std::make_shared<SLE>(escrowKeylet);
|
||||
(*slep)[sfAmount] = amount;
|
||||
(*slep)[sfAccount] = accountID_;
|
||||
(*slep)[~sfCondition] = ctx_.tx[~sfCondition];
|
||||
(*slep)[~sfSourceTag] = ctx_.tx[~sfSourceTag];
|
||||
(*slep)[sfDestination] = ctx_.tx[sfDestination];
|
||||
(*slep)[~sfCancelAfter] = ctx_.tx[~sfCancelAfter];
|
||||
(*slep)[~sfFinishAfter] = ctx_.tx[~sfFinishAfter];
|
||||
(*slep)[~sfDestinationTag] = ctx_.tx[~sfDestinationTag];
|
||||
EscrowEntryW escrow(accountID_, ctx_.tx.getSeqProxy(), ctx_.view(), j_);
|
||||
Keylet const escrowKeylet = escrow.keylet();
|
||||
escrow.newSLE();
|
||||
(*escrow)[sfAmount] = amount;
|
||||
(*escrow)[sfAccount] = accountID_;
|
||||
(*escrow)[~sfCondition] = ctx_.tx[~sfCondition];
|
||||
(*escrow)[~sfSourceTag] = ctx_.tx[~sfSourceTag];
|
||||
(*escrow)[sfDestination] = ctx_.tx[sfDestination];
|
||||
(*escrow)[~sfCancelAfter] = ctx_.tx[~sfCancelAfter];
|
||||
(*escrow)[~sfFinishAfter] = ctx_.tx[~sfFinishAfter];
|
||||
(*escrow)[~sfDestinationTag] = ctx_.tx[~sfDestinationTag];
|
||||
|
||||
if (ctx_.view().rules().enabled(fixIncludeKeyletFields))
|
||||
{
|
||||
(*slep)[sfSequence] = ctx_.tx.getSeqProxy().value();
|
||||
(*escrow)[sfSequence] = ctx_.tx.getSeqProxy().value();
|
||||
}
|
||||
|
||||
if (ctx_.view().rules().enabled(featureTokenEscrow) && !isXRP(amount))
|
||||
{
|
||||
auto const xferRate = transferRate(ctx_.view(), amount);
|
||||
if (xferRate != kParityRate)
|
||||
(*slep)[sfTransferRate] = xferRate.value;
|
||||
(*escrow)[sfTransferRate] = xferRate.value;
|
||||
}
|
||||
|
||||
ctx_.view().insert(slep);
|
||||
escrow.insert();
|
||||
|
||||
// Add escrow to sender's owner directory
|
||||
{
|
||||
@@ -512,7 +514,7 @@ EscrowCreate::doApply()
|
||||
keylet::ownerDir(accountID_), escrowKeylet, describeOwnerDir(accountID_));
|
||||
if (!page)
|
||||
return tecDIR_FULL; // LCOV_EXCL_LINE
|
||||
(*slep)[sfOwnerNode] = *page;
|
||||
(*escrow)[sfOwnerNode] = *page;
|
||||
}
|
||||
|
||||
// If it's not a self-send, add escrow to recipient's owner directory.
|
||||
@@ -523,7 +525,7 @@ EscrowCreate::doApply()
|
||||
ctx_.view().dirInsert(keylet::ownerDir(dest), escrowKeylet, describeOwnerDir(dest));
|
||||
if (!page)
|
||||
return tecDIR_FULL; // LCOV_EXCL_LINE
|
||||
(*slep)[sfDestinationNode] = *page;
|
||||
(*escrow)[sfDestinationNode] = *page;
|
||||
}
|
||||
|
||||
// IOU escrow objects are added to the issuer's owner directory to help
|
||||
@@ -536,7 +538,7 @@ EscrowCreate::doApply()
|
||||
ctx_.view().dirInsert(keylet::ownerDir(issuer), escrowKeylet, describeOwnerDir(issuer));
|
||||
if (!page)
|
||||
return tecDIR_FULL; // LCOV_EXCL_LINE
|
||||
(*slep)[sfIssuerNode] = *page;
|
||||
(*escrow)[sfIssuerNode] = *page;
|
||||
}
|
||||
|
||||
// Deduct owner's balance
|
||||
@@ -559,7 +561,7 @@ EscrowCreate::doApply()
|
||||
|
||||
// increment owner count
|
||||
increaseOwnerCount(ctx_.getApplyViewContext(), sle, 1, ctx_.journal);
|
||||
addSponsorToLedgerEntry(ctx_.getApplyViewContext(), slep);
|
||||
addSponsorToLedgerEntry(ctx_.getApplyViewContext(), escrow.mutableRawSle());
|
||||
ctx_.view().update(sle);
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/entries/EscrowEntry.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/CredentialHelpers.h>
|
||||
#include <xrpl/ledger/helpers/EscrowHelpers.h>
|
||||
@@ -211,13 +212,12 @@ EscrowFinish::preclaim(PreclaimContext const& ctx)
|
||||
if (ctx.view.rules().enabled(featureTokenEscrow))
|
||||
{
|
||||
auto const seqProxy = SeqProxy::rawSequence(ctx.tx[sfOfferSequence]);
|
||||
auto const k = keylet::escrow(ctx.tx[sfOwner], seqProxy);
|
||||
auto const slep = ctx.view.read(k);
|
||||
if (!slep)
|
||||
EscrowEntryR const escrow(ctx.tx[sfOwner], seqProxy, ctx.view);
|
||||
if (!escrow)
|
||||
return tecNO_TARGET;
|
||||
|
||||
AccountID const dest = (*slep)[sfDestination];
|
||||
STAmount const amount = (*slep)[sfAmount];
|
||||
AccountID const dest = (*escrow)[sfDestination];
|
||||
STAmount const amount = (*escrow)[sfAmount];
|
||||
|
||||
if (!isXRP(amount))
|
||||
{
|
||||
@@ -237,9 +237,8 @@ TER
|
||||
EscrowFinish::doApply()
|
||||
{
|
||||
auto const seqProxy = SeqProxy::rawSequence(ctx_.tx[sfOfferSequence]);
|
||||
auto const k = keylet::escrow(ctx_.tx[sfOwner], seqProxy);
|
||||
auto const slep = ctx_.view().peek(k);
|
||||
if (!slep)
|
||||
EscrowEntryW escrow(ctx_.tx[sfOwner], seqProxy, ctx_.view(), j_);
|
||||
if (!escrow)
|
||||
{
|
||||
if (ctx_.view().rules().enabled(featureTokenEscrow))
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
@@ -252,11 +251,11 @@ EscrowFinish::doApply()
|
||||
auto const now = ctx_.view().header().parentCloseTime;
|
||||
|
||||
// Too soon: can't execute before the finish time
|
||||
if ((*slep)[~sfFinishAfter] && !after(now, (*slep)[sfFinishAfter]))
|
||||
if ((*escrow)[~sfFinishAfter] && !after(now, (*escrow)[sfFinishAfter]))
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
// Too late: can't execute after the cancel time
|
||||
if ((*slep)[~sfCancelAfter] && after(now, (*slep)[sfCancelAfter]))
|
||||
if ((*escrow)[~sfCancelAfter] && after(now, (*escrow)[sfCancelAfter]))
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
// Check cryptocondition fulfillment
|
||||
@@ -296,7 +295,7 @@ EscrowFinish::doApply()
|
||||
return tecCRYPTOCONDITION_ERROR;
|
||||
|
||||
// Check against condition in the ledger entry:
|
||||
auto const cond = (*slep)[~sfCondition];
|
||||
auto const cond = (*escrow)[~sfCondition];
|
||||
|
||||
// If a condition wasn't specified during creation,
|
||||
// one shouldn't be included now.
|
||||
@@ -313,7 +312,7 @@ EscrowFinish::doApply()
|
||||
}
|
||||
|
||||
// NOTE: Escrow payments cannot be used to fund accounts.
|
||||
AccountID const destID = (*slep)[sfDestination];
|
||||
AccountID const destID = (*escrow)[sfDestination];
|
||||
auto const sled = ctx_.view().peek(keylet::account(destID));
|
||||
if (!sled)
|
||||
return tecNO_DST;
|
||||
@@ -323,12 +322,12 @@ EscrowFinish::doApply()
|
||||
!isTesSuccess(err))
|
||||
return err;
|
||||
|
||||
AccountID const account = (*slep)[sfAccount];
|
||||
AccountID const account = (*escrow)[sfAccount];
|
||||
|
||||
// Remove escrow from owner directory
|
||||
{
|
||||
auto const page = (*slep)[sfOwnerNode];
|
||||
if (!ctx_.view().dirRemove(keylet::ownerDir(account), page, k.key, true))
|
||||
auto const page = (*escrow)[sfOwnerNode];
|
||||
if (!ctx_.view().dirRemove(keylet::ownerDir(account), page, escrow.key(), true))
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j_.fatal()) << "Unable to delete Escrow from owner.";
|
||||
@@ -338,9 +337,9 @@ EscrowFinish::doApply()
|
||||
}
|
||||
|
||||
// Remove escrow from recipient's owner directory, if present.
|
||||
if (auto const optPage = (*slep)[~sfDestinationNode])
|
||||
if (auto const optPage = (*escrow)[~sfDestinationNode])
|
||||
{
|
||||
if (!ctx_.view().dirRemove(keylet::ownerDir(destID), *optPage, k.key, true))
|
||||
if (!ctx_.view().dirRemove(keylet::ownerDir(destID), *optPage, escrow.key(), true))
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j_.fatal()) << "Unable to delete Escrow from recipient.";
|
||||
@@ -355,9 +354,9 @@ EscrowFinish::doApply()
|
||||
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);
|
||||
decreaseOwnerCountForObject(ctx_.view(), account, escrow.mutableRawSle(), 1, ctx_.journal);
|
||||
|
||||
STAmount const amount = slep->getFieldAmount(sfAmount);
|
||||
STAmount const amount = escrow->getFieldAmount(sfAmount);
|
||||
// Transfer amount to destination
|
||||
if (isXRP(amount))
|
||||
{
|
||||
@@ -368,8 +367,8 @@ EscrowFinish::doApply()
|
||||
if (!ctx_.view().rules().enabled(featureTokenEscrow))
|
||||
return temDISABLED; // LCOV_EXCL_LINE
|
||||
|
||||
Rate lockedRate = slep->isFieldPresent(sfTransferRate)
|
||||
? xrpl::Rate(slep->getFieldU32(sfTransferRate))
|
||||
Rate lockedRate = escrow->isFieldPresent(sfTransferRate)
|
||||
? xrpl::Rate(escrow->getFieldU32(sfTransferRate))
|
||||
: kParityRate;
|
||||
auto const issuer = amount.getIssuer();
|
||||
bool const createAsset = destID == accountID_;
|
||||
@@ -392,9 +391,9 @@ EscrowFinish::doApply()
|
||||
return ret;
|
||||
|
||||
// Remove escrow from issuers owner directory, if present.
|
||||
if (auto const optPage = (*slep)[~sfIssuerNode]; optPage)
|
||||
if (auto const optPage = (*escrow)[~sfIssuerNode]; optPage)
|
||||
{
|
||||
if (!ctx_.view().dirRemove(keylet::ownerDir(issuer), *optPage, k.key, true))
|
||||
if (!ctx_.view().dirRemove(keylet::ownerDir(issuer), *optPage, escrow.key(), true))
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j_.fatal()) << "Unable to delete Escrow from recipient.";
|
||||
@@ -407,10 +406,10 @@ EscrowFinish::doApply()
|
||||
ctx_.view().update(sled);
|
||||
|
||||
if (!recycleReserve)
|
||||
decreaseOwnerCountForObject(ctx_.view(), account, slep, 1, ctx_.journal);
|
||||
decreaseOwnerCountForObject(ctx_.view(), account, escrow.mutableRawSle(), 1, ctx_.journal);
|
||||
|
||||
// Remove escrow from ledger
|
||||
ctx_.view().erase(slep);
|
||||
escrow.erase();
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
|
||||
@@ -91,7 +91,8 @@ LoanSet::preflight(PreflightContext const& ctx)
|
||||
// A proposed LoanSet is stored unsigned; its CounterpartySignature is
|
||||
// collected on-ledger afterward, so its absence here is expected, not an
|
||||
// error (On-Chain Cosigner spec §5.3.1.2).
|
||||
if (!tx.isFlag(tfInnerBatchTxn) && !counterPartySig && (ctx.flags & TapProposal) == 0)
|
||||
if (!tx.isFlag(tfInnerBatchTxn) && !counterPartySig &&
|
||||
(ctx.flags & ApplyFlags::Proposal) == ApplyFlags::None)
|
||||
{
|
||||
JLOG(ctx.j.warn()) << "LoanSet transaction must have a CounterpartySignature.";
|
||||
return temBAD_SIGNER;
|
||||
|
||||
@@ -54,8 +54,8 @@ TransactionProposalCreate::preflight(PreflightContext const& ctx)
|
||||
try
|
||||
{
|
||||
STTx const stx{STObject{proposedTx}};
|
||||
auto const inner =
|
||||
xrpl::preflight(ctx.registry, ctx.rules, stx, TapDryRun | TapProposal, ctx.j);
|
||||
auto const inner = xrpl::preflight(
|
||||
ctx.registry, ctx.rules, stx, ApplyFlags::DryRun | ApplyFlags::Proposal, ctx.j);
|
||||
if (!isTesSuccess(inner.ter))
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "TransactionProposalCreate: proposed txn "
|
||||
|
||||
@@ -205,7 +205,7 @@ SponsorshipTransfer::preflight(PreflightContext const& ctx)
|
||||
// is collected on-ledger afterward, so its absence here is expected, not
|
||||
// an error (On-Chain Cosigner spec §5.3.1.2).
|
||||
if (isAccountReserveSponsorship && !ctx.tx.isFieldPresent(sfSponsorSignature) &&
|
||||
(ctx.flags & TapProposal) == 0)
|
||||
(ctx.flags & ApplyFlags::Proposal) == ApplyFlags::None)
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "preflight: account sponsorship requires sfSponsorSignature";
|
||||
return temMALFORMED;
|
||||
|
||||
@@ -359,7 +359,8 @@ Batch::preflight(PreflightContext const& ctx)
|
||||
// TapBatch — PreflightContext with a parentBatchId requires it.
|
||||
// LoanSet already short-circuits on tfInnerBatchTxn; it is also in
|
||||
// kDisabledTxTypes, so it never reaches this call.
|
||||
ApplyFlags const innerFlags = TapBatch | (ctx.flags & (TapProposal | TapDryRun));
|
||||
ApplyFlags const innerFlags =
|
||||
ApplyFlags::Batch | (ctx.flags & (ApplyFlags::Proposal | ApplyFlags::DryRun));
|
||||
if (auto const preflightResult =
|
||||
xrpl::preflight(ctx.registry, ctx.rules, parentBatchId, stx, innerFlags, ctx.j);
|
||||
!isTesSuccess(preflightResult.ter))
|
||||
@@ -431,7 +432,7 @@ Batch::preflightSigValidated(PreflightContext const& ctx)
|
||||
// A proposed Batch is stored unsigned; its BatchSigners are collected
|
||||
// on-ledger afterward, so the signer-presence match belongs to submission
|
||||
// time, not proposal creation (On-Chain Cosigner spec §5.3.1.2).
|
||||
if ((ctx.flags & TapProposal) != 0)
|
||||
if ((ctx.flags & ApplyFlags::Proposal) != ApplyFlags::None)
|
||||
return tesSUCCESS;
|
||||
|
||||
auto const parentBatchId = ctx.tx.getTransactionID();
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/core/ServiceRegistry.h>
|
||||
#include <xrpl/ledger/AmendmentTable.h>
|
||||
#include <xrpl/ledger/entries/FeeSettingsEntry.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
@@ -260,16 +261,14 @@ Change::applyAmendment()
|
||||
TER
|
||||
Change::applyFee()
|
||||
{
|
||||
auto const k = keylet::feeSettings();
|
||||
|
||||
SLE::pointer feeObject = view().peek(k);
|
||||
FeeSettingsEntryW feeObject(view());
|
||||
|
||||
if (!feeObject)
|
||||
{
|
||||
feeObject = std::make_shared<SLE>(k);
|
||||
view().insert(feeObject);
|
||||
feeObject.newSLE();
|
||||
feeObject.insert();
|
||||
}
|
||||
auto set = [](SLE::pointer& feeObject, STTx const& tx, auto const& field) {
|
||||
auto set = [](FeeSettingsEntryW& feeObject, STTx const& tx, auto const& field) {
|
||||
feeObject->at(field) = tx[field];
|
||||
};
|
||||
if (view().rules().enabled(featureXRPFees))
|
||||
@@ -291,7 +290,7 @@ Change::applyFee()
|
||||
set(feeObject, ctx_.tx, sfReserveIncrement);
|
||||
}
|
||||
|
||||
view().update(feeObject);
|
||||
feeObject.update();
|
||||
|
||||
JLOG(j_.warn()) << "Fees have been changed";
|
||||
return tesSUCCESS;
|
||||
|
||||
@@ -0,0 +1,220 @@
|
||||
#include <xrpl/tx/transactors/token/ConfidentialMPTHolderKeyUpdate.h>
|
||||
|
||||
#include <xrpl/basics/Slice.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/core/ServiceRegistry.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/protocol/ConfidentialTransfer.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
#include <xrpl/protocol/MPTIssue.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
#include <xrpl/tx/Transactor.h>
|
||||
|
||||
#include <bit>
|
||||
#include <cstdint>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
bool
|
||||
ConfidentialMPTHolderKeyUpdate::checkExtraFeatures(PreflightContext const& ctx)
|
||||
{
|
||||
// Holder key update is only meaningful when confidential transfers are enabled.
|
||||
return ctx.rules.enabled(featureConfidentialTransfer);
|
||||
}
|
||||
|
||||
std::uint32_t
|
||||
ConfidentialMPTHolderKeyUpdate::getFlagsMask(PreflightContext const& ctx)
|
||||
{
|
||||
return tfConfidentialMPTHolderKeyUpdateMask;
|
||||
}
|
||||
|
||||
NotTEC
|
||||
ConfidentialMPTHolderKeyUpdate::preflight(PreflightContext const& ctx)
|
||||
{
|
||||
bool const rotation = ctx.tx.isFlag(tfHolderKeyRotation);
|
||||
bool const recovery = ctx.tx.isFlag(tfHolderKeyRecovery);
|
||||
bool const cancel = ctx.tx.isFlag(tfCancelRecovery);
|
||||
|
||||
// Exactly one of the three mode flags must be set.
|
||||
static constexpr auto modeFlags = tfHolderKeyRotation | tfHolderKeyRecovery | tfCancelRecovery;
|
||||
if (std::popcount(ctx.tx.getFlags() & modeFlags) != 1)
|
||||
return temINVALID_FLAG;
|
||||
|
||||
// The issuer cannot hold confidential balances.
|
||||
if (ctx.tx[sfAccount] == MPTIssue(ctx.tx[sfMPTokenIssuanceID]).getIssuer())
|
||||
return temMALFORMED;
|
||||
|
||||
bool const hasHolderKey = ctx.tx.isFieldPresent(sfHolderEncryptionKey);
|
||||
bool const hasSpending = ctx.tx.isFieldPresent(sfConfidentialBalanceSpending);
|
||||
bool const hasInbox = ctx.tx.isFieldPresent(sfConfidentialBalanceInbox);
|
||||
bool const hasProof = ctx.tx.isFieldPresent(sfZKProof);
|
||||
|
||||
if (cancel)
|
||||
{
|
||||
// Cancel mode only revokes a pending recovery authorization; it
|
||||
// carries no key material, balances, or proof.
|
||||
if (hasHolderKey || hasSpending || hasInbox || hasProof)
|
||||
return temMALFORMED;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
// Rotation and Recovery both require a holder key and a proof.
|
||||
if (!hasHolderKey || !hasProof)
|
||||
return temMALFORMED;
|
||||
|
||||
// Rotation mode requires re-encrypted balances; Recovery mode must not
|
||||
// provide them since the holder cannot decrypt the current ones.
|
||||
if (rotation && (!hasSpending || !hasInbox))
|
||||
return temMALFORMED;
|
||||
|
||||
if (recovery && (hasSpending || hasInbox))
|
||||
return temMALFORMED;
|
||||
|
||||
if (hasSpending &&
|
||||
ctx.tx[sfConfidentialBalanceSpending].length() != kEcGamalEncryptedTotalLength)
|
||||
return temBAD_CIPHERTEXT;
|
||||
|
||||
if (hasInbox && ctx.tx[sfConfidentialBalanceInbox].length() != kEcGamalEncryptedTotalLength)
|
||||
return temBAD_CIPHERTEXT;
|
||||
|
||||
if (!isValidCompressedECPoint(ctx.tx[sfHolderEncryptionKey]))
|
||||
return temMALFORMED;
|
||||
|
||||
if (hasSpending && !isValidCiphertext(ctx.tx[sfConfidentialBalanceSpending]))
|
||||
return temBAD_CIPHERTEXT;
|
||||
|
||||
if (hasInbox && !isValidCiphertext(ctx.tx[sfConfidentialBalanceInbox]))
|
||||
return temBAD_CIPHERTEXT;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
XRPAmount
|
||||
ConfidentialMPTHolderKeyUpdate::calculateBaseFee(ReadView const& view, STTx const& tx)
|
||||
{
|
||||
return Transactor::calculateBaseFee(view, tx, kConfidentialFeeMultiplier);
|
||||
}
|
||||
|
||||
TER
|
||||
ConfidentialMPTHolderKeyUpdate::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
auto const account = ctx.tx[sfAccount];
|
||||
auto const mptIssuanceID = ctx.tx[sfMPTokenIssuanceID];
|
||||
|
||||
auto const sleIssuance = ctx.view.read(keylet::mptokenIssuance(mptIssuanceID));
|
||||
if (!sleIssuance)
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
|
||||
if (!sleIssuance->isFlag(lsfMPTCanHoldConfidentialBalance))
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
auto const sleMptoken = ctx.view.read(keylet::mptoken(mptIssuanceID, account));
|
||||
if (!sleMptoken)
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
|
||||
if (!sleMptoken->isFieldPresent(sfHolderEncryptionKey) ||
|
||||
!sleMptoken->isFieldPresent(sfConfidentialBalanceSpending) ||
|
||||
!sleMptoken->isFieldPresent(sfConfidentialBalanceInbox))
|
||||
{
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
|
||||
if (ctx.tx.isFlag(tfCancelRecovery))
|
||||
{
|
||||
// Nothing to cancel if no recovery is pending.
|
||||
if (!sleMptoken->isFieldPresent(sfRecoveryKey))
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
if (ctx.tx[sfHolderEncryptionKey] == (*sleMptoken)[sfHolderEncryptionKey])
|
||||
return tecDUPLICATE;
|
||||
|
||||
// Recovery mode: reject if a recovery is already pending
|
||||
if (ctx.tx.isFlag(tfHolderKeyRecovery) && sleMptoken->isFieldPresent(sfRecoveryKey))
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
ConfidentialMPTHolderKeyUpdate::doApply()
|
||||
{
|
||||
auto const mptIssuanceID = ctx_.tx[sfMPTokenIssuanceID];
|
||||
auto sleMptoken = view().peek(keylet::mptoken(mptIssuanceID, accountID_));
|
||||
if (!sleMptoken)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
UNREACHABLE(
|
||||
"xrpl::ConfidentialMPTHolderKeyUpdate::doApply : preclaim already validated the "
|
||||
"MPToken exists");
|
||||
return tecINTERNAL;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
if (ctx_.tx.isFlag(tfCancelRecovery))
|
||||
{
|
||||
// The holder revokes their pending recovery authorization; the
|
||||
// current key and balances are left untouched.
|
||||
sleMptoken->makeFieldAbsent(sfRecoveryKey);
|
||||
view().update(sleMptoken);
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
auto const newPubKey = ctx_.tx[sfHolderEncryptionKey];
|
||||
|
||||
if (ctx_.tx.isFlag(tfHolderKeyRotation))
|
||||
{
|
||||
// Replace the key and balances in rotation mode. Rotation proves the
|
||||
// holder still has the old key, so any pending recovery is cancelled.
|
||||
(*sleMptoken)[sfHolderEncryptionKey] = newPubKey;
|
||||
(*sleMptoken)[sfConfidentialBalanceSpending] = ctx_.tx[sfConfidentialBalanceSpending];
|
||||
(*sleMptoken)[sfConfidentialBalanceInbox] = ctx_.tx[sfConfidentialBalanceInbox];
|
||||
sleMptoken->makeFieldAbsent(sfRecoveryKey);
|
||||
incrementConfidentialVersion(*sleMptoken);
|
||||
}
|
||||
else if (ctx_.tx.isFlag(tfHolderKeyRecovery))
|
||||
{
|
||||
// Recovery mode: the holder cannot decrypt their current balances,
|
||||
// so only the pending recovery key is recorded. The balances are
|
||||
// rewritten separately by the issuer via ConfidentialMPTRecoverBalance.
|
||||
(*sleMptoken)[sfRecoveryKey] = newPubKey;
|
||||
}
|
||||
else
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
UNREACHABLE("xrpl::ConfidentialMPTHolderKeyUpdate::doApply : invalid mode");
|
||||
return tecINTERNAL;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
view().update(sleMptoken);
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
void
|
||||
ConfidentialMPTHolderKeyUpdate::visitInvariantEntry(bool, SLE::ConstRef, SLE::ConstRef)
|
||||
{
|
||||
}
|
||||
|
||||
bool
|
||||
ConfidentialMPTHolderKeyUpdate::finalizeInvariants(
|
||||
STTx const&,
|
||||
TER,
|
||||
XRPAmount,
|
||||
ReadView const&,
|
||||
beast::Journal const&)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -31,6 +31,15 @@
|
||||
namespace xrpl::test {
|
||||
class AMMClawbackMPT_test : public beast::unit_test::Suite
|
||||
{
|
||||
// SingleAssetVault, LendingProtocol and MPTokensV2 enable the large
|
||||
// Number mantissa.
|
||||
static bool
|
||||
largeMantissa(FeatureBitset const& features)
|
||||
{
|
||||
return features[featureSingleAssetVault] || features[featureLendingProtocol] ||
|
||||
features[featureMPTokensV2];
|
||||
}
|
||||
|
||||
void
|
||||
testInvalidRequest(FeatureBitset features)
|
||||
{
|
||||
@@ -1724,7 +1733,7 @@ class AMMClawbackMPT_test : public beast::unit_test::Suite
|
||||
.asString();
|
||||
auto const lpTokenBalance =
|
||||
amm.ammRpcInfo()[jss::amm][jss::lp_token][jss::value].asString();
|
||||
if (features[featureSingleAssetVault] || features[featureLendingProtocol])
|
||||
if (largeMantissa(features))
|
||||
{
|
||||
BEAST_EXPECT(lpToken == "1.414213562374011" && lpTokenBalance == "1.4142135623741");
|
||||
}
|
||||
@@ -1741,16 +1750,14 @@ class AMMClawbackMPT_test : public beast::unit_test::Suite
|
||||
env(amm::ammClawback(gw, alice, usd, eur, std::nullopt));
|
||||
BEAST_EXPECT(!amm.ammExists());
|
||||
}
|
||||
else if (
|
||||
features[fixAMMv1_3] &&
|
||||
(features[featureSingleAssetVault] || features[featureLendingProtocol]))
|
||||
else if (features[fixAMMv1_3] && largeMantissa(features))
|
||||
{
|
||||
env(amm::ammClawback(gw, alice, usd, eur, std::nullopt));
|
||||
// Without the Rounding feature and with new Number a dust pool
|
||||
// amount remains
|
||||
BEAST_EXPECT(amm.ammExists());
|
||||
}
|
||||
else if (!features[featureSingleAssetVault] && !features[featureLendingProtocol])
|
||||
else if (!largeMantissa(features))
|
||||
{
|
||||
env(amm::ammClawback(gw, alice, usd, eur, std::nullopt), Ter(tecINTERNAL));
|
||||
BEAST_EXPECT(amm.ammExists());
|
||||
@@ -1794,7 +1801,7 @@ class AMMClawbackMPT_test : public beast::unit_test::Suite
|
||||
.asString();
|
||||
auto const lpTokenBalance =
|
||||
amm.ammRpcInfo()[jss::amm][jss::lp_token][jss::value].asString();
|
||||
if (!features[featureSingleAssetVault] && !features[featureLendingProtocol])
|
||||
if (!largeMantissa(features))
|
||||
{
|
||||
BEAST_EXPECT(lpToken == "1.414213562374011" && lpTokenBalance == "1.414213562374");
|
||||
}
|
||||
@@ -1811,16 +1818,14 @@ class AMMClawbackMPT_test : public beast::unit_test::Suite
|
||||
env(amm::ammClawback(gw, alice, usd, eur, std::nullopt));
|
||||
BEAST_EXPECT(!amm.ammExists());
|
||||
}
|
||||
else if (
|
||||
features[fixAMMv1_3] &&
|
||||
(features[featureSingleAssetVault] || features[featureLendingProtocol]))
|
||||
else if (features[fixAMMv1_3] && largeMantissa(features))
|
||||
{
|
||||
// Without the Rounding feature and with new Number a dust pool
|
||||
// amount remains
|
||||
env(amm::ammClawback(gw, alice, usd, eur, std::nullopt));
|
||||
BEAST_EXPECT(amm.ammExists());
|
||||
}
|
||||
else if (!features[featureSingleAssetVault] && !features[featureLendingProtocol])
|
||||
else if (!largeMantissa(features))
|
||||
{
|
||||
env(amm::ammClawback(gw, alice, usd, eur, std::nullopt), Ter(tecINTERNAL));
|
||||
BEAST_EXPECT(amm.ammExists());
|
||||
|
||||
@@ -2857,8 +2857,8 @@ class AMMClawback_test : public beast::unit_test::Suite
|
||||
{
|
||||
// For now, just disable SAV entirely, which locks in the small Number
|
||||
// mantissas
|
||||
FeatureBitset const all =
|
||||
jtx::testableAmendments() - featureSingleAssetVault - featureLendingProtocol;
|
||||
FeatureBitset const all = jtx::testableAmendments() - featureSingleAssetVault -
|
||||
featureLendingProtocol - featureMPTokensV2;
|
||||
|
||||
testInvalidRequest(all);
|
||||
testInvalidRequest(all - featureMPTokensV2);
|
||||
|
||||
@@ -116,7 +116,7 @@ private:
|
||||
Txflags(tfPartialPayment));
|
||||
|
||||
BEAST_EXPECT(ammCarol.expectBalances(
|
||||
btc(1'001'000'000'374'816), eth(100'000'000'000'000'000), ammCarol.tokens()));
|
||||
btc(1'001'000'000'374'814), eth(100'000'000'000'000'000), ammCarol.tokens()));
|
||||
|
||||
env.require(Balance(bob_, eth(200'100'000'000'000'000)));
|
||||
BEAST_EXPECT(isOffer(env, carol_, btc(49'000'000'000'000), XRP(49)));
|
||||
@@ -1890,7 +1890,7 @@ private:
|
||||
auto const flowResult = [&] {
|
||||
STAmount const deliver(eth(51'000'000));
|
||||
STAmount smax(btc(61'000'000));
|
||||
PaymentSandbox sb(env.current().get(), TapNone);
|
||||
PaymentSandbox sb(env.current().get(), ApplyFlags::None);
|
||||
STPathSet paths;
|
||||
auto ipe = [](MPTTester const& iss) {
|
||||
return STPathElement(
|
||||
@@ -1928,7 +1928,7 @@ private:
|
||||
env.app().getOpenLedger().modify([&](OpenView& view, beast::Journal j) {
|
||||
if (flowResult.removableOffers.empty())
|
||||
return false;
|
||||
Sandbox sb(&view, TapNone);
|
||||
Sandbox sb(&view, ApplyFlags::None);
|
||||
for (auto const& o : flowResult.removableOffers)
|
||||
{
|
||||
if (auto ok = sb.peek(keylet::offer(o)))
|
||||
|
||||
@@ -73,7 +73,8 @@ class AMMExtended_test : public jtx::AMMTest
|
||||
// For now, just disable SAV entirely, which locks in the small Number
|
||||
// mantissas
|
||||
FeatureBitset const all_{
|
||||
testableAmendments() - featureSingleAssetVault - featureLendingProtocol};
|
||||
testableAmendments() - featureSingleAssetVault - featureLendingProtocol -
|
||||
featureMPTokensV2};
|
||||
|
||||
private:
|
||||
void
|
||||
@@ -267,39 +268,20 @@ private:
|
||||
{features});
|
||||
|
||||
// tfPassive -- place the offer without crossing it.
|
||||
if (features[featureMPTokensV2])
|
||||
{
|
||||
Env env{*this, features};
|
||||
fund(env, gw_, {alice_, carol_}, XRP(30'000'000), {USD(30'000'000)});
|
||||
|
||||
AMM const ammAlice(env, alice_, XRP(10'100'000), USD(10'000'000));
|
||||
|
||||
// Scale the exact-quality fixture up so the visual relationship
|
||||
// stays clear: the passive CLOB offer has the same 1:1 quality as
|
||||
// the generated AMM offer, so it should not cross.
|
||||
env(offer(carol_, XRP(100'000), USD(100'000), tfPassive));
|
||||
env.close();
|
||||
BEAST_EXPECT(
|
||||
ammAlice.expectBalances(XRP(10'100'000), USD(10'000'000), ammAlice.tokens()));
|
||||
BEAST_EXPECT(expectOffers(env, carol_, 1, {{{XRP(100'000), USD(100'000)}}}));
|
||||
}
|
||||
else
|
||||
{
|
||||
testAMM(
|
||||
[&](AMM& ammAlice, Env& env) {
|
||||
// Carol creates a passive offer that could cross AMM.
|
||||
// Carol's offer should stay in the ledger.
|
||||
env(offer(carol_, XRP(100), USD(100), tfPassive));
|
||||
env.close();
|
||||
BEAST_EXPECT(ammAlice.expectBalances(
|
||||
XRP(10'100), STAmount{USD, 10'000}, ammAlice.tokens()));
|
||||
BEAST_EXPECT(expectOffers(env, carol_, 1, {{{XRP(100), STAmount{USD, 100}}}}));
|
||||
},
|
||||
{{XRP(10'100), USD(10'000)}},
|
||||
0,
|
||||
std::nullopt,
|
||||
{features});
|
||||
}
|
||||
testAMM(
|
||||
[&](AMM& ammAlice, Env& env) {
|
||||
// Carol creates a passive offer that could cross AMM.
|
||||
// Carol's offer should stay in the ledger.
|
||||
env(offer(carol_, XRP(100), USD(100), tfPassive));
|
||||
env.close();
|
||||
BEAST_EXPECT(
|
||||
ammAlice.expectBalances(XRP(10'100), STAmount{USD, 10'000}, ammAlice.tokens()));
|
||||
BEAST_EXPECT(expectOffers(env, carol_, 1, {{{XRP(100), STAmount{USD, 100}}}}));
|
||||
},
|
||||
{{XRP(10'100), USD(10'000)}},
|
||||
0,
|
||||
std::nullopt,
|
||||
{features});
|
||||
|
||||
// tfPassive -- cross only offers of better quality.
|
||||
testAMM(
|
||||
@@ -1450,7 +1432,6 @@ private:
|
||||
testRmFundedOffer(all_ - fixAMMv1_1 - fixAMMv1_3);
|
||||
testEnforceNoRipple(all_);
|
||||
testFillModes(all_);
|
||||
testFillModes(all_ - featureMPTokensV2);
|
||||
testOfferCrossWithXRP(all_);
|
||||
testOfferCrossWithLimitOverride(all_);
|
||||
testCurrencyConversionEntire(all_);
|
||||
@@ -2068,7 +2049,7 @@ private:
|
||||
auto const flowResult = [&] {
|
||||
STAmount const deliver(USD(51));
|
||||
STAmount smax(BTC(61));
|
||||
PaymentSandbox sb(env.current().get(), TapNone);
|
||||
PaymentSandbox sb(env.current().get(), ApplyFlags::None);
|
||||
STPathSet paths;
|
||||
auto ipe = [](Issue const& iss) {
|
||||
return STPathElement(
|
||||
@@ -2106,7 +2087,7 @@ private:
|
||||
env.app().getOpenLedger().modify([&](OpenView& view, beast::Journal j) {
|
||||
if (flowResult.removableOffers.empty())
|
||||
return false;
|
||||
Sandbox sb(&view, TapNone);
|
||||
Sandbox sb(&view, ApplyFlags::None);
|
||||
for (auto const& o : flowResult.removableOffers)
|
||||
{
|
||||
if (auto ok = sb.peek(keylet::offer(o)))
|
||||
|
||||
@@ -1451,12 +1451,14 @@ private:
|
||||
auto carolXRP = env.balance(carol_, XRP);
|
||||
auto carolMPT = env.balance(carol_, MPT(ammAlice[1]));
|
||||
|
||||
// The exact deposit is 201; rounding in the pool's favor at
|
||||
// the large mantissa takes one more unit.
|
||||
ammAlice.deposit(carol_, 100'000, MPT(ammAlice[1])(205));
|
||||
BEAST_EXPECT(ammAlice.expectBalances(
|
||||
XRP(10'000), MPT(ammAlice[1])(10'201), IOUAmount{10'100'000, 0}));
|
||||
XRP(10'000), MPT(ammAlice[1])(10'202), IOUAmount{10'100'000, 0}));
|
||||
|
||||
env.require(Balance(carol_, carolXRP - drops(baseFee)));
|
||||
env.require(Balance(carol_, carolMPT - MPT(ammAlice[1])(201)));
|
||||
env.require(Balance(carol_, carolMPT - MPT(ammAlice[1])(202)));
|
||||
},
|
||||
{{XRP(10'000), gAmmmpt(10'000)}});
|
||||
|
||||
@@ -1467,11 +1469,13 @@ private:
|
||||
auto carolXRP = env.balance(carol_, XRP);
|
||||
auto carolMPT = env.balance(carol_, MPT(ammAlice[1]));
|
||||
|
||||
// The exact deposit is 201 XRP; rounding in the pool's favor
|
||||
// at the large mantissa takes one more drop.
|
||||
ammAlice.deposit(carol_, 100'000, XRP(205));
|
||||
BEAST_EXPECT(ammAlice.expectBalances(
|
||||
XRP(10'201), MPT(ammAlice[1])(10'000), IOUAmount{10'100'000, 0}));
|
||||
XRPAmount{10'201'000'001}, MPT(ammAlice[1])(10'000), IOUAmount{10'100'000, 0}));
|
||||
|
||||
env.require(Balance(carol_, carolXRP - XRP(201) - drops(baseFee)));
|
||||
env.require(Balance(carol_, carolXRP - XRPAmount{201'000'001} - drops(baseFee)));
|
||||
env.require(Balance(carol_, carolMPT));
|
||||
},
|
||||
{{XRP(10'000), gAmmmpt(10'000)}});
|
||||
@@ -1551,10 +1555,10 @@ private:
|
||||
STAmount{ammAlice.lptIssue(), 2004, -6});
|
||||
|
||||
BEAST_EXPECT(ammAlice.expectBalances(
|
||||
XRP(10'000), MPT(ammAlice[1])(10'081), IOUAmount{10'039'920'31840891, -8}));
|
||||
XRP(10'000), MPT(ammAlice[1])(10'080), IOUAmount{10'039'920'3184089, -7}));
|
||||
|
||||
env.require(Balance(carol_, carolXRP - drops(baseFee)));
|
||||
env.require(Balance(carol_, carolMPT - MPT(ammAlice[1])(81)));
|
||||
env.require(Balance(carol_, carolMPT - MPT(ammAlice[1])(80)));
|
||||
},
|
||||
{{XRP(10'000), gAmmmpt(10'000)}});
|
||||
|
||||
@@ -1573,10 +1577,10 @@ private:
|
||||
STAmount{ammAlice.lptIssue(), 2004, -6});
|
||||
|
||||
BEAST_EXPECT(ammAlice.expectBalances(
|
||||
XRP(10'000), MPT(ammAlice[1])(10'081), IOUAmount{10'039'920'31840891, -8}));
|
||||
XRP(10'000), MPT(ammAlice[1])(10'080), IOUAmount{10'039'920'3184089, -7}));
|
||||
|
||||
env.require(Balance(carol_, carolXRP - drops(baseFee)));
|
||||
env.require(Balance(carol_, carolMPT - MPT(ammAlice[1])(81)));
|
||||
env.require(Balance(carol_, carolMPT - MPT(ammAlice[1])(80)));
|
||||
},
|
||||
{{XRP(10'000), gAmmmpt(10'000)}});
|
||||
|
||||
@@ -1797,9 +1801,9 @@ private:
|
||||
|
||||
ammAlice.deposit(gw_, MPT(ammAlice[0])(1000));
|
||||
BEAST_EXPECT(ammAlice.expectBalances(
|
||||
MPT(ammAlice[0])(1'010'999),
|
||||
MPT(ammAlice[0])(1'011'000),
|
||||
MPT(ammAlice[1])(1'010'000),
|
||||
IOUAmount{1'010'499'376546071, -9}));
|
||||
IOUAmount{1'010'499'876298854, -9}));
|
||||
},
|
||||
{{gAmmmpt(10'000), gAmmmpt(10'000)}});
|
||||
|
||||
@@ -2212,7 +2216,7 @@ private:
|
||||
if (env.enabled(fixAMMv1_3))
|
||||
{
|
||||
BEAST_EXPECT(ammAlice.expectBalances(
|
||||
MPT(ammAlice[0])(1), STAmount{USD, 1, -11}, IOUAmount{1, -8}));
|
||||
MPT(ammAlice[0])(1), STAmount{USD, 1, -12}, IOUAmount{1, -9}));
|
||||
}
|
||||
},
|
||||
{{gAmmmpt(10'000'000'000), USD(10'000)}},
|
||||
@@ -2541,11 +2545,12 @@ private:
|
||||
// XRP amount to withdraw is 0
|
||||
ammAlice.withdraw(
|
||||
alice_, IOUAmount{1, -5}, std::nullopt, std::nullopt, Ter(tecAMM_FAILED));
|
||||
// Calculated tokens to withdraw are 0
|
||||
// Calculated tokens to withdraw are 0: 1e-16 of 10,000 USD is
|
||||
// below the large Number mantissa's precision
|
||||
ammAlice.withdraw(
|
||||
alice_,
|
||||
std::nullopt,
|
||||
STAmount{USD, 1, -11},
|
||||
STAmount{USD, 1, -16},
|
||||
std::nullopt,
|
||||
Ter(tecAMM_INVALID_TOKENS));
|
||||
ammAlice.deposit(carol_, STAmount{USD, 1, -10});
|
||||
@@ -2707,7 +2712,7 @@ private:
|
||||
BEAST_EXPECT(ammAlice.expectBalances(
|
||||
XRPAmount(9000'000001),
|
||||
MPT(ammAlice[1])(10'000),
|
||||
IOUAmount{9'486'832'98050514, -8}));
|
||||
IOUAmount{9'486'832'980505138, -9}));
|
||||
},
|
||||
{{XRP(10'000), gAmmmpt(10'000)}});
|
||||
testAMM(
|
||||
@@ -2715,7 +2720,7 @@ private:
|
||||
// single withdraw MPT from XRP/MPT
|
||||
ammAlice.withdraw(alice_, MPT(ammAlice[1])(1'000));
|
||||
BEAST_EXPECT(ammAlice.expectBalances(
|
||||
XRP(10000), MPT(ammAlice[1])(9001), IOUAmount{9'486'832'98050514, -8}));
|
||||
XRP(10000), MPT(ammAlice[1])(9001), IOUAmount{9'486'832'980505138, -9}));
|
||||
},
|
||||
{{XRP(10'000), gAmmmpt(10'000)}});
|
||||
testAMM(
|
||||
@@ -2723,9 +2728,7 @@ private:
|
||||
// single withdraw IOU from IOU/MPT
|
||||
ammAlice.withdraw(alice_, USD(1'000));
|
||||
BEAST_EXPECT(ammAlice.expectBalances(
|
||||
STAmount{USD, UINT64_C(9000'000000000004), -12},
|
||||
MPT(ammAlice[1])(10'000),
|
||||
IOUAmount{9486'83298050514, -11}));
|
||||
USD(9'000), MPT(ammAlice[1])(10'000), IOUAmount{9486'832980505138, -12}));
|
||||
},
|
||||
{{USD(10'000), gAmmmpt(10'000)}});
|
||||
testAMM(
|
||||
@@ -2733,7 +2736,7 @@ private:
|
||||
// single withdraw MPT from IOU/MPT
|
||||
ammAlice.withdraw(alice_, MPT(ammAlice[1])(1'000));
|
||||
BEAST_EXPECT(ammAlice.expectBalances(
|
||||
USD(10'000), MPT(ammAlice[1])(9001), IOUAmount{9486'83298050514, -11}));
|
||||
USD(10'000), MPT(ammAlice[1])(9001), IOUAmount{9486'832980505138, -12}));
|
||||
},
|
||||
{{USD(10'000), gAmmmpt(10'000)}});
|
||||
testAMM(
|
||||
@@ -2743,7 +2746,7 @@ private:
|
||||
BEAST_EXPECT(ammAlice.expectBalances(
|
||||
MPT(ammAlice[0])(9001),
|
||||
MPT(ammAlice[1])(10'000),
|
||||
IOUAmount{9486'83298050514, -11}));
|
||||
IOUAmount{9486'832980505138, -12}));
|
||||
},
|
||||
{{gAmmmpt(10'000), gAmmmpt(10'000)}});
|
||||
|
||||
@@ -3024,7 +3027,7 @@ private:
|
||||
ammAlice.deposit(carol_, 1'000);
|
||||
ammAlice.withdrawAll(carol_, USD(0));
|
||||
BEAST_EXPECT(ammAlice.expectBalances(
|
||||
STAmount{USD, UINT64_C(9'090'909090909092), -12},
|
||||
STAmount{USD, UINT64_C(9'090'909090909091), -12},
|
||||
MPT(ammAlice[1])(11'000),
|
||||
IOUAmount{10'000}));
|
||||
},
|
||||
@@ -3249,7 +3252,7 @@ private:
|
||||
BEAST_EXPECT(ammAlice.expectBalances(
|
||||
MPT(ammAlice[0])(10'000'000'000),
|
||||
STAmount{USD, 1, -11},
|
||||
IOUAmount{316227765, -9}));
|
||||
IOUAmount{316227766, -9}));
|
||||
},
|
||||
{{gAmmmpt(10'000'000'000), USD(10'000)}});
|
||||
// XRP
|
||||
@@ -4155,7 +4158,7 @@ private:
|
||||
auto jtx = env.jt(tx, Seq(1), Fee(10));
|
||||
env.app().config().features.erase(featureMPTokensV2);
|
||||
PreflightContext const ctx(
|
||||
env.app(), *jtx.stx, env.current()->rules(), TapNone, env.journal);
|
||||
env.app(), *jtx.stx, env.current()->rules(), ApplyFlags::None, env.journal);
|
||||
auto pf = AMMBid::checkExtraFeatures(ctx);
|
||||
BEAST_EXPECT(pf == false);
|
||||
env.app().config().features.insert(featureMPTokensV2);
|
||||
@@ -4167,7 +4170,7 @@ private:
|
||||
jtx.jv["Asset2"].removeMember("mpt_issuance_id");
|
||||
jtx.stx = env.ust(jtx);
|
||||
PreflightContext const ctx(
|
||||
env.app(), *jtx.stx, env.current()->rules(), TapNone, env.journal);
|
||||
env.app(), *jtx.stx, env.current()->rules(), ApplyFlags::None, env.journal);
|
||||
auto pf = AMMBid::preflight(ctx);
|
||||
BEAST_EXPECT(pf == temBAD_AMM_TOKENS);
|
||||
}
|
||||
@@ -5960,7 +5963,7 @@ private:
|
||||
if (!features[fixAMMv1_3])
|
||||
{
|
||||
BEAST_EXPECT(tokensFee == IOUAmount(98'019'80198019, -8));
|
||||
BEAST_EXPECT(tokensNoFee == IOUAmount(98'495'13933556, -8));
|
||||
BEAST_EXPECT(tokensNoFee == IOUAmount(98'495'13933557, -8));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
+11
-72
@@ -84,7 +84,8 @@ private:
|
||||
{
|
||||
// For now, just disable SAV entirely, which locks in the small Number
|
||||
// mantissas
|
||||
return jtx::testableAmendments() - featureSingleAssetVault - featureLendingProtocol;
|
||||
return jtx::testableAmendments() - featureSingleAssetVault - featureLendingProtocol -
|
||||
featureMPTokensV2;
|
||||
}
|
||||
|
||||
// Seed from the local testableAmendments() which strips SAV and Lending.
|
||||
@@ -3262,7 +3263,7 @@ private:
|
||||
auto jtx = env.jt(tx, Seq(1), Fee(baseFee));
|
||||
env.app().config().features.erase(featureAMM);
|
||||
PreflightContext const pfCtx(
|
||||
env.app(), *jtx.stx, env.current()->rules(), TapNone, env.journal);
|
||||
env.app(), *jtx.stx, env.current()->rules(), ApplyFlags::None, env.journal);
|
||||
auto pf = Transactor::invokePreflight<AMMBid>(pfCtx);
|
||||
BEAST_EXPECT(pf == temDISABLED);
|
||||
env.app().config().features.insert(featureAMM);
|
||||
@@ -3273,7 +3274,7 @@ private:
|
||||
jtx.jv["TxnSignature"] = "deadbeef";
|
||||
jtx.stx = env.ust(jtx);
|
||||
PreflightContext const pfCtx(
|
||||
env.app(), *jtx.stx, env.current()->rules(), TapNone, env.journal);
|
||||
env.app(), *jtx.stx, env.current()->rules(), ApplyFlags::None, env.journal);
|
||||
auto pf = Transactor::invokePreflight<AMMBid>(pfCtx);
|
||||
BEAST_EXPECT(!isTesSuccess(pf));
|
||||
}
|
||||
@@ -3284,7 +3285,7 @@ private:
|
||||
jtx.jv["Asset2"].removeMember("issuer");
|
||||
jtx.stx = env.ust(jtx);
|
||||
PreflightContext const pfCtx(
|
||||
env.app(), *jtx.stx, env.current()->rules(), TapNone, env.journal);
|
||||
env.app(), *jtx.stx, env.current()->rules(), ApplyFlags::None, env.journal);
|
||||
auto pf = Transactor::invokePreflight<AMMBid>(pfCtx);
|
||||
BEAST_EXPECT(pf == temBAD_AMM_TOKENS);
|
||||
}
|
||||
@@ -3810,7 +3811,7 @@ private:
|
||||
BEAST_EXPECT(amm.expectBalances(XRP(1'000), USD(500), amm.tokens()));
|
||||
BEAST_EXPECT(expectOffers(env, carol_, 1, {{Amounts{XRP(100), USD(55)}}}));
|
||||
}
|
||||
else if (!features[featureMPTokensV2])
|
||||
else
|
||||
{
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
XRPAmount(909'090'909),
|
||||
@@ -3825,30 +3826,6 @@ private:
|
||||
env.balance(carol_, USD) ==
|
||||
STAmount(USD, UINT64_C(29'949'94999999494), -11));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Post-amendment the transfer fee is taken into account
|
||||
// when calculating the limit out based on limitQuality.
|
||||
// This increases the limitQuality and decreases
|
||||
// the limit out. Consequently, AMM offer size is decreased,
|
||||
// and the quality is increased, matching the overall
|
||||
// quality.
|
||||
// AMM offer ~50USD/91XRP
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
XRPAmount(909'090'910),
|
||||
STAmount{USD, UINT64_C(549'99999945), -8},
|
||||
amm.tokens()));
|
||||
// Offer ~91XRP/50USD
|
||||
BEAST_EXPECT(expectOffers(
|
||||
env,
|
||||
carol_,
|
||||
1,
|
||||
{{Amounts{XRPAmount{9'090'910}, STAmount{USD, 5'0000005, -7}}}}));
|
||||
// Carol pays 0.1% fee on ~50USD =~ 0.05USD
|
||||
BEAST_EXPECT(
|
||||
env.balance(carol_, USD) ==
|
||||
STAmount(USD, UINT64_C(29'949'95000060055), -11));
|
||||
}
|
||||
},
|
||||
{{XRP(1'000), USD(500)}},
|
||||
0,
|
||||
@@ -6498,7 +6475,7 @@ private:
|
||||
BEAST_EXPECT(expectOffers(env, bob_, 1, {{Amounts{USD(1), XRPAmount(500)}}}));
|
||||
BEAST_EXPECT(expectOffers(env, carol_, 1, {{Amounts{XRP(100), USD(55)}}}));
|
||||
}
|
||||
else if (!features[featureMPTokensV2])
|
||||
else
|
||||
{
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
XRPAmount(909'090'909),
|
||||
@@ -6511,19 +6488,6 @@ private:
|
||||
{{Amounts{XRPAmount{9'090'909}, STAmount{USD, 4'99999995, -8}}}}));
|
||||
BEAST_EXPECT(expectOffers(env, bob_, 1, {{Amounts{USD(1), XRPAmount(500)}}}));
|
||||
}
|
||||
else
|
||||
{
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
XRPAmount(909'090'910),
|
||||
STAmount{USD, UINT64_C(549'99999945), -8},
|
||||
amm.tokens()));
|
||||
BEAST_EXPECT(expectOffers(
|
||||
env,
|
||||
carol_,
|
||||
1,
|
||||
{{Amounts{XRPAmount{9'090'910}, STAmount{USD, 5'0000005, -7}}}}));
|
||||
BEAST_EXPECT(expectOffers(env, bob_, 1, {{Amounts{USD(1), XRPAmount(500)}}}));
|
||||
}
|
||||
}
|
||||
|
||||
// There is no blocking offer, the same AMM liquidity is consumed
|
||||
@@ -6535,30 +6499,10 @@ private:
|
||||
AMM const amm(env, alice_, XRP(1'000), USD(500));
|
||||
env(offer(carol_, XRP(100), USD(55)));
|
||||
env.close();
|
||||
if (!features[featureMPTokensV2])
|
||||
{
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
XRPAmount(909'090'909),
|
||||
STAmount{USD, UINT64_C(550'000000055), -9},
|
||||
amm.tokens()));
|
||||
BEAST_EXPECT(expectOffers(
|
||||
env,
|
||||
carol_,
|
||||
1,
|
||||
{{Amounts{XRPAmount{9'090'909}, STAmount{USD, 4'99999995, -8}}}}));
|
||||
}
|
||||
else
|
||||
{
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
XRPAmount(909'090'910),
|
||||
STAmount{USD, UINT64_C(549'99999945), -8},
|
||||
amm.tokens()));
|
||||
BEAST_EXPECT(expectOffers(
|
||||
env,
|
||||
carol_,
|
||||
1,
|
||||
{{Amounts{XRPAmount{9'090'910}, STAmount{USD, 5'0000005, -7}}}}));
|
||||
}
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
XRPAmount(909'090'909), STAmount{USD, UINT64_C(550'000000055), -9}, amm.tokens()));
|
||||
BEAST_EXPECT(expectOffers(
|
||||
env, carol_, 1, {{Amounts{XRPAmount{9'090'909}, STAmount{USD, 4'99999995, -8}}}}));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7448,8 +7392,6 @@ private:
|
||||
.err = Ter(tecAMM_BALANCE)});
|
||||
};
|
||||
|
||||
// Bound holds regardless of the deposit-side fix amendment.
|
||||
test(all - featureMPTokensV2);
|
||||
test(all);
|
||||
}
|
||||
|
||||
@@ -7481,7 +7423,6 @@ private:
|
||||
testFlags();
|
||||
testRippling();
|
||||
testAMMAndCLOB(all);
|
||||
testAMMAndCLOB(all - featureMPTokensV2);
|
||||
testAMMAndCLOB(all - fixAMMv1_1 - fixAMMv1_3);
|
||||
testTradingFee(all);
|
||||
testTradingFee(all - fixAMMv1_3);
|
||||
@@ -7501,10 +7442,8 @@ private:
|
||||
testOverflowOffer(all - fixAMMv1_1 - fixAMMv1_3);
|
||||
testSwapRounding();
|
||||
testFixChangeSpotPriceQuality(all);
|
||||
testFixChangeSpotPriceQuality(all - featureMPTokensV2);
|
||||
testFixChangeSpotPriceQuality(all - fixAMMv1_1 - fixAMMv1_3);
|
||||
testFixAMMOfferBlockedByLOB(all);
|
||||
testFixAMMOfferBlockedByLOB(all - featureMPTokensV2);
|
||||
testFixAMMOfferBlockedByLOB(all - fixAMMv1_1 - fixAMMv1_3);
|
||||
testLPTokenBalance(all);
|
||||
testLPTokenBalance(all - fixAMMv1_3);
|
||||
|
||||
@@ -594,7 +594,7 @@ public:
|
||||
stx->at(sfSigningPubKey) = makeSlice(std::string("badkey"));
|
||||
|
||||
env.app().getOpenLedger().modify([&](OpenView& view, beast::Journal j) {
|
||||
auto const result = xrpl::apply(env.app(), view, *stx, TapNone, j);
|
||||
auto const result = xrpl::apply(env.app(), view, *stx, ApplyFlags::None, j);
|
||||
BEAST_EXPECT(result.ter == temBAD_SIGNATURE);
|
||||
BEAST_EXPECT(!result.applied);
|
||||
return result.applied;
|
||||
|
||||
@@ -436,7 +436,7 @@ class Batch_test : public beast::unit_test::Suite
|
||||
{
|
||||
auto jtx = env.jt(pay(alice, bob, XRP(1)), Txflags(tfInnerBatchTxn));
|
||||
PreflightContext const pfCtx(
|
||||
env.app(), *jtx.stx, env.current()->rules(), TapNone, env.journal);
|
||||
env.app(), *jtx.stx, env.current()->rules(), ApplyFlags::None, env.journal);
|
||||
auto const pf = Transactor::invokePreflight<Payment>(pfCtx);
|
||||
BEAST_EXPECT(pf == temINVALID_INNER_BATCH);
|
||||
}
|
||||
@@ -451,7 +451,7 @@ class Batch_test : public beast::unit_test::Suite
|
||||
disabledEnv.app(),
|
||||
*jtx.stx,
|
||||
disabledEnv.current()->rules(),
|
||||
TapNone,
|
||||
ApplyFlags::None,
|
||||
disabledEnv.journal);
|
||||
auto const pf = Transactor::invokePreflight<Payment>(pfCtx);
|
||||
BEAST_EXPECT(pf == temINVALID_FLAG);
|
||||
@@ -462,7 +462,12 @@ class Batch_test : public beast::unit_test::Suite
|
||||
{
|
||||
auto jtx = env.jt(pay(alice, bob, XRP(1)));
|
||||
PreflightContext const pfCtx(
|
||||
env.app(), *jtx.stx, UInt256{1}, env.current()->rules(), TapBatch, env.journal);
|
||||
env.app(),
|
||||
*jtx.stx,
|
||||
UInt256{1},
|
||||
env.current()->rules(),
|
||||
ApplyFlags::Batch,
|
||||
env.journal);
|
||||
auto const pf = Transactor::invokePreflight<Payment>(pfCtx);
|
||||
BEAST_EXPECT(pf == temINVALID_INNER_BATCH);
|
||||
}
|
||||
@@ -1590,7 +1595,7 @@ class Batch_test : public beast::unit_test::Suite
|
||||
batch::Sig(std::vector<Reg>(kMaxBatchSigners + 1, bob)));
|
||||
|
||||
env.app().getOpenLedger().modify([&](OpenView& view, beast::Journal j) {
|
||||
auto const result = xrpl::apply(env.app(), view, *jt.stx, TapNone, j);
|
||||
auto const result = xrpl::apply(env.app(), view, *jt.stx, ApplyFlags::None, j);
|
||||
BEAST_EXPECT(!result.applied && result.ter == temARRAY_TOO_LARGE);
|
||||
return result.applied;
|
||||
});
|
||||
@@ -4391,7 +4396,7 @@ class Batch_test : public beast::unit_test::Suite
|
||||
BEAST_EXPECT(!passesLocalChecks(stx, reason));
|
||||
BEAST_EXPECT(reason == "Cannot submit pseudo transactions.");
|
||||
env.app().getOpenLedger().modify([&](OpenView& view, beast::Journal j) {
|
||||
auto const result = xrpl::apply(env.app(), view, stx, TapNone, j);
|
||||
auto const result = xrpl::apply(env.app(), view, stx, ApplyFlags::None, j);
|
||||
BEAST_EXPECT(!result.applied && result.ter == temINVALID_FLAG);
|
||||
return result.applied;
|
||||
});
|
||||
@@ -5637,7 +5642,7 @@ class Batch_test : public beast::unit_test::Suite
|
||||
obj.setFieldU32(sfFlags, tfInnerBatchTxn);
|
||||
});
|
||||
|
||||
auto const result = xrpl::apply(env.app(), view, stx, TapNone, j);
|
||||
auto const result = xrpl::apply(env.app(), view, stx, ApplyFlags::None, j);
|
||||
// Must NOT be applied — signature was never checked
|
||||
BEAST_EXPECT(!result.applied);
|
||||
return false;
|
||||
|
||||
Loaded 100 of 185 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user