mirror of
https://github.com/XRPLF/rippled.git
synced 2026-08-19 13:20:54 +00:00
Merge remote-tracking branch 'origin/develop' into ripple/wasmi
This commit is contained in:
@@ -132,6 +132,7 @@ words:
|
||||
- godexsoft
|
||||
- gpgcheck
|
||||
- gpgkey
|
||||
- Hinnant
|
||||
- hotwallet
|
||||
- hwaddress
|
||||
- hwrap
|
||||
@@ -165,6 +166,7 @@ words:
|
||||
- llection
|
||||
- LOCALGOOD
|
||||
- logwstream
|
||||
- Lombrozo
|
||||
- lseq
|
||||
- lsmf
|
||||
- ltype
|
||||
@@ -201,6 +203,7 @@ words:
|
||||
- nftokens
|
||||
- nftpage
|
||||
- nikb
|
||||
- Nikolaos
|
||||
- nixfmt
|
||||
- nixos
|
||||
- nixpkgs
|
||||
|
||||
19
.github/scripts/strategy-matrix/generate.py
vendored
19
.github/scripts/strategy-matrix/generate.py
vendored
@@ -33,6 +33,10 @@ def get_cmake_args(build_type: str, extra_args: str) -> str:
|
||||
# Every config must declare 'minimal'. Minimal configs form the reduced matrix
|
||||
# built for pull requests by default; the full matrix adds the rest. Packaging
|
||||
# configs declare it too, but packaging is gated in the workflow, not by it.
|
||||
#
|
||||
# Configs may also opt into 'benchmark' to smoke-run the benchmarks. Note that
|
||||
# the flag applies to every entry a config expands into, so only set it on
|
||||
# configs that expand to a single combination.
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
@@ -43,6 +47,7 @@ class LinuxConfig:
|
||||
build_type: list[str]
|
||||
arch: list[str]
|
||||
minimal: bool
|
||||
benchmark: bool = False # if true, smoke-run the benchmarks after testing
|
||||
sanitizers: list[str] = dataclasses.field(default_factory=list)
|
||||
suffix: str = ""
|
||||
extra_cmake_args: str = ""
|
||||
@@ -81,6 +86,7 @@ class PlatformConfig:
|
||||
build_type: list[str]
|
||||
minimal: bool
|
||||
build_only: bool = False # if true, skip tests (e.g. macos/Windows Debug)
|
||||
benchmark: bool = False # if true, smoke-run the benchmarks after testing
|
||||
extra_cmake_args: str = ""
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
@@ -125,6 +131,7 @@ class MatrixEntry:
|
||||
cmake_args: str
|
||||
cmake_target: str
|
||||
build_only: bool
|
||||
benchmark: bool
|
||||
build_type: str
|
||||
architecture: Architecture
|
||||
sanitizers: str
|
||||
@@ -136,7 +143,8 @@ class MatrixEntry:
|
||||
class PackagingEntry:
|
||||
"""One entry in the generated packaging strategy matrix."""
|
||||
|
||||
artifact_name: str
|
||||
xrpld_artifact_name: str
|
||||
validator_keys_artifact_name: str
|
||||
image: str
|
||||
distro: str # e.g. "debian" or "rhel"; drives package-format-specific steps
|
||||
|
||||
@@ -193,6 +201,7 @@ def expand_linux_matrix(linux: LinuxFile, minimal: bool) -> list[MatrixEntry]:
|
||||
cmake_args=get_cmake_args(build_type, cfg.extra_cmake_args),
|
||||
cmake_target="all",
|
||||
build_only=False,
|
||||
benchmark=cfg.benchmark,
|
||||
build_type=build_type,
|
||||
architecture=arch_info,
|
||||
sanitizers=sanitizer,
|
||||
@@ -210,14 +219,19 @@ def expand_linux_packaging(linux: LinuxFile) -> list[PackagingEntry]:
|
||||
the nix-based build images, because deb/rpm tooling (debhelper, rpm-build)
|
||||
is taken from the distro's archive rather than from nixpkgs. Each config
|
||||
entry carries its own 'image'.
|
||||
|
||||
The artifact names must match what the build job uploads: one artifact per
|
||||
binary, each named after the build config.
|
||||
"""
|
||||
entries = []
|
||||
for distro, configs in linux.package_configs.items():
|
||||
for cfg in configs:
|
||||
for compiler, build_type in itertools.product(cfg.compiler, cfg.build_type):
|
||||
config_name = f"{distro}-{compiler}-{build_type.lower()}-amd64"
|
||||
entries.append(
|
||||
PackagingEntry(
|
||||
artifact_name=f"xrpld-{distro}-{compiler}-{build_type.lower()}-amd64",
|
||||
xrpld_artifact_name=f"xrpld-{config_name}",
|
||||
validator_keys_artifact_name=f"validator-keys-{config_name}",
|
||||
image=cfg.image,
|
||||
distro=distro,
|
||||
)
|
||||
@@ -245,6 +259,7 @@ def expand_platform_matrix(pf: PlatformFile, minimal: bool) -> list[MatrixEntry]
|
||||
cmake_args=get_cmake_args(build_type, cfg.extra_cmake_args),
|
||||
cmake_target="install" if is_windows else "all",
|
||||
build_only=cfg.build_only,
|
||||
benchmark=cfg.benchmark,
|
||||
build_type=build_type,
|
||||
architecture=Architecture(platform=pf.platform, runner=pf.runner),
|
||||
sanitizers="",
|
||||
|
||||
9
.github/scripts/strategy-matrix/linux.json
vendored
9
.github/scripts/strategy-matrix/linux.json
vendored
@@ -14,7 +14,8 @@
|
||||
"compiler": ["clang"],
|
||||
"build_type": ["Release"],
|
||||
"arch": ["amd64"],
|
||||
"minimal": true
|
||||
"minimal": true,
|
||||
"benchmark": true
|
||||
},
|
||||
|
||||
{
|
||||
@@ -69,7 +70,8 @@
|
||||
"compiler": ["gcc"],
|
||||
"build_type": ["Release"],
|
||||
"arch": ["amd64"],
|
||||
"minimal": false
|
||||
"minimal": false,
|
||||
"extra_cmake_args": "-Dvalidator_keys=ON"
|
||||
}
|
||||
],
|
||||
|
||||
@@ -78,7 +80,8 @@
|
||||
"compiler": ["gcc"],
|
||||
"build_type": ["Release"],
|
||||
"arch": ["amd64"],
|
||||
"minimal": false
|
||||
"minimal": false,
|
||||
"extra_cmake_args": "-Dvalidator_keys=ON"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
7
.github/workflows/on-pr.yml
vendored
7
.github/workflows/on-pr.yml
vendored
@@ -82,6 +82,7 @@ jobs:
|
||||
.github/scripts/strategy-matrix/**
|
||||
.github/workflows/reusable-build-test-config.yml
|
||||
.github/workflows/reusable-build-test.yml
|
||||
.github/workflows/reusable-check-autogen.yml
|
||||
.github/workflows/reusable-clang-tidy.yml
|
||||
.github/workflows/reusable-package.yml
|
||||
.github/workflows/reusable-strategy-matrix.yml
|
||||
@@ -126,6 +127,11 @@ jobs:
|
||||
outputs:
|
||||
go: ${{ steps.go.outputs.go == 'true' }}
|
||||
|
||||
check-autogen:
|
||||
needs: should-run
|
||||
if: ${{ needs.should-run.outputs.go == 'true' }}
|
||||
uses: ./.github/workflows/reusable-check-autogen.yml
|
||||
|
||||
check-levelization:
|
||||
needs: should-run
|
||||
if: ${{ needs.should-run.outputs.go == 'true' }}
|
||||
@@ -200,6 +206,7 @@ jobs:
|
||||
passed:
|
||||
if: failure() || cancelled()
|
||||
needs:
|
||||
- check-autogen
|
||||
- check-levelization
|
||||
- check-rename
|
||||
- clang-tidy
|
||||
|
||||
4
.github/workflows/on-trigger.yml
vendored
4
.github/workflows/on-trigger.yml
vendored
@@ -20,6 +20,7 @@ on:
|
||||
- ".github/scripts/strategy-matrix/**"
|
||||
- ".github/workflows/reusable-build-test-config.yml"
|
||||
- ".github/workflows/reusable-build-test.yml"
|
||||
- ".github/workflows/reusable-check-autogen.yml"
|
||||
- ".github/workflows/reusable-clang-tidy.yml"
|
||||
- ".github/workflows/reusable-package.yml"
|
||||
- ".github/workflows/reusable-strategy-matrix.yml"
|
||||
@@ -67,6 +68,9 @@ defaults:
|
||||
shell: bash
|
||||
|
||||
jobs:
|
||||
check-autogen:
|
||||
uses: ./.github/workflows/reusable-check-autogen.yml
|
||||
|
||||
clang-tidy:
|
||||
uses: ./.github/workflows/reusable-clang-tidy.yml
|
||||
permissions:
|
||||
|
||||
70
.github/workflows/reusable-build-test-config.yml
vendored
70
.github/workflows/reusable-build-test-config.yml
vendored
@@ -3,6 +3,12 @@ name: Build and test configuration
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
benchmark:
|
||||
description: "Whether to smoke-run the benchmarks after testing."
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
build_only:
|
||||
description: 'Whether to only build or to build and test the code ("true", "false").'
|
||||
required: true
|
||||
@@ -100,9 +106,10 @@ jobs:
|
||||
# header files are copied into separate directories by CMake, which will
|
||||
# otherwise result in cache misses.
|
||||
CCACHE_SLOPPINESS: include_file_ctime,include_file_mtime
|
||||
# Determine if coverage and voidstar should be enabled.
|
||||
# Determine if coverage, voidstar and validator-keys should be enabled.
|
||||
COVERAGE_ENABLED: ${{ contains(inputs.cmake_args, '-Dcoverage=ON') }}
|
||||
VOIDSTAR_ENABLED: ${{ contains(inputs.cmake_args, '-Dvoidstar=ON') }}
|
||||
VALIDATOR_KEYS_ENABLED: ${{ contains(inputs.cmake_args, '-Dvalidator_keys=ON') }}
|
||||
SANITIZERS_ENABLED: ${{ inputs.sanitizers != '' }}
|
||||
steps:
|
||||
- name: Cleanup workspace (macOS and Windows)
|
||||
@@ -170,9 +177,9 @@ jobs:
|
||||
..
|
||||
|
||||
# Export the sanitizer options before any instrumented binary runs. The
|
||||
# protocol code-gen and build steps below invoke instrumented dependency
|
||||
# tools (protoc, grpc), so setting UBSAN_OPTIONS here lets the UBSan
|
||||
# suppression list silence their diagnostics too, not just at test time.
|
||||
# build step below invokes instrumented dependency tools (protoc, grpc),
|
||||
# so setting UBSAN_OPTIONS here lets the UBSan suppression list silence
|
||||
# their diagnostics too, not just at test time.
|
||||
# GITHUB_WORKSPACE (not the github.workspace context) is used so the path
|
||||
# resolves correctly inside the container job.
|
||||
- name: Set sanitizer options
|
||||
@@ -190,32 +197,6 @@ jobs:
|
||||
echo "UBSAN_OPTIONS=include=${SUPP}/runtime-ubsan-options.txt:suppressions=${SUPP}/ubsan.supp" >>${GITHUB_ENV}
|
||||
echo "LSAN_OPTIONS=include=${SUPP}/runtime-lsan-options.txt:suppressions=${SUPP}/lsan.supp" >>${GITHUB_ENV}
|
||||
|
||||
- name: Check protocol autogen files are up-to-date
|
||||
working-directory: ${{ env.BUILD_DIR }}
|
||||
env:
|
||||
MESSAGE: |
|
||||
|
||||
The generated protocol wrapper classes are out of date.
|
||||
|
||||
This typically happens when the macro files or generator scripts
|
||||
have changed but the generated files were not regenerated.
|
||||
|
||||
To fix this:
|
||||
1. Run: cmake --build . --target setup_code_gen
|
||||
2. Run: cmake --build . --target code_gen
|
||||
3. Commit and push the regenerated files
|
||||
run: |
|
||||
set -e
|
||||
cmake --build . --target setup_code_gen
|
||||
cmake --build . --target code_gen
|
||||
DIFF=$(git -C .. status --porcelain -- include/xrpl/protocol_autogen src/tests/libxrpl/protocol_autogen)
|
||||
if [ -n "${DIFF}" ]; then
|
||||
echo "::error::Generated protocol files are out of date"
|
||||
git -C .. diff -- include/xrpl/protocol_autogen src/tests/libxrpl/protocol_autogen
|
||||
echo "${MESSAGE}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Build the binary
|
||||
working-directory: ${{ env.BUILD_DIR }}
|
||||
env:
|
||||
@@ -249,6 +230,22 @@ jobs:
|
||||
retention-days: 3
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Run the validator-keys tests
|
||||
if: ${{ env.VALIDATOR_KEYS_ENABLED == 'true' }}
|
||||
working-directory: ${{ env.BUILD_DIR }}
|
||||
run: ./validator-keys --unittest
|
||||
|
||||
- name: Upload the validator-keys binary
|
||||
if: ${{ github.event.repository.visibility == 'public' && env.VALIDATOR_KEYS_ENABLED == 'true' }}
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: validator-keys-${{ inputs.config_name }}
|
||||
path: |
|
||||
${{ env.BUILD_DIR }}/validator-keys
|
||||
${{ env.BUILD_DIR }}/validator-keys-LICENSE
|
||||
retention-days: 3
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Upload the test binary (Linux)
|
||||
if: ${{ github.event.repository.visibility == 'public' && runner.os == 'Linux' }}
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
@@ -328,11 +325,14 @@ jobs:
|
||||
|
||||
# Smoke-run every benchmark module with a single repetition to confirm the
|
||||
# benchmarks still build and execute. This is a correctness check, not a
|
||||
# performance measurement, so it is skipped for instrumented builds
|
||||
# (sanitizers/coverage/voidstar), where it would be slow and meaningless,
|
||||
# and on Windows, where the `install` target does not build them.
|
||||
# performance measurement, so there is nothing to gain from repeating it
|
||||
# across configurations: it is opted into by a single config in the
|
||||
# strategy matrix (see the 'benchmark' flag in the JSON files), which
|
||||
# keeps it off instrumented builds (sanitizers/coverage/voidstar), where
|
||||
# it would be slow and meaningless, off Debug builds, where it is much
|
||||
# slower, and off Windows, where the `install` target does not build them.
|
||||
- name: Run the benchmarks
|
||||
if: ${{ !inputs.build_only && runner.os != 'Windows' && env.SANITIZERS_ENABLED == 'false' && env.COVERAGE_ENABLED != 'true' && env.VOIDSTAR_ENABLED != 'true' }}
|
||||
if: ${{ inputs.benchmark }}
|
||||
working-directory: ${{ env.BUILD_DIR }}
|
||||
run: |
|
||||
rc=0
|
||||
@@ -387,7 +387,7 @@ jobs:
|
||||
--target coverage
|
||||
|
||||
- name: Upload coverage report
|
||||
if: ${{ github.repository == 'XRPLF/rippled' && !inputs.build_only && env.COVERAGE_ENABLED == 'true' }}
|
||||
if: ${{ github.repository_owner == 'XRPLF' && !inputs.build_only && env.COVERAGE_ENABLED == 'true' }}
|
||||
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
|
||||
with:
|
||||
disable_search: true
|
||||
|
||||
1
.github/workflows/reusable-build-test.yml
vendored
1
.github/workflows/reusable-build-test.yml
vendored
@@ -40,6 +40,7 @@ jobs:
|
||||
fail-fast: ${{ github.event_name == 'merge_group' }}
|
||||
matrix: ${{ fromJson(needs.generate-matrix.outputs.matrix) }}
|
||||
with:
|
||||
benchmark: ${{ matrix.benchmark }}
|
||||
build_only: ${{ matrix.build_only }}
|
||||
build_type: ${{ matrix.build_type }}
|
||||
ccache_enabled: ${{ inputs.ccache_enabled }}
|
||||
|
||||
76
.github/workflows/reusable-check-autogen.yml
vendored
Normal file
76
.github/workflows/reusable-check-autogen.yml
vendored
Normal file
@@ -0,0 +1,76 @@
|
||||
# This workflow checks that the generated protocol wrapper classes are
|
||||
# up-to-date with the macro files and generator scripts they are produced from,
|
||||
# see more info in include/xrpl/protocol_autogen/README.md.
|
||||
name: Check autogen
|
||||
|
||||
# This workflow can only be triggered by other workflows.
|
||||
on: workflow_call
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}-autogen
|
||||
cancel-in-progress: true
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
env:
|
||||
BUILD_DIR: build/codegen
|
||||
|
||||
jobs:
|
||||
autogen:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
||||
with:
|
||||
python-version: "3.13"
|
||||
|
||||
# Code generation is pure Python, so the standalone project below offers
|
||||
# the same targets as the main build without needing its dependencies or
|
||||
# a compiler, which keeps this job down to a few seconds.
|
||||
- name: Configure CMake
|
||||
run: cmake -S cmake/codegen -B "${BUILD_DIR}"
|
||||
|
||||
- name: Install code generation dependencies
|
||||
run: cmake --build "${BUILD_DIR}" --target setup_code_gen
|
||||
|
||||
- name: Generate code
|
||||
run: cmake --build "${BUILD_DIR}" --target code_gen
|
||||
|
||||
- name: Check for differences
|
||||
env:
|
||||
MESSAGE: |
|
||||
|
||||
The generated protocol wrapper classes are out of date.
|
||||
|
||||
This typically happens when the macro files or generator scripts
|
||||
have changed but the generated files were not regenerated.
|
||||
|
||||
Run the following from the repository root, then commit and push
|
||||
the regenerated files. This needs neither the dependencies nor a
|
||||
compiler. See include/xrpl/protocol_autogen/README.md for more info.
|
||||
|
||||
cmake -S cmake/codegen -B build/codegen
|
||||
cmake --build build/codegen --target setup_code_gen
|
||||
cmake --build build/codegen --target code_gen
|
||||
|
||||
In an already configured build directory, the 'setup_code_gen' and
|
||||
'code_gen' targets do the same thing.
|
||||
run: |
|
||||
# Record untracked files in the index without staging their contents,
|
||||
# so that classes generated for a newly added transaction or ledger
|
||||
# entry type show up in the diff below rather than silently as an
|
||||
# empty one.
|
||||
git add --intent-to-add .
|
||||
DIFF=$(git status --porcelain)
|
||||
if [ -n "${DIFF}" ]; then
|
||||
# Print the differences to give the contributor a hint about what to
|
||||
# expect when running code generation on their own machine.
|
||||
git diff
|
||||
echo "${MESSAGE}"
|
||||
exit 1
|
||||
fi
|
||||
26
.github/workflows/reusable-package.yml
vendored
26
.github/workflows/reusable-package.yml
vendored
@@ -1,7 +1,7 @@
|
||||
# Build Linux packages (DEB and RPM) from pre-built binary artifacts.
|
||||
# Discovers which configurations to package from linux.json (configs in
|
||||
# "package_configs") and fans out one job per distro. Only linux/amd64 is
|
||||
# supported; the runner is hardcoded in the job below.
|
||||
# Build Linux packages (DEB and RPM) from pre-built binary artifacts (xrpld and
|
||||
# validator-keys). Discovers which configurations to package from linux.json
|
||||
# (configs in "package_configs") and fans out one job per distro. Only
|
||||
# linux/amd64 is supported; the runner is hardcoded in the job below.
|
||||
name: Package
|
||||
|
||||
on:
|
||||
@@ -45,7 +45,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix: ${{ fromJson(needs.generate-matrix.outputs.matrix) }}
|
||||
name: "${{ matrix.artifact_name }}"
|
||||
name: "${{ matrix.xrpld_artifact_name }}"
|
||||
permissions:
|
||||
contents: read
|
||||
runs-on: ["self-hosted", "Linux", "X64", "heavy"]
|
||||
@@ -56,14 +56,20 @@ jobs:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Download pre-built binary
|
||||
- name: Download pre-built xrpld binary
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: ${{ matrix.artifact_name }}
|
||||
name: ${{ matrix.xrpld_artifact_name }}
|
||||
path: ${{ env.BUILD_DIR }}
|
||||
|
||||
- name: Make binary executable
|
||||
run: chmod +x "${BUILD_DIR}/xrpld"
|
||||
- name: Download pre-built validator-keys binary
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: ${{ matrix.validator_keys_artifact_name }}
|
||||
path: ${{ env.BUILD_DIR }}
|
||||
|
||||
- name: Make binaries executable
|
||||
run: chmod +x "${BUILD_DIR}/xrpld" "${BUILD_DIR}/validator-keys"
|
||||
|
||||
- name: Build package
|
||||
env:
|
||||
@@ -73,7 +79,7 @@ jobs:
|
||||
- name: Upload package artifact
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: ${{ matrix.artifact_name }}-pkg
|
||||
name: ${{ matrix.xrpld_artifact_name }}-pkg
|
||||
path: |
|
||||
${{ env.BUILD_DIR }}/debbuild/*.deb
|
||||
${{ env.BUILD_DIR }}/debbuild/*.ddeb
|
||||
|
||||
@@ -42,6 +42,7 @@ This section contains changes targeting a future version.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
- `get_aggregate_price`: Duplicate entries in the `oracles` request array are now ignored. [#6586](https://github.com/XRPLF/rippled/pull/6586)
|
||||
- Peer Crawler: The `port` field in `overlay.active[]` now consistently returns an integer instead of a string for outbound peers. [#6318](https://github.com/XRPLF/rippled/pull/6318)
|
||||
- `ping`: The `ip` field is no longer returned as an empty string for proxied connections without a forwarded-for header. It is now omitted, consistent with the behavior for identified connections. [#6730](https://github.com/XRPLF/rippled/pull/6730)
|
||||
- gRPC `GetLedgerDiff`: Fixed error message that incorrectly said "base ledger not validated" when the desired ledger was not validated. [#6730](https://github.com/XRPLF/rippled/pull/6730)
|
||||
|
||||
14
BUILD.md
14
BUILD.md
@@ -42,6 +42,8 @@ Our Linux CI tooling is distro-independent and uses a Nix-based environment, so
|
||||
### macOS
|
||||
|
||||
Many `xrpld` engineers use macOS for development.
|
||||
The minimum supported version is macOS 15 (Sequoia).
|
||||
CI testing is done in macOS 26 (Tahoe), but the build defaults `CMAKE_OSX_DEPLOYMENT_TARGET` to 15.
|
||||
|
||||
### Windows
|
||||
|
||||
@@ -245,7 +247,17 @@ cmake --build . --target setup_code_gen # create venv and install dependencies
|
||||
cmake --build . --target code_gen # regenerate code
|
||||
```
|
||||
|
||||
The regenerated files should be committed alongside your changes.
|
||||
The same targets are also available as a standalone project, which does not
|
||||
need the dependencies to be configured first:
|
||||
|
||||
```
|
||||
cmake -S cmake/codegen -B build/codegen
|
||||
cmake --build build/codegen --target setup_code_gen
|
||||
cmake --build build/codegen --target code_gen
|
||||
```
|
||||
|
||||
The regenerated files should be committed alongside your changes. CI verifies
|
||||
that they are up-to-date.
|
||||
|
||||
## Coverage report
|
||||
|
||||
|
||||
@@ -13,6 +13,23 @@ if(DEFINED CMAKE_MODULE_PATH)
|
||||
endif()
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
|
||||
|
||||
# Must be set before project() because project() consumes it when configuring the compiler and SDK.
|
||||
# A user-provided -DCMAKE_OSX_DEPLOYMENT_TARGET still takes precedence.
|
||||
#
|
||||
# CMAKE_SYSTEM_NAME can't be used before project(), so CMAKE_HOST_SYSTEM_NAME is used instead.
|
||||
#
|
||||
# When CMAKE_OSX_DEPLOYMENT_TARGET is bumped to >=26.0, FastFloat dependency won't be needed anymore
|
||||
if(
|
||||
CMAKE_HOST_SYSTEM_NAME STREQUAL "Darwin"
|
||||
AND NOT DEFINED CMAKE_OSX_DEPLOYMENT_TARGET
|
||||
)
|
||||
set(CMAKE_OSX_DEPLOYMENT_TARGET
|
||||
"15.0"
|
||||
CACHE STRING
|
||||
"Minimum macOS deployment version"
|
||||
)
|
||||
endif()
|
||||
|
||||
project(xrpl)
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
set(CMAKE_CXX_STANDARD 23)
|
||||
@@ -87,6 +104,7 @@ include(deps/Boost)
|
||||
add_subdirectory(external/antithesis-sdk)
|
||||
find_package(date REQUIRED)
|
||||
find_package(ed25519 REQUIRED)
|
||||
find_package(FastFloat REQUIRED)
|
||||
find_package(gRPC REQUIRED)
|
||||
find_package(LibArchive REQUIRED)
|
||||
find_package(lz4 REQUIRED)
|
||||
@@ -103,6 +121,7 @@ target_link_libraries(
|
||||
xrpl_libs
|
||||
INTERFACE
|
||||
ed25519::ed25519
|
||||
FastFloat::fast_float
|
||||
lz4::lz4
|
||||
mpt-crypto::mpt-crypto
|
||||
OpenSSL::Crypto
|
||||
@@ -143,8 +162,10 @@ endif()
|
||||
include(XrplCore)
|
||||
include(XrplProtocolAutogen)
|
||||
include(XrplInstall)
|
||||
include(XrplPackaging)
|
||||
include(XrplValidatorKeys)
|
||||
# Must come after XrplValidatorKeys: the 'package' target depends on the
|
||||
# validator-keys target existing.
|
||||
include(XrplPackaging)
|
||||
|
||||
if(tests)
|
||||
include(CTest)
|
||||
|
||||
@@ -488,6 +488,17 @@
|
||||
# Must be a number between 100 and 1000, defaults to 250
|
||||
#
|
||||
#
|
||||
# [max_subscriptions_per_connection]
|
||||
#
|
||||
# Maximum number of account, real-time account, and account-history
|
||||
# subscriptions a single client connection may hold at once. Bounds the
|
||||
# per-connection state torn down when the connection disconnects. Book
|
||||
# subscriptions are tracked separately and are not counted here.
|
||||
#
|
||||
# Defaults to 100000 if not set; large enough for legitimate power users
|
||||
# such as block explorers.
|
||||
#
|
||||
#
|
||||
# [overlay]
|
||||
#
|
||||
# Controls settings related to the peer to peer overlay.
|
||||
@@ -538,6 +549,45 @@
|
||||
# only be used for local testing and debugging. Do not disable
|
||||
# on mainnet.
|
||||
#
|
||||
# max_untrusted_count = <number>
|
||||
#
|
||||
# The number of manifests the server keeps for validators it does not
|
||||
# list, and the number it sends and processes in a single peer protocol
|
||||
# message. Once the server holds this many, a manifest for a new
|
||||
# unlisted validator is rejected, so peer gossip cannot grow the cache
|
||||
# without end.
|
||||
#
|
||||
# This option can take any value between 50 and 1000, inclusive. If
|
||||
# the option is not present the server uses its built-in value.
|
||||
#
|
||||
# The current default (which is subject to change) is 300.
|
||||
#
|
||||
# max_trusted_count = <number>
|
||||
#
|
||||
# The number of manifests for listed validators to allow for when
|
||||
# sizing peer protocol messages. Manifests for listed validators are
|
||||
# never dropped, whether sending or receiving, because doing so would
|
||||
# delay a validator key change reaching this server. Set this above the
|
||||
# number of validators the server lists.
|
||||
#
|
||||
# Together the two counts above set the largest manifest message the
|
||||
# server accepts: bigger messages are discarded without reading them,
|
||||
# and without penalising the sender. Raising either means the server
|
||||
# accepts and sends bigger messages than a peer using the defaults, and
|
||||
# those peers will discard what this server sends. Lowering either below
|
||||
# what peers send makes this server discard their manifest messages,
|
||||
# which it does without recording anything.
|
||||
#
|
||||
# This option can take any value between 50 and 1000, inclusive. If
|
||||
# the option is not present the server uses its built-in value.
|
||||
#
|
||||
# The current default (which is subject to change) is 300.
|
||||
#
|
||||
# NOTE: These two options (max_untrusted_count and max_trusted_count)
|
||||
# are transitional. They exist to bound manifest-message size and cache
|
||||
# growth during the network upgrade. They may be removed in a future
|
||||
# release once the fleet has upgraded, and should not be relied upon as
|
||||
# stable configuration.
|
||||
#
|
||||
# [transaction_queue] EXPERIMENTAL
|
||||
#
|
||||
|
||||
@@ -2,9 +2,10 @@
|
||||
Patch executables to run in non-Nix environments.
|
||||
|
||||
The Nix toolchain links binaries against an ELF interpreter (loader)
|
||||
that lives in the Nix store, so the resulting binaries don't run elsewhere.
|
||||
`patch_nix_binary` adds a POST_BUILD step that resets the interpreter
|
||||
to the system default loader and drops the rpath.
|
||||
that lives in the Nix store, so the resulting binaries don't run elsewhere
|
||||
(including once installed from the .deb package). `patch_nix_binary` resets
|
||||
the interpreter to the system default loader and drops the rpath, once the
|
||||
binary has been linked.
|
||||
|
||||
This runs by default for Nix-toolchain builds (determined by whether the compiler resolves under /nix/store/).
|
||||
Those builds are where binaries get a Nix-store loader.
|
||||
@@ -52,13 +53,38 @@ function(patch_nix_binary target)
|
||||
if(NOT PATCH_NIX_BINARIES)
|
||||
return()
|
||||
endif()
|
||||
add_custom_command(
|
||||
TARGET ${target}
|
||||
POST_BUILD
|
||||
COMMAND
|
||||
"${PATCHELF_COMMAND}" --set-interpreter "${DEFAULT_LOADER_PATH}"
|
||||
--remove-rpath "$<TARGET_FILE:${target}>"
|
||||
COMMENT "Patching ${target}: set default loader, remove rpath"
|
||||
VERBATIM
|
||||
|
||||
set(patch_command
|
||||
"${PATCHELF_COMMAND}"
|
||||
--set-interpreter
|
||||
"${DEFAULT_LOADER_PATH}"
|
||||
--remove-rpath
|
||||
"$<TARGET_FILE:${target}>"
|
||||
)
|
||||
set(comment "Patching ${target}: set default loader, remove rpath")
|
||||
|
||||
# POST_BUILD is the cheap way to do this: it runs only when the binary is
|
||||
# relinked. It is also only available in the directory that defined the
|
||||
# target, so for a target from elsewhere (e.g. a FetchContent subproject)
|
||||
# fall back to a custom target that runs after the binary is linked. That
|
||||
# one runs on every build, which is harmless because patchelf is idempotent.
|
||||
get_target_property(target_source_dir ${target} SOURCE_DIR)
|
||||
if("${target_source_dir}" STREQUAL "${CMAKE_CURRENT_SOURCE_DIR}")
|
||||
add_custom_command(
|
||||
TARGET ${target}
|
||||
POST_BUILD
|
||||
COMMAND ${patch_command}
|
||||
COMMENT "${comment}"
|
||||
VERBATIM
|
||||
)
|
||||
else()
|
||||
add_custom_target(
|
||||
${target}-patch-nix
|
||||
ALL
|
||||
COMMAND ${patch_command}
|
||||
COMMENT "${comment}"
|
||||
VERBATIM
|
||||
)
|
||||
add_dependencies(${target}-patch-nix ${target})
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
@@ -25,6 +25,19 @@ if(NOT (RPMBUILD_EXECUTABLE OR DPKG_BUILDPACKAGE_EXECUTABLE))
|
||||
return()
|
||||
endif()
|
||||
|
||||
if(NOT TARGET xrpld)
|
||||
message(STATUS "xrpld=ON is required; 'package' target not available")
|
||||
return()
|
||||
endif()
|
||||
|
||||
if(NOT TARGET validator-keys)
|
||||
message(
|
||||
STATUS
|
||||
"validator_keys=ON is required; 'package' target not available"
|
||||
)
|
||||
return()
|
||||
endif()
|
||||
|
||||
set(package_env
|
||||
SRC_DIR=${CMAKE_SOURCE_DIR}
|
||||
BUILD_DIR=${CMAKE_BINARY_DIR}
|
||||
@@ -37,7 +50,7 @@ add_custom_target(
|
||||
${CMAKE_COMMAND} -E env ${package_env}
|
||||
${CMAKE_SOURCE_DIR}/package/build_pkg.sh
|
||||
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
|
||||
DEPENDS xrpld
|
||||
DEPENDS xrpld validator-keys
|
||||
COMMENT "Building Linux package (deb/rpm inferred from host tooling)"
|
||||
VERBATIM
|
||||
)
|
||||
|
||||
@@ -2,21 +2,22 @@
|
||||
Protocol Autogen - Code generation for protocol wrapper classes
|
||||
#]===================================================================]
|
||||
|
||||
# The repository root, derived from the location of this file rather than from
|
||||
# the including project, so that the targets below can also be offered on their
|
||||
# own by cmake/codegen/CMakeLists.txt.
|
||||
get_filename_component(XRPL_ROOT "${CMAKE_CURRENT_LIST_DIR}/.." ABSOLUTE)
|
||||
|
||||
set(CODEGEN_VENV_DIR
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/.venv"
|
||||
"${XRPL_ROOT}/.venv"
|
||||
CACHE PATH
|
||||
"Path to a Python virtual environment for code generation. A venv will be created here by setup_code_gen and used to run generation scripts."
|
||||
)
|
||||
|
||||
# Directory paths
|
||||
set(MACRO_DIR "${CMAKE_CURRENT_SOURCE_DIR}/include/xrpl/protocol/detail")
|
||||
set(AUTOGEN_HEADER_DIR
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/include/xrpl/protocol_autogen"
|
||||
)
|
||||
set(AUTOGEN_TEST_DIR
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/tests/libxrpl/protocol_autogen"
|
||||
)
|
||||
set(SCRIPTS_DIR "${CMAKE_CURRENT_SOURCE_DIR}/cmake/scripts/codegen")
|
||||
set(MACRO_DIR "${XRPL_ROOT}/include/xrpl/protocol/detail")
|
||||
set(AUTOGEN_HEADER_DIR "${XRPL_ROOT}/include/xrpl/protocol_autogen")
|
||||
set(AUTOGEN_TEST_DIR "${XRPL_ROOT}/src/tests/libxrpl/protocol_autogen")
|
||||
set(SCRIPTS_DIR "${XRPL_ROOT}/cmake/scripts/codegen")
|
||||
|
||||
# Input macro files
|
||||
set(TRANSACTIONS_MACRO "${MACRO_DIR}/transactions.macro")
|
||||
@@ -114,14 +115,14 @@ if(CODEGEN_VENV_DIR)
|
||||
setup_code_gen
|
||||
COMMAND ${Python3_EXECUTABLE} -m venv "${CODEGEN_VENV_DIR}"
|
||||
COMMAND ${CODEGEN_PYTHON} -m pip install -r "${REQUIREMENTS_FILE}"
|
||||
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
|
||||
WORKING_DIRECTORY "${XRPL_ROOT}"
|
||||
COMMENT "Creating venv and installing code generation dependencies..."
|
||||
)
|
||||
else()
|
||||
add_custom_target(
|
||||
setup_code_gen
|
||||
COMMAND ${Python3_EXECUTABLE} -m pip install -r "${REQUIREMENTS_FILE}"
|
||||
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
|
||||
WORKING_DIRECTORY "${XRPL_ROOT}"
|
||||
COMMENT "Installing code generation dependencies..."
|
||||
)
|
||||
endif()
|
||||
@@ -139,8 +140,8 @@ add_custom_target(
|
||||
-DSFIELDS_MACRO=${SFIELDS_MACRO}
|
||||
-DAUTOGEN_HEADER_DIR=${AUTOGEN_HEADER_DIR}
|
||||
-DAUTOGEN_TEST_DIR=${AUTOGEN_TEST_DIR} -P
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/cmake/XrplProtocolAutogenRun.cmake"
|
||||
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
|
||||
"${CMAKE_CURRENT_LIST_DIR}/XrplProtocolAutogenRun.cmake"
|
||||
WORKING_DIRECTORY "${XRPL_ROOT}"
|
||||
COMMENT "Running protocol code generation..."
|
||||
SOURCES ${ALL_INPUT_FILES}
|
||||
)
|
||||
|
||||
@@ -5,22 +5,39 @@ option(
|
||||
)
|
||||
|
||||
if(validator_keys)
|
||||
git_branch(current_branch)
|
||||
# default to tracking VK master branch unless we are on release
|
||||
if(NOT (current_branch STREQUAL "release"))
|
||||
set(current_branch "master")
|
||||
endif()
|
||||
message(STATUS "Tracking ValidatorKeys branch: ${current_branch}")
|
||||
# Own the install destination below rather than relying on another module
|
||||
# having pulled this in first.
|
||||
include(GNUInstallDirs)
|
||||
|
||||
# Pinned to an exact commit, not a branch: the tool ships inside our
|
||||
# packages, so the same xrpld version must always package the same
|
||||
# validator-keys. Bump this deliberately.
|
||||
set(validator_keys_commit "4c0fb75eec9601c711645998c904507e87e910ae")
|
||||
message(STATUS "Using ValidatorKeys commit: ${validator_keys_commit}")
|
||||
|
||||
FetchContent_Declare(
|
||||
validator_keys
|
||||
GIT_REPOSITORY https://github.com/ripple/validator-keys-tool.git
|
||||
GIT_TAG "${current_branch}"
|
||||
GIT_TAG "${validator_keys_commit}"
|
||||
)
|
||||
FetchContent_MakeAvailable(validator_keys)
|
||||
# The tool's own CMakeLists excludes the target from 'all' when it is built
|
||||
# as a subproject. Undo that, so validator_keys=ON really does build it.
|
||||
set_target_properties(
|
||||
validator-keys
|
||||
PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}"
|
||||
PROPERTIES
|
||||
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}"
|
||||
EXCLUDE_FROM_ALL OFF
|
||||
EXCLUDE_FROM_DEFAULT_BUILD OFF
|
||||
)
|
||||
# We ship this binary, so like xrpld it must not keep the Nix store's ELF
|
||||
# loader, or it cannot run on the target distro at all.
|
||||
patch_nix_binary(validator-keys)
|
||||
|
||||
configure_file(
|
||||
"${validator_keys_SOURCE_DIR}/LICENSE"
|
||||
"${CMAKE_BINARY_DIR}/validator-keys-LICENSE"
|
||||
COPYONLY
|
||||
)
|
||||
install(TARGETS validator-keys RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||
endif()
|
||||
|
||||
21
cmake/codegen/CMakeLists.txt
Normal file
21
cmake/codegen/CMakeLists.txt
Normal file
@@ -0,0 +1,21 @@
|
||||
#[===================================================================[
|
||||
Protocol Autogen - Standalone project
|
||||
|
||||
Exposes the 'setup_code_gen' and 'code_gen' targets on their own, without
|
||||
configuring the rest of xrpl. Code generation is pure Python, so this needs
|
||||
neither the dependencies nor a compiler, which makes it usable in CI and by
|
||||
contributors who only want to regenerate the protocol wrapper classes:
|
||||
|
||||
cmake -S cmake/codegen -B build/codegen
|
||||
cmake --build build/codegen --target setup_code_gen
|
||||
cmake --build build/codegen --target code_gen
|
||||
|
||||
The targets are identical to the ones offered by the top-level build, since
|
||||
both come from cmake/XrplProtocolAutogen.cmake.
|
||||
#]===================================================================]
|
||||
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
project(xrpl_codegen LANGUAGES NONE)
|
||||
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/../XrplProtocolAutogen.cmake")
|
||||
@@ -13,7 +13,7 @@
|
||||
"protobuf/6.33.5#ff253ead763bd8d9904a52979cd21e81%1782392410.233933",
|
||||
"openssl/3.6.3#f806de8933e3bf6f01016c6a888cee2e%1783945160.863288",
|
||||
"nudb/2.0.9#11149c73f8f2baff9a0198fe25971fc7%1782392402.297166",
|
||||
"mpt-crypto/0.4.0-rc4#ffdba12f2332357f0d8b0ae944cfff52%1784138702.932355",
|
||||
"mpt-crypto/1.0.2#b313cef0c1a493eb970ad185b2e9bab7%1784285108.866483",
|
||||
"lz4/1.10.0#982d9b673900f665a1da109e09c17cab%1782392402.164188",
|
||||
"libiconv/1.17#9923bc6dc6f106646d6967e0039a5ada%1782392792.775744",
|
||||
"libbacktrace/cci.20210118#a7691bfccd8caaf66309df196790a5a1%1782392402.420732",
|
||||
@@ -21,6 +21,7 @@
|
||||
"jemalloc/5.3.1#1fc58d55316041f10fbc1e8a2eae632a%1776700028.228",
|
||||
"gtest/1.17.0#5224b3b3ff3b4ce1133cbdd27d53ee7d%1782392402.791979",
|
||||
"grpc/1.81.1#f729f6d75992d20f9c72828e9142d62f%1783945160.094135",
|
||||
"fast_float/8.2.10#f6f28d6bb22112078e7dbda611caf681%1782494504.298",
|
||||
"ed25519/2015.03#ae761bdc52730a843f0809bdf6c1b1f6%1782307148.15562",
|
||||
"date/3.0.4#862e11e80030356b53c2c38599ceb32b%1782392402.538492",
|
||||
"c-ares/1.34.6#545240bb1c40e2cacd4362d6b8967650%1782392402.681654",
|
||||
@@ -35,7 +36,7 @@
|
||||
"protobuf/6.33.5#ff253ead763bd8d9904a52979cd21e81%1782392410.233933",
|
||||
"nasm/2.16.01#31e26f2ee3c4346ecd347911bd126904%1782395690.33162",
|
||||
"msys2/cci.latest#d22fe7b2808f5fd34d0a7923ace9c54f%1770657326.649",
|
||||
"m4/1.4.19#34c4bbc3eeebe98ca6edf2f52d602e7d%1777282960.259",
|
||||
"m4/1.4.19#1727f439cf74e83826ec96d0b4904eee%1784541921.659",
|
||||
"cmake/4.3.3#840cf00ea09777e05c2050a50a82c722%1782392418.696091",
|
||||
"b2/5.4.2#ffd6084a119587e70f11cd45d1a386e2%1782392402.624226",
|
||||
"automake/1.16.5#b91b7c384c3deaa9d535be02da14d04f%1755524470.56",
|
||||
|
||||
@@ -5,6 +5,13 @@
|
||||
{% if os == "Linux" %}
|
||||
{% set compiler_version = detect_api.default_compiler_version(compiler, version) %}
|
||||
{% endif %}
|
||||
{% if os == "Macos" %}
|
||||
{# Minimum macOS the dependencies target. #}
|
||||
{# Without this, Conan builds each dependency against the (possibly newer) host SDK, so the #}
|
||||
{# dependency objects target a newer macOS than the binary and the linker warns. #}
|
||||
{# Keep at or below CMAKE_OSX_DEPLOYMENT_TARGET in CMakeLists.txt. #}
|
||||
{% set min_macos_version = "15.0" %}
|
||||
{% endif %}
|
||||
|
||||
[settings]
|
||||
os={{ os }}
|
||||
@@ -18,6 +25,9 @@ compiler.runtime=static
|
||||
{% else %}
|
||||
compiler.libcxx={{ detect_api.detect_libcxx(compiler, version, compiler_exe) }}
|
||||
{% endif %}
|
||||
{% if os == "Macos" %}
|
||||
os.version={{ min_macos_version }}
|
||||
{% endif %}
|
||||
|
||||
[conf]
|
||||
{# The Boost recipe builds with b2, which doesn't use Conan's toolchain files. #}
|
||||
@@ -41,3 +51,13 @@ tools.build:compiler_executables={'c':'{{ cc_exe }}','cpp':'{{ cxx_exe }}'}
|
||||
{# More info: https://docs.conan.io/2/reference/extensions/binary_compatibility.html #}
|
||||
user.package:cppstd_version=23
|
||||
tools.info.package_id:confs+=["user.package:cppstd_version"]
|
||||
|
||||
{% if os == "Macos" %}
|
||||
[buildenv]
|
||||
{# os.version adds -mmacosx-version-min to compiler command lines, #}
|
||||
{# but Boost.Context's b2 assembly (.S) rule ignores it, #}
|
||||
{# so those objects keep the host SDK version and still warn at link time. #}
|
||||
{# clang's assembler honors this env var regardless, pinning them. #}
|
||||
{# Scoped to boost/* since it is the only gap. #}
|
||||
boost/*:MACOSX_DEPLOYMENT_TARGET={{ min_macos_version }}
|
||||
{% endif %}
|
||||
|
||||
@@ -29,6 +29,7 @@ class Xrpl(ConanFile):
|
||||
|
||||
requires = [
|
||||
"ed25519/2015.03",
|
||||
"fast_float/8.2.10",
|
||||
"grpc/1.81.1",
|
||||
"libarchive/3.8.7",
|
||||
"nudb/2.0.9",
|
||||
@@ -139,7 +140,7 @@ class Xrpl(ConanFile):
|
||||
if self.options.jemalloc:
|
||||
self.requires("jemalloc/5.3.1")
|
||||
self.requires("lz4/1.10.0", force=True)
|
||||
self.requires("mpt-crypto/0.4.0-rc4", transitive_headers=True)
|
||||
self.requires("mpt-crypto/1.0.2", transitive_headers=True)
|
||||
self.requires("protobuf/6.33.5", force=True)
|
||||
if self.options.rocksdb:
|
||||
self.requires("rocksdb/10.5.1")
|
||||
@@ -212,6 +213,7 @@ class Xrpl(ConanFile):
|
||||
"boost::thread",
|
||||
"date::date",
|
||||
"ed25519::ed25519",
|
||||
"fast_float::fast_float",
|
||||
"grpc::grpc++",
|
||||
"libarchive::libarchive",
|
||||
"lz4::lz4",
|
||||
|
||||
72
docs/build/nix_troubleshooting.md
vendored
72
docs/build/nix_troubleshooting.md
vendored
@@ -3,6 +3,78 @@
|
||||
Common issues encountered when using the [Nix development shell](./nix.md), and
|
||||
how to resolve them.
|
||||
|
||||
## `command not found: nix` after a macOS update
|
||||
|
||||
If a shell suddenly can't find `nix` at all:
|
||||
|
||||
```
|
||||
$ nix develop
|
||||
zsh: command not found: nix
|
||||
```
|
||||
|
||||
then Nix is almost certainly still installed — only the shell hook that puts it
|
||||
on your `PATH` is gone. Confirm that first:
|
||||
|
||||
```bash
|
||||
ls -l /nix/var/nix/profiles/default/bin/nix
|
||||
```
|
||||
|
||||
If that exists, the installation is fine and this is purely a `PATH` problem.
|
||||
|
||||
### Why it happens
|
||||
|
||||
The installer does not touch your dotfiles. Instead it sources a setup script
|
||||
from the Nix store by editing **system-wide** rc files:
|
||||
|
||||
| Shell | File the installer edits |
|
||||
| ----- | ------------------------------------- |
|
||||
| bash | `/etc/bashrc`, `/etc/bash.bashrc` |
|
||||
| zsh | `/etc/zshrc` |
|
||||
| fish | `$__fish_sysconf_dir/conf.d/nix.fish` |
|
||||
|
||||
macOS manages `/etc/zshrc`, so an OS update can replace it with the vendor copy
|
||||
and silently drop the Nix block. `/etc/bashrc` and the fish file usually survive,
|
||||
which is why the breakage often shows up in zsh only. You can verify this by
|
||||
diffing against the backup the installer left behind:
|
||||
|
||||
```bash
|
||||
diff /etc/zshrc /etc/zshrc.backup-before-nix
|
||||
```
|
||||
|
||||
If they are identical, the Nix snippet was wiped. This is upstream issue
|
||||
[NixOS/nix#3616](https://github.com/NixOS/nix/issues/3616).
|
||||
|
||||
### Fix
|
||||
|
||||
To unblock the current shell:
|
||||
|
||||
```bash
|
||||
. /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh
|
||||
```
|
||||
|
||||
For a permanent fix, add the snippet to your **user** rc file rather than
|
||||
restoring `/etc/zshrc` — user dotfiles are not clobbered by OS updates:
|
||||
|
||||
```bash
|
||||
cat >>~/.zshrc <<'EOF'
|
||||
|
||||
# Nix
|
||||
if [ -e '/nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh' ]; then
|
||||
. '/nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh'
|
||||
fi
|
||||
# End Nix
|
||||
EOF
|
||||
```
|
||||
|
||||
The scripts guard against double-sourcing via `__ETC_PROFILE_NIX_SOURCED`, so
|
||||
this is safe even if a system-wide hook is later restored.
|
||||
|
||||
> [!NOTE]
|
||||
> `/etc/zshrc` and `~/.zshrc` are only read by **interactive** zsh. If the
|
||||
> snippet is present but `zsh -c '…'`, a script, or an IDE terminal still can't
|
||||
> find `nix`, that shell is non-interactive — put the snippet in `~/.zshenv`
|
||||
> instead.
|
||||
|
||||
## Git worktrees
|
||||
|
||||
If `nix develop` fails with an error like:
|
||||
|
||||
@@ -41,6 +41,35 @@
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
namespace base64 {
|
||||
|
||||
/**
|
||||
* Returns the maximum number of characters needed to base64-encode @p nBytes bytes.
|
||||
*
|
||||
* @param nBytes Number of input bytes.
|
||||
* @return Size of the encoded string, including padding.
|
||||
*/
|
||||
constexpr std::size_t
|
||||
encodedSize(std::size_t const nBytes)
|
||||
{
|
||||
return 4 * ((nBytes + 2) / 3);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the maximum number of bytes a base64 string of @p numChars characters
|
||||
* decodes to.
|
||||
*
|
||||
* @param numChars Number of base64 characters.
|
||||
* @return Upper bound on the number of decoded bytes.
|
||||
*/
|
||||
constexpr std::size_t
|
||||
decodedSize(std::size_t const numChars)
|
||||
{
|
||||
return ((numChars / 4) * 3) + 2;
|
||||
}
|
||||
|
||||
} // namespace base64
|
||||
|
||||
std::string
|
||||
base64Encode(std::uint8_t const* data, std::size_t len);
|
||||
|
||||
|
||||
@@ -295,6 +295,20 @@ public:
|
||||
return runner_->arg();
|
||||
}
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Lets a suite compose other suites (e.g. an aggregator that reruns a
|
||||
* group of related suites under its own name) via `SuiteInfo::run`.
|
||||
*
|
||||
* @return The runner this suite is executing under.
|
||||
*/
|
||||
Runner&
|
||||
runner() const
|
||||
{
|
||||
return *runner_;
|
||||
}
|
||||
|
||||
public:
|
||||
/**
|
||||
* DEPRECATED
|
||||
* @return `true` if the test condition indicates success(a false value)
|
||||
|
||||
@@ -25,6 +25,7 @@ struct Sections
|
||||
static constexpr auto kLedgerHistory = "ledger_history";
|
||||
static constexpr auto kLedgerReplay = "ledger_replay";
|
||||
static constexpr auto kLedgerTxTables = "ledger_tx_tables";
|
||||
static constexpr auto kMaxSubscriptionsPerConnection = "max_subscriptions_per_connection";
|
||||
static constexpr auto kMaxTransactions = "max_transactions";
|
||||
static constexpr auto kNetworkId = "network_id";
|
||||
static constexpr auto kNetworkQuorum = "network_quorum";
|
||||
@@ -118,7 +119,9 @@ struct Keys
|
||||
static constexpr auto kLogInterval = "log_interval";
|
||||
static constexpr auto kMaxDivergedTime = "max_diverged_time";
|
||||
static constexpr auto kMaxLedgerCountsToStore = "max_ledger_counts_to_store";
|
||||
static constexpr auto kMaxTrustedCount = "max_trusted_count";
|
||||
static constexpr auto kMaxUnknownTime = "max_unknown_time";
|
||||
static constexpr auto kMaxUntrustedCount = "max_untrusted_count";
|
||||
static constexpr auto kMaximumTxnInLedger = "maximum_txn_in_ledger";
|
||||
static constexpr auto kMaximumTxnPerAccount = "maximum_txn_per_account";
|
||||
static constexpr auto kMemoryLevel = "memory_level";
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <ranges>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
@@ -1579,7 +1580,13 @@ Consensus<Adaptor>::updateOurPositions(std::unique_ptr<std::stringstream> const&
|
||||
JLOG(j_.info()) << ss.str();
|
||||
CLOG(clog) << ss.str();
|
||||
|
||||
for (auto const& [t, v] : closeTimeVotes)
|
||||
// Walk the votes highest-time first so that, among close times tied
|
||||
// for the most votes, the earliest wins. The smaller value is the
|
||||
// safer choice: without close-time consensus this round, the winner
|
||||
// only updates our position for the next proposal, and a too-early
|
||||
// time is bounded below by the prior ledger's close time. Only the
|
||||
// tie-break changes; the bin with the most votes still wins.
|
||||
for (auto const& [t, v] : std::views::reverse(closeTimeVotes))
|
||||
{
|
||||
JLOG(j_.debug()) << "CCTime: seq "
|
||||
<< static_cast<std::uint32_t>(previousLedger_.seq()) + 1 << ": "
|
||||
|
||||
@@ -8,7 +8,9 @@
|
||||
|
||||
#include <chrono>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
namespace xrpl {
|
||||
@@ -189,6 +191,75 @@ struct ConsensusCloseTimes
|
||||
NetClock::time_point self;
|
||||
};
|
||||
|
||||
/**
|
||||
* Offset of the network's close time relative to ours, using a weighted median.
|
||||
*
|
||||
* Treats the sample set as `{self x 1}` merged with `{t x w}` for each
|
||||
* `(t, w)` in `times.peers`, in time order, and returns `(median - self)`
|
||||
* in whole seconds. Uses the lower weighted median: the median is the
|
||||
* earliest time at which the running weight reaches half the total, so an
|
||||
* even total whose halfway point falls between two bins resolves to the
|
||||
* earlier bin.
|
||||
*
|
||||
* @param times Our own close time and the weighted close times of peers.
|
||||
* @return Weighted median of all close times minus our own, in whole seconds.
|
||||
*/
|
||||
inline std::chrono::seconds
|
||||
medianCloseOffset(ConsensusCloseTimes const& times)
|
||||
{
|
||||
using namespace std::chrono;
|
||||
using time_point = NetClock::time_point;
|
||||
|
||||
std::int64_t totalWeight = 1;
|
||||
for (auto const& [_, w] : times.peers)
|
||||
totalWeight += w;
|
||||
|
||||
std::int64_t const halfWeight = (totalWeight + 1) / 2;
|
||||
|
||||
std::optional<time_point> median{};
|
||||
std::int64_t tally = 0;
|
||||
bool selfPlaced = false;
|
||||
|
||||
// Accumulate weight in time order; the first bin to reach halfWeight is
|
||||
// the (lower) weighted median. Returns true once that bin is found.
|
||||
auto step = [&](time_point t, std::int64_t w) {
|
||||
XRPL_ASSERT(tally < halfWeight, "xrpl::medianCloseOffset::step : median not yet found");
|
||||
tally += w;
|
||||
if (tally >= halfWeight)
|
||||
{
|
||||
median = t;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
for (auto const& [t, w] : times.peers)
|
||||
{
|
||||
if (!selfPlaced && times.self <= t)
|
||||
{
|
||||
selfPlaced = true;
|
||||
if (step(times.self, 1))
|
||||
break;
|
||||
}
|
||||
if (step(t, w))
|
||||
break;
|
||||
}
|
||||
if (!selfPlaced && !median)
|
||||
step(times.self, 1);
|
||||
|
||||
if (!median)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
UNREACHABLE("xrpl::medianCloseOffset : median not found");
|
||||
median = times.self;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
return duration_cast<seconds>(
|
||||
duration<std::int64_t>{median->time_since_epoch().count()} -
|
||||
duration<std::int64_t>{times.self.time_since_epoch().count()});
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether we have or don't have a consensus
|
||||
*/
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
|
||||
@@ -15,6 +16,7 @@
|
||||
#include <xrpl/protocol/Issue.h>
|
||||
#include <xrpl/protocol/Keylet.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
#include <xrpl/protocol/MPTAmount.h>
|
||||
#include <xrpl/protocol/MPTIssue.h>
|
||||
#include <xrpl/protocol/Rate.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
@@ -241,10 +243,25 @@ escrowUnlockApplyHelper<MPTIssue>(
|
||||
auto finalAmt = amount;
|
||||
if ((!senderIssuer && !receiverIssuer) && lockedRate != kParityRate)
|
||||
{
|
||||
// compute transfer fee, if any
|
||||
auto const xferFee = amount.value() - divideRound(amount, lockedRate, amount.asset(), true);
|
||||
// compute balance to transfer
|
||||
finalAmt = amount.value() - xferFee;
|
||||
if (ctx.view.rules().enabled(fixCleanup3_4_0))
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
lockedRate >= kParityRate,
|
||||
"xrpl::escrowUnlockApplyHelper<MPTIssue> : lockedRate is at least parity");
|
||||
// MPTs are integral, so round the delivered amount down and
|
||||
// charge any fractional transfer fee to the escrowed amount.
|
||||
auto const delivered =
|
||||
mulRatio(amount.mpt(), kParityRate.value, lockedRate.value, false);
|
||||
finalAmt = STAmount(amount.asset(), delivered.value());
|
||||
}
|
||||
else
|
||||
{
|
||||
// compute transfer fee, if any
|
||||
auto const xferFee =
|
||||
amount.value() - divideRound(amount, lockedRate, amount.asset(), true);
|
||||
// compute balance to transfer
|
||||
finalAmt = amount.value() - xferFee;
|
||||
}
|
||||
}
|
||||
return unlockEscrowMPT(
|
||||
ctx.view,
|
||||
|
||||
@@ -301,14 +301,15 @@ message TMLedgerData {
|
||||
}
|
||||
|
||||
message TMPing {
|
||||
// Previously used - don't reuse.
|
||||
reserved 3, 4;
|
||||
|
||||
enum pingType {
|
||||
ptPING = 0; // we want a reply
|
||||
ptPONG = 1; // this is a reply
|
||||
}
|
||||
required pingType type = 1;
|
||||
optional uint32 seq = 2; // detect stale replies, ensure other side is reading
|
||||
optional uint64 pingTime = 3; // know when we think we sent the ping
|
||||
optional uint64 netTime = 4;
|
||||
optional uint32 seq = 2; // detect stale replies, ensure other side is reading
|
||||
}
|
||||
|
||||
message TMSquelch {
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
#include <xrpl/protocol/STXChainBridge.h>
|
||||
#include <xrpl/protocol/SeqProxy.h>
|
||||
#include <xrpl/protocol/UintTypes.h>
|
||||
|
||||
#include <array>
|
||||
@@ -21,8 +22,6 @@
|
||||
#include <utility>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
class SeqProxy;
|
||||
/**
|
||||
* Keylet computation functions.
|
||||
*
|
||||
@@ -123,7 +122,7 @@ trustLine(AccountID const& id, Issue const& issue) noexcept
|
||||
*/
|
||||
/** @{ */
|
||||
Keylet
|
||||
offer(AccountID const& id, std::uint32_t seq) noexcept;
|
||||
offer(AccountID const& id, SeqProxy const& seq) noexcept;
|
||||
|
||||
inline Keylet
|
||||
offer(uint256 const& key) noexcept
|
||||
@@ -136,7 +135,7 @@ offer(uint256 const& key) noexcept
|
||||
* The initial directory page for a specific quality
|
||||
*/
|
||||
Keylet
|
||||
quality(Keylet const& k, std::uint64_t q) noexcept;
|
||||
quality(Keylet const& k, std::uint64_t const q) noexcept;
|
||||
|
||||
/**
|
||||
* The directory for the next lower quality
|
||||
@@ -149,10 +148,7 @@ next(Keylet const& k);
|
||||
*/
|
||||
/** @{ */
|
||||
Keylet
|
||||
ticket(AccountID const& id, std::uint32_t ticketSeq);
|
||||
|
||||
Keylet
|
||||
ticket(AccountID const& id, SeqProxy ticketSeq);
|
||||
ticket(AccountID const& id, SeqProxy const& ticketSeq);
|
||||
|
||||
inline Keylet
|
||||
ticket(uint256 const& key)
|
||||
@@ -178,7 +174,7 @@ sponsorship(AccountID const& sponsor, AccountID const& sponsee) noexcept;
|
||||
*/
|
||||
/** @{ */
|
||||
Keylet
|
||||
check(AccountID const& id, std::uint32_t seq) noexcept;
|
||||
check(AccountID const& id, SeqProxy const& seq) noexcept;
|
||||
|
||||
inline Keylet
|
||||
check(uint256 const& key) noexcept
|
||||
@@ -225,10 +221,10 @@ ownerDir(AccountID const& id) noexcept;
|
||||
*/
|
||||
/** @{ */
|
||||
Keylet
|
||||
page(uint256 const& root, std::uint64_t index = 0) noexcept;
|
||||
page(uint256 const& root, std::uint64_t const index = 0) noexcept;
|
||||
|
||||
inline Keylet
|
||||
page(Keylet const& root, std::uint64_t index = 0) noexcept
|
||||
page(Keylet const& root, std::uint64_t const index = 0) noexcept
|
||||
{
|
||||
XRPL_ASSERT(root.type == ltDIR_NODE, "xrpl::keylet::page : valid root type");
|
||||
return page(root.key, index);
|
||||
@@ -239,13 +235,13 @@ page(Keylet const& root, std::uint64_t index = 0) noexcept
|
||||
* An escrow entry
|
||||
*/
|
||||
Keylet
|
||||
escrow(AccountID const& src, std::uint32_t seq) noexcept;
|
||||
escrow(AccountID const& src, SeqProxy const& seq) noexcept;
|
||||
|
||||
/**
|
||||
* A PaymentChannel
|
||||
*/
|
||||
Keylet
|
||||
payChannel(AccountID const& src, AccountID const& dst, std::uint32_t seq) noexcept;
|
||||
payChannel(AccountID const& src, AccountID const& dst, SeqProxy const& seq) noexcept;
|
||||
|
||||
/**
|
||||
* NFT page keylets
|
||||
@@ -276,7 +272,7 @@ nftokenPage(Keylet const& k, uint256 const& token);
|
||||
* An offer from an account to buy or sell an NFT
|
||||
*/
|
||||
Keylet
|
||||
nftokenOffer(AccountID const& owner, std::uint32_t seq);
|
||||
nftokenOffer(AccountID const& owner, SeqProxy const& seq);
|
||||
|
||||
inline Keylet
|
||||
nftokenOffer(uint256 const& offer)
|
||||
@@ -316,17 +312,17 @@ bridge(STXChainBridge const& bridge, STXChainBridge::ChainType chainType);
|
||||
|
||||
// `seq` is stored as `sfXChainClaimID` in the object
|
||||
Keylet
|
||||
xChainClaimID(STXChainBridge const& bridge, std::uint64_t seq);
|
||||
xChainClaimID(STXChainBridge const& bridge, std::uint64_t const seq);
|
||||
|
||||
// `seq` is stored as `sfXChainAccountCreateCount` in the object
|
||||
Keylet
|
||||
xChainCreateAccountClaimID(STXChainBridge const& bridge, std::uint64_t seq);
|
||||
xChainCreateAccountClaimID(STXChainBridge const& bridge, std::uint64_t const seq);
|
||||
|
||||
Keylet
|
||||
did(AccountID const& account) noexcept;
|
||||
|
||||
Keylet
|
||||
oracle(AccountID const& account, std::uint32_t const& documentID) noexcept;
|
||||
oracle(AccountID const& account, std::uint32_t const documentID) noexcept;
|
||||
|
||||
Keylet
|
||||
credential(AccountID const& subject, AccountID const& issuer, Slice const& credType) noexcept;
|
||||
@@ -337,9 +333,6 @@ credential(uint256 const& key) noexcept
|
||||
return {ltCREDENTIAL, key};
|
||||
}
|
||||
|
||||
Keylet
|
||||
mptokenIssuance(std::uint32_t seq, AccountID const& issuer) noexcept;
|
||||
|
||||
Keylet
|
||||
mptokenIssuance(MPTID const& issuanceID) noexcept;
|
||||
|
||||
@@ -362,7 +355,7 @@ Keylet
|
||||
mptoken(uint256 const& issuanceKey, AccountID const& holder) noexcept;
|
||||
|
||||
Keylet
|
||||
vault(AccountID const& owner, std::uint32_t seq) noexcept;
|
||||
vault(AccountID const& owner, SeqProxy const& seq) noexcept;
|
||||
|
||||
inline Keylet
|
||||
vault(uint256 const& vaultKey)
|
||||
@@ -371,7 +364,7 @@ vault(uint256 const& vaultKey)
|
||||
}
|
||||
|
||||
Keylet
|
||||
loanBroker(AccountID const& owner, std::uint32_t seq) noexcept;
|
||||
loanBroker(AccountID const& owner, SeqProxy const& seq) noexcept;
|
||||
|
||||
inline Keylet
|
||||
loanBroker(uint256 const& key)
|
||||
@@ -380,7 +373,7 @@ loanBroker(uint256 const& key)
|
||||
}
|
||||
|
||||
Keylet
|
||||
loan(uint256 const& loanBrokerID, std::uint32_t loanSeq) noexcept;
|
||||
loan(uint256 const& loanBrokerID, SeqProxy const& loanSeq) noexcept;
|
||||
|
||||
inline Keylet
|
||||
loan(uint256 const& key)
|
||||
@@ -389,7 +382,7 @@ loan(uint256 const& key)
|
||||
}
|
||||
|
||||
Keylet
|
||||
permissionedDomain(AccountID const& account, std::uint32_t seq) noexcept;
|
||||
permissionedDomain(AccountID const& account, SeqProxy const& seq) noexcept;
|
||||
|
||||
Keylet
|
||||
permissionedDomain(uint256 const& domainID) noexcept;
|
||||
@@ -407,12 +400,6 @@ getQualityNext(uint256 const& uBase);
|
||||
std::uint64_t
|
||||
getQuality(uint256 const& uBase);
|
||||
|
||||
uint256
|
||||
getTicketIndex(AccountID const& account, std::uint32_t uSequence);
|
||||
|
||||
uint256
|
||||
getTicketIndex(AccountID const& account, SeqProxy ticketSeq);
|
||||
|
||||
template <class... KeyletParams>
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init)
|
||||
struct KeyletDesc
|
||||
@@ -426,6 +413,6 @@ struct KeyletDesc
|
||||
extern std::array<KeyletDesc<AccountID const&>, 6> const kDirectAccountKeylets;
|
||||
|
||||
MPTID
|
||||
makeMptID(std::uint32_t sequence, AccountID const& account);
|
||||
makeMptID(std::uint32_t const sequence, AccountID const& account);
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -190,17 +190,6 @@ enum LedgerEntryType : std::uint16_t {
|
||||
LSF_FLAG(lsfMPTCanClawback, 0x00000040) \
|
||||
LSF_FLAG(lsfMPTCanHoldConfidentialBalance, 0x00000080)) \
|
||||
\
|
||||
LEDGER_OBJECT(MPTokenIssuanceMutable, \
|
||||
LSF_FLAG(lsmfMPTCanEnableCanLock, 0x00000002) \
|
||||
LSF_FLAG(lsmfMPTCanEnableRequireAuth, 0x00000004) \
|
||||
LSF_FLAG(lsmfMPTCanEnableCanEscrow, 0x00000008) \
|
||||
LSF_FLAG(lsmfMPTCanEnableCanTrade, 0x00000010) \
|
||||
LSF_FLAG(lsmfMPTCanEnableCanTransfer, 0x00000020) \
|
||||
LSF_FLAG(lsmfMPTCanEnableCanClawback, 0x00000040) \
|
||||
LSF_FLAG(lsmfMPTCannotEnableCanHoldConfidentialBalance, 0x00000080) \
|
||||
LSF_FLAG(lsmfMPTCanMutateMetadata, 0x00010000) \
|
||||
LSF_FLAG(lsmfMPTCanMutateTransferFee, 0x00020000)) \
|
||||
\
|
||||
LEDGER_OBJECT(MPToken, \
|
||||
LSF_FLAG2(lsfMPTLocked, 0x00000001) \
|
||||
LSF_FLAG(lsfMPTAuthorized, 0x00000002) \
|
||||
@@ -294,6 +283,17 @@ getAllLedgerFlags()
|
||||
#pragma pop_macro("TO_MAP")
|
||||
#pragma pop_macro("ALL_LEDGER_FLAGS")
|
||||
|
||||
// MPTokenIssuance ImmutableFlags (sfImmutableFlags)
|
||||
inline constexpr std::uint32_t lsifMPTCanLock = 0x00000002;
|
||||
inline constexpr std::uint32_t lsifMPTRequireAuth = 0x00000004;
|
||||
inline constexpr std::uint32_t lsifMPTCanEscrow = 0x00000008;
|
||||
inline constexpr std::uint32_t lsifMPTCanTrade = 0x00000010;
|
||||
inline constexpr std::uint32_t lsifMPTCanTransfer = 0x00000020;
|
||||
inline constexpr std::uint32_t lsifMPTCanClawback = 0x00000040;
|
||||
inline constexpr std::uint32_t lsifMPTCanHoldConfidentialBalance = 0x00000080;
|
||||
inline constexpr std::uint32_t lsifMPTMetadata = 0x00010000;
|
||||
inline constexpr std::uint32_t lsifMPTTransferFee = 0x00020000;
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
|
||||
@@ -90,7 +90,11 @@ public:
|
||||
operator=(STObject&& other);
|
||||
|
||||
STObject(SOTemplate const& type, SField const& name);
|
||||
STObject(SOTemplate const& type, SerialIter& sit, SField const& name);
|
||||
STObject(
|
||||
SOTemplate const& type,
|
||||
SerialIter& sit,
|
||||
SField const& name,
|
||||
bool requireCanonicalOrder = false);
|
||||
STObject(SerialIter& sit, SField const& name, int depth = 0);
|
||||
STObject(SerialIter&& sit, SField const& name);
|
||||
explicit STObject(SField const& name);
|
||||
@@ -123,7 +127,7 @@ public:
|
||||
set(SOTemplate const&);
|
||||
|
||||
bool
|
||||
set(SerialIter& u, int depth = 0);
|
||||
set(SerialIter& u, int depth = 0, bool requireCanonicalOrder = false);
|
||||
|
||||
[[nodiscard]] SerializedTypeID
|
||||
getSType() const override;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/basics/CountedObject.h>
|
||||
#include <xrpl/basics/UnorderedContainers.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
@@ -108,6 +109,9 @@ public:
|
||||
[[nodiscard]] bool
|
||||
isType(Type const& pe) const;
|
||||
|
||||
[[nodiscard]] size_t
|
||||
getHash() const;
|
||||
|
||||
bool
|
||||
operator==(STPathElement const& t) const;
|
||||
|
||||
@@ -171,12 +175,23 @@ public:
|
||||
reserve(size_t s);
|
||||
};
|
||||
|
||||
template <class Hasher>
|
||||
void
|
||||
hash_append(Hasher& h, STPath const& p) noexcept
|
||||
{
|
||||
for (auto const& e : p)
|
||||
{
|
||||
beast::hash_append(h, e.getHash());
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
// A set of zero or more payment paths
|
||||
class STPathSet final : public STBase, public CountedObject<STPathSet>
|
||||
{
|
||||
std::vector<STPath> value_;
|
||||
xrpl::hardened_hash_set<STPath> seenHashes_;
|
||||
|
||||
public:
|
||||
STPathSet() = default;
|
||||
@@ -205,9 +220,6 @@ public:
|
||||
std::vector<STPath>::const_reference
|
||||
operator[](std::vector<STPath>::size_type n) const;
|
||||
|
||||
std::vector<STPath>::reference
|
||||
operator[](std::vector<STPath>::size_type n);
|
||||
|
||||
[[nodiscard]] std::vector<STPath>::const_iterator
|
||||
begin() const;
|
||||
|
||||
@@ -227,6 +239,9 @@ public:
|
||||
void
|
||||
emplaceBack(Args&&... args);
|
||||
|
||||
[[nodiscard]] bool
|
||||
contains(STPath const& path) const;
|
||||
|
||||
private:
|
||||
STBase*
|
||||
copy(std::size_t n, void* buf) const override;
|
||||
@@ -515,12 +530,6 @@ STPathSet::operator[](std::vector<STPath>::size_type n) const
|
||||
return value_[n];
|
||||
}
|
||||
|
||||
inline std::vector<STPath>::reference
|
||||
STPathSet::operator[](std::vector<STPath>::size_type n)
|
||||
{
|
||||
return value_[n];
|
||||
}
|
||||
|
||||
inline std::vector<STPath>::const_iterator
|
||||
STPathSet::begin() const
|
||||
{
|
||||
@@ -549,6 +558,7 @@ inline void
|
||||
STPathSet::pushBack(STPath const& e)
|
||||
{
|
||||
value_.push_back(e);
|
||||
seenHashes_.emplace(value_.back());
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
@@ -556,6 +566,13 @@ inline void
|
||||
STPathSet::emplaceBack(Args&&... args)
|
||||
{
|
||||
value_.emplace_back(std::forward<Args>(args)...);
|
||||
seenHashes_.emplace(value_.back());
|
||||
}
|
||||
|
||||
inline bool
|
||||
STPathSet::contains(STPath const& path) const
|
||||
{
|
||||
return seenHashes_.contains(path);
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -93,12 +93,6 @@ public:
|
||||
[[nodiscard]] SeqProxy
|
||||
getSeqProxy() const;
|
||||
|
||||
/**
|
||||
* Returns the first non-zero value of (Sequence, TicketSequence).
|
||||
*/
|
||||
[[nodiscard]] std::uint32_t
|
||||
getSeqValue() const;
|
||||
|
||||
[[nodiscard]] boost::container::flat_set<AccountID>
|
||||
getMentionedAccounts() const;
|
||||
|
||||
|
||||
@@ -54,6 +54,22 @@ class STValidation final : public STObject, public CountedObject<STValidation>
|
||||
NetClock::time_point seenTime_;
|
||||
|
||||
public:
|
||||
/**
|
||||
* @struct DeserializeOptions
|
||||
* @brief Options controlling deserialization of a STValidation.
|
||||
|
||||
* @var DeserializeOptions::checkSignature
|
||||
* Whether to verify the data was signed properly
|
||||
*
|
||||
* @var DeserializeOptions::requireCanonicalOrder
|
||||
* Whether to require the fields to be in canonical order
|
||||
*/
|
||||
struct DeserializeOptions
|
||||
{
|
||||
bool checkSignature;
|
||||
bool requireCanonicalOrder;
|
||||
};
|
||||
|
||||
/**
|
||||
* Construct a STValidation from a peer from serialized data.
|
||||
*
|
||||
@@ -64,12 +80,12 @@ public:
|
||||
* that signed the validation. For manifest based
|
||||
* validators, this should be the NodeID of the master
|
||||
* public key.
|
||||
* @param checkSignature Whether to verify the data was signed properly
|
||||
* @param options Options controlling deserialization
|
||||
*
|
||||
* @note Throws if the object is not valid
|
||||
*/
|
||||
template <class LookupNodeID>
|
||||
STValidation(SerialIter& sit, LookupNodeID&& lookupNodeID, bool checkSignature);
|
||||
STValidation(SerialIter& sit, LookupNodeID&& lookupNodeID, DeserializeOptions options);
|
||||
|
||||
/**
|
||||
* Construct, sign and trust a new STValidation issued by this node.
|
||||
@@ -163,8 +179,8 @@ private:
|
||||
};
|
||||
|
||||
template <class LookupNodeID>
|
||||
STValidation::STValidation(SerialIter& sit, LookupNodeID&& lookupNodeID, bool checkSignature)
|
||||
: STObject(validationFormat(), sit, sfValidation)
|
||||
STValidation::STValidation(SerialIter& sit, LookupNodeID&& lookupNodeID, DeserializeOptions options)
|
||||
: STObject(validationFormat(), sit, sfValidation, options.requireCanonicalOrder)
|
||||
, signingPubKey_([this]() {
|
||||
auto const spk = getFieldVL(sfSigningPubKey);
|
||||
|
||||
@@ -175,7 +191,7 @@ STValidation::STValidation(SerialIter& sit, LookupNodeID&& lookupNodeID, bool ch
|
||||
}())
|
||||
, nodeID_(lookupNodeID(signingPubKey_))
|
||||
{
|
||||
if (checkSignature && !isValid())
|
||||
if (options.checkSignature && !isValid())
|
||||
{
|
||||
JLOG(debugLog().error()) << "Invalid signature in validation: "
|
||||
<< getJson(JsonOptions::Values::None);
|
||||
|
||||
@@ -53,14 +53,29 @@ public:
|
||||
operator=(SeqProxy const& other) = default;
|
||||
|
||||
/**
|
||||
* Factory function to return a sequence-based SeqProxy
|
||||
* Factory function to return a sequence-based SeqProxy.
|
||||
* Outside of tests, this function should only be used for "secondary" transaction sequences,
|
||||
* e.g. `sfOfferSequence`, or sequence fields in an existing ledger object. DO NOT use this for
|
||||
* the "primary" sequence of a transaction, `sfSequence`.
|
||||
*/
|
||||
static constexpr SeqProxy
|
||||
sequence(std::uint32_t v)
|
||||
rawSequence(std::uint32_t v)
|
||||
{
|
||||
return SeqProxy{Type::Seq, v};
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory function to return a ticket-based SeqProxy.
|
||||
* Outside of tests, this function should only be used for "secondary" transaction sequences,
|
||||
* e.g. `sfOfferSequence`, or sequence fields in an existing ledger object. DO NOT use this for
|
||||
* the "primary" ticket sequence of a transaction, `sfTicketSequence`.
|
||||
*/
|
||||
static constexpr SeqProxy
|
||||
rawTicket(std::uint32_t v)
|
||||
{
|
||||
return SeqProxy{Type::Ticket, v};
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr std::uint32_t
|
||||
value() const
|
||||
{
|
||||
|
||||
@@ -152,7 +152,14 @@ inline constexpr FlagValue tfUniversalMask = ~tfUniversal;
|
||||
\
|
||||
TRANSACTION(MPTokenIssuanceSet, \
|
||||
TF_FLAG(tfMPTLock, 0x00000001) \
|
||||
TF_FLAG(tfMPTUnlock, 0x00000002), \
|
||||
TF_FLAG(tfMPTUnlock, 0x00000002) \
|
||||
TF_FLAG(tfMPTSetCanLock, 0x00000004) \
|
||||
TF_FLAG(tfMPTSetRequireAuth, 0x00000008) \
|
||||
TF_FLAG(tfMPTSetCanEscrow, 0x00000010) \
|
||||
TF_FLAG(tfMPTSetCanTrade, 0x00000020) \
|
||||
TF_FLAG(tfMPTSetCanTransfer, 0x00000040) \
|
||||
TF_FLAG(tfMPTSetCanClawback, 0x00000080) \
|
||||
TF_FLAG(tfMPTSetCanHoldConfidentialBalance, 0x00000100), \
|
||||
MASK_ADJ(0)) \
|
||||
\
|
||||
TRANSACTION(NFTokenCreateOffer, \
|
||||
@@ -356,38 +363,26 @@ inline constexpr FlagValue tfMPTPaymentMask = ~(tfUniversal | tfPartialPayment);
|
||||
inline constexpr FlagValue tfTrustSetPermissionMask =
|
||||
~(tfUniversal | tfSetfAuth | tfSetFreeze | tfClearFreeze);
|
||||
|
||||
// MPTokenIssuanceCreate MutableFlags:
|
||||
// Indicating specific fields or flags may be changed after issuance.
|
||||
inline constexpr FlagValue tmfMPTCanEnableCanLock = lsmfMPTCanEnableCanLock;
|
||||
inline constexpr FlagValue tmfMPTCanEnableRequireAuth = lsmfMPTCanEnableRequireAuth;
|
||||
inline constexpr FlagValue tmfMPTCanEnableCanEscrow = lsmfMPTCanEnableCanEscrow;
|
||||
inline constexpr FlagValue tmfMPTCanEnableCanTrade = lsmfMPTCanEnableCanTrade;
|
||||
inline constexpr FlagValue tmfMPTCanEnableCanTransfer = lsmfMPTCanEnableCanTransfer;
|
||||
inline constexpr FlagValue tmfMPTCanEnableCanClawback = lsmfMPTCanEnableCanClawback;
|
||||
inline constexpr FlagValue tmfMPTCanMutateMetadata = lsmfMPTCanMutateMetadata;
|
||||
inline constexpr FlagValue tmfMPTCanMutateTransferFee = lsmfMPTCanMutateTransferFee;
|
||||
inline constexpr FlagValue tmfMPTCannotEnableCanHoldConfidentialBalance =
|
||||
lsmfMPTCannotEnableCanHoldConfidentialBalance;
|
||||
inline constexpr FlagValue tmfMPTokenIssuanceCreateMutableMask =
|
||||
~(tmfMPTCanEnableCanLock | tmfMPTCanEnableRequireAuth | tmfMPTCanEnableCanEscrow |
|
||||
tmfMPTCanEnableCanTrade | tmfMPTCanEnableCanTransfer | tmfMPTCanEnableCanClawback |
|
||||
tmfMPTCanMutateMetadata | tmfMPTCanMutateTransferFee |
|
||||
tmfMPTCannotEnableCanHoldConfidentialBalance);
|
||||
// MPTokenIssuanceCreate / MPTokenIssuanceSet ImmutableFlags:
|
||||
// Defines the immutable fields and flags specific to MPTokenIssuance.
|
||||
inline constexpr FlagValue tifMPTCanLock = lsifMPTCanLock;
|
||||
inline constexpr FlagValue tifMPTRequireAuth = lsifMPTRequireAuth;
|
||||
inline constexpr FlagValue tifMPTCanEscrow = lsifMPTCanEscrow;
|
||||
inline constexpr FlagValue tifMPTCanTrade = lsifMPTCanTrade;
|
||||
inline constexpr FlagValue tifMPTCanTransfer = lsifMPTCanTransfer;
|
||||
inline constexpr FlagValue tifMPTCanClawback = lsifMPTCanClawback;
|
||||
inline constexpr FlagValue tifMPTMetadata = lsifMPTMetadata;
|
||||
inline constexpr FlagValue tifMPTTransferFee = lsifMPTTransferFee;
|
||||
inline constexpr FlagValue tifMPTCanHoldConfidentialBalance = lsifMPTCanHoldConfidentialBalance;
|
||||
inline constexpr FlagValue tifMPTokenIssuanceImmutableMask =
|
||||
~(tifMPTCanLock | tifMPTRequireAuth | tifMPTCanEscrow | tifMPTCanTrade | tifMPTCanTransfer |
|
||||
tifMPTCanClawback | tifMPTMetadata | tifMPTTransferFee | tifMPTCanHoldConfidentialBalance);
|
||||
|
||||
// MPTokenIssuanceSet MutableFlags:
|
||||
// Enable mutable capability flags. These flags are one-way: once enabled,
|
||||
// the corresponding capability cannot be disabled by MPTokenIssuanceSet.
|
||||
|
||||
inline constexpr FlagValue tmfMPTSetCanLock = 0x00000001;
|
||||
inline constexpr FlagValue tmfMPTSetRequireAuth = 0x00000002;
|
||||
inline constexpr FlagValue tmfMPTSetCanEscrow = 0x00000004;
|
||||
inline constexpr FlagValue tmfMPTSetCanTrade = 0x00000008;
|
||||
inline constexpr FlagValue tmfMPTSetCanTransfer = 0x00000010;
|
||||
inline constexpr FlagValue tmfMPTSetCanClawback = 0x00000020;
|
||||
inline constexpr FlagValue tmfMPTSetCanHoldConfidentialBalance = 0x00000040;
|
||||
inline constexpr FlagValue tmfMPTokenIssuanceSetMutableMask =
|
||||
~(tmfMPTSetCanLock | tmfMPTSetRequireAuth | tmfMPTSetCanEscrow | tmfMPTSetCanTrade |
|
||||
tmfMPTSetCanTransfer | tmfMPTSetCanClawback | tmfMPTSetCanHoldConfidentialBalance);
|
||||
// MPTokenIssuanceSet set of flags that is used to enable capabilities on an MPTokenIssuance.
|
||||
// Used as `txFlags & tfMPTokenIssuanceSetEnableFlagMask` to extract the capability-enabling bits.
|
||||
inline constexpr FlagValue tfMPTokenIssuanceSetEnableFlagMask = tfMPTSetCanLock |
|
||||
tfMPTSetRequireAuth | tfMPTSetCanEscrow | tfMPTSetCanTrade | tfMPTSetCanTransfer |
|
||||
tfMPTSetCanClawback | tfMPTSetCanHoldConfidentialBalance;
|
||||
|
||||
// Prior to fixRemoveNFTokenAutoTrustLine, transfer of an NFToken between accounts allowed a
|
||||
// TrustLine to be added to the issuer of that token without explicit permission from that issuer.
|
||||
|
||||
@@ -59,7 +59,6 @@ XRPL_FIX (PreviousTxnID, Supported::Yes, VoteBehavior::DefaultNo
|
||||
XRPL_FIX (XChainRewardRounding, Supported::Yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (EmptyDID, Supported::Yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(PriceOracle, Supported::Yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (AMMOverflowOffer, Supported::Yes, VoteBehavior::DefaultYes)
|
||||
XRPL_FIX (FillOrKill, Supported::Yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(DID, Supported::Yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(XChainBridge, Supported::Yes, VoteBehavior::DefaultNo)
|
||||
@@ -100,6 +99,7 @@ XRPL_RETIRE_FIX(1578)
|
||||
XRPL_RETIRE_FIX(1623)
|
||||
XRPL_RETIRE_FIX(1781)
|
||||
XRPL_RETIRE_FIX(AmendmentMajorityCalc)
|
||||
XRPL_RETIRE_FIX(AMMOverflowOffer)
|
||||
XRPL_RETIRE_FIX(CheckThreading)
|
||||
XRPL_RETIRE_FIX(DisallowIncomingV1)
|
||||
XRPL_RETIRE_FIX(InnerObjTemplate)
|
||||
|
||||
@@ -404,7 +404,7 @@ LEDGER_ENTRY(ltMPTOKEN_ISSUANCE, 0x007e, MPTokenIssuance, mpt_issuance, ({
|
||||
{sfPreviousTxnID, SoeRequired},
|
||||
{sfPreviousTxnLgrSeq, SoeRequired},
|
||||
{sfDomainID, SoeOptional},
|
||||
{sfMutableFlags, SoeDefault},
|
||||
{sfImmutableFlags, SoeDefault},
|
||||
{sfReferenceHolding, SoeOptional},
|
||||
{sfIssuerEncryptionKey, SoeOptional},
|
||||
{sfAuditorEncryptionKey, SoeOptional},
|
||||
|
||||
@@ -98,7 +98,7 @@ TYPED_SFIELD(sfVoteWeight, UINT32, 48)
|
||||
TYPED_SFIELD(sfFirstNFTokenSequence, UINT32, 50)
|
||||
TYPED_SFIELD(sfOracleDocumentID, UINT32, 51)
|
||||
TYPED_SFIELD(sfPermissionValue, UINT32, 52)
|
||||
TYPED_SFIELD(sfMutableFlags, UINT32, 53)
|
||||
TYPED_SFIELD(sfImmutableFlags, UINT32, 53)
|
||||
TYPED_SFIELD(sfStartDate, UINT32, 54)
|
||||
TYPED_SFIELD(sfPaymentInterval, UINT32, 55)
|
||||
TYPED_SFIELD(sfGracePeriod, UINT32, 56)
|
||||
@@ -239,6 +239,7 @@ TYPED_SFIELD(sfManagementFeeOutstanding, NUMBER, 17, SField::kSmdNeedsAsset
|
||||
|
||||
// int32
|
||||
TYPED_SFIELD(sfLoanScale, INT32, 1)
|
||||
TYPED_SFIELD(sfRemainingOwnerCountDelta, INT32, 2)
|
||||
|
||||
// currency amount (common)
|
||||
TYPED_SFIELD(sfAmount, AMOUNT, 1)
|
||||
@@ -278,6 +279,7 @@ TYPED_SFIELD(sfMinAccountCreateAmount, AMOUNT, 30)
|
||||
TYPED_SFIELD(sfLPTokenBalance, AMOUNT, 31)
|
||||
TYPED_SFIELD(sfFeeAmount, AMOUNT, 32)
|
||||
TYPED_SFIELD(sfMaxFee, AMOUNT, 33)
|
||||
TYPED_SFIELD(sfFeeAmountDelta, AMOUNT, 34)
|
||||
|
||||
// variable length (common)
|
||||
TYPED_SFIELD(sfPublicKey, VL, 1)
|
||||
|
||||
@@ -705,7 +705,7 @@ TRANSACTION(ttMPTOKEN_ISSUANCE_CREATE, 54, MPTokenIssuanceCreate,
|
||||
{sfMaximumAmount, SoeOptional},
|
||||
{sfMPTokenMetadata, SoeOptional},
|
||||
{sfDomainID, SoeOptional},
|
||||
{sfMutableFlags, SoeOptional},
|
||||
{sfImmutableFlags, SoeOptional},
|
||||
}))
|
||||
|
||||
/** This transaction type destroys a MPTokensIssuance instance */
|
||||
@@ -734,7 +734,7 @@ TRANSACTION(ttMPTOKEN_ISSUANCE_SET, 56, MPTokenIssuanceSet,
|
||||
{sfDomainID, SoeOptional},
|
||||
{sfMPTokenMetadata, SoeOptional},
|
||||
{sfTransferFee, SoeOptional},
|
||||
{sfMutableFlags, SoeOptional},
|
||||
{sfImmutableFlags, SoeOptional},
|
||||
{sfIssuerEncryptionKey, SoeOptional},
|
||||
{sfAuditorEncryptionKey, SoeOptional},
|
||||
}))
|
||||
@@ -1085,7 +1085,7 @@ TRANSACTION(ttLOAN_PAY, 84, LoanPay,
|
||||
# include <xrpl/tx/transactors/token/ConfidentialMPTConvert.h>
|
||||
#endif
|
||||
TRANSACTION(ttCONFIDENTIAL_MPT_CONVERT, 85, ConfidentialMPTConvert,
|
||||
Delegation::Delegable,
|
||||
Delegation::NotDelegable,
|
||||
featureConfidentialTransfer,
|
||||
NoPriv,
|
||||
({
|
||||
@@ -1189,9 +1189,9 @@ TRANSACTION(ttSPONSORSHIP_SET, 91, SponsorshipSet,
|
||||
({
|
||||
{sfCounterpartySponsor, SoeOptional},
|
||||
{sfSponsee, SoeOptional},
|
||||
{sfFeeAmount, SoeOptional},
|
||||
{sfFeeAmountDelta, SoeOptional},
|
||||
{sfMaxFee, SoeOptional},
|
||||
{sfRemainingOwnerCount, SoeOptional},
|
||||
{sfRemainingOwnerCountDelta, SoeOptional},
|
||||
}))
|
||||
|
||||
/** This system-generated transaction type is used to update the status of the various amendments.
|
||||
|
||||
@@ -23,6 +23,16 @@ By default, `CODEGEN_VENV_DIR` points to `.venv` in the project root. The
|
||||
`setup_code_gen` target creates a venv there and installs the required packages.
|
||||
The `code_gen` target then uses the venv's Python interpreter to run generation.
|
||||
|
||||
Generation is pure Python, so the same targets are also available as a
|
||||
standalone project that needs neither the dependencies nor a compiler. This is
|
||||
what CI uses, and it is handy if you only want to regenerate these files:
|
||||
|
||||
```bash
|
||||
cmake -S cmake/codegen -B build/codegen
|
||||
cmake --build build/codegen --target setup_code_gen
|
||||
cmake --build build/codegen --target code_gen
|
||||
```
|
||||
|
||||
### Python Dependencies
|
||||
|
||||
The code generation requires the following Python packages (installed by `setup_code_gen`):
|
||||
|
||||
@@ -256,27 +256,27 @@ public:
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfMutableFlags (SoeDefault)
|
||||
* @brief Get sfImmutableFlags (SoeDefault)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_UINT32::type::value_type>
|
||||
getMutableFlags() const
|
||||
getImmutableFlags() const
|
||||
{
|
||||
if (hasMutableFlags())
|
||||
return this->sle_->at(sfMutableFlags);
|
||||
if (hasImmutableFlags())
|
||||
return this->sle_->at(sfImmutableFlags);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfMutableFlags is present.
|
||||
* @brief Check if sfImmutableFlags is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasMutableFlags() const
|
||||
hasImmutableFlags() const
|
||||
{
|
||||
return this->sle_->isFieldPresent(sfMutableFlags);
|
||||
return this->sle_->isFieldPresent(sfImmutableFlags);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -557,13 +557,13 @@ public:
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfMutableFlags (SoeDefault)
|
||||
* @brief Set sfImmutableFlags (SoeDefault)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
MPTokenIssuanceBuilder&
|
||||
setMutableFlags(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
setImmutableFlags(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
{
|
||||
object_[sfMutableFlags] = value;
|
||||
object_[sfImmutableFlags] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ class ConfidentialMPTConvertBuilder;
|
||||
* @brief Transaction: ConfidentialMPTConvert
|
||||
*
|
||||
* Type: ttCONFIDENTIAL_MPT_CONVERT (85)
|
||||
* Delegable: Delegation::Delegable
|
||||
* Delegable: Delegation::NotDelegable
|
||||
* Amendment: featureConfidentialTransfer
|
||||
* Privileges: NoPriv
|
||||
*
|
||||
|
||||
@@ -178,29 +178,29 @@ public:
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfMutableFlags (SoeOptional)
|
||||
* @brief Get sfImmutableFlags (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_UINT32::type::value_type>
|
||||
getMutableFlags() const
|
||||
getImmutableFlags() const
|
||||
{
|
||||
if (hasMutableFlags())
|
||||
if (hasImmutableFlags())
|
||||
{
|
||||
return this->tx_->at(sfMutableFlags);
|
||||
return this->tx_->at(sfImmutableFlags);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfMutableFlags is present.
|
||||
* @brief Check if sfImmutableFlags is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasMutableFlags() const
|
||||
hasImmutableFlags() const
|
||||
{
|
||||
return this->tx_->isFieldPresent(sfMutableFlags);
|
||||
return this->tx_->isFieldPresent(sfImmutableFlags);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -302,13 +302,13 @@ public:
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfMutableFlags (SoeOptional)
|
||||
* @brief Set sfImmutableFlags (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
MPTokenIssuanceCreateBuilder&
|
||||
setMutableFlags(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
setImmutableFlags(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
{
|
||||
object_[sfMutableFlags] = value;
|
||||
object_[sfImmutableFlags] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
@@ -163,29 +163,29 @@ public:
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfMutableFlags (SoeOptional)
|
||||
* @brief Get sfImmutableFlags (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_UINT32::type::value_type>
|
||||
getMutableFlags() const
|
||||
getImmutableFlags() const
|
||||
{
|
||||
if (hasMutableFlags())
|
||||
if (hasImmutableFlags())
|
||||
{
|
||||
return this->tx_->at(sfMutableFlags);
|
||||
return this->tx_->at(sfImmutableFlags);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfMutableFlags is present.
|
||||
* @brief Check if sfImmutableFlags is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasMutableFlags() const
|
||||
hasImmutableFlags() const
|
||||
{
|
||||
return this->tx_->isFieldPresent(sfMutableFlags);
|
||||
return this->tx_->isFieldPresent(sfImmutableFlags);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -341,13 +341,13 @@ public:
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfMutableFlags (SoeOptional)
|
||||
* @brief Set sfImmutableFlags (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
MPTokenIssuanceSetBuilder&
|
||||
setMutableFlags(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
setImmutableFlags(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
{
|
||||
object_[sfMutableFlags] = value;
|
||||
object_[sfImmutableFlags] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
@@ -100,29 +100,29 @@ public:
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfFeeAmount (SoeOptional)
|
||||
* @brief Get sfFeeAmountDelta (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_AMOUNT::type::value_type>
|
||||
getFeeAmount() const
|
||||
getFeeAmountDelta() const
|
||||
{
|
||||
if (hasFeeAmount())
|
||||
if (hasFeeAmountDelta())
|
||||
{
|
||||
return this->tx_->at(sfFeeAmount);
|
||||
return this->tx_->at(sfFeeAmountDelta);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfFeeAmount is present.
|
||||
* @brief Check if sfFeeAmountDelta is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasFeeAmount() const
|
||||
hasFeeAmountDelta() const
|
||||
{
|
||||
return this->tx_->isFieldPresent(sfFeeAmount);
|
||||
return this->tx_->isFieldPresent(sfFeeAmountDelta);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -152,29 +152,29 @@ public:
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfRemainingOwnerCount (SoeOptional)
|
||||
* @brief Get sfRemainingOwnerCountDelta (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_UINT32::type::value_type>
|
||||
getRemainingOwnerCount() const
|
||||
protocol_autogen::Optional<SF_INT32::type::value_type>
|
||||
getRemainingOwnerCountDelta() const
|
||||
{
|
||||
if (hasRemainingOwnerCount())
|
||||
if (hasRemainingOwnerCountDelta())
|
||||
{
|
||||
return this->tx_->at(sfRemainingOwnerCount);
|
||||
return this->tx_->at(sfRemainingOwnerCountDelta);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfRemainingOwnerCount is present.
|
||||
* @brief Check if sfRemainingOwnerCountDelta is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasRemainingOwnerCount() const
|
||||
hasRemainingOwnerCountDelta() const
|
||||
{
|
||||
return this->tx_->isFieldPresent(sfRemainingOwnerCount);
|
||||
return this->tx_->isFieldPresent(sfRemainingOwnerCountDelta);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -243,13 +243,13 @@ public:
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfFeeAmount (SoeOptional)
|
||||
* @brief Set sfFeeAmountDelta (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
SponsorshipSetBuilder&
|
||||
setFeeAmount(std::decay_t<typename SF_AMOUNT::type::value_type> const& value)
|
||||
setFeeAmountDelta(std::decay_t<typename SF_AMOUNT::type::value_type> const& value)
|
||||
{
|
||||
object_[sfFeeAmount] = value;
|
||||
object_[sfFeeAmountDelta] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -265,13 +265,13 @@ public:
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfRemainingOwnerCount (SoeOptional)
|
||||
* @brief Set sfRemainingOwnerCountDelta (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
SponsorshipSetBuilder&
|
||||
setRemainingOwnerCount(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
setRemainingOwnerCountDelta(std::decay_t<typename SF_INT32::type::value_type> const& value)
|
||||
{
|
||||
object_[sfRemainingOwnerCount] = value;
|
||||
object_[sfRemainingOwnerCountDelta] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ extern Charge const kFeeRequestNoReply; // A request that we cannot satisfy.
|
||||
extern Charge const kFeeInvalidSignature; // An object whose signature we had to check that failed.
|
||||
extern Charge const kFeeUselessData; // Data we have no use for.
|
||||
extern Charge const kFeeInvalidData; // Data we have to verify before rejecting.
|
||||
extern Charge const kFeeMalformedData; // Data that no honest peer would send.
|
||||
|
||||
// RPC loads
|
||||
extern Charge const kFeeMalformedRpc; // An RPC request that we can immediately tell is invalid.
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <xrpl/server/Manifest.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
@@ -22,6 +23,39 @@ namespace xrpl {
|
||||
// Operations that clients may wish to perform against the network
|
||||
// Master operational handler, server sequencer, network tracker
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
constexpr std::size_t kMaxSubscriptionsPerConnection = 100'000;
|
||||
|
||||
/**
|
||||
* Whether adding @p additional subscriptions to a connection already holding
|
||||
* @p current would exceed the cap.
|
||||
*
|
||||
* Pure arithmetic split out so it can be unit-tested without a live
|
||||
* connection. The first term avoids underflow in the subtraction.
|
||||
*
|
||||
* @param current Subscriptions already tracked on the connection.
|
||||
* @param additional Subscriptions a request would add.
|
||||
* @param cap The effective per-connection cap. Defaults to the
|
||||
* built-in limit; callers may pass a configured override.
|
||||
* @return true if the request must be rejected to stay within the cap.
|
||||
*/
|
||||
[[nodiscard]] constexpr bool
|
||||
exceedsSubscriptionCap(
|
||||
std::size_t current,
|
||||
std::size_t additional,
|
||||
std::size_t cap = kMaxSubscriptionsPerConnection)
|
||||
{
|
||||
return additional > cap || current > cap - additional;
|
||||
}
|
||||
|
||||
class InfoSubRequest : public CountedObject<InfoSubRequest>
|
||||
{
|
||||
public:
|
||||
@@ -44,12 +78,12 @@ public:
|
||||
* map.
|
||||
*
|
||||
* @note Lifetime contract: every `InfoSub` instance MUST be destroyed
|
||||
* before the backing `Source`. NetworkOPsImp shutdown drops all
|
||||
* subscriber strong refs before its own teardown to satisfy this.
|
||||
* before the backing `Source`. NetworkOPsImp shutdown drops all
|
||||
* subscriber strong refs before its own teardown to satisfy this.
|
||||
* @note Thread-safety: per-instance state is guarded by `lock_`. The
|
||||
* destructor reads tracking sets without taking `lock_` because
|
||||
* the strong-pointer ref-count is zero at destruction time, so
|
||||
* no other thread can be calling the public mutators.
|
||||
* destructor reads tracking sets without taking `lock_` because
|
||||
* the strong-pointer ref-count is zero at destruction time, so
|
||||
* no other thread can be calling the public mutators.
|
||||
*/
|
||||
class InfoSub : public CountedObject<InfoSub>
|
||||
{
|
||||
@@ -117,6 +151,34 @@ public:
|
||||
AccountID const& account,
|
||||
bool historyOnly) = 0;
|
||||
|
||||
/**
|
||||
* Schedule the server-side teardown of a disconnecting connection's
|
||||
* account subscriptions off the destructor thread.
|
||||
*
|
||||
* The implementation posts a low-priority JobQueue task that erases the
|
||||
* entries in bounded chunks, so `~InfoSub` returns immediately instead
|
||||
* of running the erase loop inline. The sets are taken by value so the
|
||||
* job owns its copies and never references the destroyed `InfoSub`.
|
||||
* Cleanup is keyed on `seq` (unique per connection), so deferring it
|
||||
* cannot disturb a reconnected client reusing the same accounts.
|
||||
*
|
||||
* @param seq The disconnecting connection's unique subscription id.
|
||||
* @param rtAccounts Real-time account subscriptions to remove.
|
||||
* @param normalAccounts Normal account subscriptions to remove.
|
||||
* @param historyAccounts Account-history subscriptions to remove.
|
||||
*
|
||||
* @note The implementing `Source` must outlive any job it posts. If the
|
||||
* JobQueue is already stopping (process shutdown), the job is not
|
||||
* enqueued; the cleanup is skipped because the server-side maps
|
||||
* are about to be destroyed and no publishing can run.
|
||||
*/
|
||||
virtual void
|
||||
scheduleAccountCleanup(
|
||||
std::uint64_t seq,
|
||||
hash_set<AccountID> rtAccounts,
|
||||
hash_set<AccountID> normalAccounts,
|
||||
hash_set<AccountID> historyAccounts) = 0;
|
||||
|
||||
// VFALCO TODO Document the bool return value
|
||||
virtual bool
|
||||
subLedger(ref ispListener, json::Value& jvResult) = 0;
|
||||
@@ -153,12 +215,12 @@ public:
|
||||
* @param ispListener The subscriber requesting removal.
|
||||
* @param book The order book to unsubscribe from.
|
||||
* @return true if the entry was present and removed, false if the
|
||||
* subscriber was not subscribed to @p book.
|
||||
* subscriber was not subscribed to @p book.
|
||||
*
|
||||
* @note Thread-safety: acquires subLock_ internally.
|
||||
* @note Thread-safety: acquires bookLock_ internally.
|
||||
* @note Do NOT call from ~InfoSub(). Use unsubBookInternal instead
|
||||
* to avoid a redundant write-back to bookSubscriptions_ on a
|
||||
* partially-destroyed object.
|
||||
* to avoid a redundant write-back to bookSubscriptions_ on a
|
||||
* partially-destroyed object.
|
||||
*/
|
||||
virtual bool
|
||||
unsubBook(ref ispListener, Book const&) = 0;
|
||||
@@ -173,9 +235,9 @@ public:
|
||||
* @param uListener The sequence number of the subscriber being torn down.
|
||||
* @param book The order book entry to remove.
|
||||
* @return true if the entry was present and removed, false otherwise
|
||||
* (e.g., already removed by a concurrent RPC unsubscribe).
|
||||
* (e.g., already removed by a concurrent RPC unsubscribe).
|
||||
*
|
||||
* @note Thread-safety: acquires subLock_ internally.
|
||||
* @note Thread-safety: acquires bookLock_ internally.
|
||||
*/
|
||||
virtual bool
|
||||
unsubBookInternal(std::uint64_t uListener, Book const&) = 0;
|
||||
@@ -221,8 +283,8 @@ public:
|
||||
|
||||
/**
|
||||
* Journal used by InfoSub for diagnostics that occur after the
|
||||
* owning subsystem (e.g. application-level Logs) is the only
|
||||
* surviving sink — primarily destructor-time cleanup failures.
|
||||
* owning subsystem (e.g. application-level Logs) is the only
|
||||
* surviving sink — primarily destructor-time cleanup failures.
|
||||
*/
|
||||
[[nodiscard]] virtual beast::Journal const&
|
||||
journal() const = 0;
|
||||
@@ -243,6 +305,56 @@ public:
|
||||
[[nodiscard]] std::uint64_t
|
||||
getSeq() const;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @return The total tracked subscription count for this connection.
|
||||
*
|
||||
* @note Thread-safe: takes `lock_` for the read; read-only.
|
||||
*/
|
||||
[[nodiscard]] std::size_t
|
||||
totalSubscriptionCount() const;
|
||||
|
||||
/**
|
||||
* Enforce the cap and reserve a request's net-new accounts, atomically.
|
||||
*
|
||||
* Under one hold of `lock_`: count the net-new entries in the two sets,
|
||||
* check the total against @p cap, and insert them only if it fits.
|
||||
* All-or-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 accounts. The server-side
|
||||
* maps are populated afterwards by subAccount, whose re-insert is a no-op.
|
||||
*
|
||||
* @param proposedAccounts Real-time (accounts_proposed) ids to reserve.
|
||||
* @param normalAccounts Normal (accounts) 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
|
||||
tryReserveAccountSubscriptions(
|
||||
hash_set<AccountID> const& proposedAccounts,
|
||||
hash_set<AccountID> const& normalAccounts,
|
||||
std::size_t cap);
|
||||
|
||||
/**
|
||||
* Whether this connection already tracks an account-history for @p account.
|
||||
*
|
||||
* `doSubscribe` reads this to charge the cap for an account_history_tx_stream
|
||||
* only when it is net-new, matching the account branches.
|
||||
*
|
||||
* @param account The account an account_history_tx_stream would add.
|
||||
* @return true if @p account is already in the account-history set.
|
||||
* @note Thread-safe: takes `lock_`; read-only.
|
||||
*/
|
||||
[[nodiscard]] bool
|
||||
hasAccountHistorySubscription(AccountID const& account) const;
|
||||
|
||||
void
|
||||
onSendEmpty();
|
||||
|
||||
@@ -302,7 +414,9 @@ public:
|
||||
getApiVersion() const noexcept;
|
||||
|
||||
protected:
|
||||
std::mutex lock_;
|
||||
// Mutable so the read-only totalSubscriptionCount() accessor can lock it
|
||||
// from a const method; locking semantics are otherwise unchanged.
|
||||
mutable std::mutex lock_;
|
||||
|
||||
private:
|
||||
Consumer consumer_;
|
||||
|
||||
@@ -3,12 +3,14 @@
|
||||
#include <xrpl/basics/Blob.h>
|
||||
#include <xrpl/basics/Slice.h>
|
||||
#include <xrpl/basics/UnorderedContainers.h>
|
||||
#include <xrpl/basics/base64.h>
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/protocol/PublicKey.h>
|
||||
#include <xrpl/protocol/SecretKey.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
@@ -43,12 +45,15 @@ namespace xrpl {
|
||||
dynamically generates the signatureless form when it needs to verify
|
||||
the signature.
|
||||
|
||||
An instance of ManifestCache stores, for each trusted validator, (a) its
|
||||
An instance of ManifestCache stores, for each known validator, (a) its
|
||||
master public key, and (b) the most senior of all valid manifests it has
|
||||
seen for that validator, if any. On startup, the [validator_token] config
|
||||
entry (which contains the manifest for this validator) is decoded and
|
||||
added to the manifest cache. Other manifests are added as "gossip"
|
||||
received from xrpld peers.
|
||||
received from xrpld peers, including ones for validators this node does not
|
||||
trust. Manifests for untrusted validators are capped (kMaxUntrustedCount)
|
||||
so peer gossip cannot grow the cache without bound; trusted validators are
|
||||
not capped. Entries are never evicted, so a stored revocation is permanent.
|
||||
|
||||
When an ephemeral key is compromised, a new signing key pair is created,
|
||||
along with a new manifest vouching for it (with a higher sequence number),
|
||||
@@ -164,6 +169,100 @@ struct Manifest
|
||||
std::string
|
||||
to_string(Manifest const& m);
|
||||
|
||||
/**
|
||||
* Largest a valid manifest can be, in decoded bytes.
|
||||
*
|
||||
* A manifest has a fixed set of fields. Each is serialized as a field header
|
||||
* (1-2 bytes), an optional length prefix (1 byte for these sizes), and the
|
||||
* field body. Taking every field at its largest gives the maximum below, so
|
||||
* anything larger cannot be a valid manifest.
|
||||
*
|
||||
* Field header + length + body = bytes
|
||||
* sfVersion (U16) 2 0 2 4
|
||||
* sfSequence (U32) 1 0 4 5
|
||||
* sfPublicKey (33) 1 1 33 35
|
||||
* sfSigningPubKey (33) 1 1 33 35
|
||||
* sfSignature (72) 1 1 72 74
|
||||
* sfMasterSignature (72) 2 1 72 75
|
||||
* sfDomain (128) 1 1 128 130
|
||||
* -----
|
||||
* 358
|
||||
*/
|
||||
constexpr std::size_t kMaxManifestBytes = 358;
|
||||
|
||||
/**
|
||||
* Largest a valid manifest can be, in base64 characters.
|
||||
*
|
||||
* base64 encodes 3 bytes as 4 characters, so this is the encoded form of
|
||||
* @ref kMaxManifestBytes. Callers that receive a base64 manifest should
|
||||
* reject anything longer than this before decoding, to avoid allocating
|
||||
* memory for an oversized input.
|
||||
*/
|
||||
constexpr std::size_t kMaxManifestBase64 = base64::encodedSize(kMaxManifestBytes);
|
||||
|
||||
/**
|
||||
* Default number of untrusted manifests to store in cache and allowed
|
||||
* in one Manifest message.
|
||||
*
|
||||
* Bounds unlisted validators two ways. In the cache, a manifest for a
|
||||
* brand-new unlisted key is rejected once this many are held, so peer gossip
|
||||
* cannot grow the cache without end. In a TMManifests message, this many are
|
||||
* sent and processed, so a peer sending its whole cache cannot force unbounded
|
||||
* work.
|
||||
*
|
||||
* Operators can override this with `[overlay] max_untrusted_count`. Both users
|
||||
* read the configured value and fall back to this default.
|
||||
*/
|
||||
constexpr std::size_t kMaxUntrustedCount = 300;
|
||||
|
||||
/**
|
||||
* Default number of trusted manifests allowed in a Manifest message.
|
||||
* Not used atm while creating the message, but used to calculate the higher limit on
|
||||
* received message size. Introduced to maintain consistency. Future implementation
|
||||
* will use this limit.
|
||||
*
|
||||
* Trusted manifests are never dropped: every one this node holds is sent, and
|
||||
* every one received is processed, since dropping one would delay a validator
|
||||
* key rotation. This count only sizes the largest message accepted, so it must
|
||||
* stay above any realistic validator list. Cap can be increased in the config
|
||||
* file if messages get rejected with actual trusted manifest count crossing
|
||||
* configured(or else default) value.
|
||||
* Operators can override this with `[overlay] max_trusted_count`.
|
||||
*/
|
||||
constexpr std::size_t kMaxTrustedCount = 300;
|
||||
|
||||
/**
|
||||
* Number of untrusted manifests to store in cache and allowed
|
||||
* in one Manifest message..
|
||||
*
|
||||
* Returns the operator's override when one is configured, otherwise
|
||||
* @ref kMaxUntrustedCount. Config stores an override rather than the default
|
||||
* itself because the core module cannot depend on this module.
|
||||
*
|
||||
* @param configured The value from `[overlay] max_untrusted_count`, or
|
||||
* `std::nullopt` when the operator did not set it.
|
||||
*/
|
||||
constexpr std::size_t
|
||||
untrustedManifestCount(std::optional<std::size_t> const& configured)
|
||||
{
|
||||
return configured.value_or(kMaxUntrustedCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Number of trusted manifests allowed in a Manifest message.
|
||||
*
|
||||
* Not a cap on how many are sent or processed; see @ref kMaxTrustedCount.
|
||||
* but used to calculate the higher limit on received message size.
|
||||
*
|
||||
* @param configured The value from `[overlay] max_trusted_count`, or
|
||||
* `std::nullopt` when the operator did not set it.
|
||||
*/
|
||||
constexpr std::size_t
|
||||
trustedManifestCount(std::optional<std::size_t> const& configured)
|
||||
{
|
||||
return configured.value_or(kMaxTrustedCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs Manifest from serialized string
|
||||
*
|
||||
@@ -172,7 +271,7 @@ to_string(Manifest const& m);
|
||||
* @return `std::nullopt` if string is invalid
|
||||
*
|
||||
* @note This does not verify manifest signatures.
|
||||
* `Manifest::verify` should be called after constructing manifest.
|
||||
* `Manifest::verify` should be called after constructing manifest.
|
||||
*/
|
||||
/** @{ */
|
||||
std::optional<Manifest>
|
||||
@@ -225,30 +324,17 @@ loadValidatorToken(
|
||||
beast::Journal journal = beast::Journal(beast::Journal::getNullSink()));
|
||||
|
||||
enum class ManifestDisposition {
|
||||
/**
|
||||
* Manifest is valid
|
||||
*/
|
||||
Accepted = 0,
|
||||
Accepted = 0, ///< Manifest is valid
|
||||
|
||||
/**
|
||||
* Sequence is too old
|
||||
*/
|
||||
Stale,
|
||||
Stale, ///< Sequence is too old
|
||||
|
||||
/**
|
||||
* The master key is not acceptable to us
|
||||
*/
|
||||
BadMasterKey,
|
||||
BadMasterKey, ///< The master key is not acceptable to us
|
||||
|
||||
/**
|
||||
* The ephemeral key is not acceptable to us
|
||||
*/
|
||||
BadEphemeralKey,
|
||||
BadEphemeralKey, ///< The ephemeral key is not acceptable to us
|
||||
|
||||
/**
|
||||
* Timely, but invalid signature
|
||||
*/
|
||||
Invalid
|
||||
Invalid, ///< Timely, but invalid signature
|
||||
|
||||
UntrustedCapacity ///< Unlisted and limit reached
|
||||
};
|
||||
|
||||
inline std::string
|
||||
@@ -266,11 +352,25 @@ to_string(ManifestDisposition m)
|
||||
return "badEphemeralKey";
|
||||
case ManifestDisposition::Invalid:
|
||||
return "invalid";
|
||||
case ManifestDisposition::UntrustedCapacity:
|
||||
return "untrustedCapacity";
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a manifest counts against the 'untrusted' cache cap.
|
||||
*
|
||||
* Passed to `ManifestCache::applyManifest` with no default, so every caller
|
||||
* must choose. `Capped` is the safe, flood-resistant value; only listed or
|
||||
* configured keys should use `Uncapped`.
|
||||
*/
|
||||
enum class ManifestRateLimitCapPolicy : std::uint8_t {
|
||||
Capped, ///< Subject to the untrusted cap (unlisted peer gossip)
|
||||
Uncapped ///< Bypasses the cap (listed/trusted or config manifests)
|
||||
};
|
||||
|
||||
class DatabaseCon;
|
||||
|
||||
/**
|
||||
@@ -294,8 +394,51 @@ private:
|
||||
|
||||
std::atomic<std::uint32_t> seq_{0};
|
||||
|
||||
/**
|
||||
* Master keys of cached manifests for validators this node does not list.
|
||||
*
|
||||
* One entry per capped key in `map_`; its size enforces the cap below.
|
||||
* A key is added when first cached under `Capped` and removed when it
|
||||
* becomes listed (see `promoteToTrusted`) or an `Uncapped` update arrives,
|
||||
* never re-added on de-listing. Uncapped keys are not tracked here.
|
||||
*/
|
||||
hash_set<PublicKey> untrustedKeys_;
|
||||
|
||||
/**
|
||||
* Maximum number of untrusted master keys kept in the cache.
|
||||
*
|
||||
* Once reached, a manifest for a brand-new unlisted key is rejected. Set
|
||||
* from the config, defaulting to @ref kMaxUntrustedCount.
|
||||
*/
|
||||
std::size_t const maxUntrustedCount_;
|
||||
|
||||
/**
|
||||
* Running count of manifests rejected because the untrusted cap was full.
|
||||
*
|
||||
* Drives throttled logging (see `kUntrustedRejectCount`). Atomic because
|
||||
* `applyManifest` may run concurrently.
|
||||
*/
|
||||
std::atomic<std::uint64_t> untrustedRejectCount_{0};
|
||||
|
||||
/**
|
||||
* Number of cap rejections between summary warnings.
|
||||
*
|
||||
* @see untrustedRejectCount_
|
||||
*/
|
||||
static constexpr std::uint64_t kUntrustedRejectCount = 10000;
|
||||
|
||||
public:
|
||||
explicit ManifestCache(beast::Journal j = beast::Journal(beast::Journal::getNullSink())) : j_(j)
|
||||
/**
|
||||
* @param j Journal for logging.
|
||||
*
|
||||
* @param maxUntrustedCount Untrusted master keys to keep. Pass the
|
||||
* configured value; defaults to @ref kMaxUntrustedCount. Taken as a
|
||||
* parameter because this module cannot depend on the config.
|
||||
*/
|
||||
explicit ManifestCache(
|
||||
beast::Journal j = beast::Journal(beast::Journal::getNullSink()),
|
||||
std::size_t maxUntrustedCount = kMaxUntrustedCount)
|
||||
: j_(j), maxUntrustedCount_(maxUntrustedCount)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -378,17 +521,44 @@ public:
|
||||
/**
|
||||
* Add manifest to cache.
|
||||
*
|
||||
* A brand-new unlisted key is rejected once the untrusted cap is full;
|
||||
* updates to a cached key and `Uncapped` manifests bypass the cap. The
|
||||
* caller decides `cap` before calling so the cache lock is not held while
|
||||
* consulting the validator list, which would risk a lock-ordering deadlock.
|
||||
*
|
||||
* @param m Manifest to add
|
||||
*
|
||||
* @return `ManifestDisposition::accepted` if successful, or
|
||||
* `stale` or `invalid` otherwise
|
||||
* @param cap `Uncapped` skips the untrusted cap; use it for keys that are
|
||||
* listed, configured, or loaded from the DB. Note `Uncapped` does not
|
||||
* assert the key is currently trusted (a DB entry may predate a
|
||||
* de-listing). Callers must state this explicitly so a manifest is
|
||||
* never left uncapped by omission.
|
||||
*
|
||||
* @return `Accepted` if stored, `Stale` if superseded, `Invalid`/
|
||||
* `BadEphemeralKey` if malformed, or `UntrustedCapacity` if the
|
||||
* untrusted cap is full.
|
||||
*
|
||||
* @par Thread Safety
|
||||
*
|
||||
* May be called concurrently
|
||||
*/
|
||||
ManifestDisposition
|
||||
applyManifest(Manifest m);
|
||||
applyManifest(Manifest m, ManifestRateLimitCapPolicy cap);
|
||||
|
||||
/**
|
||||
* Stop counting a master key against the untrusted cap.
|
||||
*
|
||||
* Called when a cached untrusted key becomes listed, freeing its slot.
|
||||
* Idempotent and a no-op for keys that were never counted.
|
||||
*
|
||||
* @param pk Master public key that is now listed/trusted
|
||||
*
|
||||
* @par Thread Safety
|
||||
*
|
||||
* May be called concurrently
|
||||
*/
|
||||
void
|
||||
promoteToTrusted(PublicKey const& pk);
|
||||
|
||||
/**
|
||||
* Populate manifest cache with manifests in database and config.
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Concepts.h>
|
||||
#include <xrpl/protocol/Quality.h>
|
||||
#include <xrpl/protocol/Rules.h>
|
||||
#include <xrpl/tx/transactors/dex/AMMContext.h>
|
||||
|
||||
#include <cstdint>
|
||||
@@ -124,17 +123,12 @@ private:
|
||||
generateFibSeqOffer(TAmounts<TIn, TOut> const& balances) const;
|
||||
|
||||
/**
|
||||
* Generate max offer.
|
||||
* If `fixAMMOverflowOffer` is active, the offer is generated as:
|
||||
* Generate max offer. The offer is generated as:
|
||||
* takerGets = 99% * balances.out takerPays = swapOut(takerGets).
|
||||
* Return nullopt if takerGets is 0 or takerGets == balances.out.
|
||||
*
|
||||
* If `fixAMMOverflowOffer` is not active, the offer is generated as:
|
||||
* takerPays = max input amount;
|
||||
* takerGets = swapIn(takerPays).
|
||||
*/
|
||||
[[nodiscard]] std::optional<AMMOffer<TIn, TOut>>
|
||||
maxOffer(TAmounts<TIn, TOut> const& balances, Rules const& rules) const;
|
||||
maxOffer(TAmounts<TIn, TOut> const& balances) const;
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Keylet.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
@@ -16,7 +18,7 @@ namespace xrpl {
|
||||
class SponsorshipSet : public Transactor
|
||||
{
|
||||
public:
|
||||
static constexpr auto kConsequencesFactory = ConsequencesFactoryType::Normal;
|
||||
static constexpr auto kConsequencesFactory = ConsequencesFactoryType::Custom;
|
||||
|
||||
explicit SponsorshipSet(ApplyContext& ctx) : Transactor(ctx)
|
||||
{
|
||||
@@ -47,6 +49,15 @@ public:
|
||||
XRPAmount fee,
|
||||
ReadView const& view,
|
||||
beast::Journal const& j) override;
|
||||
|
||||
private:
|
||||
TER
|
||||
createSponsorship(
|
||||
Keylet const& sponsorshipKeylet,
|
||||
AccountID const& sponsorID,
|
||||
AccountID const& sponseeID,
|
||||
SLE::ref sponsorAccSle,
|
||||
SLE::ref reserveSponsorAccSle);
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -32,7 +32,7 @@ struct MPTCreateArgs
|
||||
std::optional<std::uint16_t> transferFee = std::nullopt;
|
||||
std::optional<Slice> const& metadata{};
|
||||
std::optional<uint256> domainId = std::nullopt;
|
||||
std::optional<std::uint32_t> mutableFlags = std::nullopt;
|
||||
std::optional<std::uint32_t> immutableFlags = std::nullopt;
|
||||
// Set only by callers that issue an MPT representing a wrapped asset
|
||||
// (e.g. VaultCreate's share token). The keylet must point to an
|
||||
// existing MPToken or RippleState owned by `account`. Surfaces on
|
||||
|
||||
@@ -3,12 +3,15 @@
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/core/ServiceRegistry.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
#include <xrpl/tx/ApplyContext.h>
|
||||
#include <xrpl/tx/Transactor.h>
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
|
||||
namespace xrpl {
|
||||
@@ -22,6 +25,37 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
// Maps each MPTokenIssuanceSet set flag(e.g., tfMPTSetCanLock), to the issuance's
|
||||
// corresponding immutable flag (e.g., lsifMPTCanLock) and the target ledger flag (e.g.,
|
||||
// lsfMPTCanLock).
|
||||
struct FlagMapping
|
||||
{
|
||||
std::uint32_t setFlag;
|
||||
std::uint32_t immutableFlag;
|
||||
std::uint32_t ledgerFlag;
|
||||
};
|
||||
|
||||
static constexpr std::array<FlagMapping, 7> flagMapping = {
|
||||
{{.setFlag = tfMPTSetCanLock, .immutableFlag = lsifMPTCanLock, .ledgerFlag = lsfMPTCanLock},
|
||||
{.setFlag = tfMPTSetRequireAuth,
|
||||
.immutableFlag = lsifMPTRequireAuth,
|
||||
.ledgerFlag = lsfMPTRequireAuth},
|
||||
{.setFlag = tfMPTSetCanEscrow,
|
||||
.immutableFlag = lsifMPTCanEscrow,
|
||||
.ledgerFlag = lsfMPTCanEscrow},
|
||||
{.setFlag = tfMPTSetCanTrade,
|
||||
.immutableFlag = lsifMPTCanTrade,
|
||||
.ledgerFlag = lsfMPTCanTrade},
|
||||
{.setFlag = tfMPTSetCanTransfer,
|
||||
.immutableFlag = lsifMPTCanTransfer,
|
||||
.ledgerFlag = lsfMPTCanTransfer},
|
||||
{.setFlag = tfMPTSetCanClawback,
|
||||
.immutableFlag = lsifMPTCanClawback,
|
||||
.ledgerFlag = lsfMPTCanClawback},
|
||||
{.setFlag = tfMPTSetCanHoldConfidentialBalance,
|
||||
.immutableFlag = lsifMPTCanHoldConfidentialBalance,
|
||||
.ledgerFlag = lsfMPTCanHoldConfidentialBalance}}};
|
||||
|
||||
static bool
|
||||
checkExtraFeatures(PreflightContext const& ctx);
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
# Linux Packaging
|
||||
|
||||
This directory contains all files needed to build RPM and Debian packages for `xrpld`.
|
||||
This directory contains all files needed to build RPM and Debian packages for
|
||||
`xrpld`. The packages also ship the `validator-keys` tool, so packaging requires
|
||||
a build configured with `-Dvalidator_keys=ON`.
|
||||
|
||||
## Directory layout
|
||||
|
||||
@@ -46,17 +48,28 @@ To print the full packaging matrix (artifact names and images) for the current
|
||||
Caller workflows (`on-pr.yml`, `on-tag.yml`, `on-trigger.yml`) call
|
||||
`reusable-package.yml`. That workflow generates its own packaging matrix from
|
||||
`package_configs` in `linux.json` (via `generate.py --packaging`) and fans out
|
||||
one job per distro. Each job downloads the pre-built `xrpld` binary artifact and
|
||||
runs in that distro's container, so the package format follows from the
|
||||
container's package manager. The packaging script derives the package version
|
||||
from the downloaded binary's `xrpld --version` output; no CMake configure or
|
||||
build step is needed inside the packaging job.
|
||||
one job per distro. Each job downloads the pre-built `xrpld` and `validator-keys`
|
||||
binary artifacts and runs in that distro's container, so the package format
|
||||
follows from the container's package manager. The packaging script derives the
|
||||
package version from the downloaded binary's `xrpld --version` output; no CMake
|
||||
configure or build step is needed inside the packaging job.
|
||||
|
||||
The binaries come from the `debian` and `rhel` build configurations in
|
||||
`linux.json`'s `configs` section, which pass `-Dvalidator_keys=ON` so that the
|
||||
build job produces `validator-keys` next to `xrpld` and uploads it as the
|
||||
`validator-keys-<config name>` artifact. The packaging entry for a distro names
|
||||
both artifacts (`xrpld_artifact_name` and `validator_keys_artifact_name`), so a
|
||||
packaged configuration must keep `-Dvalidator_keys=ON`.
|
||||
|
||||
`validator-keys` is fetched from an exact commit pinned in
|
||||
[`cmake/XrplValidatorKeys.cmake`](../cmake/XrplValidatorKeys.cmake), so a given
|
||||
`xrpld` version always packages the same tool; bump that commit deliberately.
|
||||
|
||||
### Locally (mirrors CI)
|
||||
|
||||
With an `xrpld` binary already built at `build/xrpld`, run the packaging step
|
||||
inside the same container CI uses. The image tag is derived from `linux.json`
|
||||
so you don't need to hardcode a SHA.
|
||||
With `xrpld` and `validator-keys` binaries already built at `build/xrpld` and
|
||||
`build/validator-keys`, run the packaging step inside the same container CI uses.
|
||||
The image tag is derived from `linux.json` so you don't need to hardcode a SHA.
|
||||
|
||||
```bash
|
||||
# From the repo root. Each distro's container image is the `image` field of its
|
||||
@@ -87,6 +100,7 @@ needed, but the host toolchain replaces the pinned CI image:
|
||||
```bash
|
||||
cmake \
|
||||
-Dxrpld=ON \
|
||||
-Dvalidator_keys=ON \
|
||||
-Dpkg_release=1 \
|
||||
-Dtests=OFF \
|
||||
..
|
||||
@@ -95,9 +109,11 @@ cmake --build . --target package # deb on Debian/Ubuntu, rpm on RHEL
|
||||
```
|
||||
|
||||
The `cmake/XrplPackaging.cmake` module defines the `package` target only if at
|
||||
least one of `rpmbuild` / `dpkg-buildpackage` is present; `build_pkg.sh` then
|
||||
infers the package format from the host's package manager. The packaging script
|
||||
installs to FHS-standard paths (`/usr/bin`, `/etc/xrpld`, etc.) regardless of
|
||||
least one of `rpmbuild` / `dpkg-buildpackage` is present and both the `xrpld` and
|
||||
`validator-keys` targets exist (`-Dxrpld=ON -Dvalidator_keys=ON`); the target
|
||||
builds both binaries before packaging. `build_pkg.sh` then infers the package
|
||||
format from the host's package manager. The packaging script installs to
|
||||
FHS-standard paths (`/usr/bin`, `/etc/xrpld`, etc.) regardless of
|
||||
`CMAKE_INSTALL_PREFIX`.
|
||||
|
||||
The package version is not a CMake input on this path: `build_pkg.sh` derives it
|
||||
@@ -156,13 +172,17 @@ CMake/CI integration. The CI workflow and the CMake `package` target both invoke
|
||||
and lets the script use defaults for the rest.
|
||||
|
||||
It resolves `SRC_DIR` and `BUILD_DIR` to absolute paths, then calls
|
||||
`stage_common()` to copy the binary, config files, and shared support files
|
||||
into the staging area, and invokes the platform build tool.
|
||||
`stage_common()` to copy the `xrpld` and `validator-keys` binaries, config files,
|
||||
and shared support files into the staging area, and invokes the platform build
|
||||
tool. Both binaries must be present in `BUILD_DIR` and must run in the packaging
|
||||
environment; a missing or non-runnable one fails early. That runtime check is
|
||||
what catches a binary still linked against the Nix store's ELF loader (see
|
||||
`patch_nix_binary` in `cmake/PatchNixBinary.cmake`).
|
||||
|
||||
### RPM
|
||||
|
||||
1. Creates the standard `rpmbuild/{BUILD,BUILDROOT,RPMS,SOURCES,SPECS,SRPMS}` tree inside the build directory.
|
||||
2. Copies `xrpld.spec` and all shared source files (binary, configs, service files) into `SOURCES/`.
|
||||
2. Copies `xrpld.spec` and all shared source files (binaries, configs, service files) into `SOURCES/`.
|
||||
3. Runs `rpmbuild -bb`, passing the normalized package metadata version as the
|
||||
`pkg_version` RPM macro and `PKG_RELEASE` as the `pkg_release` RPM macro.
|
||||
The spec uses manual `install` commands to place files, disables `dwz`, and
|
||||
@@ -182,7 +202,8 @@ service restart.
|
||||
### DEB
|
||||
|
||||
1. Creates a staging source tree at `debbuild/source/` inside the build directory.
|
||||
2. Stages the binary, configs, `README.md`, and `LICENSE.md`.
|
||||
2. Stages the binaries, configs, `README.md`, `LICENSE.md`, and
|
||||
`validator-keys-LICENSE`.
|
||||
3. Copies `package/debian/` control files into `debbuild/source/debian/`.
|
||||
4. Copies shared service/sysusers/tmpfiles into `debian/` where `dh_installsystemd`, `dh_installsysusers`, and `dh_installtmpfiles` pick them up automatically.
|
||||
5. Generates a minimal `debian/changelog` using `${pkg_version}-${PKG_RELEASE}`,
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Build an RPM or Debian package from a pre-built xrpld binary.
|
||||
# Build an RPM or Debian package from the pre-built xrpld and validator-keys
|
||||
# binaries.
|
||||
#
|
||||
# Flags override env vars; env vars override defaults.
|
||||
|
||||
@@ -11,7 +12,9 @@ Usage: build_pkg.sh [options]
|
||||
|
||||
Options (each can also be set via the env var shown):
|
||||
--src-dir DIR repo root [SRC_DIR; default: ${PWD}]
|
||||
--build-dir DIR directory holding xrpld [BUILD_DIR; default: ${PWD}/build]
|
||||
--build-dir DIR directory holding the
|
||||
xrpld and validator-keys
|
||||
binaries [BUILD_DIR; default: ${PWD}/build]
|
||||
--pkg-release N package release iteration [PKG_RELEASE; default: 1]
|
||||
--source-date-epoch SECS reproducibility timestamp [SOURCE_DATE_EPOCH; latest git ctime; fallback: current time]
|
||||
-h, --help show this help and exit
|
||||
@@ -69,15 +72,44 @@ SRC_DIR="$(cd "${SRC_DIR:-${PWD}}" && pwd)"
|
||||
BUILD_DIR="${BUILD_DIR:-${PWD}/build}"
|
||||
if [[ ! -d "${BUILD_DIR}" ]]; then
|
||||
echo "build_pkg.sh: build directory not found: ${BUILD_DIR}" >&2
|
||||
echo "Build xrpld before packaging, or set BUILD_DIR to the directory containing xrpld." >&2
|
||||
echo "Build the binaries before packaging, or set BUILD_DIR to the directory containing them." >&2
|
||||
exit 1
|
||||
fi
|
||||
BUILD_DIR="$(cd "${BUILD_DIR}" && pwd)"
|
||||
|
||||
xrpld_binary="${BUILD_DIR}/xrpld"
|
||||
if [[ ! -x "${xrpld_binary}" ]]; then
|
||||
echo "build_pkg.sh: expected executable xrpld binary at ${xrpld_binary}." >&2
|
||||
echo "Build xrpld before packaging, or set BUILD_DIR to the directory containing xrpld." >&2
|
||||
validator_keys_binary="${BUILD_DIR}/validator-keys"
|
||||
|
||||
# Report both binaries at once: they share a single BUILD_DIR, so telling the
|
||||
# reader to point it at one of them in isolation is advice they cannot follow.
|
||||
missing=()
|
||||
[[ -x "${xrpld_binary}" ]] || missing+=(xrpld)
|
||||
[[ -x "${validator_keys_binary}" ]] || missing+=(validator-keys)
|
||||
|
||||
if [[ ${#missing[@]} -gt 0 ]]; then
|
||||
echo "build_pkg.sh: missing or not executable in ${BUILD_DIR}: ${missing[*]}" >&2
|
||||
echo "Both binaries come from a single CMake build directory configured with" >&2
|
||||
echo "-Dxrpld=ON -Dvalidator_keys=ON. Build them, then point BUILD_DIR at that" >&2
|
||||
echo "directory." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Shipping validator-keys means shipping its notice, so treat it as required
|
||||
# rather than letting a package go out without the attribution.
|
||||
validator_keys_license="${BUILD_DIR}/validator-keys-LICENSE"
|
||||
if [[ ! -f "${validator_keys_license}" ]]; then
|
||||
echo "build_pkg.sh: missing ${validator_keys_license}." >&2
|
||||
echo "cmake/XrplValidatorKeys.cmake copies it out of the fetched" >&2
|
||||
echo "validator-keys-tool source, so reconfigure with -Dvalidator_keys=ON." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# The binary must also *run* here. Packaging happens in a vanilla distro
|
||||
# container, so this is what catches a binary still pointing at the Nix store's
|
||||
# ELF loader (see patch_nix_binary in cmake/PatchNixBinary.cmake); xrpld is
|
||||
# covered implicitly by the version query below.
|
||||
if ! "${validator_keys_binary}" --version >/dev/null; then
|
||||
echo "build_pkg.sh: ${validator_keys_binary} exists but does not run here." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -150,7 +182,9 @@ stage_common() {
|
||||
local dest="$1"
|
||||
mkdir -p "${dest}"
|
||||
|
||||
cp "${BUILD_DIR}/xrpld" "${dest}/xrpld"
|
||||
cp "${xrpld_binary}" "${dest}/xrpld"
|
||||
cp "${validator_keys_binary}" "${dest}/validator-keys"
|
||||
cp "${validator_keys_license}" "${dest}/validator-keys-LICENSE"
|
||||
cp "${SRC_DIR}/cfg/xrpld-example.cfg" "${dest}/xrpld.cfg"
|
||||
cp "${SRC_DIR}/cfg/validators-example.txt" "${dest}/validators.txt"
|
||||
cp "${SRC_DIR}/LICENSE.md" "${dest}/LICENSE.md"
|
||||
|
||||
@@ -18,6 +18,8 @@ Depends:
|
||||
${shlibs:Depends},
|
||||
${misc:Depends}
|
||||
Description: XRP Ledger daemon
|
||||
Reference implementation of the XRP Ledger protocol.
|
||||
Participates in the peer-to-peer network, processes transactions,
|
||||
and maintains a local ledger copy.
|
||||
xrpld is the reference implementation of the XRP Ledger protocol. It
|
||||
participates in the peer-to-peer XRP Ledger network, processes
|
||||
transactions, and maintains the ledger database.
|
||||
This package also includes the validator-keys tool for validator key
|
||||
management.
|
||||
|
||||
@@ -4,6 +4,25 @@ Source: https://github.com/XRPLF/rippled
|
||||
|
||||
Files: *
|
||||
Copyright: 2011-present, the XRP Ledger developers
|
||||
License: ISC
|
||||
|
||||
Files: validator-keys
|
||||
Copyright: 2016, Ripple Labs Inc.
|
||||
2011, Arthur Britto, David Schwartz, Jed McCaleb, Vinnie Falco, Bob Way,
|
||||
Eric Lombrozo, Nikolaos D. Bougalis, Howard Hinnant
|
||||
2013, Raw Material Software Ltd.
|
||||
2003-2011, Christopher M. Kohlhoff
|
||||
2009-2010, Satoshi Nakamoto
|
||||
2011, The Bitcoin developers
|
||||
2003-2005, Tom Wu
|
||||
License: ISC
|
||||
Comment: Built from https://github.com/ripple/validator-keys-tool at the commit
|
||||
pinned in cmake/XrplValidatorKeys.cmake. Besides ISC-licensed code it
|
||||
incorporates work under the Boost Software License 1.0 (ASIO), the MIT/X11
|
||||
license (Bitcoin) and Tom Wu's license, whose terms require its notice to be
|
||||
retained intact. The complete upstream notice is therefore shipped verbatim as
|
||||
/usr/share/doc/xrpld/validator-keys-LICENSE.
|
||||
|
||||
License: ISC
|
||||
Permission to use, copy, modify, and distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
|
||||
@@ -18,6 +18,7 @@ override_dh_installsysusers:
|
||||
|
||||
override_dh_install:
|
||||
install -D -m 0755 xrpld debian/xrpld/usr/bin/xrpld
|
||||
install -D -m 0755 validator-keys debian/xrpld/usr/bin/validator-keys
|
||||
install -D -m 0644 xrpld.cfg debian/xrpld/etc/xrpld/xrpld.cfg
|
||||
install -D -m 0644 validators.txt debian/xrpld/etc/xrpld/validators.txt
|
||||
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
README.md
|
||||
validator-keys-LICENSE
|
||||
|
||||
@@ -32,6 +32,8 @@ BuildRequires: systemd-rpm-macros
|
||||
xrpld is the reference implementation of the XRP Ledger protocol. It
|
||||
participates in the peer-to-peer XRP Ledger network, processes
|
||||
transactions, and maintains the ledger database.
|
||||
This package also includes the validator-keys tool for validator key
|
||||
management.
|
||||
|
||||
%prep
|
||||
:
|
||||
@@ -41,6 +43,7 @@ transactions, and maintains the ledger database.
|
||||
|
||||
%install
|
||||
install -Dm0755 %{_sourcedir}/xrpld %{buildroot}%{_bindir}/%{name}
|
||||
install -Dm0755 %{_sourcedir}/validator-keys %{buildroot}%{_bindir}/validator-keys
|
||||
install -Dm0644 %{_sourcedir}/xrpld.cfg %{buildroot}%{_sysconfdir}/%{name}/xrpld.cfg
|
||||
install -Dm0644 %{_sourcedir}/validators.txt %{buildroot}%{_sysconfdir}/%{name}/validators.txt
|
||||
|
||||
@@ -59,6 +62,8 @@ install -Dm0644 %{_sourcedir}/xrpld.logrotate %{buildroot}%{_sysconfdir}/lo
|
||||
# Docs
|
||||
install -Dm0644 %{_sourcedir}/LICENSE.md %{buildroot}%{_docdir}/%{name}/LICENSE.md
|
||||
install -Dm0644 %{_sourcedir}/README.md %{buildroot}%{_docdir}/%{name}/README.md
|
||||
# Upstream notice for the bundled validator-keys tool.
|
||||
install -Dm0644 %{_sourcedir}/validator-keys-LICENSE %{buildroot}%{_docdir}/%{name}/validator-keys-LICENSE
|
||||
|
||||
# Legacy compatibility for pre-FHS package layouts.
|
||||
# TODO: remove after rippled fully deprecated.
|
||||
@@ -80,11 +85,13 @@ systemd-tmpfiles --create %{_tmpfilesdir}/xrpld.conf || :
|
||||
|
||||
%files
|
||||
%license %{_docdir}/%{name}/LICENSE.md
|
||||
%license %{_docdir}/%{name}/validator-keys-LICENSE
|
||||
%doc %{_docdir}/%{name}/README.md
|
||||
|
||||
%dir %{_sysconfdir}/%{name}
|
||||
|
||||
%{_bindir}/%{name}
|
||||
%{_bindir}/validator-keys
|
||||
|
||||
%config(noreplace) %{_sysconfdir}/%{name}/xrpld.cfg
|
||||
%config(noreplace) %{_sysconfdir}/%{name}/validators.txt
|
||||
|
||||
@@ -102,6 +102,10 @@ undefined:nudb
|
||||
# Snappy compression library intentional overflows
|
||||
unsigned-integer-overflow:snappy.cc
|
||||
|
||||
# fast_float parses floats with a SWAR trick (parse_eight_digits_unrolled) that
|
||||
# multiplies eight packed digits modulo 2^64; the wraparound is by design.
|
||||
unsigned-integer-overflow:fast_float
|
||||
|
||||
# Abseil intentional overflows in hashing, RNG and time arithmetic.
|
||||
# Matched at library scope (like boost above): the wraparound is by design
|
||||
# across many absl files (hash mixing, raw_hash_set probing, duration math,
|
||||
@@ -194,7 +198,7 @@ unsigned-integer-overflow:tests/libxrpl/basics/RangeSet.cpp
|
||||
unsigned-integer-overflow:test/app/Batch_test.cpp
|
||||
unsigned-integer-overflow:test/app/ConfidentialTransfer_test.cpp
|
||||
unsigned-integer-overflow:test/app/Invariants_test.cpp
|
||||
unsigned-integer-overflow:test/app/Loan_test.cpp
|
||||
unsigned-integer-overflow:test/app/lending/LoanSecurity_test.cpp
|
||||
unsigned-integer-overflow:test/app/NFToken_test.cpp
|
||||
unsigned-integer-overflow:test/app/OfferMPT_test.cpp
|
||||
unsigned-integer-overflow:test/app/Offer_test.cpp
|
||||
|
||||
@@ -76,24 +76,6 @@ getInverse()
|
||||
return &kTab[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns max chars needed to encode a base64 string
|
||||
*/
|
||||
constexpr std::size_t
|
||||
encodedSize(std::size_t n)
|
||||
{
|
||||
return 4 * ((n + 2) / 3);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns max bytes needed to decode a base64 string
|
||||
*/
|
||||
constexpr std::size_t
|
||||
decodedSize(std::size_t n)
|
||||
{
|
||||
return ((n / 4) * 3) + 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode a series of octets as a padded, base64 string.
|
||||
*
|
||||
|
||||
@@ -3,9 +3,11 @@
|
||||
#include <xrpl/basics/contract.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
|
||||
#include <fast_float/fast_float.h> // IWYU pragma: keep
|
||||
#include <fast_float/parse_number.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <charconv>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <istream>
|
||||
@@ -605,8 +607,14 @@ Reader::decodeNumber(Token& token)
|
||||
bool
|
||||
Reader::decodeDouble(Token& token)
|
||||
{
|
||||
// Sanity check to avoid buffer overflow exploits.
|
||||
if (token.end < token.start)
|
||||
{
|
||||
return addError("Unable to parse token length", token);
|
||||
}
|
||||
|
||||
double value = 0;
|
||||
auto const [ptr, ec] = std::from_chars(token.start, token.end, value);
|
||||
auto const [ptr, ec] = fast_float::from_chars(token.start, token.end, value);
|
||||
|
||||
// Reject anything from_chars could not turn into a finite double:
|
||||
// - ec != std::errc{}: no valid conversion, or an out-of-range magnitude
|
||||
|
||||
@@ -925,7 +925,7 @@ tokenOfferCreateApply(
|
||||
priorBalance < accountReserve(view, acct, j, {.ownerCountDelta = 1}))
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
|
||||
auto const offerID = keylet::nftokenOffer(acctID, seqProxy.value());
|
||||
auto const offerID = keylet::nftokenOffer(acctID, seqProxy);
|
||||
|
||||
// Create the offer:
|
||||
{
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace {
|
||||
//------------------------------------------------------------------------------
|
||||
// clang-format off
|
||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||
char const* const versionString = "3.3.0-rc1"
|
||||
char const* const versionString = "3.4.0-b0"
|
||||
// clang-format on
|
||||
;
|
||||
|
||||
|
||||
@@ -180,26 +180,13 @@ getQuality(uint256 const& uBase)
|
||||
return boost::endian::load_big_u64(uBase.end() - 8);
|
||||
}
|
||||
|
||||
uint256
|
||||
getTicketIndex(AccountID const& account, std::uint32_t ticketSeq)
|
||||
{
|
||||
return indexHash(LedgerNameSpace::Ticket, account, ticketSeq);
|
||||
}
|
||||
|
||||
uint256
|
||||
getTicketIndex(AccountID const& account, SeqProxy ticketSeq)
|
||||
{
|
||||
XRPL_ASSERT(ticketSeq.isTicket(), "xrpl::getTicketIndex : valid input");
|
||||
return getTicketIndex(account, ticketSeq.value());
|
||||
}
|
||||
|
||||
MPTID
|
||||
makeMptID(std::uint32_t sequence, AccountID const& account)
|
||||
makeMptID(std::uint32_t const sequence, AccountID const& account)
|
||||
{
|
||||
MPTID u;
|
||||
sequence = boost::endian::native_to_big(sequence);
|
||||
memcpy(u.data(), &sequence, sizeof(sequence));
|
||||
memcpy(u.data() + sizeof(sequence), account.data(), sizeof(account));
|
||||
auto const bigEndianSequence = boost::endian::native_to_big(sequence);
|
||||
memcpy(u.data(), &bigEndianSequence, sizeof(bigEndianSequence));
|
||||
memcpy(u.data() + sizeof(bigEndianSequence), account.data(), sizeof(account));
|
||||
return u;
|
||||
}
|
||||
|
||||
@@ -286,13 +273,13 @@ trustLine(AccountID const& id0, AccountID const& id1, Currency const& currency)
|
||||
}
|
||||
|
||||
Keylet
|
||||
offer(AccountID const& id, std::uint32_t seq) noexcept
|
||||
offer(AccountID const& id, SeqProxy const& seq) noexcept
|
||||
{
|
||||
return {ltOFFER, indexHash(LedgerNameSpace::Offer, id, seq)};
|
||||
return {ltOFFER, indexHash(LedgerNameSpace::Offer, id, seq.value())};
|
||||
}
|
||||
|
||||
Keylet
|
||||
quality(Keylet const& k, std::uint64_t q) noexcept
|
||||
quality(Keylet const& k, std::uint64_t const q) noexcept
|
||||
{
|
||||
XRPL_ASSERT(k.type == ltDIR_NODE, "xrpl::keylet::quality : valid input type");
|
||||
|
||||
@@ -320,22 +307,17 @@ next(Keylet const& k)
|
||||
}
|
||||
|
||||
Keylet
|
||||
ticket(AccountID const& id, std::uint32_t ticketSeq)
|
||||
ticket(AccountID const& id, SeqProxy const& seq)
|
||||
{
|
||||
return {ltTICKET, getTicketIndex(id, ticketSeq)};
|
||||
}
|
||||
|
||||
Keylet
|
||||
ticket(AccountID const& id, SeqProxy ticketSeq)
|
||||
{
|
||||
return {ltTICKET, getTicketIndex(id, ticketSeq)};
|
||||
XRPL_ASSERT(seq.isTicket(), "xrpl::keylet::ticket : valid input");
|
||||
return {ltTICKET, indexHash(LedgerNameSpace::Ticket, id, seq.value())};
|
||||
}
|
||||
|
||||
// This function is presently static, since it's never accessed from anywhere
|
||||
// else. If we ever support multiple pages of signer lists, this would be the
|
||||
// keylet used to locate them.
|
||||
static Keylet
|
||||
signerList(AccountID const& account, std::uint32_t page) noexcept
|
||||
signerList(AccountID const& account, std::uint32_t const page) noexcept
|
||||
{
|
||||
return {ltSIGNER_LIST, indexHash(LedgerNameSpace::SignerList, account, page)};
|
||||
}
|
||||
@@ -353,9 +335,9 @@ sponsorship(AccountID const& sponsor, AccountID const& sponsee) noexcept
|
||||
}
|
||||
|
||||
Keylet
|
||||
check(AccountID const& id, std::uint32_t seq) noexcept
|
||||
check(AccountID const& id, SeqProxy const& seq) noexcept
|
||||
{
|
||||
return {ltCHECK, indexHash(LedgerNameSpace::Check, id, seq)};
|
||||
return {ltCHECK, indexHash(LedgerNameSpace::Check, id, seq.value())};
|
||||
}
|
||||
|
||||
Keylet
|
||||
@@ -394,7 +376,7 @@ ownerDir(AccountID const& id) noexcept
|
||||
}
|
||||
|
||||
Keylet
|
||||
page(uint256 const& key, std::uint64_t index) noexcept
|
||||
page(uint256 const& key, std::uint64_t const index) noexcept
|
||||
{
|
||||
if (index == 0)
|
||||
return {ltDIR_NODE, key};
|
||||
@@ -403,15 +385,15 @@ page(uint256 const& key, std::uint64_t index) noexcept
|
||||
}
|
||||
|
||||
Keylet
|
||||
escrow(AccountID const& src, std::uint32_t seq) noexcept
|
||||
escrow(AccountID const& src, SeqProxy const& seq) noexcept
|
||||
{
|
||||
return {ltESCROW, indexHash(LedgerNameSpace::Escrow, src, seq)};
|
||||
return {ltESCROW, indexHash(LedgerNameSpace::Escrow, src, seq.value())};
|
||||
}
|
||||
|
||||
Keylet
|
||||
payChannel(AccountID const& src, AccountID const& dst, std::uint32_t seq) noexcept
|
||||
payChannel(AccountID const& src, AccountID const& dst, SeqProxy const& seq) noexcept
|
||||
{
|
||||
return {ltPAYCHAN, indexHash(LedgerNameSpace::XRPPaymentChannel, src, dst, seq)};
|
||||
return {ltPAYCHAN, indexHash(LedgerNameSpace::XRPPaymentChannel, src, dst, seq.value())};
|
||||
}
|
||||
|
||||
Keylet
|
||||
@@ -438,9 +420,9 @@ nftokenPage(Keylet const& k, uint256 const& token)
|
||||
}
|
||||
|
||||
Keylet
|
||||
nftokenOffer(AccountID const& owner, std::uint32_t seq)
|
||||
nftokenOffer(AccountID const& owner, SeqProxy const& seq)
|
||||
{
|
||||
return {ltNFTOKEN_OFFER, indexHash(LedgerNameSpace::NftokenOffer, owner, seq)};
|
||||
return {ltNFTOKEN_OFFER, indexHash(LedgerNameSpace::NftokenOffer, owner, seq.value())};
|
||||
}
|
||||
|
||||
Keylet
|
||||
@@ -512,7 +494,7 @@ bridge(STXChainBridge const& bridge, STXChainBridge::ChainType chainType)
|
||||
}
|
||||
|
||||
Keylet
|
||||
xChainClaimID(STXChainBridge const& bridge, std::uint64_t seq)
|
||||
xChainClaimID(STXChainBridge const& bridge, std::uint64_t const seq)
|
||||
{
|
||||
return {
|
||||
ltXCHAIN_OWNED_CLAIM_ID,
|
||||
@@ -526,7 +508,7 @@ xChainClaimID(STXChainBridge const& bridge, std::uint64_t seq)
|
||||
}
|
||||
|
||||
Keylet
|
||||
xChainCreateAccountClaimID(STXChainBridge const& bridge, std::uint64_t seq)
|
||||
xChainCreateAccountClaimID(STXChainBridge const& bridge, std::uint64_t const seq)
|
||||
{
|
||||
return {
|
||||
ltXCHAIN_OWNED_CREATE_ACCOUNT_CLAIM_ID,
|
||||
@@ -546,17 +528,11 @@ did(AccountID const& account) noexcept
|
||||
}
|
||||
|
||||
Keylet
|
||||
oracle(AccountID const& account, std::uint32_t const& documentID) noexcept
|
||||
oracle(AccountID const& account, std::uint32_t const documentID) noexcept
|
||||
{
|
||||
return {ltORACLE, indexHash(LedgerNameSpace::Oracle, account, documentID)};
|
||||
}
|
||||
|
||||
Keylet
|
||||
mptokenIssuance(std::uint32_t seq, AccountID const& issuer) noexcept
|
||||
{
|
||||
return mptokenIssuance(makeMptID(seq, issuer));
|
||||
}
|
||||
|
||||
Keylet
|
||||
mptokenIssuance(MPTID const& issuanceID) noexcept
|
||||
{
|
||||
@@ -582,27 +558,29 @@ credential(AccountID const& subject, AccountID const& issuer, Slice const& credT
|
||||
}
|
||||
|
||||
Keylet
|
||||
vault(AccountID const& owner, std::uint32_t seq) noexcept
|
||||
vault(AccountID const& owner, SeqProxy const& seq) noexcept
|
||||
{
|
||||
return vault(indexHash(LedgerNameSpace::Vault, owner, seq));
|
||||
return vault(indexHash(LedgerNameSpace::Vault, owner, seq.value()));
|
||||
}
|
||||
|
||||
Keylet
|
||||
loanBroker(AccountID const& owner, std::uint32_t seq) noexcept
|
||||
loanBroker(AccountID const& owner, SeqProxy const& seq) noexcept
|
||||
{
|
||||
return loanBroker(indexHash(LedgerNameSpace::LoanBroker, owner, seq));
|
||||
return loanBroker(indexHash(LedgerNameSpace::LoanBroker, owner, seq.value()));
|
||||
}
|
||||
|
||||
Keylet
|
||||
loan(uint256 const& loanBrokerID, std::uint32_t loanSeq) noexcept
|
||||
loan(uint256 const& loanBrokerID, SeqProxy const& loanSeq) noexcept
|
||||
{
|
||||
return loan(indexHash(LedgerNameSpace::Loan, loanBrokerID, loanSeq));
|
||||
return loan(indexHash(LedgerNameSpace::Loan, loanBrokerID, loanSeq.value()));
|
||||
}
|
||||
|
||||
Keylet
|
||||
permissionedDomain(AccountID const& account, std::uint32_t seq) noexcept
|
||||
permissionedDomain(AccountID const& account, SeqProxy const& seq) noexcept
|
||||
{
|
||||
return {ltPERMISSIONED_DOMAIN, indexHash(LedgerNameSpace::PermissionedDomain, account, seq)};
|
||||
return {
|
||||
ltPERMISSIONED_DOMAIN,
|
||||
indexHash(LedgerNameSpace::PermissionedDomain, account, seq.value())};
|
||||
}
|
||||
|
||||
Keylet
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
#include <xrpl/protocol/Serializer.h>
|
||||
#include <xrpl/protocol/UintTypes.h>
|
||||
|
||||
#include <boost/endian/conversion.hpp>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
@@ -45,6 +47,10 @@ STIssue::STIssue(SerialIter& sit, SField const& name) : STBase{name}
|
||||
{
|
||||
MPTID mptID;
|
||||
std::uint32_t sequence = sit.get32();
|
||||
// MPTID stores the sequence in canonical big-endian bytes. STIssue
|
||||
// ledger bytes are the legacy LE-host encoding, so convert the
|
||||
// native get32() value to LE bytes before copying into the MPTID.
|
||||
sequence = boost::endian::native_to_little(sequence);
|
||||
static_assert(MPTID::size() == sizeof(sequence) + sizeof(currencyOrAccount));
|
||||
memcpy(mptID.data(), &sequence, sizeof(sequence));
|
||||
memcpy(
|
||||
@@ -100,6 +106,10 @@ STIssue::add(Serializer& s) const
|
||||
s.addBitString(noAccount());
|
||||
std::uint32_t sequence = 0;
|
||||
memcpy(&sequence, issue.getMptID().data(), sizeof(sequence));
|
||||
// The MPTID bytes are canonical big-endian. Interpret those bytes
|
||||
// as the legacy LE-host value so add32() writes the preserved
|
||||
// STIssue wire bytes on every host endian.
|
||||
sequence = boost::endian::little_to_native(sequence);
|
||||
s.add32(sequence);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -56,10 +56,15 @@ STObject::STObject(SOTemplate const& type, SField const& name) : STBase(name)
|
||||
set(type);
|
||||
}
|
||||
|
||||
STObject::STObject(SOTemplate const& type, SerialIter& sit, SField const& name) : STBase(name)
|
||||
STObject::STObject(
|
||||
SOTemplate const& type,
|
||||
SerialIter& sit,
|
||||
SField const& name,
|
||||
bool requireCanonicalOrder)
|
||||
: STBase(name)
|
||||
{
|
||||
v_.reserve(type.size());
|
||||
set(sit);
|
||||
set(sit, 0, requireCanonicalOrder);
|
||||
applyTemplate(type); // May throw
|
||||
}
|
||||
|
||||
@@ -208,12 +213,13 @@ STObject::applyTemplateFromSField(SField const& sField)
|
||||
|
||||
// return true = terminated with end-of-object
|
||||
bool
|
||||
STObject::set(SerialIter& sit, int depth)
|
||||
STObject::set(SerialIter& sit, int depth, bool requireCanonicalOrder)
|
||||
{
|
||||
bool reachedEndOfObject = false;
|
||||
|
||||
v_.clear();
|
||||
|
||||
std::optional<int> prevFieldCode;
|
||||
// Consume data in the pipe until we run out or reach the end
|
||||
while (!sit.empty())
|
||||
{
|
||||
@@ -238,7 +244,6 @@ STObject::set(SerialIter& sit, int depth)
|
||||
}
|
||||
|
||||
auto const& fn = SField::getField(type, field);
|
||||
|
||||
if (fn.isInvalid())
|
||||
{
|
||||
JLOG(debugLog().error())
|
||||
@@ -246,6 +251,13 @@ STObject::set(SerialIter& sit, int depth)
|
||||
Throw<std::runtime_error>("Unknown field");
|
||||
}
|
||||
|
||||
if (requireCanonicalOrder && prevFieldCode.has_value() && fn.fieldCodeMem <= *prevFieldCode)
|
||||
{
|
||||
JLOG(debugLog().error()) << "Fields in object are not in canonical order";
|
||||
Throw<std::runtime_error>("Fields in object are not in canonical order");
|
||||
}
|
||||
prevFieldCode = fn.fieldCodeMem;
|
||||
|
||||
// Unflatten the field
|
||||
v_.emplace_back(sit, fn, depth + 1);
|
||||
|
||||
|
||||
@@ -51,6 +51,12 @@ STPathElement::getHash(STPathElement const& element)
|
||||
return (hashAccount ^ hashCurrency ^ hashIssuer);
|
||||
}
|
||||
|
||||
[[nodiscard]] size_t
|
||||
STPathElement::getHash() const
|
||||
{
|
||||
return STPathElement::getHash(*this);
|
||||
}
|
||||
|
||||
STPathSet::STPathSet(SerialIter& sit, SField const& name) : STBase(name)
|
||||
{
|
||||
std::vector<STPathElement> path;
|
||||
@@ -126,21 +132,15 @@ STPathSet::move(std::size_t n, void* buf)
|
||||
bool
|
||||
STPathSet::assembleAdd(STPath const& base, STPathElement const& tail)
|
||||
{ // assemble base+tail and add it to the set if it's not a duplicate
|
||||
value_.push_back(base);
|
||||
STPath combined = base;
|
||||
combined.pushBack(tail);
|
||||
|
||||
auto it = value_.rbegin();
|
||||
|
||||
STPath& newPath = *it;
|
||||
newPath.pushBack(tail);
|
||||
|
||||
while (++it != value_.rend())
|
||||
if (!seenHashes_.insert(combined).second)
|
||||
{
|
||||
if (*it == newPath)
|
||||
{
|
||||
value_.pop_back();
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
value_.push_back(std::move(combined));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -200,22 +200,16 @@ STTx::getSeqProxy() const
|
||||
{
|
||||
std::uint32_t const seq{getFieldU32(sfSequence)};
|
||||
if (seq != 0)
|
||||
return SeqProxy::sequence(seq);
|
||||
return SeqProxy::rawSequence(seq);
|
||||
|
||||
std::optional<std::uint32_t> const ticketSeq{operator[](~sfTicketSequence)};
|
||||
std::optional<std::uint32_t> const ticketSeq{at(~sfTicketSequence)};
|
||||
if (!ticketSeq)
|
||||
{
|
||||
// No TicketSequence specified. Return the Sequence, whatever it is.
|
||||
return SeqProxy::sequence(seq);
|
||||
return SeqProxy::rawSequence(seq);
|
||||
}
|
||||
|
||||
return SeqProxy{SeqProxy::Type::Ticket, *ticketSeq};
|
||||
}
|
||||
|
||||
std::uint32_t
|
||||
STTx::getSeqValue() const
|
||||
{
|
||||
return getSeqProxy().value();
|
||||
return SeqProxy::rawTicket(*ticketSeq);
|
||||
}
|
||||
|
||||
void
|
||||
@@ -459,7 +453,7 @@ STTx::checkBatchSingleSign(STObject const& batchSigner, std::vector<uint256> con
|
||||
{
|
||||
XRPL_ASSERT(getTxnType() == ttBATCH, "STTx::checkBatchSingleSign : batch transaction");
|
||||
Serializer msg;
|
||||
serializeBatch(msg, getAccountID(sfAccount), getSeqValue(), getFlags(), txIds);
|
||||
serializeBatch(msg, getAccountID(sfAccount), getSeqProxy().value(), getFlags(), txIds);
|
||||
finishMultiSigningData(batchSigner.getAccountID(sfAccount), msg);
|
||||
return singleSignHelper(batchSigner, msg.slice());
|
||||
}
|
||||
@@ -553,7 +547,7 @@ STTx::checkBatchMultiSign(
|
||||
// with the stuff that stays constant from signature to signature.
|
||||
auto const batchSignerAccount = batchSigner.getAccountID(sfAccount);
|
||||
Serializer dataStart;
|
||||
serializeBatch(dataStart, getAccountID(sfAccount), getSeqValue(), getFlags(), txIds);
|
||||
serializeBatch(dataStart, getAccountID(sfAccount), getSeqProxy().value(), getFlags(), txIds);
|
||||
dataStart.addBitString(batchSignerAccount);
|
||||
return multiSignHelper(
|
||||
batchSigner,
|
||||
@@ -812,16 +806,19 @@ invalidMPTAmountInTx(STObject const& tx)
|
||||
static bool
|
||||
isBatchRawTransactionOkay(STTx const& tx, std::string& reason)
|
||||
{
|
||||
if (!tx.isFieldPresent(sfRawTransactions))
|
||||
XRPL_ASSERT(
|
||||
tx.getTxnType() == ttBATCH || !tx.isFieldPresent(sfRawTransactions),
|
||||
"xrpl::isBatchRawTransactionOkay : raw transactions only on batch");
|
||||
|
||||
if (tx.getTxnType() != ttBATCH)
|
||||
return true;
|
||||
|
||||
// sfRawTransactions only appears on a Batch. passesLocalChecks runs on
|
||||
// unverified user and peer input, so reject (rather than assert) a non-batch
|
||||
// transaction that carries it.
|
||||
if (tx.getTxnType() != ttBATCH)
|
||||
if (!tx.isFieldPresent(sfRawTransactions))
|
||||
{
|
||||
reason = "Only Batch transactions may contain raw transactions.";
|
||||
// LCOV_EXCL_START
|
||||
reason = "Batch transactions must contain raw transactions.";
|
||||
return false;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
if (tx.isFieldPresent(sfBatchSigners) &&
|
||||
|
||||
@@ -9,6 +9,7 @@ Charge const kFeeRequestNoReply(10, "unsatisfiable request");
|
||||
Charge const kFeeInvalidSignature(2000, "invalid signature");
|
||||
Charge const kFeeUselessData(150, "useless data");
|
||||
Charge const kFeeInvalidData(400, "invalid data");
|
||||
Charge const kFeeMalformedData(2000, "malformed data");
|
||||
|
||||
Charge const kFeeMalformedRpc(100, "malformed RPC");
|
||||
Charge const kFeeReferenceRpc(20, "reference RPC");
|
||||
|
||||
@@ -7,10 +7,12 @@
|
||||
#include <xrpl/protocol/Book.h>
|
||||
#include <xrpl/resource/Consumer.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <exception>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <utility>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
@@ -64,6 +66,9 @@ InfoSub::InfoSub(Source& source, Consumer consumer)
|
||||
|
||||
InfoSub::~InfoSub()
|
||||
{
|
||||
// Stream unsubscribes are O(1): each erases this connection's single seq_
|
||||
// from one stream map, so they are cheap enough to run inline on the
|
||||
// disconnect thread.
|
||||
// Each Source teardown call below acquires a server-side lock and
|
||||
// can throw. Wrap each independent call so partial failure does not
|
||||
// skip the remaining teardown steps.
|
||||
@@ -79,29 +84,48 @@ InfoSub::~InfoSub()
|
||||
safeUnsub(seq_, [&] { source_.unsubPeerStatus(seq_); }, j);
|
||||
safeUnsub(seq_, [&] { source_.unsubConsensus(seq_); }, j);
|
||||
|
||||
// Use the internal unsubscribe so that it won't call
|
||||
// back to us and modify its own parameter
|
||||
if (!realTimeSubscriptions_.empty())
|
||||
{
|
||||
safeUnsub(
|
||||
seq_, [&] { source_.unsubAccountInternal(seq_, realTimeSubscriptions_, true); }, j);
|
||||
}
|
||||
|
||||
if (!normalSubscriptions_.empty())
|
||||
{
|
||||
safeUnsub(
|
||||
seq_, [&] { source_.unsubAccountInternal(seq_, normalSubscriptions_, false); }, j);
|
||||
}
|
||||
|
||||
for (auto const& account : accountHistorySubscriptions_)
|
||||
{
|
||||
safeUnsub(seq_, [&] { source_.unsubAccountHistoryInternal(seq_, account, false); }, 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
|
||||
// O(1) erase and releases it, so even a large set never holds a lock across
|
||||
// the whole loop - a competing book publish can interleave between erases.
|
||||
// The disconnect thread still does O(N) brief acquisitions. Use the internal
|
||||
// variant so it does not write back to bookSubscriptions_ on this
|
||||
// partially-destroyed object.
|
||||
for (auto const& book : bookSubscriptions_)
|
||||
{
|
||||
safeUnsub(seq_, [&] { source_.unsubBookInternal(seq_, book); }, j);
|
||||
}
|
||||
|
||||
// Hand the account sets off (by move) to the Source for a chunked,
|
||||
// off-thread teardown keyed on seq_, instead of erasing them inline here.
|
||||
// This keeps the destructor from holding the account lock across a large
|
||||
// erase loop. The job never references this object, which is being
|
||||
// destroyed.
|
||||
//
|
||||
// Moving the sets without holding lock_ is safe: the destructor runs only
|
||||
// when the last shared_ptr to this InfoSub is released, so by the
|
||||
// shared_ptr contract no other thread holds a reference. Subscription maps
|
||||
// store weak_ptrs, so a concurrent publisher must weak_ptr::lock() first;
|
||||
// that succeeds only while a strong reference exists, which cannot overlap
|
||||
// with destruction. No other thread can observe the moved-from sets.
|
||||
//
|
||||
// Wrapped like the steps above: scheduleAccountCleanup enqueues a JobQueue
|
||||
// task, which allocates and locks and so can throw. A throw out of this
|
||||
// noexcept destructor would terminate the process. Skipping the cleanup on
|
||||
// throw is harmless: the account/rt maps hold weak_ptrs that the next
|
||||
// publish prunes once this InfoSub is gone, and any history paging job
|
||||
// self-terminates when its weak sink can no longer be locked.
|
||||
safeUnsub(
|
||||
seq_,
|
||||
[&] {
|
||||
source_.scheduleAccountCleanup(
|
||||
seq_,
|
||||
std::move(realTimeSubscriptions_),
|
||||
std::move(normalSubscriptions_),
|
||||
std::move(accountHistorySubscriptions_));
|
||||
},
|
||||
j);
|
||||
}
|
||||
|
||||
resource::Consumer&
|
||||
@@ -121,6 +145,53 @@ InfoSub::onSendEmpty()
|
||||
{
|
||||
}
|
||||
|
||||
std::size_t
|
||||
InfoSub::totalSubscriptionCount() const
|
||||
{
|
||||
// Hold lock_ for the whole read so the three 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();
|
||||
}
|
||||
|
||||
bool
|
||||
InfoSub::tryReserveAccountSubscriptions(
|
||||
hash_set<AccountID> const& proposedAccounts,
|
||||
hash_set<AccountID> const& normalAccounts,
|
||||
std::size_t cap)
|
||||
{
|
||||
// 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 = [](hash_set<AccountID> const& requested,
|
||||
hash_set<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))
|
||||
return false;
|
||||
|
||||
realTimeSubscriptions_.insert(proposedAccounts.begin(), proposedAccounts.end());
|
||||
normalSubscriptions_.insert(normalAccounts.begin(), normalAccounts.end());
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
InfoSub::insertSubAccountInfo(AccountID const& account, bool rt)
|
||||
{
|
||||
@@ -165,6 +236,13 @@ InfoSub::deleteSubAccountHistory(AccountID const& account)
|
||||
accountHistorySubscriptions_.erase(account);
|
||||
}
|
||||
|
||||
bool
|
||||
InfoSub::hasAccountHistorySubscription(AccountID const& account) const
|
||||
{
|
||||
std::scoped_lock const sl(lock_);
|
||||
return accountHistorySubscriptions_.contains(account);
|
||||
}
|
||||
|
||||
void
|
||||
InfoSub::insertBookSubscription(Book const& book)
|
||||
{
|
||||
|
||||
@@ -62,6 +62,11 @@ deserializeManifest(Slice s, beast::Journal journal)
|
||||
if (s.empty())
|
||||
return std::nullopt;
|
||||
|
||||
// A valid manifest has a fixed maximum size, so reject anything larger
|
||||
// before parsing it.
|
||||
if (s.size() > kMaxManifestBytes)
|
||||
return std::nullopt;
|
||||
|
||||
static SOTemplate const kManifestFormat{
|
||||
// A manifest must include:
|
||||
// - the master public key
|
||||
@@ -377,16 +382,20 @@ ManifestCache::revoked(PublicKey const& pk) const
|
||||
}
|
||||
|
||||
ManifestDisposition
|
||||
ManifestCache::applyManifest(Manifest m)
|
||||
ManifestCache::applyManifest(Manifest m, ManifestRateLimitCapPolicy const cap)
|
||||
{
|
||||
bool const uncapped = cap == ManifestRateLimitCapPolicy::Uncapped;
|
||||
|
||||
// The signature is checked only on the first `prewriteCheck` run (under the
|
||||
// read lock). It is expensive, so `checkSignature` is cleared the first
|
||||
// time it is read; the second run (under the write lock) skips it.
|
||||
bool checkSignature = true;
|
||||
|
||||
// Check the manifest against the conditions that do not require a
|
||||
// `unique_lock` (write lock) on the `mutex_`. Since the signature can be
|
||||
// relatively expensive, the `checkSignature` parameter determines if the
|
||||
// signature should be checked. Since `prewriteCheck` is run twice (see
|
||||
// comment below), `checkSignature` only needs to be set to true on the
|
||||
// first run.
|
||||
auto prewriteCheck = [this, &m](auto const& iter, bool checkSignature, auto const& lock)
|
||||
-> std::optional<ManifestDisposition> {
|
||||
// `unique_lock` (write lock) on the `mutex_`.
|
||||
auto prewriteCheck = [this, &m, &checkSignature](
|
||||
auto const& iter,
|
||||
auto const& lock) -> std::optional<ManifestDisposition> {
|
||||
XRPL_ASSERT(lock.owns_lock(), "xrpl::ManifestCache::applyManifest::prewriteCheck : locked");
|
||||
(void)lock; // not used. parameter is present to ensure the mutex is
|
||||
// locked when the lambda is called.
|
||||
@@ -401,11 +410,15 @@ ManifestCache::applyManifest(Manifest m)
|
||||
return ManifestDisposition::Stale;
|
||||
}
|
||||
|
||||
if (checkSignature && !m.verify())
|
||||
if (checkSignature)
|
||||
{
|
||||
if (auto stream = j_.warn())
|
||||
logMftAct(stream, "Invalid", m.masterKey, m.sequence);
|
||||
return ManifestDisposition::Invalid;
|
||||
checkSignature = false;
|
||||
if (!m.verify())
|
||||
{
|
||||
if (auto stream = j_.warn())
|
||||
logMftAct(stream, "Invalid", m.masterKey, m.sequence);
|
||||
return ManifestDisposition::Invalid;
|
||||
}
|
||||
}
|
||||
|
||||
// If the master key associated with a manifest is or might be
|
||||
@@ -465,14 +478,51 @@ ManifestCache::applyManifest(Manifest m)
|
||||
return std::nullopt;
|
||||
};
|
||||
|
||||
// Reject a brand-new manifest for an unlisted key once the untrusted cap
|
||||
// is full. Updates to a cached key and uncapped manifests always pass.
|
||||
// Called under both the read and write lock, since the cap can be reached
|
||||
// between the two. The lock param enforces that.
|
||||
auto atUntrustedCap = [this, &m, uncapped](auto const& iter, auto const& lock) {
|
||||
XRPL_ASSERT(
|
||||
lock.owns_lock(), "xrpl::ManifestCache::applyManifest::atUntrustedCap : locked");
|
||||
(void)lock; // not used. parameter is present to ensure the mutex is
|
||||
// locked when the lambda is called.
|
||||
if (iter == map_.end() && !uncapped && untrustedKeys_.size() >= maxUntrustedCount_)
|
||||
{
|
||||
// Log each rejection at debug, but warn only once per interval so a
|
||||
// flood does not fill the log.
|
||||
if (auto stream = j_.debug())
|
||||
logMftAct(stream, "UntrustedCapacity", m.masterKey, m.sequence);
|
||||
if (auto const n = untrustedRejectCount_.fetch_add(1) + 1;
|
||||
n % kUntrustedRejectCount == 0)
|
||||
{
|
||||
JLOG(j_.warn()) << "Untrusted manifest cap reached; " << n
|
||||
<< " manifests rejected so far";
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
{
|
||||
std::shared_lock const sl{mutex_};
|
||||
if (auto d = prewriteCheck(map_.find(m.masterKey), /*checkSig*/ true, sl))
|
||||
auto const iter = map_.find(m.masterKey);
|
||||
|
||||
if (atUntrustedCap(iter, sl))
|
||||
return ManifestDisposition::UntrustedCapacity;
|
||||
|
||||
if (auto d = prewriteCheck(iter, sl); d.has_value())
|
||||
return *d;
|
||||
}
|
||||
|
||||
std::unique_lock const sl{mutex_};
|
||||
auto const iter = map_.find(m.masterKey);
|
||||
|
||||
// Re-check the cap under the write lock: the cache may have grown while the
|
||||
// read lock above was released.
|
||||
if (atUntrustedCap(iter, sl))
|
||||
return ManifestDisposition::UntrustedCapacity;
|
||||
|
||||
// Since we released the previously held read lock, it's possible that the
|
||||
// collections have been written to. This means we need to run
|
||||
// `prewriteCheck` again. This re-does work, but `prewriteCheck` is
|
||||
@@ -482,7 +532,7 @@ ManifestCache::applyManifest(Manifest m)
|
||||
// doesn't need to happen again (signature checks are somewhat expensive).
|
||||
// Note: It's a mistake to use an upgradable lock. This is a recipe for
|
||||
// deadlock.
|
||||
if (auto d = prewriteCheck(iter, /*checkSig*/ false, sl))
|
||||
if (auto d = prewriteCheck(iter, sl); d.has_value())
|
||||
return *d;
|
||||
|
||||
bool const revoked = m.revoked();
|
||||
@@ -501,6 +551,12 @@ ManifestCache::applyManifest(Manifest m)
|
||||
}
|
||||
|
||||
auto masterKey = m.masterKey;
|
||||
|
||||
// Count this key against the untrusted cap. Uncapped keys (listed,
|
||||
// configured, or DB-loaded) are not tracked.
|
||||
if (!uncapped)
|
||||
untrustedKeys_.insert(masterKey);
|
||||
|
||||
map_.emplace(std::move(masterKey), std::move(m));
|
||||
|
||||
// Something has changed. Keep track of it.
|
||||
@@ -514,6 +570,11 @@ ManifestCache::applyManifest(Manifest m)
|
||||
if (auto stream = j_.info())
|
||||
logMftAct(stream, "AcceptedUpdate", m.masterKey, m.sequence, iter->second.sequence);
|
||||
|
||||
// If this key was counted against the cap but now arrives uncapped, free
|
||||
// its slot without waiting for promoteToTrusted.
|
||||
if (uncapped)
|
||||
untrustedKeys_.erase(m.masterKey);
|
||||
|
||||
signingToMasterKeys_.erase(
|
||||
*iter->second.signingKey); // NOLINT(bugprone-unchecked-optional-access) prewriteCheck
|
||||
// ensures old manifest is not revoked
|
||||
@@ -521,8 +582,8 @@ ManifestCache::applyManifest(Manifest m)
|
||||
if (!revoked)
|
||||
{
|
||||
signingToMasterKeys_.emplace(
|
||||
*m.signingKey, m.masterKey); // NOLINT(bugprone-unchecked-optional-access) non-revoked
|
||||
// manifest always has signingKey
|
||||
*m.signingKey, m.masterKey); // NOLINT(bugprone-unchecked-optional-access)
|
||||
// non-revoked manifest always has signingKey
|
||||
}
|
||||
|
||||
iter->second = std::move(m);
|
||||
@@ -533,6 +594,16 @@ ManifestCache::applyManifest(Manifest m)
|
||||
return ManifestDisposition::Accepted;
|
||||
}
|
||||
|
||||
void
|
||||
ManifestCache::promoteToTrusted(PublicKey const& pk)
|
||||
{
|
||||
// Frees the key's untrusted slot; a no-op (and idempotent) if the key was
|
||||
// never counted. Not re-added on de-listing, so list/de-list cannot grow
|
||||
// the count.
|
||||
std::unique_lock const sl{mutex_};
|
||||
untrustedKeys_.erase(pk);
|
||||
}
|
||||
|
||||
void
|
||||
ManifestCache::load(DatabaseCon& dbCon, std::string const& dbTable)
|
||||
{
|
||||
@@ -563,7 +634,8 @@ ManifestCache::load(
|
||||
JLOG(j_.warn()) << "Configured manifest revokes public key";
|
||||
}
|
||||
|
||||
if (applyManifest(std::move(*mo)) == ManifestDisposition::Invalid)
|
||||
if (applyManifest(std::move(*mo), ManifestRateLimitCapPolicy::Uncapped) ==
|
||||
ManifestDisposition::Invalid)
|
||||
{
|
||||
JLOG(j_.error()) << "Manifest in config was rejected";
|
||||
return false;
|
||||
@@ -585,7 +657,9 @@ ManifestCache::load(
|
||||
|
||||
auto mo = deserializeManifest(base64Decode(revocationStr));
|
||||
|
||||
if (!mo || !mo->revoked() || applyManifest(std::move(*mo)) == ManifestDisposition::Invalid)
|
||||
if (!mo || !mo->revoked() ||
|
||||
applyManifest(std::move(*mo), ManifestRateLimitCapPolicy::Uncapped) ==
|
||||
ManifestDisposition::Invalid)
|
||||
{
|
||||
JLOG(j_.error()) << "Invalid validator key revocation in config";
|
||||
return false;
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
#include <soci/use.h>
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
@@ -77,7 +78,9 @@ getManifests(
|
||||
continue;
|
||||
}
|
||||
|
||||
cache.applyManifest(std::move(*mo));
|
||||
// Only trusted manifests are persisted (see saveManifests), so
|
||||
// anything loaded from the DB bypasses the untrusted cap.
|
||||
cache.applyManifest(std::move(*mo), ManifestRateLimitCapPolicy::Uncapped);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -107,19 +110,27 @@ saveManifests(
|
||||
{
|
||||
soci::transaction tr(session);
|
||||
session << "DELETE FROM " << dbTable;
|
||||
// Count skipped untrusted manifests and log one summary afterwards, since
|
||||
// the cache can hold many and per-entry logging would flood at shutdown.
|
||||
std::size_t skipped = 0;
|
||||
for (auto const& v : map)
|
||||
{
|
||||
// Save all revocation manifests,
|
||||
// but only save trusted non-revocation manifests.
|
||||
if (!v.second.revoked() && !isTrusted(v.second.masterKey))
|
||||
// Persist only trusted keys. Untrusted gossip is left out so a flood
|
||||
// cannot survive a restart on disk.
|
||||
if (!isTrusted(v.second.masterKey))
|
||||
{
|
||||
JLOG(j.info()) << "Untrusted manifest in cache not saved to db";
|
||||
++skipped;
|
||||
continue;
|
||||
}
|
||||
|
||||
saveManifest(session, dbTable, v.second.serialized);
|
||||
}
|
||||
tr.commit();
|
||||
|
||||
if (skipped != 0)
|
||||
{
|
||||
JLOG(j.info()) << skipped << " untrusted manifest(s) in cache not saved to db";
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
@@ -709,7 +709,7 @@ Transactor::checkSeqProxy(ReadView const& view, STTx const& tx, beast::Journal j
|
||||
}
|
||||
|
||||
SeqProxy const tSeqProx = tx.getSeqProxy();
|
||||
SeqProxy const aSeq = SeqProxy::sequence((*sle)[sfSequence]);
|
||||
SeqProxy const aSeq = SeqProxy::rawSequence((*sle)[sfSequence]);
|
||||
|
||||
if (tSeqProx.isSeq())
|
||||
{
|
||||
@@ -791,16 +791,17 @@ TER
|
||||
Transactor::consumeSeqProxy(SLE::pointer const& sleAccount)
|
||||
{
|
||||
XRPL_ASSERT(sleAccount, "xrpl::Transactor::consumeSeqProxy : non-null account");
|
||||
SeqProxy const seqProx = ctx_.tx.getSeqProxy();
|
||||
if (seqProx.isSeq())
|
||||
SeqProxy const seqProxy = ctx_.tx.getSeqProxy();
|
||||
if (seqProxy.isSeq())
|
||||
{
|
||||
// Note that if this transaction is a TicketCreate, then
|
||||
// the transaction will modify the account root sfSequence
|
||||
// yet again.
|
||||
sleAccount->setFieldU32(sfSequence, seqProx.value() + 1);
|
||||
sleAccount->setFieldU32(sfSequence, seqProxy.value() + 1);
|
||||
return tesSUCCESS;
|
||||
}
|
||||
return ticketDelete(view(), accountID_, getTicketIndex(accountID_, seqProx), j_);
|
||||
auto const keylet = keylet::ticket(accountID_, seqProxy);
|
||||
return ticketDelete(view(), accountID_, keylet.key, j_);
|
||||
}
|
||||
|
||||
// Remove a single Ticket from the ledger.
|
||||
|
||||
@@ -251,7 +251,7 @@ TxConsequences::TxConsequences(NotTEC pfResult)
|
||||
: isBlocker_(false)
|
||||
, fee_(beast::kZero)
|
||||
, potentialSpend_(beast::kZero)
|
||||
, seqProx_(SeqProxy::sequence(0))
|
||||
, seqProx_(SeqProxy::rawSequence(0))
|
||||
, sequencesConsumed_(0)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
|
||||
@@ -465,7 +465,7 @@ ValidVault::finalize(
|
||||
|
||||
if (afterVault.assetsAvailable < kZero)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: assets available must be positive";
|
||||
JLOG(j.fatal()) << "Invariant failed: assets available must not be negative";
|
||||
result = false;
|
||||
}
|
||||
|
||||
@@ -491,13 +491,13 @@ ValidVault::finalize(
|
||||
|
||||
if (afterVault.assetsTotal < kZero)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: assets outstanding must be positive";
|
||||
JLOG(j.fatal()) << "Invariant failed: assets outstanding must not be negative";
|
||||
result = false;
|
||||
}
|
||||
|
||||
if (afterVault.assetsMaximum < kZero)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: assets maximum must be positive";
|
||||
JLOG(j.fatal()) << "Invariant failed: assets maximum must not be negative";
|
||||
result = false;
|
||||
}
|
||||
|
||||
|
||||
@@ -133,17 +133,8 @@ maxOut(T const& out, Asset const& asset)
|
||||
|
||||
template <typename TIn, typename TOut>
|
||||
std::optional<AMMOffer<TIn, TOut>>
|
||||
AMMLiquidity<TIn, TOut>::maxOffer(TAmounts<TIn, TOut> const& balances, Rules const& rules) const
|
||||
AMMLiquidity<TIn, TOut>::maxOffer(TAmounts<TIn, TOut> const& balances) const
|
||||
{
|
||||
if (!rules.enabled(fixAMMOverflowOffer))
|
||||
{
|
||||
return AMMOffer<TIn, TOut>(
|
||||
*this,
|
||||
{maxAmount<TIn>(), swapAssetIn(balances, maxAmount<TIn>(), tradingFee_)},
|
||||
balances,
|
||||
Quality{balances});
|
||||
}
|
||||
|
||||
auto const out = maxOut<TOut>(balances.out, assetOut());
|
||||
if (out <= TOut{0} || out >= balances.out)
|
||||
return std::nullopt;
|
||||
@@ -206,7 +197,7 @@ AMMLiquidity<TIn, TOut>::getOffer(ReadView const& view, std::optional<Quality> c
|
||||
// changed in BookStep per either deliver amount limit, or
|
||||
// sendmax, or available output or input funds. Might return
|
||||
// nullopt if the pool is small.
|
||||
return maxOffer(balances, view.rules());
|
||||
return maxOffer(balances);
|
||||
}
|
||||
if (auto const amounts =
|
||||
changeSpotPriceQuality(balances, *clobQuality, tradingFee_, view.rules(), j_))
|
||||
@@ -215,7 +206,7 @@ AMMLiquidity<TIn, TOut>::getOffer(ReadView const& view, std::optional<Quality> c
|
||||
}
|
||||
if (view.rules().enabled(fixAMMv1_2))
|
||||
{
|
||||
if (auto const maxAMMOffer = maxOffer(balances, view.rules());
|
||||
if (auto const maxAMMOffer = maxOffer(balances);
|
||||
maxAMMOffer && Quality{maxAMMOffer->amount()} > *clobQuality)
|
||||
return maxAMMOffer;
|
||||
}
|
||||
@@ -223,10 +214,6 @@ AMMLiquidity<TIn, TOut>::getOffer(ReadView const& view, std::optional<Quality> c
|
||||
catch (std::overflow_error const& e)
|
||||
{
|
||||
JLOG(j_.error()) << "AMMLiquidity::getOffer overflow " << e.what();
|
||||
if (!view.rules().enabled(fixAMMOverflowOffer))
|
||||
{
|
||||
return maxOffer(balances, view.rules());
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
@@ -134,11 +134,13 @@ AMMOffer<TIn, TOut>::checkInvariant(TAmounts<TIn, TOut> const& consumed, beast::
|
||||
{
|
||||
if (consumed.in > amounts_.in || consumed.out > amounts_.out)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j.error()) << "AMMOffer::checkInvariant failed: consumed " << to_string(consumed.in)
|
||||
<< " " << to_string(consumed.out) << " amounts " << to_string(amounts_.in)
|
||||
<< " " << to_string(amounts_.out);
|
||||
|
||||
return false;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
Number const product = balances_.in * balances_.out;
|
||||
@@ -149,6 +151,7 @@ AMMOffer<TIn, TOut>::checkInvariant(TAmounts<TIn, TOut> const& consumed, beast::
|
||||
if (newProduct >= product || withinRelativeDistance(product, newProduct, Number{1, -7}))
|
||||
return true;
|
||||
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j.error()) << "AMMOffer::checkInvariant failed: balances " << to_string(balances_.in)
|
||||
<< " " << to_string(balances_.out) << " new balances "
|
||||
<< to_string(newBalances.in) << " " << to_string(newBalances.out)
|
||||
@@ -156,6 +159,7 @@ AMMOffer<TIn, TOut>::checkInvariant(TAmounts<TIn, TOut> const& consumed, beast::
|
||||
<< (product != Number{0} ? to_string((product - newProduct) / product)
|
||||
: "undefined");
|
||||
return false;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
template class AMMOffer<IOUAmount, IOUAmount>;
|
||||
|
||||
@@ -865,12 +865,9 @@ BookStep<TIn, TOut, TDerived>::consumeOffer(
|
||||
{
|
||||
if (!offer.checkInvariant(ofrAmt, j_))
|
||||
{
|
||||
// purposely written as separate if statements so we get logging even
|
||||
// when the amendment isn't active.
|
||||
if (sb.rules().enabled(fixAMMOverflowOffer))
|
||||
{
|
||||
Throw<FlowException>(tecINVARIANT_FAILED, "AMM pool product invariant failed.");
|
||||
}
|
||||
// LCOV_EXCL_START
|
||||
Throw<FlowException>(tecINVARIANT_FAILED, "AMM pool product invariant failed.");
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
// The offer owner gets the ofrAmt. The difference between ofrAmt and
|
||||
|
||||
@@ -241,6 +241,8 @@ AccountDelete::preclaim(PreclaimContext const& ctx)
|
||||
if (!ctx.tx.isFieldPresent(sfCredentialIDs))
|
||||
{
|
||||
// Check whether the destination account requires deposit authorization.
|
||||
// This also checks if destination is a pseudo-account, since pseudo-accounts have the
|
||||
// lsfDepositAuth flag set by default
|
||||
if (sleDst->isFlag(lsfDepositAuth))
|
||||
{
|
||||
if (!ctx.view.exists(keylet::depositPreauth(dst, account)))
|
||||
|
||||
@@ -19,8 +19,9 @@
|
||||
#include <xrpl/protocol/Issue.h>
|
||||
#include <xrpl/protocol/Keylet.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
#include <xrpl/protocol/MPTAmount.h>
|
||||
#include <xrpl/protocol/MPTIssue.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
#include <xrpl/protocol/Quality.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
@@ -376,18 +377,29 @@ CheckCash::doApply()
|
||||
else
|
||||
{
|
||||
// Note that for DeliverMin we don't know exactly how much
|
||||
// currency we want flow to deliver. We can't ask for the
|
||||
// maximum possible currency because there might be a gateway
|
||||
// transfer rate to account for. Since the transfer rate cannot
|
||||
// exceed 200%, we use 1/2 maxValue as our limit.
|
||||
// currency we want flow to deliver. For IOUs, use a value
|
||||
// higher than any real delivery as the request. MPTs are
|
||||
// bounded integral amounts, so use the maximum output the check
|
||||
// can actually deliver without exceeding SendMax.
|
||||
auto const maxDeliverMin = [&]() {
|
||||
return optDeliverMin->asset().visit(
|
||||
[&](Issue const&) {
|
||||
return STAmount(
|
||||
optDeliverMin->asset(), STAmount::kMaxValue / 2, STAmount::kMaxOffset);
|
||||
},
|
||||
[&](MPTIssue const&) {
|
||||
return STAmount(optDeliverMin->asset(), kMaxMpTokenAmount / 2);
|
||||
[&](MPTIssue const& issue) {
|
||||
MPTAmount maxDeliver = sendMax.mpt();
|
||||
auto const& issuer = issue.getIssuer();
|
||||
if (srcId != issuer && accountID_ != issuer)
|
||||
{
|
||||
auto const rate = transferRate(psb, issue.getMptID());
|
||||
// Request at most floor(SendMax / rate). The endpoint reverse pass
|
||||
// will quote ceil(output * rate), so this keeps the input
|
||||
// representable and within SendMax.
|
||||
maxDeliver =
|
||||
mulRatio(maxDeliver, QUALITY_ONE, rate.value, /*roundUp*/ false);
|
||||
}
|
||||
return STAmount(maxDeliver, issue);
|
||||
});
|
||||
};
|
||||
STAmount const flowDeliver{
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
#include <xrpl/tx/Transactor.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
@@ -201,14 +200,14 @@ CheckCreate::doApply()
|
||||
return ret;
|
||||
// Note that we use the value from the sequence or ticket as the
|
||||
// Check sequence. For more explanation see comments in SeqProxy.h.
|
||||
std::uint32_t const seq = ctx_.tx.getSeqValue();
|
||||
auto const seq = ctx_.tx.getSeqProxy();
|
||||
Keylet const checkKeylet = keylet::check(accountID_, seq);
|
||||
auto sleCheck = std::make_shared<SLE>(checkKeylet);
|
||||
|
||||
sleCheck->setAccountID(sfAccount, accountID_);
|
||||
AccountID const dstAccountId = ctx_.tx[sfDestination];
|
||||
sleCheck->setAccountID(sfDestination, dstAccountId);
|
||||
sleCheck->setFieldU32(sfSequence, seq);
|
||||
sleCheck->setFieldU32(sfSequence, seq.value());
|
||||
sleCheck->setFieldAmount(sfSendMax, ctx_.tx[sfSendMax]);
|
||||
if (auto const srcTag = ctx_.tx[~sfSourceTag])
|
||||
sleCheck->setFieldU32(sfSourceTag, *srcTag);
|
||||
|
||||
@@ -84,7 +84,9 @@ CredentialCreate::preclaim(PreclaimContext const& ctx)
|
||||
auto const credType(ctx.tx[sfCredentialType]);
|
||||
auto const subject = ctx.tx[sfSubject];
|
||||
|
||||
if (!ctx.view.exists(keylet::account(subject)))
|
||||
auto const subjectSle = ctx.view.read(keylet::account(subject));
|
||||
|
||||
if (!subjectSle)
|
||||
{
|
||||
JLOG(ctx.j.trace()) << "Subject doesn't exist.";
|
||||
return tecNO_TARGET;
|
||||
@@ -96,6 +98,12 @@ CredentialCreate::preclaim(PreclaimContext const& ctx)
|
||||
return tecDUPLICATE;
|
||||
}
|
||||
|
||||
if (ctx.view.rules().enabled(fixCleanup3_3_0) && isPseudoAccount(subjectSle))
|
||||
{
|
||||
JLOG(ctx.j.trace()) << "Subject is a pseudo-account.";
|
||||
return tecPSEUDO_ACCOUNT;
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ DelegateSet::preclaim(PreclaimContext const& ctx)
|
||||
return tecNO_TARGET;
|
||||
|
||||
if (isPseudoAccount(sleAuthorize))
|
||||
return tecNO_PERMISSION;
|
||||
return tecPSEUDO_ACCOUNT;
|
||||
|
||||
// Deleting the delegate object is invalid if it doesn’t exist.
|
||||
if (ctx.tx.getFieldArray(sfPermissions).empty() &&
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
#include <cstdint>
|
||||
#include <exception>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
|
||||
namespace xrpl {
|
||||
@@ -437,11 +438,10 @@ AMMDeposit::applyGuts(Sandbox& sb)
|
||||
|
||||
auto const subTxType = ctx_.tx.getFlags() & tfDepositSubTx;
|
||||
|
||||
auto const [result, newLPTokenBalance] = [&,
|
||||
&amountBalance = amountBalance,
|
||||
&amount2Balance = amount2Balance,
|
||||
&lptAMMBalance =
|
||||
lptAMMBalance]() -> std::pair<TER, STAmount> {
|
||||
auto dispatchToDeposit = [&,
|
||||
&amountBalance = amountBalance,
|
||||
&amount2Balance = amount2Balance,
|
||||
&lptAMMBalance = lptAMMBalance]() -> std::pair<TER, STAmount> {
|
||||
if (subTxType & tfTwoAsset)
|
||||
{
|
||||
return equalDepositLimit(
|
||||
@@ -493,6 +493,28 @@ AMMDeposit::applyGuts(Sandbox& sb)
|
||||
JLOG(j_.error()) << "AMM Deposit: invalid options.";
|
||||
return std::make_pair(tecINTERNAL, STAmount{});
|
||||
// LCOV_EXCL_STOP
|
||||
};
|
||||
|
||||
auto const [result, newLPTokenBalance] = [&]() -> std::pair<TER, STAmount> {
|
||||
try
|
||||
{
|
||||
return dispatchToDeposit();
|
||||
}
|
||||
catch (std::runtime_error const& e)
|
||||
{
|
||||
REACHABLE("xrpl::AMMDeposit::applyGuts : deposit amount out of range reached");
|
||||
// A deposit whose solved amount exceeds the integral asset's range
|
||||
// throws while converting to STAmount: past int64max
|
||||
// Number::operator rep() throws std::overflow_error; above the asset
|
||||
// maximum STAmount::canonicalize throws std::runtime_error. Fail
|
||||
// cleanly with a tec rather than letting it escape doApply as
|
||||
// tefEXCEPTION. Any other exception is left to propagate.
|
||||
// Gated by fixCleanup3_4_0 to preserve the legacy result pre-amendment.
|
||||
if (!sb.rules().enabled(fixCleanup3_4_0))
|
||||
throw; // LCOV_EXCL_LINE - preserve legacy tefEXCEPTION
|
||||
JLOG(j_.error()) << "AMMDeposit: deposit amount out of range " << e.what();
|
||||
return std::make_pair(tecAMM_FAILED, STAmount{});
|
||||
}
|
||||
}();
|
||||
|
||||
if (isTesSuccess(result))
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
#include <cstdint>
|
||||
#include <exception>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
|
||||
@@ -374,11 +375,10 @@ AMMWithdraw::applyGuts(Sandbox& sb)
|
||||
auto const [amountBalance, amount2Balance, lptAMMBalance] = *expected;
|
||||
auto const subTxType = ctx_.tx.getFlags() & tfWithdrawSubTx;
|
||||
|
||||
auto const [result, newLPTokenBalance] = [&,
|
||||
&amountBalance = amountBalance,
|
||||
&amount2Balance = amount2Balance,
|
||||
&lptAMMBalance =
|
||||
lptAMMBalance]() -> std::pair<TER, STAmount> {
|
||||
auto dispatchToWithdraw = [&,
|
||||
&amountBalance = amountBalance,
|
||||
&amount2Balance = amount2Balance,
|
||||
&lptAMMBalance = lptAMMBalance]() -> std::pair<TER, STAmount> {
|
||||
if (subTxType & tfTwoAsset)
|
||||
{
|
||||
return equalWithdrawLimit(
|
||||
@@ -432,6 +432,29 @@ AMMWithdraw::applyGuts(Sandbox& sb)
|
||||
JLOG(j_.error()) << "AMM Withdraw: invalid options.";
|
||||
return std::make_pair(tecINTERNAL, STAmount{});
|
||||
// LCOV_EXCL_STOP
|
||||
};
|
||||
|
||||
auto const [result, newLPTokenBalance] = [&]() -> std::pair<TER, STAmount> {
|
||||
try
|
||||
{
|
||||
return dispatchToWithdraw();
|
||||
}
|
||||
catch (std::runtime_error const& e)
|
||||
{
|
||||
// Defense in-depth for amount overflow/out-of-range: the withdrawal
|
||||
// counterpart of the AMMDeposit guard. Unlike deposit, no known
|
||||
// withdraw path can throw here - preclaim bounds the requested
|
||||
// amounts by the pool balances, and the only historical throw
|
||||
// (denom == 0 in singleWithdrawEPrice) is guarded under
|
||||
// fixCleanup3_3_0. Gated by fixCleanup3_4_0 to preserve the
|
||||
// legacy tefEXCEPTION pre-amendment.
|
||||
if (!sb.rules().enabled(fixCleanup3_4_0))
|
||||
throw;
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j_.error()) << "AMMWithdraw: amount out of range " << e.what();
|
||||
return std::make_pair(tecAMM_FAILED, STAmount{});
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
}();
|
||||
|
||||
if (!isTesSuccess(result))
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/SeqProxy.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
#include <xrpl/tx/Transactor.h>
|
||||
@@ -57,7 +58,8 @@ OfferCancel::doApply()
|
||||
if (!sle)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
if (auto sleOffer = view().peek(keylet::offer(accountID_, offerSequence)))
|
||||
auto const seqProxy = SeqProxy::rawSequence(offerSequence);
|
||||
if (auto sleOffer = view().peek(keylet::offer(accountID_, seqProxy)))
|
||||
{
|
||||
JLOG(j_.debug()) << "Trying to cancel offer #" << offerSequence;
|
||||
return offerDelete(view(), sleOffer, ctx_.registry.get().getJournal("View"));
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
#include <xrpl/protocol/STPathSet.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/SeqProxy.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
#include <xrpl/protocol/UintTypes.h>
|
||||
@@ -634,7 +635,7 @@ OfferCreate::applyGuts(Sandbox& sb, Sandbox& sbCancel)
|
||||
|
||||
// Note that we use the value from the sequence or ticket as the
|
||||
// offer sequence. For more explanation see comments in SeqProxy.h.
|
||||
auto const offerSequence = ctx_.tx.getSeqValue();
|
||||
auto const offerSequence = ctx_.tx.getSeqProxy();
|
||||
|
||||
// This is the original rate of the offer, and is the rate at which
|
||||
// it will be placed, even if crossing offers change the amounts that
|
||||
@@ -648,7 +649,8 @@ OfferCreate::applyGuts(Sandbox& sb, Sandbox& sbCancel)
|
||||
// Process a cancellation request that's passed along with an offer.
|
||||
if (cancelSequence)
|
||||
{
|
||||
auto const sleCancel = sb.peek(keylet::offer(accountID_, *cancelSequence));
|
||||
auto const seqProxy = SeqProxy::rawSequence(*cancelSequence);
|
||||
auto const sleCancel = sb.peek(keylet::offer(accountID_, seqProxy));
|
||||
|
||||
// It's not an error to not find the offer to cancel: it might have
|
||||
// been consumed or removed. If it is found, however, it's an error
|
||||
@@ -933,7 +935,7 @@ OfferCreate::applyGuts(Sandbox& sb, Sandbox& sbCancel)
|
||||
|
||||
auto sleOffer = std::make_shared<SLE>(offerIndex);
|
||||
sleOffer->setAccountID(sfAccount, accountID_);
|
||||
sleOffer->setFieldU32(sfSequence, offerSequence);
|
||||
sleOffer->setFieldU32(sfSequence, offerSequence.value());
|
||||
sleOffer->setFieldH256(sfBookDirectory, dir.key);
|
||||
sleOffer->setFieldAmount(sfTakerPays, saTakerPays);
|
||||
sleOffer->setFieldAmount(sfTakerGets, saTakerGets);
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/SeqProxy.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
#include <xrpl/tx/Transactor.h>
|
||||
@@ -92,7 +93,8 @@ EscrowCancel::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
if (ctx.view.rules().enabled(featureTokenEscrow))
|
||||
{
|
||||
auto const k = keylet::escrow(ctx.tx[sfOwner], ctx.tx[sfOfferSequence]);
|
||||
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)
|
||||
return tecNO_TARGET;
|
||||
@@ -117,7 +119,8 @@ EscrowCancel::preclaim(PreclaimContext const& ctx)
|
||||
TER
|
||||
EscrowCancel::doApply()
|
||||
{
|
||||
auto const k = keylet::escrow(ctx_.tx[sfOwner], ctx_.tx[sfOfferSequence]);
|
||||
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)
|
||||
{
|
||||
|
||||
@@ -476,7 +476,7 @@ 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.getSeqValue());
|
||||
Keylet const escrowKeylet = keylet::escrow(accountID_, ctx_.tx.getSeqProxy());
|
||||
auto const slep = std::make_shared<SLE>(escrowKeylet);
|
||||
(*slep)[sfAmount] = amount;
|
||||
(*slep)[sfAccount] = accountID_;
|
||||
@@ -489,7 +489,7 @@ EscrowCreate::doApply()
|
||||
|
||||
if (ctx_.view().rules().enabled(fixIncludeKeyletFields))
|
||||
{
|
||||
(*slep)[sfSequence] = ctx_.tx.getSeqValue();
|
||||
(*slep)[sfSequence] = ctx_.tx.getSeqProxy().value();
|
||||
}
|
||||
|
||||
if (ctx_.view().rules().enabled(featureTokenEscrow) && !isXRP(amount))
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/SeqProxy.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
#include <xrpl/tx/Transactor.h>
|
||||
@@ -203,7 +204,8 @@ EscrowFinish::preclaim(PreclaimContext const& ctx)
|
||||
|
||||
if (ctx.view.rules().enabled(featureTokenEscrow))
|
||||
{
|
||||
auto const k = keylet::escrow(ctx.tx[sfOwner], ctx.tx[sfOfferSequence]);
|
||||
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)
|
||||
return tecNO_TARGET;
|
||||
@@ -228,7 +230,8 @@ EscrowFinish::preclaim(PreclaimContext const& ctx)
|
||||
TER
|
||||
EscrowFinish::doApply()
|
||||
{
|
||||
auto const k = keylet::escrow(ctx_.tx[sfOwner], ctx_.tx[sfOfferSequence]);
|
||||
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)
|
||||
{
|
||||
|
||||
@@ -218,7 +218,7 @@ LoanBrokerSet::doApply()
|
||||
}
|
||||
auto const vaultPseudoID = sleVault->at(sfAccount);
|
||||
auto const vaultAsset = sleVault->at(sfAsset);
|
||||
auto const sequence = tx.getSeqValue();
|
||||
auto const sequence = tx.getSeqProxy();
|
||||
|
||||
auto owner = view.peek(keylet::account(accountID_));
|
||||
if (!owner)
|
||||
@@ -253,7 +253,7 @@ LoanBrokerSet::doApply()
|
||||
return ter;
|
||||
|
||||
// Initialize data fields:
|
||||
broker->at(sfSequence) = sequence;
|
||||
broker->at(sfSequence) = sequence.value();
|
||||
broker->at(sfVaultID) = vaultID;
|
||||
broker->at(sfOwner) = accountID_;
|
||||
broker->at(sfAccount) = pseudoId;
|
||||
|
||||
@@ -813,7 +813,7 @@ LoanPay::doApply()
|
||||
XRPL_ASSERT_PARTS(
|
||||
vaultBalanceAfter >= beast::kZero && brokerBalanceAfter >= beast::kZero,
|
||||
"xrpl::LoanPay::doApply",
|
||||
"positive vault and broker balances");
|
||||
"non-negative vault and broker balances");
|
||||
XRPL_ASSERT_PARTS(
|
||||
vaultBalanceAfter >= vaultBalanceBefore,
|
||||
"xrpl::LoanPay::doApply",
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include <xrpl/protocol/STObject.h>
|
||||
#include <xrpl/protocol/STTakesAsset.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/SeqProxy.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
#include <xrpl/protocol/Units.h>
|
||||
@@ -594,7 +595,8 @@ LoanSet::doApply()
|
||||
auto loanSequenceProxy = brokerSle->at(sfLoanSequence);
|
||||
|
||||
// Create the loan
|
||||
auto loan = std::make_shared<SLE>(keylet::loan(brokerID, *loanSequenceProxy));
|
||||
auto loan =
|
||||
std::make_shared<SLE>(keylet::loan(brokerID, SeqProxy::rawSequence(*loanSequenceProxy)));
|
||||
|
||||
// Prevent copy/paste errors
|
||||
auto setLoanField = [&loan, &tx](auto const& field, std::uint32_t const defValue = 0) {
|
||||
|
||||
@@ -103,9 +103,16 @@ DepositPreauth::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
// Verify that the Authorize account is present in the ledger.
|
||||
AccountID const auth{ctx.tx[sfAuthorize]};
|
||||
if (!ctx.view.exists(keylet::account(auth)))
|
||||
auto const sleAuth = ctx.view.read(keylet::account(auth));
|
||||
if (!sleAuth)
|
||||
return tecNO_TARGET;
|
||||
|
||||
if (ctx.view.rules().enabled(fixCleanup3_3_0) && isPseudoAccount(sleAuth))
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "Authorized account is a pseudo-account.";
|
||||
return tecPSEUDO_ACCOUNT;
|
||||
}
|
||||
|
||||
// Verify that the Preauth entry they asked to add is not already
|
||||
// in the ledger.
|
||||
if (ctx.view.exists(keylet::depositPreauth(account, auth)))
|
||||
|
||||
@@ -169,7 +169,7 @@ PaymentChannelCreate::doApply()
|
||||
//
|
||||
// Note that we use the value from the sequence or ticket as the
|
||||
// payChan sequence. For more explanation see comments in SeqProxy.h.
|
||||
Keylet const payChanKeylet = keylet::payChannel(account, dst, ctx_.tx.getSeqValue());
|
||||
Keylet const payChanKeylet = keylet::payChannel(account, dst, ctx_.tx.getSeqProxy());
|
||||
auto const slep = std::make_shared<SLE>(payChanKeylet);
|
||||
|
||||
// Funds held in this channel
|
||||
@@ -185,7 +185,7 @@ PaymentChannelCreate::doApply()
|
||||
(*slep)[~sfDestinationTag] = ctx_.tx[~sfDestinationTag];
|
||||
if (ctx_.view().rules().enabled(fixIncludeKeyletFields))
|
||||
{
|
||||
(*slep)[sfSequence] = ctx_.tx.getSeqValue();
|
||||
(*slep)[sfSequence] = ctx_.tx.getSeqProxy().value();
|
||||
}
|
||||
|
||||
ctx_.view().insert(slep);
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
#include <xrpl/protocol/STObject.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/SeqProxy.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
#include <xrpl/tx/Transactor.h>
|
||||
@@ -114,12 +115,13 @@ PermissionedDomainSet::doApply()
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
|
||||
bool const fixEnabled = view().rules().enabled(fixCleanup3_1_3);
|
||||
auto const seq = fixEnabled ? ctx_.tx.getSeqValue() : ctx_.tx.getFieldU32(sfSequence);
|
||||
auto const seq = fixEnabled ? ctx_.tx.getSeqProxy()
|
||||
: SeqProxy::rawSequence(ctx_.tx.getFieldU32(sfSequence));
|
||||
Keylet const pdKeylet = keylet::permissionedDomain(accountID_, seq);
|
||||
auto slePd = std::make_shared<SLE>(pdKeylet);
|
||||
|
||||
slePd->setAccountID(sfOwner, accountID_);
|
||||
slePd->setFieldU32(sfSequence, seq);
|
||||
slePd->setFieldU32(sfSequence, seq.value());
|
||||
slePd->peekFieldArray(sfAcceptedCredentials) = std::move(sortedLE);
|
||||
auto const page =
|
||||
view().dirInsert(keylet::ownerDir(accountID_), pdKeylet, describeOwnerDir(accountID_));
|
||||
|
||||
@@ -3,13 +3,16 @@
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/beast/utility/Zero.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/core/ServiceRegistry.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
|
||||
#include <xrpl/ledger/helpers/SponsorHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/Keylet.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
@@ -17,36 +20,62 @@
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
#include <xrpl/tx/Transactor.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
// Compute the resulting RemainingOwnerCount using signed 64-bit arithmetic to
|
||||
// avoid unsigned wraparound. A missing SLE (object creation) or absent field
|
||||
// counts as zero. Callers handle the out-of-range results: a negative value is
|
||||
// clamped to zero (field absent) and overflow is rejected in preclaim.
|
||||
static std::int64_t
|
||||
totalRemainingOwnerCount(
|
||||
SLE::const_ref sponsorshipSle,
|
||||
std::optional<std::int32_t> const& remainingOwnerCountDelta)
|
||||
{
|
||||
std::uint32_t const currentCount =
|
||||
sponsorshipSle ? (*sponsorshipSle)[~sfRemainingOwnerCount].value_or(0u) : 0u;
|
||||
return static_cast<std::int64_t>(currentCount) + remainingOwnerCountDelta.value_or(0);
|
||||
}
|
||||
|
||||
static bool
|
||||
hasSponsorshipBudget(
|
||||
SLE::const_ref sponsorshipSle,
|
||||
std::optional<STAmount> const& feeAmount,
|
||||
std::optional<std::uint32_t> const& remainingOwnerCount)
|
||||
std::optional<STAmount> const& feeAmountDelta,
|
||||
std::optional<std::int32_t> const& remainingOwnerCountDelta)
|
||||
{
|
||||
// A field the transaction omits keeps whatever the existing object holds,
|
||||
// sfFeeAmountDelta and sfRemainingOwnerCountDelta must be non-negative when creating a new
|
||||
// Sponsorship object.
|
||||
if (!sponsorshipSle)
|
||||
{
|
||||
if (feeAmountDelta.has_value() && *feeAmountDelta <= beast::kZero)
|
||||
return false;
|
||||
|
||||
if (remainingOwnerCountDelta.has_value() && *remainingOwnerCountDelta <= 0)
|
||||
return false;
|
||||
}
|
||||
// If the transaction omits a field, it keeps whatever the existing object holds,
|
||||
// so fall back to the current SLE value when the tx does not set it.
|
||||
bool const hasFeeAmount = feeAmount
|
||||
? *feeAmount > beast::kZero
|
||||
: sponsorshipSle && (*sponsorshipSle)[~sfFeeAmount].value_or(STAmount{0}) > beast::kZero;
|
||||
STAmount const currentFee =
|
||||
sponsorshipSle ? (*sponsorshipSle)[~sfFeeAmount].value_or(STAmount{0}) : STAmount{0};
|
||||
STAmount const newFee = currentFee + feeAmountDelta.value_or(STAmount{0});
|
||||
|
||||
bool const hasRemainingOwnerCount = remainingOwnerCount
|
||||
? *remainingOwnerCount > 0
|
||||
: sponsorshipSle && (*sponsorshipSle)[~sfRemainingOwnerCount].value_or(0) > 0;
|
||||
std::int64_t const newCount =
|
||||
totalRemainingOwnerCount(sponsorshipSle, remainingOwnerCountDelta);
|
||||
|
||||
return hasFeeAmount || hasRemainingOwnerCount;
|
||||
return newFee > beast::kZero || newCount > 0;
|
||||
}
|
||||
|
||||
TxConsequences
|
||||
SponsorshipSet::makeTxConsequences(PreflightContext const& ctx)
|
||||
{
|
||||
auto const feeAmount = ctx.tx[~sfFeeAmount];
|
||||
return TxConsequences{ctx.tx, feeAmount.has_value() ? feeAmount->xrp() : beast::kZero};
|
||||
auto const feeAmount = ctx.tx[~sfFeeAmountDelta];
|
||||
auto const feeAmountDelta = std::max(STAmount{0}, feeAmount.value_or(STAmount{0}));
|
||||
return TxConsequences{ctx.tx, feeAmountDelta.xrp()};
|
||||
}
|
||||
|
||||
std::uint32_t
|
||||
@@ -90,8 +119,8 @@ SponsorshipSet::preflight(PreflightContext const& ctx)
|
||||
return temINVALID_FLAG;
|
||||
|
||||
// Transactions deleting `Sponsorship` cannot include modification fields.
|
||||
if (ctx.tx.isFieldPresent(sfFeeAmount) || ctx.tx.isFieldPresent(sfRemainingOwnerCount) ||
|
||||
ctx.tx.isFieldPresent(sfMaxFee))
|
||||
if (ctx.tx.isFieldPresent(sfFeeAmountDelta) ||
|
||||
ctx.tx.isFieldPresent(sfRemainingOwnerCountDelta) || ctx.tx.isFieldPresent(sfMaxFee))
|
||||
return temMALFORMED;
|
||||
}
|
||||
else
|
||||
@@ -101,27 +130,26 @@ SponsorshipSet::preflight(PreflightContext const& ctx)
|
||||
if (account != sponsorID)
|
||||
return temMALFORMED;
|
||||
|
||||
// FeeAmount and MaxFee must be non-negative XRP amounts when present.
|
||||
auto const checkOptionalAmountField = [&](SField const& field) -> NotTEC {
|
||||
if (!ctx.tx.isFieldPresent(field))
|
||||
return tesSUCCESS;
|
||||
// FeeAmountDelta must be a non-zero XRP amount when present.
|
||||
if (auto const feeAmt = ctx.tx[~sfFeeAmountDelta];
|
||||
feeAmt && (!isXRP(*feeAmt) || *feeAmt == beast::kZero))
|
||||
return temBAD_AMOUNT;
|
||||
|
||||
auto const amount = ctx.tx.getFieldAmount(field);
|
||||
// MaxFee must be a non-negative XRP amount when present.
|
||||
if (auto const maxFee = ctx.tx[~sfMaxFee];
|
||||
maxFee && (!isXRP(*maxFee) || *maxFee < beast::kZero))
|
||||
return temBAD_AMOUNT;
|
||||
|
||||
if (!isXRP(amount))
|
||||
return temBAD_AMOUNT;
|
||||
// RemainingOwnerCountDelta must be a non-zero integer when present.
|
||||
if (auto const remainingOwnerCountDelta = ctx.tx[~sfRemainingOwnerCountDelta];
|
||||
remainingOwnerCountDelta && *remainingOwnerCountDelta == 0)
|
||||
return temINVALID;
|
||||
|
||||
if (amount.xrp() < beast::kZero)
|
||||
return temBAD_AMOUNT;
|
||||
|
||||
return tesSUCCESS;
|
||||
};
|
||||
|
||||
if (auto const ret = checkOptionalAmountField(sfFeeAmount); !isTesSuccess(ret))
|
||||
return ret;
|
||||
|
||||
if (auto const ret = checkOptionalAmountField(sfMaxFee); !isTesSuccess(ret))
|
||||
return ret;
|
||||
// nothing specified in the tx
|
||||
if (!ctx.tx.isFieldPresent(sfRemainingOwnerCountDelta) &&
|
||||
!ctx.tx.isFieldPresent(sfFeeAmountDelta) && !ctx.tx.isFieldPresent(sfMaxFee) &&
|
||||
((ctx.tx.getFlags() & tfUniversalMask) == 0))
|
||||
return temREDUNDANT;
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
@@ -146,7 +174,7 @@ SponsorshipSet::preclaim(PreclaimContext const& ctx)
|
||||
|
||||
// Pseudo-accounts cannot participate in sponsorship.
|
||||
if (isPseudoAccount(sponsorAccSle) || isPseudoAccount(sponseeSle))
|
||||
return tecNO_PERMISSION;
|
||||
return tecPSEUDO_ACCOUNT;
|
||||
|
||||
auto const sponsorshipSle = ctx.view.read(keylet::sponsorship(sponsorID, sponseeID));
|
||||
|
||||
@@ -154,12 +182,21 @@ SponsorshipSet::preclaim(PreclaimContext const& ctx)
|
||||
if (ctx.tx.isFlag(tfDeleteObject) && !sponsorshipSle)
|
||||
return tecNO_ENTRY;
|
||||
|
||||
// Reject creating or updating a Sponsorship that would be left with no
|
||||
// budget (neither a positive FeeAmount nor a positive RemainingOwnerCount).
|
||||
// Such an object is unusable yet still consumes the sponsor's reserve.
|
||||
if (!ctx.tx.isFlag(tfDeleteObject) &&
|
||||
!hasSponsorshipBudget(sponsorshipSle, ctx.tx[~sfFeeAmount], ctx.tx[~sfRemainingOwnerCount]))
|
||||
return tecNO_PERMISSION;
|
||||
if (!ctx.tx.isFlag(tfDeleteObject))
|
||||
{
|
||||
// Reject if applying the delta would overflow uint32_t. A negative delta
|
||||
// that underflows is clamped to zero (field absent) rather than erroring.
|
||||
if (totalRemainingOwnerCount(sponsorshipSle, ctx.tx[~sfRemainingOwnerCountDelta]) >
|
||||
static_cast<std::int64_t>(std::numeric_limits<std::uint32_t>::max()))
|
||||
return tecLIMIT_EXCEEDED;
|
||||
|
||||
// Reject creating or updating a Sponsorship that would be left with no
|
||||
// budget (neither a positive FeeAmount nor a positive RemainingOwnerCount).
|
||||
// Such an object is unusable yet still consumes the sponsor's reserve.
|
||||
if (!hasSponsorshipBudget(
|
||||
sponsorshipSle, ctx.tx[~sfFeeAmountDelta], ctx.tx[~sfRemainingOwnerCountDelta]))
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
@@ -208,6 +245,91 @@ deleteSponsorship(ApplyView& view, SLE::ref sle, beast::Journal j)
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
SponsorshipSet::createSponsorship(
|
||||
Keylet const& sponsorshipKeylet,
|
||||
AccountID const& sponsorID,
|
||||
AccountID const& sponseeID,
|
||||
SLE::ref sponsorAccSle,
|
||||
SLE::ref reserveSponsorAccSle)
|
||||
{
|
||||
auto const feeAmountDelta = ctx_.tx[~sfFeeAmountDelta];
|
||||
auto const maxFee = ctx_.tx[~sfMaxFee];
|
||||
auto const remainingOwnerCountDelta = ctx_.tx[~sfRemainingOwnerCountDelta];
|
||||
|
||||
bool const hasPositiveFeeAmount = feeAmountDelta.has_value() && *feeAmountDelta > beast::kZero;
|
||||
|
||||
// Create a new Sponsorship object between the sponsor and sponsee.
|
||||
auto newSle = std::make_shared<SLE>(sponsorshipKeylet);
|
||||
STAmount sponsorBalanceAfterFee = (*sponsorAccSle)[sfBalance];
|
||||
// sfFeeAmountDelta must be positive if the sponsorship object doesn't exist. This is
|
||||
// checked in preclaim.
|
||||
XRPL_ASSERT(
|
||||
!feeAmountDelta.has_value() || *feeAmountDelta > beast::kZero,
|
||||
"xrpl::SponsorshipSet::doApply : new sponsorship has positive fee amount");
|
||||
|
||||
(*newSle)[sfOwner] = sponsorID;
|
||||
(*newSle)[sfSponsee] = sponseeID;
|
||||
if (feeAmountDelta && feeAmountDelta->xrp() > sponsorBalanceAfterFee.xrp())
|
||||
return tecUNFUNDED;
|
||||
|
||||
if (hasPositiveFeeAmount)
|
||||
sponsorBalanceAfterFee -= *feeAmountDelta;
|
||||
|
||||
if (auto const ret = checkReserve(
|
||||
ctx_.getApplyViewContext(),
|
||||
sponsorAccSle,
|
||||
sponsorBalanceAfterFee.xrp(),
|
||||
reserveSponsorAccSle,
|
||||
{.ownerCountDelta = 1},
|
||||
ctx_.journal,
|
||||
tecUNFUNDED);
|
||||
!isTesSuccess(ret))
|
||||
{
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (hasPositiveFeeAmount)
|
||||
{
|
||||
// New object: FeeAmount starts absent, so deduct and record the full amount
|
||||
(*newSle)[sfFeeAmount] = *feeAmountDelta;
|
||||
(*sponsorAccSle)[sfBalance] -= *feeAmountDelta;
|
||||
}
|
||||
|
||||
if (maxFee && *maxFee > beast::kZero)
|
||||
(*newSle)[sfMaxFee] = *maxFee;
|
||||
if (remainingOwnerCountDelta && *remainingOwnerCountDelta > 0)
|
||||
(*newSle)[sfRemainingOwnerCount] = *remainingOwnerCountDelta;
|
||||
|
||||
std::uint32_t flags = 0;
|
||||
if (ctx_.tx.isFlag(tfSponsorshipSetRequireSignForFee))
|
||||
flags |= lsfSponsorshipRequireSignForFee;
|
||||
|
||||
if (ctx_.tx.isFlag(tfSponsorshipSetRequireSignForReserve))
|
||||
flags |= lsfSponsorshipRequireSignForReserve;
|
||||
|
||||
(*newSle)[sfFlags] = flags;
|
||||
|
||||
auto const sponsorPage = view().dirInsert(
|
||||
keylet::ownerDir(sponsorID), sponsorshipKeylet, describeOwnerDir(sponsorID));
|
||||
if (!sponsorPage)
|
||||
return tecDIR_FULL; // LCOV_EXCL_LINE
|
||||
(*newSle)[sfOwnerNode] = *sponsorPage;
|
||||
|
||||
auto const sponseePage = view().dirInsert(
|
||||
keylet::ownerDir(sponseeID), sponsorshipKeylet, describeOwnerDir(sponseeID));
|
||||
if (!sponseePage)
|
||||
return tecDIR_FULL; // LCOV_EXCL_LINE
|
||||
(*newSle)[sfSponseeNode] = *sponseePage;
|
||||
|
||||
// NOLINTNEXTLINE(readability-suspicious-call-argument)
|
||||
increaseOwnerCount(view(), sponsorAccSle, reserveSponsorAccSle, 1, ctx_.journal);
|
||||
addSponsorToLedgerEntry(newSle, reserveSponsorAccSle);
|
||||
|
||||
ctx_.view().insert(newSle);
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
SponsorshipSet::doApply()
|
||||
{
|
||||
@@ -224,8 +346,8 @@ SponsorshipSet::doApply()
|
||||
if (!ctx_.view().exists(keylet::account(sponseeID)))
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const sponsorKeylet = keylet::sponsorship(sponsorID, sponseeID);
|
||||
auto const sponsorshipSle = ctx_.view().peek(sponsorKeylet);
|
||||
auto const sponsorshipKeylet = keylet::sponsorship(sponsorID, sponseeID);
|
||||
auto const sponsorshipSle = ctx_.view().peek(sponsorshipKeylet);
|
||||
|
||||
if (ctx_.tx.isFlag(tfDeleteObject))
|
||||
{
|
||||
@@ -235,11 +357,9 @@ SponsorshipSet::doApply()
|
||||
return deleteSponsorship(ctx_.view(), sponsorshipSle, ctx_.journal);
|
||||
}
|
||||
|
||||
auto const feeAmount = ctx_.tx[~sfFeeAmount];
|
||||
auto const feeAmountDelta = ctx_.tx[~sfFeeAmountDelta];
|
||||
auto const maxFee = ctx_.tx[~sfMaxFee];
|
||||
auto const remainingOwnerCount = ctx_.tx[~sfRemainingOwnerCount];
|
||||
|
||||
bool const hasPositiveFeeAmount = feeAmount.has_value() && *feeAmount > beast::kZero;
|
||||
auto const remainingOwnerCountDelta = ctx_.tx[~sfRemainingOwnerCountDelta];
|
||||
|
||||
auto reserveSponsorAccSle = getTxReserveSponsor(ctx_.getApplyViewContext());
|
||||
if (!reserveSponsorAccSle)
|
||||
@@ -247,24 +367,33 @@ SponsorshipSet::doApply()
|
||||
|
||||
if (!sponsorshipSle)
|
||||
{
|
||||
// Create a new Sponsorship object between the sponsor and sponsee.
|
||||
auto newSle = std::make_shared<SLE>(sponsorKeylet);
|
||||
return createSponsorship(
|
||||
sponsorshipKeylet, sponsorID, sponseeID, sponsorAccSle, *reserveSponsorAccSle);
|
||||
}
|
||||
|
||||
(*newSle)[sfOwner] = sponsorID;
|
||||
(*newSle)[sfSponsee] = sponseeID;
|
||||
if (feeAmount && (*feeAmount).xrp() > (*sponsorAccSle)[sfBalance])
|
||||
// Update the existing Sponsorship object.
|
||||
if (feeAmountDelta)
|
||||
{
|
||||
auto actualDelta = feeAmountDelta.value();
|
||||
auto const currentFee = (*sponsorshipSle)[~sfFeeAmount].valueOr(XRPAmount{0});
|
||||
|
||||
// Clamp negative delta to avoid underflow.
|
||||
if (actualDelta < beast::kZero && -actualDelta > currentFee)
|
||||
actualDelta = -currentFee;
|
||||
// Reject if the sponsor cannot afford the (positive) delta.
|
||||
if (actualDelta > beast::kZero && actualDelta > (*sponsorAccSle)[sfBalance])
|
||||
return tecUNFUNDED;
|
||||
|
||||
STAmount sponsorBalanceAfterFee = (*sponsorAccSle)[sfBalance];
|
||||
if (hasPositiveFeeAmount)
|
||||
sponsorBalanceAfterFee -= *feeAmount;
|
||||
// Move the FeeAmount delta between the sponsor balance and Sponsorship
|
||||
// object.
|
||||
(*sponsorAccSle)[sfBalance] -= actualDelta;
|
||||
|
||||
if (auto const ret = checkReserve(
|
||||
ctx_.getApplyViewContext(),
|
||||
sponsorAccSle,
|
||||
sponsorBalanceAfterFee.xrp(),
|
||||
(*sponsorAccSle)[sfBalance]->xrp(),
|
||||
*reserveSponsorAccSle,
|
||||
{.ownerCountDelta = 1},
|
||||
{},
|
||||
ctx_.journal,
|
||||
tecUNFUNDED);
|
||||
!isTesSuccess(ret))
|
||||
@@ -272,87 +401,19 @@ SponsorshipSet::doApply()
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (hasPositiveFeeAmount)
|
||||
STAmount const newFee = currentFee + actualDelta;
|
||||
// checked in preclaim
|
||||
XRPL_ASSERT(
|
||||
newFee >= beast::kZero, "xrpl::SponsorshipSet::doApply : new fee is non-negative");
|
||||
if (newFee == beast::kZero)
|
||||
{
|
||||
// New object: FeeAmount starts absent, so deduct and record the full amount
|
||||
(*newSle)[sfFeeAmount] = *feeAmount;
|
||||
(*sponsorAccSle)[sfBalance] -= *feeAmount;
|
||||
sponsorshipSle->makeFieldAbsent(sfFeeAmount);
|
||||
}
|
||||
|
||||
if (maxFee && *maxFee > beast::kZero)
|
||||
(*newSle)[sfMaxFee] = *maxFee;
|
||||
if (remainingOwnerCount && *remainingOwnerCount > 0)
|
||||
(*newSle)[sfRemainingOwnerCount] = *remainingOwnerCount;
|
||||
|
||||
std::uint32_t flags = 0;
|
||||
if (ctx_.tx.isFlag(tfSponsorshipSetRequireSignForFee))
|
||||
flags |= lsfSponsorshipRequireSignForFee;
|
||||
|
||||
if (ctx_.tx.isFlag(tfSponsorshipSetRequireSignForReserve))
|
||||
flags |= lsfSponsorshipRequireSignForReserve;
|
||||
|
||||
(*newSle)[sfFlags] = flags;
|
||||
|
||||
auto const sponsorPage = view().dirInsert(
|
||||
keylet::ownerDir(sponsorID), sponsorKeylet, describeOwnerDir(sponsorID));
|
||||
if (!sponsorPage)
|
||||
return tecDIR_FULL; // LCOV_EXCL_LINE
|
||||
(*newSle)[sfOwnerNode] = *sponsorPage;
|
||||
|
||||
auto const sponseePage = view().dirInsert(
|
||||
keylet::ownerDir(sponseeID), sponsorKeylet, describeOwnerDir(sponseeID));
|
||||
if (!sponseePage)
|
||||
return tecDIR_FULL; // LCOV_EXCL_LINE
|
||||
(*newSle)[sfSponseeNode] = *sponseePage;
|
||||
|
||||
// NOLINTNEXTLINE(readability-suspicious-call-argument)
|
||||
increaseOwnerCount(view(), sponsorAccSle, *reserveSponsorAccSle, 1, ctx_.journal);
|
||||
addSponsorToLedgerEntry(newSle, *reserveSponsorAccSle);
|
||||
|
||||
ctx_.view().insert(newSle);
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
// Update the existing Sponsorship object.
|
||||
if (feeAmount)
|
||||
{
|
||||
auto const currentFeeAmount = (*sponsorshipSle)[~sfFeeAmount].valueOr(XRPAmount{0});
|
||||
auto const feeAmountDelta = XRPAmount(*feeAmount - currentFeeAmount);
|
||||
|
||||
if (feeAmountDelta > beast::kZero && feeAmountDelta > (*sponsorAccSle)[sfBalance])
|
||||
return tecUNFUNDED;
|
||||
|
||||
// Move the FeeAmount delta between the sponsor balance and Sponsorship
|
||||
// object.
|
||||
if (feeAmountDelta != beast::kZero)
|
||||
else
|
||||
{
|
||||
STAmount sponsorBalanceAfterFee = (*sponsorAccSle)[sfBalance];
|
||||
sponsorBalanceAfterFee -= feeAmountDelta;
|
||||
|
||||
if (auto const ret = checkReserve(
|
||||
ctx_.getApplyViewContext(),
|
||||
sponsorAccSle,
|
||||
sponsorBalanceAfterFee.xrp(),
|
||||
*reserveSponsorAccSle,
|
||||
{},
|
||||
ctx_.journal,
|
||||
tecUNFUNDED);
|
||||
!isTesSuccess(ret))
|
||||
{
|
||||
return ret;
|
||||
}
|
||||
|
||||
(*sponsorAccSle)[sfBalance] -= feeAmountDelta;
|
||||
if (*feeAmount == beast::kZero)
|
||||
{
|
||||
(*sponsorshipSle).makeFieldAbsent(sfFeeAmount);
|
||||
}
|
||||
else
|
||||
{
|
||||
(*sponsorshipSle).setFieldAmount(sfFeeAmount, *feeAmount);
|
||||
}
|
||||
ctx_.view().update(sponsorAccSle);
|
||||
(*sponsorshipSle)[sfFeeAmount] = newFee;
|
||||
}
|
||||
ctx_.view().update(sponsorAccSle);
|
||||
}
|
||||
|
||||
if (maxFee)
|
||||
@@ -367,15 +428,21 @@ SponsorshipSet::doApply()
|
||||
}
|
||||
}
|
||||
|
||||
if (remainingOwnerCount)
|
||||
if (remainingOwnerCountDelta)
|
||||
{
|
||||
if (*remainingOwnerCount == 0)
|
||||
std::int64_t const newCount =
|
||||
totalRemainingOwnerCount(sponsorshipSle, remainingOwnerCountDelta);
|
||||
// Overflow is rejected in preclaim; underflow clamps to zero (field absent).
|
||||
XRPL_ASSERT(
|
||||
newCount <= static_cast<std::int64_t>(std::numeric_limits<std::uint32_t>::max()),
|
||||
"xrpl::SponsorshipSet::doApply : RemainingOwnerCount does not overflow");
|
||||
if (newCount <= 0)
|
||||
{
|
||||
sponsorshipSle->makeFieldAbsent(sfRemainingOwnerCount);
|
||||
}
|
||||
else
|
||||
{
|
||||
sponsorshipSle->at(sfRemainingOwnerCount) = *remainingOwnerCount;
|
||||
sponsorshipSle->at(sfRemainingOwnerCount) = static_cast<std::uint32_t>(newCount);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user