mirror of
https://github.com/Xahau/xahaud.git
synced 2026-06-16 15:16:36 +00:00
Compare commits
4 Commits
feature-ex
...
json-tx
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8916355c54 | ||
|
|
63bedc2d06 | ||
|
|
b9119b189f | ||
|
|
9b7a9668e5 |
37
.codecov.yml
37
.codecov.yml
@@ -1,37 +0,0 @@
|
||||
codecov:
|
||||
require_ci_to_pass: true
|
||||
|
||||
comment:
|
||||
behavior: default
|
||||
layout: reach,diff,flags,tree,reach
|
||||
show_carryforward_flags: false
|
||||
|
||||
coverage:
|
||||
range: "60..80"
|
||||
precision: 1
|
||||
round: nearest
|
||||
status:
|
||||
project:
|
||||
default:
|
||||
target: 60%
|
||||
threshold: 2%
|
||||
patch:
|
||||
default:
|
||||
target: auto
|
||||
threshold: 2%
|
||||
changes: false
|
||||
|
||||
github_checks:
|
||||
annotations: true
|
||||
|
||||
parsers:
|
||||
cobertura:
|
||||
partials_as_hits: true
|
||||
handle_missing_conditions : true
|
||||
|
||||
slack_app: false
|
||||
|
||||
ignore:
|
||||
- "src/test/"
|
||||
- "include/xrpl/beast/test/"
|
||||
- "include/xrpl/beast/unit_test/"
|
||||
@@ -1,25 +1,8 @@
|
||||
# This feature requires Git >= 2.24
|
||||
# To use it by default in git blame:
|
||||
# git config blame.ignoreRevsFile .git-blame-ignore-revs
|
||||
# Format first-party source according to .clang-format
|
||||
50760c693510894ca368e90369b0cc2dabfd07f3
|
||||
# Reintroduce Clang-Format & Levelization
|
||||
da1d20d6d5d862716125d60899b80fab5302954a
|
||||
# Consolidate external libraries
|
||||
da1d20d6d5d862716125d60899b80fab5302954a
|
||||
# Rename .hpp to .h
|
||||
0345a2645d0f5ad900f4fbbcaff96040d3a887fc
|
||||
# Format formerly .hpp files
|
||||
5a227dc719016e10045e17c9396ad401118044f1
|
||||
# Rewrite includes
|
||||
e61880699997398f5a746e6c4034edc7632661f5
|
||||
# Move CMake directory (#4997)
|
||||
e47b1c1b3b97c3f6d11858ee02f463596e29e7f0
|
||||
# Rearrange sources (#4997)
|
||||
bfafa2bb39e562901736d656806bd700c3699a2f
|
||||
# Rewrite includes (#4997)
|
||||
e61880699997398f5a746e6c4034edc7632661f5
|
||||
# Recompute loops (#4997)
|
||||
d25b5dcd568bb96c18e347d55fac10fe901a1bfb
|
||||
# Reformat code with clang-format-18
|
||||
02749feea88ce61c1f7eeb2d61a57d8ecf07ab11
|
||||
e2384885f5f630c8f0ffe4bf21a169b433a16858
|
||||
241b9ddde9e11beb7480600fd5ed90e1ef109b21
|
||||
760f16f56835663d9286bd29294d074de26a7ba6
|
||||
0eebe6a5f4246fced516d52b83ec4e7f47373edd
|
||||
|
||||
29
.github/actions/xahau-ga-build/action.yml
vendored
29
.github/actions/xahau-ga-build/action.yml
vendored
@@ -2,14 +2,6 @@ name: build
|
||||
description: 'Builds the project with ccache integration'
|
||||
|
||||
inputs:
|
||||
cmake-target:
|
||||
description: 'CMake target to build'
|
||||
required: false
|
||||
default: all
|
||||
cmake-args:
|
||||
description: 'Additional CMake arguments'
|
||||
required: false
|
||||
default: null
|
||||
generator:
|
||||
description: 'CMake generator to use'
|
||||
required: true
|
||||
@@ -28,10 +20,6 @@ inputs:
|
||||
description: 'C++ compiler to use'
|
||||
required: false
|
||||
default: ''
|
||||
gcov:
|
||||
description: 'Gcov to use'
|
||||
required: false
|
||||
default: ''
|
||||
compiler-id:
|
||||
description: 'Unique identifier: compiler-version-stdlib[-gccversion] (e.g. clang-14-libstdcxx-gcc11, gcc-13-libstdcxx)'
|
||||
required: false
|
||||
@@ -53,11 +41,10 @@ inputs:
|
||||
required: false
|
||||
default: 'dev'
|
||||
stdlib:
|
||||
description: 'C++ standard library to use (default = compiler default, e.g. GCC always uses libstdc++)'
|
||||
description: 'C++ standard library to use'
|
||||
required: true
|
||||
type: choice
|
||||
options:
|
||||
- default
|
||||
- libstdcxx
|
||||
- libcxx
|
||||
clang_gcc_toolchain:
|
||||
@@ -100,6 +87,11 @@ runs:
|
||||
export CCACHE_CONFIGPATH="$HOME/.config/ccache/ccache.conf"
|
||||
echo "CCACHE_CONFIGPATH=$CCACHE_CONFIGPATH" >> $GITHUB_ENV
|
||||
|
||||
# Keep config separate from cache_dir so configs aren't swapped when CCACHE_DIR changes between steps
|
||||
mkdir -p ~/.config/ccache
|
||||
export CCACHE_CONFIGPATH="$HOME/.config/ccache/ccache.conf"
|
||||
echo "CCACHE_CONFIGPATH=$CCACHE_CONFIGPATH" >> $GITHUB_ENV
|
||||
|
||||
# Configure ccache settings AFTER cache restore (prevents stale cached config)
|
||||
ccache --set-config=max_size=${{ inputs.ccache_max_size }}
|
||||
ccache --set-config=hash_dir=${{ inputs.ccache_hash_dir }}
|
||||
@@ -130,10 +122,6 @@ runs:
|
||||
export CXX="${{ inputs.cxx }}"
|
||||
fi
|
||||
|
||||
if [ -n "${{ inputs.gcov }}" ]; then
|
||||
ln -sf /usr/bin/${{ inputs.gcov }} /usr/local/bin/gcov
|
||||
fi
|
||||
|
||||
# Create wrapper toolchain that overlays ccache on top of Conan's toolchain
|
||||
# This enables ccache for the main app build without affecting Conan dependency builds
|
||||
if [ "${{ inputs.ccache_enabled }}" = "true" ]; then
|
||||
@@ -197,8 +185,7 @@ runs:
|
||||
-DCMAKE_TOOLCHAIN_FILE:FILEPATH=${TOOLCHAIN_FILE} \
|
||||
-DCMAKE_BUILD_TYPE=${{ inputs.configuration }} \
|
||||
-Dtests=TRUE \
|
||||
-Dxrpld=TRUE \
|
||||
${{ inputs.cmake-args }}
|
||||
-Dxrpld=TRUE
|
||||
|
||||
- name: Show ccache config before build
|
||||
if: inputs.ccache_enabled == 'true'
|
||||
@@ -222,7 +209,7 @@ runs:
|
||||
VERBOSE_FLAG="-- -v"
|
||||
fi
|
||||
|
||||
cmake --build . --config ${{ inputs.configuration }} --parallel $(nproc) --target ${{ inputs.cmake-target }} ${VERBOSE_FLAG}
|
||||
cmake --build . --config ${{ inputs.configuration }} --parallel $(nproc) ${VERBOSE_FLAG}
|
||||
|
||||
- name: Show ccache statistics
|
||||
if: inputs.ccache_enabled == 'true'
|
||||
|
||||
107
.github/workflows/check-genesis-hooks.yml
vendored
107
.github/workflows/check-genesis-hooks.yml
vendored
@@ -1,107 +0,0 @@
|
||||
name: Check Genesis Hooks
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
check-genesis-hooks:
|
||||
runs-on: ubuntu-24.04
|
||||
env:
|
||||
CLANG_VERSION: 18
|
||||
name: Verify xahau.h is in sync with genesis hooks
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
|
||||
# Install binaryen from GitHub Releases (pinned to version 100)
|
||||
- name: Install binaryen (version 100)
|
||||
run: |
|
||||
curl -LO https://github.com/WebAssembly/binaryen/releases/download/version_100/binaryen-version_100-x86_64-linux.tar.gz
|
||||
tar -xzf binaryen-version_100-x86_64-linux.tar.gz
|
||||
sudo cp binaryen-version_100/bin/* /usr/local/bin/
|
||||
wasm-opt --version
|
||||
|
||||
- name: Install clang-format
|
||||
run: |
|
||||
codename=$( lsb_release --codename --short )
|
||||
sudo tee /etc/apt/sources.list.d/llvm.list >/dev/null <<EOF
|
||||
deb http://apt.llvm.org/${codename}/ llvm-toolchain-${codename}-${CLANG_VERSION} main
|
||||
deb-src http://apt.llvm.org/${codename}/ llvm-toolchain-${codename}-${CLANG_VERSION} main
|
||||
EOF
|
||||
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add
|
||||
sudo apt-get update
|
||||
sudo apt-get install clang-format-${CLANG_VERSION}
|
||||
clang-format --version
|
||||
|
||||
# Install wasienv (WebAssembly SDK)
|
||||
- name: Install wasienv
|
||||
run: |
|
||||
# Download install.sh
|
||||
curl -o /tmp/wasienv-install.sh https://raw.githubusercontent.com/wasienv/wasienv/master/install.sh
|
||||
|
||||
# Replace /bin to /local/bin
|
||||
sed -i 's|/bin|/local/bin|g' /tmp/wasienv-install.sh
|
||||
|
||||
# Execute the installed script
|
||||
bash /tmp/wasienv-install.sh
|
||||
|
||||
# Add wasienv to PATH for subsequent steps
|
||||
- name: Setup wasienv
|
||||
run: |
|
||||
echo "$HOME/.wasienv/bin" >> $GITHUB_PATH
|
||||
wasmcc -v || true
|
||||
|
||||
# Build and install hook-cleaner tool
|
||||
- name: Build and install hook-cleaner
|
||||
run: |
|
||||
git clone https://github.com/richardah/hook-cleaner-c.git /tmp/hook-cleaner
|
||||
cd /tmp/hook-cleaner
|
||||
make
|
||||
cp hook-cleaner /usr/local/bin/
|
||||
chmod +x /usr/local/bin/hook-cleaner
|
||||
|
||||
# Build and install guard_checker tool
|
||||
- name: Build and install guard_checker
|
||||
run: |
|
||||
cd include/xrpl/hook
|
||||
make
|
||||
cp guard_checker /usr/local/bin/
|
||||
chmod +x /usr/local/bin/guard_checker
|
||||
|
||||
# Verify all required tools are available
|
||||
- name: Verify required tools
|
||||
run: |
|
||||
echo "Checking tool availability..."
|
||||
command -v wasmcc || (echo "Error: wasmcc not found" && exit 1)
|
||||
command -v wasm-opt || (echo "Error: wasm-opt not found" && exit 1)
|
||||
command -v hook-cleaner || (echo "Error: hook-cleaner not found" && exit 1)
|
||||
command -v guard_checker || (echo "Error: guard_checker not found" && exit 1)
|
||||
command -v xxd || (echo "Error: xxd not found" && exit 1)
|
||||
command -v clang-format || (echo "Error: clang-format not found" && exit 1)
|
||||
echo "All tools verified successfully"
|
||||
|
||||
# Execute build script to regenerate xahau.h
|
||||
- name: Run build_xahau_h.sh
|
||||
run: |
|
||||
cd hook/genesis
|
||||
./build_xahau_h.sh
|
||||
|
||||
# Check if xahau.h has changed (fail if out of sync)
|
||||
- name: Verify xahau.h is in sync
|
||||
run: |
|
||||
if ! git diff --exit-code include/xrpl/hook/xahau.h; then
|
||||
echo ""
|
||||
echo "❌ ERROR: xahau.h is out of sync with genesis hooks"
|
||||
echo ""
|
||||
echo "The generated xahau.h differs from the committed version."
|
||||
echo "Please run the following command and commit the changes:"
|
||||
echo ""
|
||||
echo " cd hook/genesis && ./build_xahau_h.sh"
|
||||
echo ""
|
||||
echo "Diff:"
|
||||
git diff include/xrpl/hook/xahau.h
|
||||
exit 1
|
||||
fi
|
||||
echo "✅ xahau.h is in sync with genesis hooks"
|
||||
2
.github/workflows/clang-format.yml
vendored
2
.github/workflows/clang-format.yml
vendored
@@ -20,7 +20,7 @@ jobs:
|
||||
sudo apt-get update
|
||||
sudo apt-get install clang-format-${CLANG_VERSION}
|
||||
- name: Format first-party sources
|
||||
run: find include src -type f \( -name '*.cpp' -o -name '*.hpp' -o -name '*.h' -o -name '*.ipp' \) -exec clang-format-${CLANG_VERSION} -i {} +
|
||||
run: find include src -type f \( -name '*.cpp' -o -name '*.hpp' -o -name '*.h' -o -name '*.ipp' \) -not -path "src/magic/magic_enum.h" -exec clang-format-${CLANG_VERSION} -i {} +
|
||||
- name: Check for differences
|
||||
id: assert
|
||||
run: |
|
||||
|
||||
@@ -18,10 +18,6 @@ jobs:
|
||||
generator: bash ./hook/generate_sfcodes.sh
|
||||
- target: hook/tts.h
|
||||
generator: ./hook/generate_tts.sh
|
||||
- target: hook/ls_flags.h
|
||||
generator: ./hook/generate_lsflags.sh
|
||||
- target: hook/tx_flags.h
|
||||
generator: ./hook/generate_txflags.sh
|
||||
runs-on: ubuntu-24.04
|
||||
env:
|
||||
CLANG_VERSION: 18
|
||||
|
||||
132
.github/workflows/xahau-ga-nix.yml
vendored
132
.github/workflows/xahau-ga-nix.yml
vendored
@@ -57,9 +57,8 @@ jobs:
|
||||
"cc": "gcc-11",
|
||||
"cxx": "g++-11",
|
||||
"compiler_version": 11,
|
||||
"stdlib": "default",
|
||||
"configuration": "Debug",
|
||||
"job_type": "build"
|
||||
"stdlib": "libstdcxx",
|
||||
"configuration": "Debug"
|
||||
},
|
||||
{
|
||||
"compiler_id": "gcc-13-libstdcxx",
|
||||
@@ -67,20 +66,8 @@ jobs:
|
||||
"cc": "gcc-13",
|
||||
"cxx": "g++-13",
|
||||
"compiler_version": 13,
|
||||
"stdlib": "default",
|
||||
"configuration": "Debug",
|
||||
"job_type": "build"
|
||||
},
|
||||
{
|
||||
"compiler_id": "gcc-13-libstdcxx",
|
||||
"compiler": "gcc",
|
||||
"cc": "gcc-13",
|
||||
"cxx": "g++-13",
|
||||
"gcov": "gcov-13",
|
||||
"compiler_version": 13,
|
||||
"stdlib": "default",
|
||||
"configuration": "Debug",
|
||||
"job_type": "coverage"
|
||||
"stdlib": "libstdcxx",
|
||||
"configuration": "Debug"
|
||||
},
|
||||
{
|
||||
"compiler_id": "clang-14-libstdcxx-gcc11",
|
||||
@@ -90,8 +77,7 @@ jobs:
|
||||
"compiler_version": 14,
|
||||
"stdlib": "libstdcxx",
|
||||
"clang_gcc_toolchain": 11,
|
||||
"configuration": "Debug",
|
||||
"job_type": "build"
|
||||
"configuration": "Debug"
|
||||
},
|
||||
{
|
||||
"compiler_id": "clang-16-libstdcxx-gcc13",
|
||||
@@ -101,8 +87,7 @@ jobs:
|
||||
"compiler_version": 16,
|
||||
"stdlib": "libstdcxx",
|
||||
"clang_gcc_toolchain": 13,
|
||||
"configuration": "Debug",
|
||||
"job_type": "build"
|
||||
"configuration": "Debug"
|
||||
},
|
||||
{
|
||||
"compiler_id": "clang-17-libcxx",
|
||||
@@ -111,8 +96,7 @@ jobs:
|
||||
"cxx": "clang++-17",
|
||||
"compiler_version": 17,
|
||||
"stdlib": "libcxx",
|
||||
"configuration": "Debug",
|
||||
"job_type": "build"
|
||||
"configuration": "Debug"
|
||||
},
|
||||
{
|
||||
# Clang 18 - testing if it's faster than Clang 17 with libc++
|
||||
@@ -123,16 +107,14 @@ jobs:
|
||||
"cxx": "clang++-18",
|
||||
"compiler_version": 18,
|
||||
"stdlib": "libcxx",
|
||||
"configuration": "Debug",
|
||||
"job_type": "build"
|
||||
"configuration": "Debug"
|
||||
}
|
||||
]
|
||||
|
||||
# Minimal matrix for PRs and feature branches
|
||||
minimal_matrix = [
|
||||
full_matrix[1], # gcc-13 (middle-ground gcc)
|
||||
full_matrix[2], # gcc-13 coverage
|
||||
full_matrix[3] # clang-14 (mature, stable clang)
|
||||
full_matrix[2] # clang-14 (mature, stable clang)
|
||||
]
|
||||
|
||||
# Determine which matrix to use based on the target branch
|
||||
@@ -207,21 +189,14 @@ jobs:
|
||||
# Select the appropriate matrix
|
||||
if use_full:
|
||||
if force_full:
|
||||
print(f"Using FULL matrix (7 configs) - forced by [ci-nix-full-matrix] tag")
|
||||
print(f"Using FULL matrix (6 configs) - forced by [ci-nix-full-matrix] tag")
|
||||
else:
|
||||
print(f"Using FULL matrix (7 configs) - targeting main branch")
|
||||
print(f"Using FULL matrix (6 configs) - targeting main branch")
|
||||
matrix = full_matrix
|
||||
else:
|
||||
print(f"Using MINIMAL matrix (3 configs) - feature branch/PR")
|
||||
print(f"Using MINIMAL matrix (2 configs) - feature branch/PR")
|
||||
matrix = minimal_matrix
|
||||
|
||||
# Add runs_on based on job_type
|
||||
for entry in matrix:
|
||||
if entry.get("job_type") == "coverage":
|
||||
entry["runs_on"] = '["self-hosted", "generic", 24.04]'
|
||||
else:
|
||||
entry["runs_on"] = '["self-hosted", "generic", 20.04]'
|
||||
|
||||
|
||||
# Output the matrix as JSON
|
||||
output = json.dumps({"include": matrix})
|
||||
with open(os.environ['GITHUB_OUTPUT'], 'a') as f:
|
||||
@@ -229,10 +204,7 @@ jobs:
|
||||
|
||||
build:
|
||||
needs: matrix-setup
|
||||
runs-on: ${{ fromJSON(matrix.runs_on) }}
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: read
|
||||
runs-on: [self-hosted, generic, 20.04]
|
||||
container:
|
||||
image: ubuntu:24.04
|
||||
volumes:
|
||||
@@ -261,7 +233,7 @@ jobs:
|
||||
apt-get install -y software-properties-common
|
||||
add-apt-repository ppa:ubuntu-toolchain-r/test -y
|
||||
apt-get update
|
||||
apt-get install -y git python3 python-is-python3 pipx
|
||||
apt-get install -y python3 python-is-python3 pipx
|
||||
pipx ensurepath
|
||||
apt-get install -y cmake ninja-build ${{ matrix.cc }} ${{ matrix.cxx }} ccache
|
||||
apt-get install -y perl # for openssl build
|
||||
@@ -332,12 +304,6 @@ jobs:
|
||||
pipx install "conan>=2.0,<3"
|
||||
echo "$HOME/.local/bin" >> $GITHUB_PATH
|
||||
|
||||
# Install gcovr for coverage jobs
|
||||
if [ "${{ matrix.job_type }}" = "coverage" ]; then
|
||||
pipx install "gcovr>=7,<9"
|
||||
apt-get install -y curl lcov
|
||||
fi
|
||||
|
||||
- name: Check environment
|
||||
run: |
|
||||
echo "PATH:"
|
||||
@@ -347,13 +313,6 @@ jobs:
|
||||
which ${{ matrix.cc }} && ${{ matrix.cc }} --version || echo "${{ matrix.cc }} not found"
|
||||
which ${{ matrix.cxx }} && ${{ matrix.cxx }} --version || echo "${{ matrix.cxx }} not found"
|
||||
which ccache && ccache --version || echo "ccache not found"
|
||||
|
||||
# Check gcovr for coverage jobs
|
||||
if [ "${{ matrix.job_type }}" = "coverage" ]; then
|
||||
which gcov && gcov --version || echo "gcov not found"
|
||||
which gcovr && gcovr --version || echo "gcovr not found"
|
||||
fi
|
||||
|
||||
echo "---- Full Environment ----"
|
||||
env
|
||||
|
||||
@@ -381,7 +340,6 @@ jobs:
|
||||
gha_cache_enabled: 'false' # Disable caching for self hosted runner
|
||||
|
||||
- name: Build
|
||||
if: matrix.job_type == 'build'
|
||||
uses: ./.github/actions/xahau-ga-build
|
||||
with:
|
||||
generator: Ninja
|
||||
@@ -396,27 +354,7 @@ jobs:
|
||||
clang_gcc_toolchain: ${{ matrix.clang_gcc_toolchain || '' }}
|
||||
ccache_max_size: '100G'
|
||||
|
||||
- name: Build (Coverage)
|
||||
if: matrix.job_type == 'coverage'
|
||||
uses: ./.github/actions/xahau-ga-build
|
||||
with:
|
||||
generator: Ninja
|
||||
configuration: ${{ matrix.configuration }}
|
||||
build_dir: ${{ env.build_dir }}
|
||||
cc: ${{ matrix.cc }}
|
||||
cxx: ${{ matrix.cxx }}
|
||||
gcov: ${{ matrix.gcov }}
|
||||
compiler-id: ${{ matrix.compiler_id }}
|
||||
cache_version: ${{ env.CACHE_VERSION }}
|
||||
main_branch: ${{ env.MAIN_BRANCH_NAME }}
|
||||
stdlib: ${{ matrix.stdlib }}
|
||||
# Coverage builds are slower due to instrumentation; use fewer parallel jobs to avoid flakiness
|
||||
cmake-args: '-Dcoverage=ON -Dcoverage_format=xml -Dcoverage_test_parallelism=$(($(nproc)/2)) -DCODE_COVERAGE_VERBOSE=ON -DCMAKE_CXX_FLAGS="-O0" -DCMAKE_C_FLAGS="-O0"'
|
||||
cmake-target: 'coverage'
|
||||
ccache_max_size: '100G'
|
||||
|
||||
- name: Set artifact name
|
||||
if: matrix.job_type == 'build'
|
||||
id: set-artifact-name
|
||||
run: |
|
||||
ARTIFACT_NAME="build-output-nix-${{ github.run_id }}-${{ matrix.compiler }}-${{ matrix.configuration }}"
|
||||
@@ -429,7 +367,6 @@ jobs:
|
||||
ls -la ${{ env.build_dir }} || echo "Build directory not found or empty"
|
||||
|
||||
- name: Run tests
|
||||
if: matrix.job_type == 'build'
|
||||
run: |
|
||||
# Ensure the binary exists before trying to run
|
||||
if [ -f "${{ env.build_dir }}/rippled" ]; then
|
||||
@@ -438,42 +375,3 @@ jobs:
|
||||
echo "Error: rippled executable not found in ${{ env.build_dir }}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Coverage-specific steps
|
||||
- name: Move coverage report
|
||||
if: matrix.job_type == 'coverage'
|
||||
shell: bash
|
||||
run: |
|
||||
mv "${{ env.build_dir }}/coverage.xml" ./
|
||||
|
||||
- name: Archive coverage report
|
||||
if: matrix.job_type == 'coverage'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: coverage.xml
|
||||
path: coverage.xml
|
||||
retention-days: 30
|
||||
|
||||
- name: Upload coverage report
|
||||
if: matrix.job_type == 'coverage'
|
||||
uses: codecov/codecov-action@v5
|
||||
with:
|
||||
files: coverage.xml
|
||||
fail_ci_if_error: true
|
||||
disable_search: true
|
||||
verbose: true
|
||||
plugins: noop
|
||||
use_oidc: true
|
||||
|
||||
- name: Export server definitions
|
||||
if: matrix.job_type == 'build' && matrix.compiler_id == 'gcc-13-libstdcxx'
|
||||
run: |
|
||||
${{ env.build_dir }}/rippled --definitions | python3 -m json.tool > server_definitions.json
|
||||
|
||||
- name: Upload server definitions
|
||||
if: matrix.job_type == 'build' && matrix.compiler_id == 'gcc-13-libstdcxx'
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: server-definitions
|
||||
path: server_definitions.json
|
||||
archive: false
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -127,8 +127,5 @@ bld.rippled/
|
||||
generated
|
||||
.vscode
|
||||
|
||||
# AI docs (local working documents)
|
||||
.ai-docs/
|
||||
|
||||
# Suggested in-tree build directory
|
||||
/.build/
|
||||
|
||||
4
.testnet/.gitignore
vendored
4
.testnet/.gitignore
vendored
@@ -1,4 +0,0 @@
|
||||
output/
|
||||
__pycache__/
|
||||
scenarios/odd-cases/
|
||||
scenarios/suite-experiments.yml
|
||||
@@ -1,29 +0,0 @@
|
||||
"""Scenario: ConsensusEntropy amendment crashes non-supporting node.
|
||||
|
||||
Votes ConsensusEntropy accept on all nodes except n4, then waits for n4
|
||||
to crash as the amendment activates without its support.
|
||||
|
||||
x-testnet run --scenario-script consensus_entropy_crash.py
|
||||
"""
|
||||
|
||||
from helpers import CONSENSUS_ENTROPY_FEATURE
|
||||
|
||||
|
||||
async def scenario(ctx, log):
|
||||
await ctx.wait_for_ledger_close()
|
||||
ctx.feature(CONSENSUS_ENTROPY_FEATURE, vetoed=False, exclude_nodes=[4])
|
||||
|
||||
log("Waiting for ConsensusEntropy to be voted for...")
|
||||
await ctx.wait_for_feature(
|
||||
CONSENSUS_ENTROPY_FEATURE,
|
||||
check=lambda s: not s.get("vetoed"),
|
||||
exclude_nodes=[4],
|
||||
timeout=60,
|
||||
)
|
||||
|
||||
log("Waiting for n4 to crash...")
|
||||
op = await ctx.wait_for_nodes_down(nodes=[4], timeout=600)
|
||||
|
||||
ctx.assert_log("unsupported amendments activated", since=op.started, nodes=[4])
|
||||
ctx.assert_exit_status(0, nodes=[4])
|
||||
log("PASS: n4 shut down due to unsupported amendment")
|
||||
@@ -1,52 +0,0 @@
|
||||
""":descr: entropy stays valid under transaction load"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from helpers import require_entropy, get_entropy_tx, assert_valid_entropy
|
||||
|
||||
variants = [
|
||||
{"label": "light", "min_txns": 5, "max_txns": 10},
|
||||
{"label": "heavy", "min_txns": 50, "max_txns": 60},
|
||||
{"label": "super_heavy", "min_txns": 90, "max_txns": 120},
|
||||
]
|
||||
|
||||
|
||||
async def scenario(ctx, log, *, min_txns=5, max_txns=10, **_):
|
||||
await require_entropy(ctx, log)
|
||||
|
||||
gen = ctx.txn_generator(min_txns=min_txns, max_txns=max_txns)
|
||||
await gen.start()
|
||||
await gen.wait_until_ready()
|
||||
log(f"Transaction generator ready ({min_txns}-{max_txns} txns/ledger)")
|
||||
|
||||
# Wait for pipeline warmup + a few txn-bearing ledgers.
|
||||
await ctx.wait_for_ledgers(3, node_id=0, timeout=60)
|
||||
|
||||
start_seq = ctx.validated_ledger_index(0)
|
||||
await ctx.wait_for_ledgers(10, node_id=0, timeout=120)
|
||||
end_seq = ctx.validated_ledger_index(0)
|
||||
log(f"Inspecting ledgers {start_seq + 1} → {end_seq}")
|
||||
|
||||
digests = set()
|
||||
total_user_txns = 0
|
||||
|
||||
for seq in range(start_seq + 1, end_seq + 1):
|
||||
ce, user_txns = get_entropy_tx(ctx, seq)
|
||||
digest, count = assert_valid_entropy(ce, seq, seen_digests=digests)
|
||||
total_user_txns += len(user_txns)
|
||||
log(
|
||||
f" Ledger {seq}: EntropyCount={count} "
|
||||
f"user_txns={len(user_txns)} Digest={digest[:16]}..."
|
||||
)
|
||||
|
||||
await gen.stop()
|
||||
|
||||
log(
|
||||
f"Verified {end_seq - start_seq} ledgers: {total_user_txns} user txns, "
|
||||
f"all entropy valid and unique"
|
||||
)
|
||||
|
||||
if total_user_txns == 0:
|
||||
raise AssertionError("No user transactions were included in any ledger")
|
||||
|
||||
log("PASS")
|
||||
@@ -1,158 +0,0 @@
|
||||
""":descr: 5/6 validator_quorum, 4/6 participant_aligned (tier 2), recovery
|
||||
|
||||
Requires node_count: 6 (see suite.yml) — the smallest NON-degenerate Tier 2
|
||||
size. At n=6: tier2 floor = 4, validator quorum = 5, validation quorum = 5. So
|
||||
6/6, 5/6 present -> validator_quorum (EntropyTier=3)
|
||||
4/6 present -> participant_aligned (EntropyTier=2, count 4) <-- the band
|
||||
3/6 present -> consensus_fallback (EntropyTier=1)
|
||||
n=5 has NO tier-2 band (tier2 == quorum == 4), which is why the existing
|
||||
degradation smoke at 5 nodes only ever sees tier 3 / fallback.
|
||||
|
||||
KEY: the 4/6 window is BELOW the 80% validation quorum (5). The 4 survivors
|
||||
keep CLOSING ledgers that carry tier-2 entropy, but those ledgers do NOT
|
||||
validate until the network recovers — exactly the transition window Tier 2
|
||||
serves. So validated_ledger_index() stalls; we instead inspect a surviving
|
||||
node's CLOSED ledger (its LCL) directly, and cross-check the injection from the
|
||||
cohort's logs.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from helpers import (
|
||||
require_entropy,
|
||||
get_entropy_tx,
|
||||
assert_participant_aligned,
|
||||
assert_validator_quorum,
|
||||
)
|
||||
|
||||
|
||||
def _closed_entropy(result):
|
||||
"""(seq, ConsensusEntropy tx) from a ctx.ledger('closed', transactions=True)
|
||||
result, or (None, None) if the fetch returned no usable ledger.
|
||||
|
||||
Enforces the per-ledger invariant that an entropy-enabled closed ledger
|
||||
carries EXACTLY ONE ConsensusEntropy pseudo-tx (mirroring get_entropy_tx):
|
||||
a duplicate or missing injection raises here with a clear error instead of
|
||||
being silently skipped and resurfacing later as a generic 'no tier-2 ledger'.
|
||||
"""
|
||||
if not result or not isinstance(result.get("ledger"), dict):
|
||||
return None, None
|
||||
led = result["ledger"]
|
||||
try:
|
||||
seq = int(led.get("ledger_index"))
|
||||
except (TypeError, ValueError):
|
||||
return None, None
|
||||
ce = [
|
||||
t
|
||||
for t in led.get("transactions", [])
|
||||
if isinstance(t, dict) and t.get("TransactionType") == "ConsensusEntropy"
|
||||
]
|
||||
if len(ce) != 1:
|
||||
raise AssertionError(
|
||||
f"Closed ledger {seq}: expected 1 ConsensusEntropy txn, got {len(ce)}"
|
||||
)
|
||||
return seq, ce[0]
|
||||
|
||||
|
||||
async def scenario(ctx, log):
|
||||
await require_entropy(ctx, log)
|
||||
|
||||
# Baseline: healthy 6/6 produces validator_quorum entropy.
|
||||
await ctx.wait_for_ledgers(1, node_id=0, timeout=30)
|
||||
|
||||
# --- 5/6: settles back to validator_quorum (5 present >= quorum 5) ---
|
||||
val_before_drop = ctx.validated_ledger_index(0)
|
||||
ctx.stop_node(5)
|
||||
await ctx.wait_for_nodes_down(nodes=[5], timeout=30)
|
||||
# Settle a few ledgers past the membership change. The ledger right at a
|
||||
# validator drop can carry a transient consensus_fallback (tier 1, count 0,
|
||||
# deterministic and by design) before the commit/reveal pipeline re-primes,
|
||||
# so we do NOT assume any single post-drop ledger is already tier 3.
|
||||
await ctx.wait_for_ledgers(4, node_id=0, timeout=90)
|
||||
|
||||
# 5/6 is at/above the 80% quorum (5), so steady state is validator_quorum.
|
||||
# Scan the post-drop validated ledgers (all carry the 5-node cohort, so a
|
||||
# tier-3 here has count == 5) and require at least one clean validator_quorum
|
||||
# — EntropyTier=3, count >= quorum, non-zero digest — tolerating the
|
||||
# transition fallback instead of depending on where the tip happened to land.
|
||||
val_5of6 = ctx.validated_ledger_index(0)
|
||||
t3_seq = None
|
||||
for seq in range(val_5of6, val_before_drop, -1):
|
||||
ce, _ = get_entropy_tx(ctx, seq)
|
||||
tier = ce.get("EntropyTier")
|
||||
log(f" 5/6 ledger {seq}: tier={tier} count={ce.get('EntropyCount')}")
|
||||
if tier == 3:
|
||||
assert_validator_quorum(ce, seq, min_count=5)
|
||||
t3_seq = seq
|
||||
break
|
||||
if t3_seq is None:
|
||||
raise AssertionError(
|
||||
f"5/6: no validator_quorum (tier 3) entropy in post-drop validated "
|
||||
f"ledgers {val_before_drop + 1}..{val_5of6}"
|
||||
)
|
||||
log(f"5/6: validator_quorum at validated seq {t3_seq}")
|
||||
|
||||
# --- 4/6: participant_aligned (Tier 2) degraded window ---
|
||||
ctx.stop_node(4)
|
||||
await ctx.wait_for_nodes_down(nodes=[4], timeout=30)
|
||||
|
||||
# ~12s window: confirm tier-2 INJECTION from the cohort's logs, and that the
|
||||
# round is NOT the impossible/fallback path (which is what distinguishes the
|
||||
# tier-2 band from the tier-1 fallback regime).
|
||||
op = await ctx.sleep(12, name="tier2_window")
|
||||
selected_t2 = ctx.search_logs(
|
||||
r"RNG: entropy selected seq=\d+ tier=2 count=4",
|
||||
within=op.window,
|
||||
nodes=[0, 1, 2, 3],
|
||||
)
|
||||
log(f"4/6: 'entropy selected tier=2 count=4' logs: {selected_t2.count}")
|
||||
if selected_t2.count == 0:
|
||||
raise AssertionError(
|
||||
"4/6 window injected no participant_aligned (tier 2) entropy: no "
|
||||
"'RNG: entropy selected ... tier=2 count=4' on the surviving cohort"
|
||||
)
|
||||
ctx.assert_not_log(
|
||||
r"reason=impossible-entropy-gate", within=op.window, nodes=[0, 1, 2, 3]
|
||||
)
|
||||
|
||||
# Verify the on-ledger EntropyTier=2 DIRECTLY: validation is stalled (4 < 5),
|
||||
# so sample the surviving cohort's CLOSED ledger (its LCL — built but not yet
|
||||
# validated). At least one must be participant_aligned with EntropyCount=4.
|
||||
tier2_on_ledger = 0
|
||||
last_seq = None
|
||||
for _ in range(5):
|
||||
seq, ce = _closed_entropy(
|
||||
ctx.ledger("closed", transactions=True, node_id=0)
|
||||
)
|
||||
if ce is not None and seq is not None and seq != last_seq:
|
||||
last_seq = seq
|
||||
tier = ce.get("EntropyTier")
|
||||
count = ce.get("EntropyCount", -1)
|
||||
log(f" closed ledger {seq}: tier={tier} count={count}")
|
||||
if tier == 2:
|
||||
assert_participant_aligned(ce, seq, expected_count=4)
|
||||
tier2_on_ledger += 1
|
||||
await ctx.sleep(3)
|
||||
|
||||
if tier2_on_ledger == 0:
|
||||
raise AssertionError(
|
||||
"no closed participant_aligned (tier 2) ledger observed during the "
|
||||
"4/6 window (tier 2 was injected per logs, but not seen on a closed "
|
||||
"ledger)"
|
||||
)
|
||||
log(f"4/6: {tier2_on_ledger} participant_aligned closed ledger(s) verified")
|
||||
|
||||
# --- Recovery: liveness — validation resumes once quorum is restored ---
|
||||
ctx.start_node(4)
|
||||
ctx.start_node(5)
|
||||
await ctx.wait_for_ledgers(1, node_id=0, timeout=120)
|
||||
|
||||
val_recovered = ctx.validated_ledger_index(0)
|
||||
if not val_recovered or val_recovered <= val_5of6:
|
||||
raise AssertionError(
|
||||
f"Validated ledger did not advance after recovery "
|
||||
f"({val_5of6} -> {val_recovered})"
|
||||
)
|
||||
log(f"Recovered: validated seq {val_5of6} -> {val_recovered}")
|
||||
|
||||
log("PASS")
|
||||
@@ -1,148 +0,0 @@
|
||||
""":descr: 4/5 liveness, 3/5 fallback-entropy (consensus_fallback), recovery"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from helpers import ZERO_DIGEST, require_entropy, get_entropy_tx, entropy_fields
|
||||
|
||||
|
||||
async def scenario(ctx, log):
|
||||
await require_entropy(ctx, log)
|
||||
|
||||
# Baseline: wait 1 ledger to confirm network is healthy.
|
||||
await ctx.wait_for_ledgers(1, node_id=0, timeout=30)
|
||||
|
||||
# --- 4/5 liveness ---
|
||||
ctx.stop_node(4)
|
||||
await ctx.wait_for_nodes_down(nodes=[4], timeout=30)
|
||||
await ctx.wait_for_ledgers(1, node_id=0, timeout=30)
|
||||
log("4/5: liveness OK")
|
||||
|
||||
# Snapshot validated seq before dropping to 3/5.
|
||||
val_before = ctx.validated_ledger_index(0)
|
||||
|
||||
# --- 3/5 degraded window ---
|
||||
ctx.stop_node(3)
|
||||
await ctx.wait_for_nodes_down(nodes=[3], timeout=30)
|
||||
|
||||
# 10s ≈ 3 rounds at 3s cadence.
|
||||
await ctx.sleep(10)
|
||||
|
||||
val_after = ctx.validated_ledger_index(0)
|
||||
log(f"3/5: validated ledger {val_before} → {val_after}")
|
||||
|
||||
# Accepted/built ledgers may still later appear as validated once the full
|
||||
# network rejoins. For ConsensusEntropy the key invariant is that every
|
||||
# ledger created during this sub-quorum window carries FALLBACK entropy
|
||||
# (consensus_fallback: non-zero consensus-bound digest, count 0) — never
|
||||
# validator-tier entropy.
|
||||
degraded_fallback = 0
|
||||
degraded_end = val_after or val_before
|
||||
if val_before and degraded_end and degraded_end > val_before:
|
||||
for seq in range(val_before + 1, degraded_end + 1):
|
||||
ce, _ = get_entropy_tx(ctx, seq)
|
||||
digest, entropy_count, is_fallback = entropy_fields(ce)
|
||||
tier = ce.get("EntropyTier")
|
||||
|
||||
# consensus_fallback (EntropyTier=1): explicit tier, count 0,
|
||||
# deterministic NON-zero digest.
|
||||
if tier != 1:
|
||||
raise AssertionError(
|
||||
f"Ledger {seq}: expected EntropyTier==1 "
|
||||
f"(consensus_fallback) during 3/5 window, got {tier} "
|
||||
f"(EntropyCount={entropy_count})"
|
||||
)
|
||||
if entropy_count != 0:
|
||||
raise AssertionError(
|
||||
f"Ledger {seq}: fallback EntropyCount must be 0, got "
|
||||
f"{entropy_count}"
|
||||
)
|
||||
if not digest or digest == ZERO_DIGEST:
|
||||
raise AssertionError(
|
||||
f"Ledger {seq}: fallback digest must be non-zero "
|
||||
f"(consensus_fallback), got {digest[:16]}..."
|
||||
)
|
||||
assert is_fallback # tier==1 implies fallback
|
||||
|
||||
degraded_fallback += 1
|
||||
log(
|
||||
f" Degraded ledger {seq}: EntropyCount={entropy_count} "
|
||||
f"FALLBACK"
|
||||
)
|
||||
|
||||
log(f"3/5 entropy summary: {degraded_fallback} fallback")
|
||||
|
||||
# Log checks tied to current transition mechanics:
|
||||
# - commit-set SHAMap publication is the observable output of entering the
|
||||
# commit sidecar phase
|
||||
# - ConvergingCommit transition is the gateway out of seq=0-only behavior
|
||||
# - reason=impossible-entropy-gate is the explicit degraded-window fallback path
|
||||
ctx.log_level("LedgerConsensus", "trace")
|
||||
ctx.log_level("ConsensusExtensions", "trace")
|
||||
op = await ctx.sleep(6, name="stall_window")
|
||||
|
||||
ctx.assert_not_log(
|
||||
r"RNG: transitioned to ConvergingCommit", within=op.window, nodes=[0, 1, 2]
|
||||
)
|
||||
ctx.assert_not_log(
|
||||
r"RNG: built commitSet SHAMap", within=op.window, nodes=[0, 1, 2]
|
||||
)
|
||||
|
||||
gate_blocked = ctx.search_logs(
|
||||
r"STALLDIAG: establish gate blocked reason=(pause|no-tx-consensus)",
|
||||
within=op.window,
|
||||
nodes=[0, 1, 2],
|
||||
)
|
||||
log(f"3/5: establish gate-blocked logs in 6s: {gate_blocked.count}")
|
||||
|
||||
impossible = ctx.search_logs(
|
||||
r"RNG: skipping commit wait reason=impossible-entropy-gate",
|
||||
within=op.window,
|
||||
nodes=[0, 1, 2],
|
||||
)
|
||||
log(f"3/5: RNG impossible-entropy-gate skips in 6s: {impossible.count}")
|
||||
|
||||
# --- Recovery: restart nodes, verify ledger advancement ---
|
||||
ctx.start_node(3)
|
||||
ctx.start_node(4)
|
||||
await ctx.wait_for_ledgers(1, node_id=0, timeout=120)
|
||||
|
||||
val_recovered = ctx.validated_ledger_index(0)
|
||||
pre_recovery = max(v for v in [val_before, val_after] if v is not None)
|
||||
log(f"Recovered: validated seq {pre_recovery} → {val_recovered}")
|
||||
|
||||
if not val_recovered or val_recovered <= pre_recovery:
|
||||
raise AssertionError(
|
||||
f"Validated ledger did not advance after recovery "
|
||||
f"({pre_recovery} → {val_recovered})"
|
||||
)
|
||||
|
||||
# Inspect post-recovery ledgers separately from the degraded window above.
|
||||
# Once the network is back at quorum, validator-tier entropy is expected
|
||||
# again (transitional fallback ledgers are fine) and must be quorum-met.
|
||||
fallback_count = 0
|
||||
validator_count = 0
|
||||
for seq in range(pre_recovery + 1, val_recovered + 1):
|
||||
ce, _ = get_entropy_tx(ctx, seq)
|
||||
digest, entropy_count, is_fallback = entropy_fields(ce)
|
||||
|
||||
if is_fallback:
|
||||
fallback_count += 1
|
||||
else:
|
||||
validator_count += 1
|
||||
if entropy_count < 4:
|
||||
raise AssertionError(
|
||||
f"Ledger {seq}: validator entropy with sub-quorum "
|
||||
f"EntropyCount={entropy_count} (need >= 4)"
|
||||
)
|
||||
|
||||
log(
|
||||
f" Ledger {seq}: EntropyCount={entropy_count} "
|
||||
f"{'FALLBACK' if is_fallback else 'VALIDATOR'}"
|
||||
)
|
||||
|
||||
log(
|
||||
f"Entropy summary: {fallback_count} fallback, "
|
||||
f"{validator_count} validator"
|
||||
)
|
||||
|
||||
log("PASS")
|
||||
@@ -1,44 +0,0 @@
|
||||
""":descr: drop 2 nodes (3/5 stall), restart both, verify recovery"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from helpers import require_entropy
|
||||
|
||||
|
||||
async def scenario(ctx, log):
|
||||
await require_entropy(ctx, log)
|
||||
|
||||
await ctx.wait_for_ledgers(1, node_id=0, timeout=60)
|
||||
log("Baseline OK")
|
||||
|
||||
# Drop 2 nodes → validation stall.
|
||||
ctx.stop_node(3)
|
||||
ctx.stop_node(4)
|
||||
await ctx.wait_for_nodes_down(nodes=[3, 4], timeout=30)
|
||||
|
||||
info = ctx.rpc.server_info(node_id=0)
|
||||
val_before = info.get("info", {}).get("validated_ledger", {}).get("seq", 0)
|
||||
log(f"Stalled at validated seq {val_before}")
|
||||
|
||||
# Let it sit for a few rounds in degraded state.
|
||||
await ctx.sleep(6)
|
||||
|
||||
# Bring both nodes back.
|
||||
ctx.start_node(3)
|
||||
ctx.start_node(4)
|
||||
log("Restarted n3 and n4, waiting for recovery...")
|
||||
|
||||
# Recovery: wait for ANY validated ledger advance on n0.
|
||||
await ctx.wait_for_ledger_close(node_id=0, timeout=60)
|
||||
|
||||
info = ctx.rpc.server_info(node_id=0)
|
||||
val_after = info.get("info", {}).get("validated_ledger", {}).get("seq", 0)
|
||||
log(f"Recovered: validated seq {val_before} → {val_after}")
|
||||
|
||||
if val_after <= val_before:
|
||||
raise AssertionError(
|
||||
f"Validated ledger did not advance after recovery "
|
||||
f"({val_before} → {val_after})"
|
||||
)
|
||||
|
||||
log("PASS")
|
||||
@@ -1,27 +0,0 @@
|
||||
""":descr: all 5 nodes healthy, every ledger has valid unique quorum-met entropy"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from helpers import require_entropy, get_entropy_tx, assert_valid_entropy
|
||||
|
||||
|
||||
async def scenario(ctx, log):
|
||||
await require_entropy(ctx, log)
|
||||
|
||||
# Wait for RNG pipeline to warm up past bootstrap skip.
|
||||
await ctx.wait_for_ledgers(3, node_id=0, timeout=60)
|
||||
log("Pipeline warmed up")
|
||||
|
||||
start_seq = ctx.validated_ledger_index(0)
|
||||
await ctx.wait_for_ledgers(10, node_id=0, timeout=120)
|
||||
end_seq = ctx.validated_ledger_index(0)
|
||||
log(f"Inspecting ledgers {start_seq + 1} → {end_seq}")
|
||||
|
||||
digests = set()
|
||||
for seq in range(start_seq + 1, end_seq + 1):
|
||||
ce, _ = get_entropy_tx(ctx, seq)
|
||||
digest, count = assert_valid_entropy(ce, seq, seen_digests=digests)
|
||||
log(f" Ledger {seq}: EntropyCount={count} Digest={digest[:16]}...")
|
||||
|
||||
log(f"Verified {end_seq - start_seq} ledgers: all quorum entropy, all unique")
|
||||
log("PASS")
|
||||
@@ -1,88 +0,0 @@
|
||||
defaults:
|
||||
network:
|
||||
node_count: 5
|
||||
launcher: tmux
|
||||
find_ports: true
|
||||
slave_delay: 0.2
|
||||
features:
|
||||
- ConsensusEntropy
|
||||
- Export
|
||||
track_features:
|
||||
- ConsensusEntropy
|
||||
- Export
|
||||
log_levels:
|
||||
TxQ: info
|
||||
Protocol: debug
|
||||
Peer: debug
|
||||
LedgerConsensus: debug
|
||||
ConsensusExtensions: debug
|
||||
NetworkOPs: info
|
||||
env:
|
||||
XAHAU_RESOURCE_PER_PORT: "1"
|
||||
XAHAU_RNG_POLL_MS: "333"
|
||||
|
||||
tests:
|
||||
# --- CE + Export (80% quorum, SHAMap convergence) ---
|
||||
- name: steady_state_export_ce
|
||||
script: .testnet/scenarios/export/steady_state_export.py
|
||||
|
||||
- name: retriable_export_ce
|
||||
script: .testnet/scenarios/export/retriable_export.py
|
||||
|
||||
- name: export_degradation_ce
|
||||
script: .testnet/scenarios/export/export_degradation.py
|
||||
network:
|
||||
node_env:
|
||||
3:
|
||||
XAHAUD_NO_EXPORT_SIG: "1"
|
||||
4:
|
||||
XAHAUD_NO_EXPORT_SIG: "1"
|
||||
|
||||
# CE + Export: 1 node suppressed, 4/5 = 80% quorum, should succeed
|
||||
- name: export_ce_one_node_down
|
||||
script: .testnet/scenarios/export/export_quorum.py
|
||||
params:
|
||||
expect_success: true
|
||||
network:
|
||||
node_env:
|
||||
4:
|
||||
XAHAUD_NO_EXPORT_SIG: "1"
|
||||
|
||||
# --- Export only, no CE (80% active-view quorum) ---
|
||||
- name: export_only_all_up
|
||||
script: .testnet/scenarios/export/export_quorum.py
|
||||
params:
|
||||
expect_success: true
|
||||
network:
|
||||
features:
|
||||
- Export
|
||||
track_features:
|
||||
- Export
|
||||
|
||||
- name: export_only_one_node_down
|
||||
script: .testnet/scenarios/export/export_quorum.py
|
||||
params:
|
||||
expect_success: true
|
||||
network:
|
||||
features:
|
||||
- Export
|
||||
track_features:
|
||||
- Export
|
||||
node_env:
|
||||
4:
|
||||
XAHAUD_NO_EXPORT_SIG: "1"
|
||||
|
||||
- name: export_only_two_nodes_down
|
||||
script: .testnet/scenarios/export/export_quorum.py
|
||||
params:
|
||||
expect_success: false
|
||||
network:
|
||||
features:
|
||||
- Export
|
||||
track_features:
|
||||
- Export
|
||||
node_env:
|
||||
3:
|
||||
XAHAUD_NO_EXPORT_SIG: "1"
|
||||
4:
|
||||
XAHAUD_NO_EXPORT_SIG: "1"
|
||||
@@ -1,118 +0,0 @@
|
||||
""":descr: Submit ttEXPORT with 2 nodes suppressing export sigs, verify it
|
||||
retries via terRETRY_EXPORT until LLS expiry (insufficient signatures).
|
||||
|
||||
Nodes 3 and 4 have XAHAUD_NO_EXPORT_SIG=1, so only 3/5 nodes provide
|
||||
export signatures. With 80% quorum = ceil(5*0.8) = 4 required, the
|
||||
export cannot reach quorum and should expire via tecEXPORT_EXPIRED.
|
||||
|
||||
Flow:
|
||||
1. Fund alice and bob
|
||||
2. alice submits ttEXPORT with tight LLS
|
||||
3. Export retries (only 3/5 sigs available, need 4)
|
||||
4. Verify export expires with tecEXPORT_EXPIRED
|
||||
5. Verify subsequent payment still works (sequence not permanently blocked)
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from export_helpers import require_export, assert_shadow_ticket
|
||||
|
||||
|
||||
async def scenario(ctx, log):
|
||||
await require_export(ctx, log)
|
||||
|
||||
# --- Setup ---
|
||||
await ctx.fund_accounts({"alice": 10000, "bob": 1000})
|
||||
log("Accounts funded")
|
||||
|
||||
alice = ctx.account("alice")
|
||||
bob = ctx.account("bob")
|
||||
current_seq = ctx.validated_ledger_index(0)
|
||||
|
||||
log(f"Current ledger: {current_seq}")
|
||||
log("Nodes 3,4 have XAHAUD_NO_EXPORT_SIG=1 (3/5 sigs, need 4)")
|
||||
|
||||
# --- Submit ttEXPORT (should retry then expire -- only 3/5 sigs) ---
|
||||
export_start = ctx.mark("export-degradation-submit-start")
|
||||
result = await ctx.submit_and_wait(
|
||||
{
|
||||
"TransactionType": "Export",
|
||||
"LastLedgerSequence": current_seq + 8,
|
||||
"Fee": "1000000",
|
||||
"ExportedTxn": {
|
||||
"TransactionType": "Payment",
|
||||
"Account": alice.address,
|
||||
"Destination": bob.address,
|
||||
"Amount": "1000000",
|
||||
"Fee": "10",
|
||||
"Sequence": 0,
|
||||
"TicketSequence": 1,
|
||||
"FirstLedgerSequence": current_seq + 1,
|
||||
"LastLedgerSequence": current_seq + 6,
|
||||
"Flags": 2147483648,
|
||||
"SigningPubKey": "",
|
||||
},
|
||||
},
|
||||
alice.wallet,
|
||||
timeout=60,
|
||||
)
|
||||
export_end = ctx.mark("export-degradation-submit-end")
|
||||
|
||||
final_seq = ctx.validated_ledger_index(0)
|
||||
engine_result = result.get("engine_result", "")
|
||||
log(f"Export completed at ledger {final_seq}, result: {engine_result}")
|
||||
|
||||
# With only 3/5 sigs and 80% quorum (4 required), export MUST fail
|
||||
if engine_result == "tesSUCCESS":
|
||||
raise AssertionError(
|
||||
"Export should NOT have succeeded with only 3/5 sigs "
|
||||
"(need 4 for 80% quorum) -- check XAHAUD_NO_EXPORT_SIG config"
|
||||
)
|
||||
|
||||
# Should be tecEXPORT_EXPIRED (LLS reached without quorum)
|
||||
if engine_result != "tecEXPORT_EXPIRED":
|
||||
log(f"WARNING: expected tecEXPORT_EXPIRED, got {engine_result}")
|
||||
|
||||
log(f"Export failed as expected ({engine_result})")
|
||||
|
||||
retry_logs = ctx.assert_log(
|
||||
r"Export: insufficient signatures .*result=terRETRY_EXPORT",
|
||||
since=export_start,
|
||||
until=export_end,
|
||||
)
|
||||
log(f"Export insufficient-signature retries: {retry_logs.count}")
|
||||
|
||||
expired_logs = ctx.assert_log(
|
||||
r"Export: last ledger expired .*result=tecEXPORT_EXPIRED",
|
||||
since=export_start,
|
||||
until=export_end,
|
||||
)
|
||||
log(f"Export LLS expiry logs: {expired_logs.count}")
|
||||
|
||||
# No shadow ticket should exist (export never reached quorum)
|
||||
assert_shadow_ticket(ctx, alice.address, log, expect_exists=False)
|
||||
|
||||
# --- Verify subsequent payment works regardless ---
|
||||
log("Submitting payment from alice to bob...")
|
||||
pay_result = await ctx.submit_and_wait(
|
||||
{
|
||||
"TransactionType": "Payment",
|
||||
"Destination": bob.address,
|
||||
"Amount": "1000000",
|
||||
"Fee": "12",
|
||||
},
|
||||
alice.wallet,
|
||||
timeout=30,
|
||||
)
|
||||
|
||||
pay_engine = pay_result.get("engine_result", "")
|
||||
log(f"Payment result: {pay_engine}")
|
||||
|
||||
if pay_engine != "tesSUCCESS":
|
||||
raise AssertionError(
|
||||
f"Payment failed after expired export: {pay_engine} "
|
||||
f"-- sequence may be blocked"
|
||||
)
|
||||
|
||||
log("Payment succeeded -- account not permanently blocked")
|
||||
log("PASS")
|
||||
@@ -1,146 +0,0 @@
|
||||
"""Shared helpers for Export scenario tests."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from xahaud_scripts.testnet.config import feature_name_to_hash
|
||||
|
||||
|
||||
async def require_export(ctx, log):
|
||||
"""Wait for first ledger and assert Export is enabled."""
|
||||
await ctx.wait_for_ledger_close(timeout=120)
|
||||
feature = ctx.feature_check(feature_name_to_hash("Export"), node_id=0)
|
||||
if not feature or not feature.get("enabled", False):
|
||||
raise AssertionError(f"Export not enabled: {feature}")
|
||||
log("Export enabled")
|
||||
|
||||
|
||||
def find_export_txns(ctx, seq):
|
||||
"""Find Export transactions in a ledger.
|
||||
|
||||
Returns list of Export transaction dicts.
|
||||
"""
|
||||
result = ctx.ledger(seq, transactions=True)
|
||||
if not result:
|
||||
return []
|
||||
|
||||
txns = result.get("ledger", {}).get("transactions", [])
|
||||
return [tx for tx in txns if tx.get("TransactionType") == "Export"]
|
||||
|
||||
|
||||
def dst_param(address):
|
||||
"""Encode an address as a HookParameter entry for the DST param."""
|
||||
from xrpl.core.addresscodec import decode_classic_address
|
||||
|
||||
dst_hex = decode_classic_address(address).hex().upper()
|
||||
return {
|
||||
"HookParameter": {
|
||||
"HookParameterName": "445354", # "DST"
|
||||
"HookParameterValue": dst_hex,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def assert_hook_accepted(meta, log, *, expected_emits=1):
|
||||
"""Assert hook executed with ACCEPT and the expected emit count.
|
||||
|
||||
Checks sfHookExecutions in transaction metadata.
|
||||
Returns the hook execution entry for further inspection.
|
||||
"""
|
||||
hook_execs = meta.get("HookExecutions", [])
|
||||
if not hook_execs:
|
||||
raise AssertionError("No HookExecutions in metadata")
|
||||
|
||||
exec_entry = hook_execs[0].get("HookExecution", {})
|
||||
hook_result = exec_entry.get("HookResult", -1)
|
||||
emit_count = exec_entry.get("HookEmitCount", -1)
|
||||
return_code = exec_entry.get("HookReturnCode", "")
|
||||
|
||||
log(f" HookResult={hook_result} EmitCount={emit_count} ReturnCode={return_code}")
|
||||
|
||||
# HookResult 3 = ExitType::ACCEPT
|
||||
if hook_result != 3:
|
||||
raise AssertionError(
|
||||
f"Hook did not ACCEPT: HookResult={hook_result} "
|
||||
f"ReturnCode={return_code}"
|
||||
)
|
||||
|
||||
if emit_count != expected_emits:
|
||||
raise AssertionError(
|
||||
f"Expected {expected_emits} emits, got {emit_count}"
|
||||
)
|
||||
|
||||
# ReturnCode 0 = success; non-zero = ASSERT line number in hook
|
||||
if return_code and str(return_code) != "0":
|
||||
raise AssertionError(
|
||||
f"Hook returned error code {return_code} "
|
||||
f"(likely ASSERT failure at that line)"
|
||||
)
|
||||
|
||||
return exec_entry
|
||||
|
||||
|
||||
def assert_export_result(meta, log, *, require_signers=True):
|
||||
"""Assert ExportResult is present and well-formed in metadata.
|
||||
|
||||
Returns the ExportResult dict.
|
||||
"""
|
||||
export_result = meta.get("ExportResult", {})
|
||||
if not export_result:
|
||||
raise AssertionError("ExportResult not found in metadata")
|
||||
|
||||
# Must have LedgerSequence and TransactionHash
|
||||
if "LedgerSequence" not in export_result:
|
||||
raise AssertionError("ExportResult missing LedgerSequence")
|
||||
if "TransactionHash" not in export_result:
|
||||
raise AssertionError("ExportResult missing TransactionHash")
|
||||
|
||||
# Must have the inner ExportedTxn object
|
||||
inner = export_result.get("ExportedTxn", {})
|
||||
if not inner:
|
||||
raise AssertionError("ExportResult missing ExportedTxn (multisigned blob)")
|
||||
|
||||
log(f" ExportResult: seq={export_result['LedgerSequence']} "
|
||||
f"hash={export_result['TransactionHash'][:16]}...")
|
||||
|
||||
# Inner tx should have Account, Destination, TransactionType
|
||||
if "Account" not in inner:
|
||||
raise AssertionError("ExportedTxn missing Account")
|
||||
if "TransactionType" not in inner:
|
||||
raise AssertionError("ExportedTxn missing TransactionType")
|
||||
|
||||
# Should have empty SigningPubKey (multisigned)
|
||||
if inner.get("SigningPubKey", "NOT_EMPTY") != "":
|
||||
raise AssertionError(
|
||||
f"ExportedTxn SigningPubKey should be empty, "
|
||||
f"got '{inner.get('SigningPubKey')}'"
|
||||
)
|
||||
|
||||
if require_signers:
|
||||
signers = inner.get("Signers", [])
|
||||
if not signers:
|
||||
raise AssertionError("ExportedTxn has no Signers (multisig not applied)")
|
||||
log(f" Signers: {len(signers)} validator(s)")
|
||||
|
||||
return export_result
|
||||
|
||||
|
||||
def assert_shadow_ticket(ctx, account_address, log, *, expect_exists=True):
|
||||
"""Assert shadow ticket exists (or doesn't) for the account."""
|
||||
obj_result = ctx.rpc.request(
|
||||
0, "account_objects", {"account": account_address}
|
||||
)
|
||||
all_objects = (obj_result or {}).get("account_objects", [])
|
||||
shadow_tickets = [
|
||||
obj for obj in all_objects
|
||||
if obj.get("LedgerEntryType") == "ShadowTicket"
|
||||
]
|
||||
log(f" Shadow tickets: {len(shadow_tickets)}")
|
||||
|
||||
if expect_exists and not shadow_tickets:
|
||||
raise AssertionError("Expected shadow ticket but none found")
|
||||
if not expect_exists and shadow_tickets:
|
||||
raise AssertionError(
|
||||
f"Expected no shadow tickets but found {len(shadow_tickets)}"
|
||||
)
|
||||
|
||||
return shadow_tickets
|
||||
@@ -1,112 +0,0 @@
|
||||
""":descr: Test Export quorum behavior. When enough active validators sign,
|
||||
the export should succeed whether or not CE is enabled. When fewer than the
|
||||
active-view quorum sign, the export should expire.
|
||||
|
||||
Parameterized via `expect_success` kwarg from suite.yml.
|
||||
|
||||
Flow:
|
||||
1. Fund alice and bob
|
||||
2. alice submits ttEXPORT
|
||||
3. Verify result matches expectation (tesSUCCESS or tecEXPORT_EXPIRED)
|
||||
4. Verify ExportResult + shadow ticket on success, absence on failure
|
||||
5. Verify subsequent payment works regardless
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from export_helpers import (
|
||||
require_export,
|
||||
assert_export_result,
|
||||
assert_shadow_ticket,
|
||||
)
|
||||
|
||||
|
||||
async def scenario(ctx, log, expect_success=True):
|
||||
await require_export(ctx, log)
|
||||
|
||||
# --- Setup ---
|
||||
await ctx.fund_accounts({"alice": 10000, "bob": 1000})
|
||||
log("Accounts funded")
|
||||
|
||||
alice = ctx.account("alice")
|
||||
bob = ctx.account("bob")
|
||||
current_seq = ctx.validated_ledger_index(0)
|
||||
|
||||
log(f"Current ledger: {current_seq}")
|
||||
outcome = "success" if expect_success else "failure (below quorum)"
|
||||
log(f"Expecting export {outcome}")
|
||||
|
||||
# --- Submit ttEXPORT ---
|
||||
result = await ctx.submit_and_wait(
|
||||
{
|
||||
"TransactionType": "Export",
|
||||
"LastLedgerSequence": current_seq + 10,
|
||||
"Fee": "1000000",
|
||||
"ExportedTxn": {
|
||||
"TransactionType": "Payment",
|
||||
"Account": alice.address,
|
||||
"Destination": bob.address,
|
||||
"Amount": "1000000",
|
||||
"Fee": "10",
|
||||
"Sequence": 0,
|
||||
"TicketSequence": 1,
|
||||
"FirstLedgerSequence": current_seq + 1,
|
||||
"LastLedgerSequence": current_seq + 8,
|
||||
"Flags": 2147483648,
|
||||
"SigningPubKey": "",
|
||||
},
|
||||
},
|
||||
alice.wallet,
|
||||
timeout=60,
|
||||
)
|
||||
|
||||
final_seq = ctx.validated_ledger_index(0)
|
||||
engine_result = result.get("engine_result", "")
|
||||
meta = result.get("meta", {})
|
||||
|
||||
log(f"Export at ledger {final_seq}, result: {engine_result}")
|
||||
|
||||
if expect_success:
|
||||
if engine_result != "tesSUCCESS":
|
||||
raise AssertionError(
|
||||
f"Expected tesSUCCESS, got {engine_result}"
|
||||
)
|
||||
|
||||
# Assert ExportResult is well-formed with signers
|
||||
assert_export_result(meta, log, require_signers=True)
|
||||
|
||||
# Assert shadow ticket was created
|
||||
assert_shadow_ticket(ctx, alice.address, log, expect_exists=True)
|
||||
|
||||
log("Export succeeded as expected (active-view quorum reached)")
|
||||
else:
|
||||
if engine_result == "tesSUCCESS":
|
||||
raise AssertionError(
|
||||
"Export should NOT have succeeded below active-view quorum"
|
||||
)
|
||||
log(f"Export failed as expected ({engine_result})")
|
||||
|
||||
# No shadow ticket should exist
|
||||
assert_shadow_ticket(ctx, alice.address, log, expect_exists=False)
|
||||
|
||||
# --- Verify subsequent payment works ---
|
||||
log("Submitting payment from alice to bob...")
|
||||
pay_result = await ctx.submit_and_wait(
|
||||
{
|
||||
"TransactionType": "Payment",
|
||||
"Destination": bob.address,
|
||||
"Amount": "1000000",
|
||||
"Fee": "12",
|
||||
},
|
||||
alice.wallet,
|
||||
timeout=30,
|
||||
)
|
||||
|
||||
pay_engine = pay_result.get("engine_result", "")
|
||||
log(f"Payment result: {pay_engine}")
|
||||
|
||||
if pay_engine != "tesSUCCESS":
|
||||
raise AssertionError(f"Payment failed: {pay_engine}")
|
||||
|
||||
log("Payment succeeded -- account not blocked")
|
||||
log("PASS")
|
||||
@@ -1,94 +0,0 @@
|
||||
""":descr: Submit ttEXPORT directly (no hook), verify it succeeds with
|
||||
ExportResult in metadata. Then submit a payment from the same account
|
||||
to verify sequence handling doesn't block subsequent transactions.
|
||||
|
||||
Flow:
|
||||
1. Fund alice and bob
|
||||
2. alice submits ttEXPORT with inner payment -> tesSUCCESS (provisional)
|
||||
3. Validators attach sigs via proposals -> quorum -> ExportResult in metadata
|
||||
4. alice submits a Payment to bob -> should succeed (sequence not blocked)
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from export_helpers import require_export, assert_export_result, assert_shadow_ticket
|
||||
|
||||
|
||||
async def scenario(ctx, log):
|
||||
await require_export(ctx, log)
|
||||
|
||||
# --- Setup ---
|
||||
await ctx.fund_accounts({"alice": 10000, "bob": 1000})
|
||||
log("Accounts funded")
|
||||
|
||||
alice = ctx.account("alice")
|
||||
bob = ctx.account("bob")
|
||||
current_seq = ctx.validated_ledger_index(0)
|
||||
|
||||
log(f"Current ledger: {current_seq}")
|
||||
|
||||
# --- 1. Submit ttEXPORT ---
|
||||
result = await ctx.submit_and_wait(
|
||||
{
|
||||
"TransactionType": "Export",
|
||||
"LastLedgerSequence": current_seq + 15,
|
||||
"Fee": "1000000",
|
||||
"ExportedTxn": {
|
||||
"TransactionType": "Payment",
|
||||
"Account": alice.address,
|
||||
"Destination": bob.address,
|
||||
"Amount": "1000000",
|
||||
"Fee": "10",
|
||||
"Sequence": 0,
|
||||
"TicketSequence": 1,
|
||||
"FirstLedgerSequence": current_seq + 1,
|
||||
"LastLedgerSequence": current_seq + 10,
|
||||
"Flags": 2147483648,
|
||||
"SigningPubKey": "",
|
||||
},
|
||||
},
|
||||
alice.wallet,
|
||||
timeout=60,
|
||||
)
|
||||
|
||||
export_seq = ctx.validated_ledger_index(0)
|
||||
engine_result = result.get("engine_result", "")
|
||||
log(f"Export completed at ledger {export_seq}, result: {engine_result}")
|
||||
|
||||
if engine_result != "tesSUCCESS":
|
||||
raise AssertionError(
|
||||
f"Expected tesSUCCESS for export, got {engine_result}"
|
||||
)
|
||||
|
||||
# Assert ExportResult is well-formed with signers
|
||||
meta = result.get("meta", {})
|
||||
assert_export_result(meta, log, require_signers=True)
|
||||
|
||||
# Assert shadow ticket was created
|
||||
assert_shadow_ticket(ctx, alice.address, log, expect_exists=True)
|
||||
|
||||
# --- 2. Submit Payment from same account ---
|
||||
log("Submitting payment from alice to bob...")
|
||||
pay_result = await ctx.submit_and_wait(
|
||||
{
|
||||
"TransactionType": "Payment",
|
||||
"Destination": bob.address,
|
||||
"Amount": "1000000",
|
||||
"Fee": "12",
|
||||
},
|
||||
alice.wallet,
|
||||
timeout=30,
|
||||
)
|
||||
|
||||
pay_engine = pay_result.get("engine_result", "")
|
||||
log(f"Payment result: {pay_engine}")
|
||||
|
||||
if pay_engine != "tesSUCCESS":
|
||||
raise AssertionError(f"Payment failed: {pay_engine}")
|
||||
|
||||
log(
|
||||
f"Both transactions succeeded: "
|
||||
f"Export at ledger {export_seq}, Payment at ledger {ctx.validated_ledger_index(0)}"
|
||||
)
|
||||
log("Sequence handling OK - export didn't block subsequent txns")
|
||||
log("PASS")
|
||||
@@ -1,211 +0,0 @@
|
||||
""":descr: install xport hook, trigger export, verify emitted ttEXPORT lifecycle
|
||||
|
||||
1. Fund alice (hook holder), bob (trigger), carol (export destination)
|
||||
2. Install xport hook on alice
|
||||
3. bob pays alice with DST=carol → hook calls xport() → emits ttEXPORT
|
||||
4. Emitted ttEXPORT enters open ledger, validators attach sigs via proposals
|
||||
5. Verify Export transaction appears in a subsequent ledger
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from export_helpers import (
|
||||
require_export,
|
||||
find_export_txns,
|
||||
dst_param,
|
||||
assert_hook_accepted,
|
||||
assert_export_result,
|
||||
assert_shadow_ticket,
|
||||
)
|
||||
|
||||
# C source for the xport hook — verbatim from src/test/app/Export_test_hooks.h
|
||||
# On Payment to the hook account, exports a 1 XAH payment to the DST param.
|
||||
XPORT_HOOK_C = r"""
|
||||
#include <stdint.h>
|
||||
extern int32_t _g(uint32_t id, uint32_t maxiter);
|
||||
extern int64_t accept(uint32_t read_ptr, uint32_t read_len, int64_t error_code);
|
||||
extern int64_t rollback(uint32_t read_ptr, uint32_t read_len, int64_t error_code);
|
||||
extern int64_t xport(uint32_t write_ptr, uint32_t write_len, uint32_t read_ptr, uint32_t read_len);
|
||||
extern int64_t xport_reserve(uint32_t count);
|
||||
extern int64_t hook_account(uint32_t write_ptr, uint32_t write_len);
|
||||
extern int64_t otxn_param(uint32_t write_ptr, uint32_t write_len, uint32_t name_ptr, uint32_t name_len);
|
||||
extern int64_t otxn_type(void);
|
||||
extern int64_t ledger_seq(void);
|
||||
|
||||
#define SBUF(x) (uint32_t)(x), sizeof(x)
|
||||
#define ASSERT(x) if (!(x)) rollback((uint32_t)#x, sizeof(#x), __LINE__)
|
||||
|
||||
#define ttPAYMENT 0
|
||||
#define tfCANONICAL 0x80000000UL
|
||||
#define amAMOUNT 1
|
||||
#define amFEE 8
|
||||
#define atACCOUNT 1
|
||||
#define atDESTINATION 3
|
||||
|
||||
#define ENCODE_TT(buf_out, tt) \
|
||||
buf_out[0] = 0x12U; buf_out[1] = (tt >> 8) & 0xFFU; buf_out[2] = tt & 0xFFU; buf_out += 3;
|
||||
|
||||
#define ENCODE_FLAGS(buf_out, flags) \
|
||||
buf_out[0] = 0x22U; buf_out[1] = (flags >> 24) & 0xFFU; buf_out[2] = (flags >> 16) & 0xFFU; \
|
||||
buf_out[3] = (flags >> 8) & 0xFFU; buf_out[4] = flags & 0xFFU; buf_out += 5;
|
||||
|
||||
#define ENCODE_SEQUENCE(buf_out, seq) \
|
||||
buf_out[0] = 0x24U; buf_out[1] = (seq >> 24) & 0xFFU; buf_out[2] = (seq >> 16) & 0xFFU; \
|
||||
buf_out[3] = (seq >> 8) & 0xFFU; buf_out[4] = seq & 0xFFU; buf_out += 5;
|
||||
|
||||
#define ENCODE_FLS(buf_out, fls) \
|
||||
buf_out[0] = 0x20U; buf_out[1] = 0x1AU; buf_out[2] = (fls >> 24) & 0xFFU; \
|
||||
buf_out[3] = (fls >> 16) & 0xFFU; buf_out[4] = (fls >> 8) & 0xFFU; \
|
||||
buf_out[5] = fls & 0xFFU; buf_out += 6;
|
||||
|
||||
#define ENCODE_LLS(buf_out, lls) \
|
||||
buf_out[0] = 0x20U; buf_out[1] = 0x1BU; buf_out[2] = (lls >> 24) & 0xFFU; \
|
||||
buf_out[3] = (lls >> 16) & 0xFFU; buf_out[4] = (lls >> 8) & 0xFFU; \
|
||||
buf_out[5] = lls & 0xFFU; buf_out += 6;
|
||||
|
||||
#define ENCODE_DROPS(buf_out, drops, amt_type) \
|
||||
buf_out[0] = 0x60U + amt_type; buf_out[1] = 0x40U + ((drops >> 56) & 0x3FU); \
|
||||
buf_out[2] = (drops >> 48) & 0xFFU; buf_out[3] = (drops >> 40) & 0xFFU; \
|
||||
buf_out[4] = (drops >> 32) & 0xFFU; buf_out[5] = (drops >> 24) & 0xFFU; \
|
||||
buf_out[6] = (drops >> 16) & 0xFFU; buf_out[7] = (drops >> 8) & 0xFFU; \
|
||||
buf_out[8] = drops & 0xFFU; buf_out += 9;
|
||||
|
||||
#define ENCODE_SIGNING_PUBKEY_EMPTY(buf_out) \
|
||||
buf_out[0] = 0x73U; buf_out[1] = 0x00U; buf_out += 2;
|
||||
|
||||
#define ENCODE_ACCOUNT(buf_out, acc, acc_type) \
|
||||
buf_out[0] = 0x80U + acc_type; buf_out[1] = 0x14U; \
|
||||
for (int i = 0; i < 20; ++i) buf_out[2+i] = acc[i]; buf_out += 22;
|
||||
|
||||
#define PREPARE_PAYMENT_SIMPLE_SIZE 270U
|
||||
|
||||
int64_t hook(uint32_t reserved) {
|
||||
_g(1, 1);
|
||||
|
||||
if (otxn_type() != ttPAYMENT)
|
||||
return accept(0, 0, 0);
|
||||
|
||||
ASSERT(xport_reserve(1) == 1);
|
||||
|
||||
uint8_t dst[20];
|
||||
int64_t dst_len = otxn_param(SBUF(dst), "DST", 3);
|
||||
ASSERT(dst_len == 20);
|
||||
|
||||
uint8_t acc[20];
|
||||
ASSERT(hook_account(SBUF(acc)) == 20);
|
||||
|
||||
uint32_t cls = (uint32_t)ledger_seq();
|
||||
|
||||
uint8_t tx[PREPARE_PAYMENT_SIMPLE_SIZE];
|
||||
uint8_t* buf = tx;
|
||||
|
||||
ENCODE_TT(buf, ttPAYMENT);
|
||||
ENCODE_FLAGS(buf, tfCANONICAL);
|
||||
ENCODE_SEQUENCE(buf, 0);
|
||||
ENCODE_FLS(buf, cls + 1);
|
||||
ENCODE_LLS(buf, cls + 5);
|
||||
// sfTicketSequence = UINT32 field 41 = 0x20 0x29
|
||||
buf[0] = 0x20U; buf[1] = 0x29U;
|
||||
buf[2] = 0; buf[3] = 0; buf[4] = 0; buf[5] = 1;
|
||||
buf += 6;
|
||||
|
||||
uint64_t drops = 1000000;
|
||||
ENCODE_DROPS(buf, drops, amAMOUNT);
|
||||
ENCODE_DROPS(buf, 10, amFEE);
|
||||
|
||||
ENCODE_SIGNING_PUBKEY_EMPTY(buf);
|
||||
ENCODE_ACCOUNT(buf, acc, atACCOUNT);
|
||||
ENCODE_ACCOUNT(buf, dst, atDESTINATION);
|
||||
|
||||
uint8_t hash[32];
|
||||
int64_t xport_result = xport(SBUF(hash), (uint32_t)tx, buf - tx);
|
||||
ASSERT(xport_result == 32);
|
||||
|
||||
return accept(0, 0, 0);
|
||||
}
|
||||
"""
|
||||
|
||||
|
||||
async def scenario(ctx, log):
|
||||
# Wait for network to start and amendments to activate
|
||||
await require_export(ctx, log)
|
||||
|
||||
# --- Setup ---
|
||||
await ctx.fund_accounts({"alice": 10000, "bob": 10000, "carol": 1000})
|
||||
log("Accounts funded")
|
||||
|
||||
alice = ctx.account("alice")
|
||||
carol = ctx.account("carol")
|
||||
|
||||
# Compile and install xport hook on alice
|
||||
wasm = ctx.compile_hook(XPORT_HOOK_C, label="xport")
|
||||
await ctx.submit_and_wait(
|
||||
{
|
||||
"TransactionType": "SetHook",
|
||||
"Hooks": [
|
||||
{
|
||||
"Hook": {
|
||||
"CreateCode": wasm.hex().upper(),
|
||||
"HookOn": "0" * 64,
|
||||
"HookNamespace": "0" * 64,
|
||||
"HookApiVersion": 0,
|
||||
"Flags": 1, # hsfOVERRIDE
|
||||
}
|
||||
}
|
||||
],
|
||||
"Fee": "100000000",
|
||||
},
|
||||
alice.wallet,
|
||||
)
|
||||
log(
|
||||
f"Hook installed on alice ({alice.address[:12]}...) "
|
||||
f"ledger {ctx.validated_ledger_index(0)}"
|
||||
)
|
||||
|
||||
# --- Trigger ---
|
||||
# bob pays alice → hook calls xport() → emits ttEXPORT
|
||||
trigger_result = await ctx.submit_and_wait(
|
||||
{
|
||||
"TransactionType": "Payment",
|
||||
"Destination": alice.address,
|
||||
"Amount": "100000000",
|
||||
"Fee": "1000000",
|
||||
"HookParameters": [dst_param(carol.address)],
|
||||
},
|
||||
ctx.account("bob").wallet,
|
||||
)
|
||||
trigger_seq = ctx.validated_ledger_index(0)
|
||||
log(f"Export triggered at ledger {trigger_seq}")
|
||||
|
||||
# Assert hook fired with ACCEPT and emitted 1 tx
|
||||
trigger_meta = trigger_result.get("meta", {})
|
||||
assert_hook_accepted(trigger_meta, log, expected_emits=1)
|
||||
|
||||
# --- Verify: check each ledger close for the Export transaction ---
|
||||
max_ledgers = 10
|
||||
for i in range(max_ledgers):
|
||||
await ctx.wait_for_ledgers(1, node_id=0, timeout=30)
|
||||
seq = ctx.validated_ledger_index(0)
|
||||
exports = find_export_txns(ctx, seq)
|
||||
if exports:
|
||||
export_tx = exports[0]
|
||||
meta = export_tx.get("meta", export_tx.get("metaData", {}))
|
||||
result = meta.get("TransactionResult", "")
|
||||
log(f"Ledger {seq}: Export txn found, result={result}")
|
||||
|
||||
if result != "tesSUCCESS":
|
||||
raise AssertionError(f"Export did not succeed: {result}")
|
||||
|
||||
# Assert ExportResult is well-formed with signers and inner tx
|
||||
assert_export_result(meta, log, require_signers=True)
|
||||
|
||||
# Assert shadow ticket was created
|
||||
assert_shadow_ticket(ctx, alice.address, log, expect_exists=True)
|
||||
|
||||
log("PASS")
|
||||
return
|
||||
log(f"Ledger {seq}: no Export txn yet")
|
||||
|
||||
raise AssertionError(
|
||||
f"No Export transaction found after {max_ledgers} ledger closes"
|
||||
)
|
||||
@@ -1,180 +0,0 @@
|
||||
"""Shared helpers for ConsensusEntropy scenario tests."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from xahaud_scripts.testnet.config import feature_name_to_hash
|
||||
|
||||
ZERO_DIGEST = "0" * 64
|
||||
CONSENSUS_ENTROPY_FEATURE = feature_name_to_hash("ConsensusEntropy")
|
||||
|
||||
|
||||
def feature_hash(name: str) -> str:
|
||||
"""Return the amendment hash accepted by feature RPC."""
|
||||
return feature_name_to_hash(name)
|
||||
|
||||
|
||||
def feature_status(ctx, name: str, node_id=0):
|
||||
"""Query a feature by amendment hash; feature RPC names are ambiguous."""
|
||||
return ctx.feature_check(feature_hash(name), node_id=node_id)
|
||||
|
||||
|
||||
def consensus_entropy_feature(ctx, node_id=0):
|
||||
"""Query ConsensusEntropy by amendment hash."""
|
||||
return feature_status(ctx, "ConsensusEntropy", node_id=node_id)
|
||||
|
||||
|
||||
async def require_entropy(ctx, log):
|
||||
"""Wait for first ledger and assert ConsensusEntropy is enabled."""
|
||||
await ctx.wait_for_ledger_close(timeout=120)
|
||||
feature = consensus_entropy_feature(ctx, node_id=0)
|
||||
if not feature or not feature.get("enabled", False):
|
||||
raise AssertionError(f"ConsensusEntropy not enabled: {feature}")
|
||||
log("ConsensusEntropy enabled")
|
||||
|
||||
|
||||
def get_entropy_tx(ctx, seq):
|
||||
"""Fetch ledger and return (ce_tx, user_txns) or raise."""
|
||||
result = ctx.ledger(seq, transactions=True)
|
||||
if not result:
|
||||
raise AssertionError(f"Ledger {seq}: fetch failed")
|
||||
|
||||
ledger = result.get("ledger")
|
||||
if not isinstance(ledger, dict):
|
||||
raise AssertionError(f"Ledger {seq}: fetch returned no ledger: {result}")
|
||||
|
||||
txns = ledger.get("transactions", [])
|
||||
ce = [tx for tx in txns if tx.get("TransactionType") == "ConsensusEntropy"]
|
||||
user = [tx for tx in txns if tx.get("TransactionType") != "ConsensusEntropy"]
|
||||
|
||||
if len(ce) != 1:
|
||||
raise AssertionError(
|
||||
f"Ledger {seq}: expected 1 ConsensusEntropy txn, got {len(ce)}"
|
||||
)
|
||||
|
||||
return ce[0], user
|
||||
|
||||
|
||||
def entropy_fields(ce_tx):
|
||||
"""Return (digest, entropy_count, is_fallback) from a ConsensusEntropy tx.
|
||||
|
||||
consensus_fallback rounds carry a deterministic non-zero consensus-bound
|
||||
digest with EntropyCount=0 and EntropyTier=1 (consensus_fallback).
|
||||
Validator entropy has EntropyTier=3 (validator_quorum).
|
||||
|
||||
WARNING: is_fallback is ``tier != 3``, so it lumps participant_aligned
|
||||
(Tier 2) in with fallback. It is only safe where no Tier 2 band exists
|
||||
(e.g. 5-node networks, where tier2 == quorum). For band-aware scenarios use
|
||||
the explicit assert_consensus_fallback / assert_participant_aligned /
|
||||
assert_validator_quorum helpers, which check EntropyTier directly.
|
||||
"""
|
||||
digest = ce_tx.get("Digest", "")
|
||||
entropy_count = ce_tx.get("EntropyCount", -1)
|
||||
tier = ce_tx.get("EntropyTier", None)
|
||||
if tier is not None:
|
||||
is_fallback = tier != 3
|
||||
else:
|
||||
is_fallback = entropy_count == 0
|
||||
return digest, entropy_count, is_fallback
|
||||
|
||||
|
||||
def assert_participant_aligned(ce_tx, seq, expected_count=None):
|
||||
"""Assert participant_aligned (Tier 2) entropy on a ConsensusEntropy tx.
|
||||
|
||||
Tier 2 is the sub-quorum band: the agreed reveal cohort is >= the
|
||||
participant floor but < the 80% validator quorum, so it carries
|
||||
EntropyTier=2 with a deterministic non-zero digest. NOTE entropy_fields()'s
|
||||
is_fallback lumps tier 2 in with fallback (is_fallback = tier != 3), so the
|
||||
tier must be checked EXPLICITLY here.
|
||||
"""
|
||||
digest = ce_tx.get("Digest", "")
|
||||
count = ce_tx.get("EntropyCount", -1)
|
||||
tier = ce_tx.get("EntropyTier", None)
|
||||
if tier != 2:
|
||||
raise AssertionError(
|
||||
f"Ledger {seq}: expected EntropyTier==2 (participant_aligned), "
|
||||
f"got {tier} (EntropyCount={count})"
|
||||
)
|
||||
if not digest or digest == ZERO_DIGEST:
|
||||
raise AssertionError(
|
||||
f"Ledger {seq}: participant_aligned digest must be non-zero, got "
|
||||
f"{digest[:16]}..."
|
||||
)
|
||||
if expected_count is not None and count != expected_count:
|
||||
raise AssertionError(
|
||||
f"Ledger {seq}: participant_aligned EntropyCount must be "
|
||||
f"{expected_count} (the surviving cohort), got {count}"
|
||||
)
|
||||
return digest, count
|
||||
|
||||
|
||||
def assert_validator_quorum(ce_tx, seq, min_count=None):
|
||||
"""Assert validator_quorum (Tier 3) entropy on a ConsensusEntropy tx:
|
||||
EntropyTier=3, a deterministic non-zero digest, and (optionally)
|
||||
EntropyCount >= min_count (the active quorum). The count can EXCEED the
|
||||
quorum (e.g. a still-full 6/6 ledger caught at a 6->5 transition), so check
|
||||
>=, not ==.
|
||||
"""
|
||||
digest = ce_tx.get("Digest", "")
|
||||
count = ce_tx.get("EntropyCount", -1)
|
||||
tier = ce_tx.get("EntropyTier", None)
|
||||
if tier != 3:
|
||||
raise AssertionError(
|
||||
f"Ledger {seq}: expected EntropyTier==3 (validator_quorum), got "
|
||||
f"{tier} (EntropyCount={count})"
|
||||
)
|
||||
if not digest or digest == ZERO_DIGEST:
|
||||
raise AssertionError(
|
||||
f"Ledger {seq}: validator_quorum digest must be non-zero, got "
|
||||
f"{digest[:16]}..."
|
||||
)
|
||||
if min_count is not None and count < min_count:
|
||||
raise AssertionError(
|
||||
f"Ledger {seq}: validator_quorum EntropyCount={count} < quorum "
|
||||
f"{min_count}"
|
||||
)
|
||||
return digest, count
|
||||
|
||||
|
||||
def assert_consensus_fallback(ce_tx, seq):
|
||||
"""Assert consensus_fallback (Tier 1) entropy on a ConsensusEntropy tx:
|
||||
EntropyTier=1, EntropyCount=0, and a deterministic NON-zero digest.
|
||||
"""
|
||||
digest = ce_tx.get("Digest", "")
|
||||
count = ce_tx.get("EntropyCount", -1)
|
||||
tier = ce_tx.get("EntropyTier", None)
|
||||
if tier != 1:
|
||||
raise AssertionError(
|
||||
f"Ledger {seq}: expected EntropyTier==1 (consensus_fallback), got "
|
||||
f"{tier} (EntropyCount={count})"
|
||||
)
|
||||
if count != 0:
|
||||
raise AssertionError(
|
||||
f"Ledger {seq}: consensus_fallback EntropyCount must be 0, got "
|
||||
f"{count}"
|
||||
)
|
||||
if not digest or digest == ZERO_DIGEST:
|
||||
raise AssertionError(
|
||||
f"Ledger {seq}: consensus_fallback digest must be non-zero, got "
|
||||
f"{digest[:16]}..."
|
||||
)
|
||||
return digest, count
|
||||
|
||||
|
||||
def assert_valid_entropy(ce_tx, seq, seen_digests=None):
|
||||
"""Assert quorum-met validator entropy. Optionally check uniqueness."""
|
||||
digest, entropy_count, is_fallback = entropy_fields(ce_tx)
|
||||
|
||||
if is_fallback or not digest or digest == ZERO_DIGEST:
|
||||
raise AssertionError(f"Ledger {seq}: fallback/empty Digest")
|
||||
|
||||
if entropy_count < 4:
|
||||
raise AssertionError(
|
||||
f"Ledger {seq}: EntropyCount={entropy_count} < 4 (sub-quorum)"
|
||||
)
|
||||
|
||||
if seen_digests is not None:
|
||||
if digest in seen_digests:
|
||||
raise AssertionError(f"Ledger {seq}: duplicate Digest {digest[:16]}...")
|
||||
seen_digests.add(digest)
|
||||
|
||||
return digest, entropy_count
|
||||
@@ -1,55 +0,0 @@
|
||||
defaults:
|
||||
network:
|
||||
node_count: 5
|
||||
launcher: tmux
|
||||
find_ports: true
|
||||
slave_delay: 0.2
|
||||
features:
|
||||
- ConsensusEntropy
|
||||
track_features:
|
||||
- ConsensusEntropy
|
||||
log_levels:
|
||||
TxQ: info
|
||||
Protocol: debug
|
||||
Peer: debug
|
||||
LedgerConsensus: debug
|
||||
ConsensusExtensions: debug
|
||||
NetworkOPs: info
|
||||
env:
|
||||
XAHAU_RESOURCE_PER_PORT: "1"
|
||||
XAHAU_RNG_POLL_MS: "333"
|
||||
|
||||
tests:
|
||||
- name: steady_state_entropy
|
||||
script: .testnet/scenarios/entropy/steady_state_entropy.py
|
||||
|
||||
- name: steady_state_entropy_fast_start
|
||||
script: .testnet/scenarios/entropy/steady_state_entropy.py
|
||||
network:
|
||||
env:
|
||||
XAHAUD_BOOTSTRAP_FAST_START: "1"
|
||||
|
||||
- name: entropy_with_transactions
|
||||
script: .testnet/scenarios/entropy/entropy_with_transactions.py
|
||||
|
||||
- name: quorum_recovery_smoke
|
||||
script: .testnet/scenarios/entropy/quorum_recovery_smoke.py
|
||||
|
||||
- name: quorum_degradation_smoke
|
||||
script: .testnet/scenarios/entropy/quorum_degradation_smoke.py
|
||||
network:
|
||||
log_levels:
|
||||
LedgerConsensus: trace
|
||||
ConsensusExtensions: trace
|
||||
|
||||
# Tier 2 (participant_aligned) needs 6 nodes: n=5 has no band (tier2 ==
|
||||
# quorum). At 6, the 4/6 window is the participant_aligned band.
|
||||
- name: participant_aligned_smoke
|
||||
script: .testnet/scenarios/entropy/participant_aligned_smoke.py
|
||||
network:
|
||||
node_count: 6
|
||||
log_levels:
|
||||
LedgerConsensus: trace
|
||||
ConsensusExtensions: trace
|
||||
|
||||
# Export scenarios: see export-suite.yml
|
||||
@@ -12,7 +12,6 @@ libxrpl.server > xrpl.basics
|
||||
libxrpl.server > xrpl.json
|
||||
libxrpl.server > xrpl.protocol
|
||||
libxrpl.server > xrpl.server
|
||||
test.app > test.shamap
|
||||
test.app > test.toplevel
|
||||
test.app > test.unit_test
|
||||
test.app > xrpl.basics
|
||||
@@ -44,7 +43,6 @@ test.consensus > xrpld.app
|
||||
test.consensus > xrpld.consensus
|
||||
test.consensus > xrpld.core
|
||||
test.consensus > xrpld.ledger
|
||||
test.consensus > xrpl.json
|
||||
test.consensus > xrpl.protocol
|
||||
test.core > test.jtx
|
||||
test.core > test.toplevel
|
||||
@@ -86,7 +84,6 @@ test.nodestore > xrpl.basics
|
||||
test.nodestore > xrpld.core
|
||||
test.nodestore > xrpld.nodestore
|
||||
test.nodestore > xrpld.unity
|
||||
test.nodestore > xrpl.protocol
|
||||
test.overlay > test.jtx
|
||||
test.overlay > test.toplevel
|
||||
test.overlay > test.unit_test
|
||||
@@ -121,7 +118,6 @@ test.rpc > xrpld.core
|
||||
test.rpc > xrpld.net
|
||||
test.rpc > xrpld.overlay
|
||||
test.rpc > xrpld.rpc
|
||||
test.rpc > xrpld.shamap
|
||||
test.rpc > xrpl.hook
|
||||
test.rpc > xrpl.json
|
||||
test.rpc > xrpl.protocol
|
||||
|
||||
@@ -122,7 +122,6 @@ endif()
|
||||
find_package(nudb REQUIRED)
|
||||
find_package(date REQUIRED)
|
||||
find_package(xxHash REQUIRED)
|
||||
find_package(magic_enum REQUIRED)
|
||||
|
||||
include(deps/WasmEdge)
|
||||
if(TARGET nudb::core)
|
||||
|
||||
@@ -12,16 +12,17 @@ echo "-- GITHUB_REPOSITORY: $1"
|
||||
echo "-- GITHUB_SHA: $2"
|
||||
echo "-- GITHUB_RUN_NUMBER: $4"
|
||||
|
||||
umask 0000
|
||||
umask 0000;
|
||||
|
||||
####
|
||||
|
||||
cd /io
|
||||
mkdir -p src/certs
|
||||
curl --silent -k https://raw.githubusercontent.com/RichardAH/rippled-release-builder/main/ca-bundle/certbundle.h -o src/certs/certbundle.h
|
||||
if [ "$(grep certbundle.h src/xrpld/net/detail/RegisterSSLCerts.cpp | wc -l)" -eq "0" ]; then
|
||||
cp src/xrpld/net/detail/RegisterSSLCerts.cpp src/xrpld/net/detail/RegisterSSLCerts.cpp.old
|
||||
perl -i -pe "s/^{/{
|
||||
cd /io;
|
||||
mkdir -p src/certs;
|
||||
curl --silent -k https://raw.githubusercontent.com/RichardAH/rippled-release-builder/main/ca-bundle/certbundle.h -o src/certs/certbundle.h;
|
||||
if [ "`grep certbundle.h src/xrpld/net/detail/RegisterSSLCerts.cpp | wc -l`" -eq "0" ]
|
||||
then
|
||||
cp src/xrpld/net/detail/RegisterSSLCerts.cpp src/xrpld/net/detail/RegisterSSLCerts.cpp.old
|
||||
perl -i -pe "s/^{/{
|
||||
#ifdef EMBEDDED_CA_BUNDLE
|
||||
BIO *cbio = BIO_new_mem_buf(ca_bundle.data(), ca_bundle.size());
|
||||
X509_STORE *cts = SSL_CTX_get_cert_store(ctx.native_handle());
|
||||
@@ -67,14 +68,15 @@ fi
|
||||
source /opt/rh/gcc-toolset-11/enable
|
||||
export PATH=/usr/local/bin:$PATH
|
||||
export CC='/usr/lib64/ccache/gcc' &&
|
||||
export CXX='/usr/lib64/ccache/g++' &&
|
||||
echo "-- Build Rippled --" &&
|
||||
pwd &&
|
||||
echo "MOVING TO [ build-core.sh ]"
|
||||
export CXX='/usr/lib64/ccache/g++' &&
|
||||
echo "-- Build Rippled --" &&
|
||||
pwd &&
|
||||
|
||||
printenv >.env.temp
|
||||
cat .env.temp | grep '=' | sed s/\\\(^[^=]\\+=\\\)/\\1\\\"/g | sed s/\$/\\\"/g >.env
|
||||
rm .env.temp
|
||||
echo "MOVING TO [ build-core.sh ]";
|
||||
|
||||
printenv > .env.temp;
|
||||
cat .env.temp | grep '=' | sed s/\\\(^[^=]\\+=\\\)/\\1\\\"/g|sed s/\$/\\\"/g > .env;
|
||||
rm .env.temp;
|
||||
|
||||
echo "Persisting ENV:"
|
||||
cat .env
|
||||
|
||||
@@ -95,9 +95,6 @@
|
||||
# - replace both functions setup_target_for_coverage_gcovr_* with a single setup_target_for_coverage_gcovr
|
||||
# - add support for all gcovr output formats
|
||||
#
|
||||
# 2024-04-03, Bronek Kozicki
|
||||
# - add support for output formats: jacoco, clover, lcov
|
||||
#
|
||||
# USAGE:
|
||||
#
|
||||
# 1. Copy this file into your cmake modules path.
|
||||
@@ -259,10 +256,10 @@ endif()
|
||||
# BASE_DIRECTORY "../" # Base directory for report
|
||||
# # (defaults to PROJECT_SOURCE_DIR)
|
||||
# FORMAT "cobertura" # Output format, one of:
|
||||
# # xml cobertura sonarqube jacoco clover
|
||||
# # json-summary json-details coveralls csv
|
||||
# # txt html-single html-nested html-details
|
||||
# # lcov (xml is an alias to cobertura;
|
||||
# # xml cobertura sonarqube json-summary
|
||||
# # json-details coveralls csv txt
|
||||
# # html-single html-nested html-details
|
||||
# # (xml is an alias to cobertura;
|
||||
# # if no format is set, defaults to xml)
|
||||
# EXCLUDE "src/dir1/*" "src/dir2/*" # Patterns to exclude (can be relative
|
||||
# # to BASE_DIRECTORY, with CMake 3.4+)
|
||||
@@ -311,8 +308,6 @@ function(setup_target_for_coverage_gcovr)
|
||||
set(GCOVR_OUTPUT_FILE ${Coverage_NAME}.txt)
|
||||
elseif(Coverage_FORMAT STREQUAL "csv")
|
||||
set(GCOVR_OUTPUT_FILE ${Coverage_NAME}.csv)
|
||||
elseif(Coverage_FORMAT STREQUAL "lcov")
|
||||
set(GCOVR_OUTPUT_FILE ${Coverage_NAME}.lcov)
|
||||
else()
|
||||
set(GCOVR_OUTPUT_FILE ${Coverage_NAME}.xml)
|
||||
endif()
|
||||
@@ -325,14 +320,6 @@ function(setup_target_for_coverage_gcovr)
|
||||
set(Coverage_FORMAT cobertura) # overwrite xml
|
||||
elseif(Coverage_FORMAT STREQUAL "sonarqube")
|
||||
list(APPEND GCOVR_ADDITIONAL_ARGS --sonarqube "${GCOVR_OUTPUT_FILE}" )
|
||||
elseif(Coverage_FORMAT STREQUAL "jacoco")
|
||||
list(APPEND GCOVR_ADDITIONAL_ARGS --jacoco "${GCOVR_OUTPUT_FILE}" )
|
||||
list(APPEND GCOVR_ADDITIONAL_ARGS --jacoco-pretty )
|
||||
elseif(Coverage_FORMAT STREQUAL "clover")
|
||||
list(APPEND GCOVR_ADDITIONAL_ARGS --clover "${GCOVR_OUTPUT_FILE}" )
|
||||
list(APPEND GCOVR_ADDITIONAL_ARGS --clover-pretty )
|
||||
elseif(Coverage_FORMAT STREQUAL "lcov")
|
||||
list(APPEND GCOVR_ADDITIONAL_ARGS --lcov "${GCOVR_OUTPUT_FILE}" )
|
||||
elseif(Coverage_FORMAT STREQUAL "json-summary")
|
||||
list(APPEND GCOVR_ADDITIONAL_ARGS --json-summary "${GCOVR_OUTPUT_FILE}" )
|
||||
list(APPEND GCOVR_ADDITIONAL_ARGS --json-summary-pretty)
|
||||
@@ -393,7 +380,6 @@ function(setup_target_for_coverage_gcovr)
|
||||
${GCOVR_PATH}
|
||||
--gcov-executable ${GCOV_TOOL}
|
||||
--gcov-ignore-parse-errors=negative_hits.warn_once_per_file
|
||||
--gcov-ignore-parse-errors=suspicious_hits.warn_once_per_file
|
||||
-r ${BASEDIR}
|
||||
${GCOVR_ADDITIONAL_ARGS}
|
||||
${GCOVR_EXCLUDE_ARGS}
|
||||
|
||||
@@ -54,7 +54,6 @@ add_library(xrpl.imports.main INTERFACE)
|
||||
target_link_libraries(xrpl.imports.main
|
||||
INTERFACE
|
||||
LibArchive::LibArchive
|
||||
magic_enum::magic_enum
|
||||
OpenSSL::Crypto
|
||||
Ripple::boost
|
||||
wasmedge::wasmedge
|
||||
|
||||
@@ -22,9 +22,6 @@ target_compile_definitions (opts
|
||||
$<$<BOOL:${beast_no_unit_test_inline}>:BEAST_NO_UNIT_TEST_INLINE=1>
|
||||
$<$<BOOL:${beast_disable_autolink}>:BEAST_DONT_AUTOLINK_TO_WIN32_LIBRARIES=1>
|
||||
$<$<BOOL:${single_io_service_thread}>:RIPPLE_SINGLE_IO_SERVICE_THREAD=1>
|
||||
# Enhanced logging is enabled for Debug builds, or explicitly via
|
||||
# -DBEAST_ENHANCED_LOGGING=ON for other build types.
|
||||
$<$<OR:$<CONFIG:Debug>,$<BOOL:${BEAST_ENHANCED_LOGGING}>>:BEAST_ENHANCED_LOGGING=1>
|
||||
$<$<BOOL:${voidstar}>:ENABLE_VOIDSTAR>)
|
||||
target_compile_options (opts
|
||||
INTERFACE
|
||||
|
||||
@@ -29,7 +29,6 @@ class Xrpl(ConanFile):
|
||||
'date/3.0.3',
|
||||
'grpc/1.50.1',
|
||||
'libarchive/3.7.6',
|
||||
'magic_enum/0.9.5',
|
||||
'nudb/2.0.8',
|
||||
'openssl/3.6.0',
|
||||
'soci/4.0.3@xahaud/stable',
|
||||
|
||||
@@ -47,8 +47,5 @@
|
||||
#define MEM_OVERLAP -43
|
||||
#define TOO_MANY_STATE_MODIFICATIONS -44
|
||||
#define TOO_MANY_NAMESPACES -45
|
||||
#define EXPORT_FAILURE -46
|
||||
#define TOO_MANY_EXPORTED_TXN -47
|
||||
#define TOO_LITTLE_ENTROPY -48
|
||||
#define HOOK_ERROR_CODES
|
||||
#endif //HOOK_ERROR_CODES
|
||||
|
||||
@@ -2,9 +2,6 @@
|
||||
// Generated using generate_extern.sh
|
||||
#include <stdint.h>
|
||||
#ifndef HOOK_EXTERN
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
extern int32_t __attribute__((noduplicate))
|
||||
_g(uint32_t guard_id, uint32_t maxiter);
|
||||
@@ -339,31 +336,5 @@ prepare(
|
||||
uint32_t read_ptr,
|
||||
uint32_t read_len);
|
||||
|
||||
extern int64_t
|
||||
xport_reserve(uint32_t count);
|
||||
|
||||
extern int64_t
|
||||
xport(
|
||||
uint32_t write_ptr,
|
||||
uint32_t write_len,
|
||||
uint32_t read_ptr,
|
||||
uint32_t read_len);
|
||||
|
||||
extern int64_t
|
||||
xport_cancel(uint32_t ticket_seq);
|
||||
|
||||
extern int64_t
|
||||
dice(uint32_t sides, uint32_t min_tier, uint32_t min_count);
|
||||
|
||||
extern int64_t
|
||||
random(
|
||||
uint32_t write_ptr,
|
||||
uint32_t write_len,
|
||||
uint32_t min_tier,
|
||||
uint32_t min_count);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#define HOOK_EXTERN
|
||||
#endif // HOOK_EXTERN
|
||||
|
||||
@@ -9,7 +9,7 @@ ENUM_FILE="$SCRIPT_DIR/../include/xrpl/hook/Enum.h"
|
||||
echo '// For documentation please see: https://xrpl-hooks.readme.io/reference/'
|
||||
echo '// Generated using generate_error.sh'
|
||||
echo '#ifndef HOOK_ERROR_CODES'
|
||||
sed -n '/enum class hook_return_code/,/};/p' "$ENUM_FILE" |
|
||||
sed -n '/enum hook_return_code/,/};/p' "$ENUM_FILE" |
|
||||
awk '
|
||||
function ltrim(s) { sub(/^[[:space:]]+/, "", s); return s }
|
||||
function rtrim(s) { sub(/[[:space:]]+$/, "", s); return s }
|
||||
@@ -31,7 +31,7 @@ sed -n '/enum class hook_return_code/,/};/p' "$ENUM_FILE" |
|
||||
|
||||
{
|
||||
line = $0
|
||||
if (line ~ /enum[[:space:]]+class[[:space:]]+hook_return_code/)
|
||||
if (line ~ /enum[[:space:]]+hook_return_code/)
|
||||
next
|
||||
if (line ~ /^[[:space:]]*\{/)
|
||||
next
|
||||
|
||||
@@ -11,9 +11,6 @@ APPLY_HOOK="$SCRIPT_DIR/../include/xrpl/hook/hook_api.macro"
|
||||
echo '// Generated using generate_extern.sh'
|
||||
echo '#include <stdint.h>'
|
||||
echo '#ifndef HOOK_EXTERN'
|
||||
echo '#ifdef __cplusplus'
|
||||
echo 'extern "C" {'
|
||||
echo '#endif'
|
||||
echo
|
||||
awk '
|
||||
function trim(s) {
|
||||
@@ -49,9 +46,6 @@ APPLY_HOOK="$SCRIPT_DIR/../include/xrpl/hook/hook_api.macro"
|
||||
}
|
||||
' "$APPLY_HOOK"
|
||||
|
||||
echo '#ifdef __cplusplus'
|
||||
echo '}'
|
||||
echo '#endif'
|
||||
echo '#define HOOK_EXTERN'
|
||||
echo '#endif // HOOK_EXTERN'
|
||||
} | (
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -eu
|
||||
|
||||
SCRIPT_DIR=$(dirname "$0")
|
||||
SCRIPT_DIR=$(cd "$SCRIPT_DIR" && pwd)
|
||||
|
||||
RIPPLED_ROOT="$SCRIPT_DIR/../include/xrpl"
|
||||
LEDGER_FORMATS="$RIPPLED_ROOT/protocol/LedgerFormats.h"
|
||||
|
||||
echo '// Generated using generate_lsflags.sh'
|
||||
echo ''
|
||||
echo '#ifndef HOOKLSFLAGS_INCLUDED'
|
||||
echo '#define HOOKLSFLAGS_INCLUDED 1'
|
||||
echo ''
|
||||
awk '
|
||||
function ltrim(s) { sub(/^[[:space:]]+/, "", s); return s }
|
||||
function rtrim(s) { sub(/[[:space:]]+$/, "", s); return s }
|
||||
function trim(s) { return rtrim(ltrim(s)) }
|
||||
|
||||
function flush_group() {
|
||||
if (entry_count > 0 && group != "") {
|
||||
printf "enum %s {\n", group
|
||||
for (i = 1; i <= entry_count; i++) {
|
||||
printf " %s,\n", entries[i]
|
||||
}
|
||||
printf "};\n"
|
||||
}
|
||||
delete entries
|
||||
entry_count = 0
|
||||
}
|
||||
|
||||
/enum LedgerSpecificFlags \{/ { inside = 1; next }
|
||||
inside && /^\};/ { inside = 0; flush_group(); next }
|
||||
!inside { next }
|
||||
|
||||
# Group header comments: // ltFOO or // remarks
|
||||
/^[[:space:]]*\/\/[[:space:]]*(lt[A-Z_]+|remarks)[[:space:]]*$/ {
|
||||
flush_group()
|
||||
line = $0
|
||||
sub(/.*\/\/[[:space:]]*/, "", line)
|
||||
group = trim(line)
|
||||
next
|
||||
}
|
||||
|
||||
# Skip pure comment lines (not group headers)
|
||||
/^[[:space:]]*\/\// { next }
|
||||
|
||||
# Skip blank lines
|
||||
/^[[:space:]]*$/ { next }
|
||||
|
||||
# Accumulate flag lines (handle multi-line values)
|
||||
{
|
||||
line = $0
|
||||
# Strip inline comments
|
||||
sub(/\/\/.*/, "", line)
|
||||
line = trim(line)
|
||||
if (line == "") next
|
||||
|
||||
if (pending != "") {
|
||||
pending = pending " " line
|
||||
} else {
|
||||
pending = line
|
||||
}
|
||||
|
||||
# If line ends with comma, the entry is complete
|
||||
if (pending ~ /,$/) {
|
||||
# Remove trailing comma
|
||||
sub(/,$/, "", pending)
|
||||
entries[++entry_count] = pending
|
||||
pending = ""
|
||||
}
|
||||
}
|
||||
|
||||
BEGIN {
|
||||
inside = 0
|
||||
group = ""
|
||||
pending = ""
|
||||
entry_count = 0
|
||||
}
|
||||
' "$LEDGER_FORMATS"
|
||||
echo ''
|
||||
echo '#endif // HOOKLSFLAGS_INCLUDED'
|
||||
@@ -1,25 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -eu
|
||||
|
||||
SCRIPT_DIR=$(dirname "$0")
|
||||
SCRIPT_DIR=$(cd "$SCRIPT_DIR" && pwd)
|
||||
|
||||
RIPPLED_ROOT="$SCRIPT_DIR/../include/xrpl"
|
||||
TX_FLAGS="$RIPPLED_ROOT/protocol/TxFlags.h"
|
||||
|
||||
echo '// Generated using generate_txflags.sh'
|
||||
echo '#include "ls_flags.h"'
|
||||
echo '#include <stdint.h>'
|
||||
echo ''
|
||||
cat "$TX_FLAGS" |
|
||||
awk '
|
||||
/^[[:space:]]*enum / {
|
||||
if (count > 0) print ""
|
||||
inside = 1
|
||||
count++
|
||||
}
|
||||
inside {
|
||||
print
|
||||
if (/};/) inside = 0
|
||||
}
|
||||
'
|
||||
@@ -1,203 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# build_xahau_h.sh
|
||||
# Builds genesis hook WASMs and updates xahau.h with hex arrays
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Color codes for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Script directory and path constants
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
XAHAU_H="${SCRIPT_DIR}/../../include/xrpl/hook/xahau.h"
|
||||
TEMP_DIR="${SCRIPT_DIR}/.temp"
|
||||
|
||||
# Hook file mappings (space-separated: name:file)
|
||||
HOOK_FILES=(
|
||||
"GovernanceHook:govern.wasm"
|
||||
"RewardHook:reward.wasm"
|
||||
# "MintHook:mint.wasm"
|
||||
)
|
||||
|
||||
# Cleanup function
|
||||
cleanup() {
|
||||
local exit_code=$?
|
||||
if [ ${exit_code} -eq 0 ] && [ -d "${TEMP_DIR}" ]; then
|
||||
rm -rf "${TEMP_DIR}"
|
||||
elif [ ${exit_code} -ne 0 ]; then
|
||||
echo -e "${RED}Error: Script failed with exit code ${exit_code}${NC}" >&2
|
||||
if [ -d "${TEMP_DIR}" ]; then
|
||||
echo -e "${YELLOW}Temp files preserved at: ${TEMP_DIR}${NC}" >&2
|
||||
fi
|
||||
fi
|
||||
exit ${exit_code}
|
||||
}
|
||||
|
||||
trap cleanup EXIT INT TERM
|
||||
|
||||
# Tool verification
|
||||
echo -e "${BLUE}==> Checking required tools...${NC}"
|
||||
REQUIRED_TOOLS=("make" "xxd" "sed" "clang-format" "wasm-opt")
|
||||
for tool in "${REQUIRED_TOOLS[@]}"; do
|
||||
if ! command -v "${tool}" &> /dev/null; then
|
||||
echo -e "${RED}Error: Required tool '${tool}' not found${NC}" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo -e "${GREEN} ✓ ${tool}${NC}"
|
||||
done
|
||||
|
||||
# Verify wasm-opt version is exactly 100
|
||||
WASM_OPT_VERSION=$(wasm-opt --version | grep -oE '[0-9]+' | head -1)
|
||||
if [ "${WASM_OPT_VERSION}" != "100" ]; then
|
||||
echo -e "${RED}Error: wasm-opt version must be 100, but found ${WASM_OPT_VERSION}${NC}" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo -e "${GREEN} ✓ wasm-opt version 100${NC}"
|
||||
|
||||
# Verify xahau.h exists
|
||||
if [ ! -f "${XAHAU_H}" ]; then
|
||||
echo -e "${RED}Error: xahau.h not found at ${XAHAU_H}${NC}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Create temp directory
|
||||
mkdir -p "${TEMP_DIR}"
|
||||
|
||||
# Build all WASM files
|
||||
echo -e "${BLUE}==> Building WASM files with 'make all'...${NC}"
|
||||
cd "${SCRIPT_DIR}"
|
||||
make all
|
||||
echo -e "${GREEN} Build completed successfully${NC}"
|
||||
|
||||
# Function to convert WASM to hex array
|
||||
wasm_to_hex_array() {
|
||||
local wasm_file="$1"
|
||||
local indent=" "
|
||||
|
||||
if [ ! -f "${wasm_file}" ]; then
|
||||
echo -e "${RED}Error: WASM file not found: ${wasm_file}${NC}" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Convert to hex with xxd, format with sed
|
||||
xxd -p -u -c 10 "${wasm_file}" | \
|
||||
sed 's/../0x&U,/g' | \
|
||||
sed "s/^/${indent}/g" | \
|
||||
sed '$ s/,$//'
|
||||
}
|
||||
|
||||
# Function to update hook array in xahau.h
|
||||
update_hook_array() {
|
||||
local hook_name="$1"
|
||||
local hex_array="$2"
|
||||
local temp_file="${TEMP_DIR}/xahau.h.tmp"
|
||||
|
||||
echo -e "${BLUE}==> Updating ${hook_name}...${NC}"
|
||||
|
||||
# Check if hook already exists
|
||||
if grep -q "static const std::vector<uint8_t> ${hook_name} = {" "${XAHAU_H}"; then
|
||||
echo -e "${YELLOW} Replacing existing ${hook_name}${NC}"
|
||||
|
||||
# Use awk to replace the array content
|
||||
awk -v hook="${hook_name}" -v hex="${hex_array}" '
|
||||
BEGIN { in_array=0 }
|
||||
{
|
||||
if ($0 ~ "static const std::vector<uint8_t> " hook " = {") {
|
||||
print $0
|
||||
print hex
|
||||
in_array=1
|
||||
next
|
||||
}
|
||||
if (in_array && $0 ~ /};/) {
|
||||
print "};"
|
||||
in_array=0
|
||||
next
|
||||
}
|
||||
if (!in_array) {
|
||||
print $0
|
||||
}
|
||||
}
|
||||
' "${XAHAU_H}" > "${temp_file}"
|
||||
|
||||
mv "${temp_file}" "${XAHAU_H}"
|
||||
else
|
||||
echo -e "${YELLOW} Adding new ${hook_name}${NC}"
|
||||
|
||||
# Find the position before #endif and add the new hook
|
||||
awk -v hook="${hook_name}" -v hex="${hex_array}" '
|
||||
{
|
||||
if ($0 ~ /#endif.*XAHAU_GENESIS_HOOKS/) {
|
||||
print ""
|
||||
print "static const std::vector<uint8_t> " hook " = {"
|
||||
print hex
|
||||
print "};"
|
||||
print ""
|
||||
print $0
|
||||
} else {
|
||||
print $0
|
||||
}
|
||||
}
|
||||
' "${XAHAU_H}" > "${temp_file}"
|
||||
|
||||
mv "${temp_file}" "${XAHAU_H}"
|
||||
fi
|
||||
|
||||
echo -e "${GREEN} ✓ ${hook_name} updated${NC}"
|
||||
}
|
||||
|
||||
# Process each hook
|
||||
for hook_entry in "${HOOK_FILES[@]}"; do
|
||||
hook_name="${hook_entry%%:*}"
|
||||
wasm_file="${SCRIPT_DIR}/${hook_entry##*:}"
|
||||
|
||||
echo -e "${BLUE}==> Converting ${wasm_file} to hex array...${NC}"
|
||||
hex_array=$(wasm_to_hex_array "${wasm_file}")
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo -e "${RED}Error: Failed to convert ${wasm_file}${NC}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo -e "${GREEN} Conversion successful ($(echo "${hex_array}" | wc -l) lines)${NC}"
|
||||
|
||||
update_hook_array "${hook_name}" "${hex_array}"
|
||||
done
|
||||
|
||||
# Format with clang-format
|
||||
echo -e "${BLUE}==> Formatting with clang-format...${NC}"
|
||||
cp "${XAHAU_H}" "${TEMP_DIR}/xahau.h.before_format"
|
||||
clang-format -i "${XAHAU_H}"
|
||||
echo -e "${GREEN} Formatting completed${NC}"
|
||||
|
||||
# Verification
|
||||
echo -e "${BLUE}==> Verifying changes...${NC}"
|
||||
for hook_entry in "${HOOK_FILES[@]}"; do
|
||||
hook_name="${hook_entry%%:*}"
|
||||
if grep -q "static const std::vector<uint8_t> ${hook_name} = {" "${XAHAU_H}"; then
|
||||
echo -e "${GREEN} ✓ ${hook_name} found in xahau.h${NC}"
|
||||
else
|
||||
echo -e "${RED} ✗ ${hook_name} NOT found in xahau.h${NC}" >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
# Show summary
|
||||
echo ""
|
||||
echo -e "${GREEN}========================================${NC}"
|
||||
echo -e "${GREEN}Successfully updated xahau.h${NC}"
|
||||
echo -e "${GREEN}========================================${NC}"
|
||||
echo -e "Updated hooks:"
|
||||
for hook_entry in "${HOOK_FILES[@]}"; do
|
||||
hook_name="${hook_entry%%:*}"
|
||||
wasm_file="${SCRIPT_DIR}/${hook_entry##*:}"
|
||||
size=$(wc -c < "${wasm_file}" | tr -d ' ')
|
||||
echo -e " - ${hook_name}: ${size} bytes"
|
||||
done
|
||||
echo ""
|
||||
echo -e "File location: ${XAHAU_H}"
|
||||
echo ""
|
||||
@@ -1,46 +0,0 @@
|
||||
// For documentation please see: https://xrpl-hooks.readme.io/reference/
|
||||
// Generated using generate_error.sh
|
||||
#ifndef HOOK_ERROR_CODES
|
||||
#define SUCCESS 0
|
||||
#define OUT_OF_BOUNDS -1
|
||||
#define INTERNAL_ERROR -2
|
||||
#define TOO_BIG -3
|
||||
#define TOO_SMALL -4
|
||||
#define DOESNT_EXIST -5
|
||||
#define NO_FREE_SLOTS -6
|
||||
#define INVALID_ARGUMENT -7
|
||||
#define ALREADY_SET -8
|
||||
#define PREREQUISITE_NOT_MET -9
|
||||
#define FEE_TOO_LARGE -10
|
||||
#define EMISSION_FAILURE -11
|
||||
#define TOO_MANY_NONCES -12
|
||||
#define TOO_MANY_EMITTED_TXN -13
|
||||
#define NOT_IMPLEMENTED -14
|
||||
#define INVALID_ACCOUNT -15
|
||||
#define GUARD_VIOLATION -16
|
||||
#define INVALID_FIELD -17
|
||||
#define PARSE_ERROR -18
|
||||
#define RC_ROLLBACK -19
|
||||
#define RC_ACCEPT -20
|
||||
#define NO_SUCH_KEYLET -21
|
||||
#define NOT_AN_ARRAY -22
|
||||
#define NOT_AN_OBJECT -23
|
||||
#define INVALID_FLOAT -10024
|
||||
#define DIVISION_BY_ZERO -25
|
||||
#define MANTISSA_OVERSIZED -26
|
||||
#define MANTISSA_UNDERSIZED -27
|
||||
#define EXPONENT_OVERSIZED -28
|
||||
#define EXPONENT_UNDERSIZED -29
|
||||
#define OVERFLOW -30
|
||||
#define NOT_IOU_AMOUNT -31
|
||||
#define NOT_AN_AMOUNT -32
|
||||
#define CANT_RETURN_NEGATIVE -33
|
||||
#define NOT_AUTHORIZED -34
|
||||
#define PREVIOUS_FAILURE_PREVENTS_RETRY -35
|
||||
#define TOO_MANY_PARAMS -36
|
||||
#define INVALID_TXN -37
|
||||
#define RESERVE_INSUFFICIENT -38
|
||||
#define COMPLEX_NOT_SUPPORTED -39
|
||||
#define DOES_NOT_MATCH -40
|
||||
#define HOOK_ERROR_CODES
|
||||
#endif //HOOK_ERROR_CODES
|
||||
@@ -1,352 +0,0 @@
|
||||
// For documentation please see: https://xrpl-hooks.readme.io/reference/
|
||||
// Generated using generate_extern.sh
|
||||
#include <stdint.h>
|
||||
#ifndef HOOK_EXTERN
|
||||
|
||||
extern int32_t __attribute__((noduplicate))
|
||||
_g(uint32_t guard_id, uint32_t maxiter);
|
||||
|
||||
extern int64_t
|
||||
accept(uint32_t read_ptr, uint32_t read_len, int64_t error_code);
|
||||
|
||||
extern int64_t
|
||||
emit(
|
||||
uint32_t write_ptr,
|
||||
uint32_t write_len,
|
||||
uint32_t read_ptr,
|
||||
uint32_t read_len);
|
||||
|
||||
extern int64_t
|
||||
etxn_burden(void);
|
||||
|
||||
extern int64_t
|
||||
etxn_details(uint32_t write_ptr, uint32_t write_len);
|
||||
|
||||
extern int64_t
|
||||
etxn_fee_base(uint32_t read_ptr, uint32_t read_len);
|
||||
|
||||
extern int64_t
|
||||
etxn_generation(void);
|
||||
|
||||
extern int64_t
|
||||
etxn_nonce(uint32_t write_ptr, uint32_t write_len);
|
||||
|
||||
extern int64_t
|
||||
etxn_reserve(uint32_t count);
|
||||
|
||||
extern int64_t
|
||||
fee_base(void);
|
||||
|
||||
extern int64_t
|
||||
float_compare(int64_t float1, int64_t float2, uint32_t mode);
|
||||
|
||||
extern int64_t
|
||||
float_divide(int64_t float1, int64_t float2);
|
||||
|
||||
extern int64_t
|
||||
float_exponent(int64_t float1);
|
||||
|
||||
extern int64_t
|
||||
float_exponent_set(int64_t float1, int32_t exponent);
|
||||
|
||||
extern int64_t
|
||||
float_int(int64_t float1, uint32_t decimal_places, uint32_t abs);
|
||||
|
||||
extern int64_t
|
||||
float_invert(int64_t float1);
|
||||
|
||||
extern int64_t
|
||||
float_log(int64_t float1);
|
||||
|
||||
extern int64_t
|
||||
float_mantissa(int64_t float1);
|
||||
|
||||
extern int64_t
|
||||
float_mantissa_set(int64_t float1, int64_t mantissa);
|
||||
|
||||
extern int64_t
|
||||
float_mulratio(
|
||||
int64_t float1,
|
||||
uint32_t round_up,
|
||||
uint32_t numerator,
|
||||
uint32_t denominator);
|
||||
|
||||
extern int64_t
|
||||
float_multiply(int64_t float1, int64_t float2);
|
||||
|
||||
extern int64_t
|
||||
float_negate(int64_t float1);
|
||||
|
||||
extern int64_t
|
||||
float_one(void);
|
||||
|
||||
extern int64_t
|
||||
float_root(int64_t float1, uint32_t n);
|
||||
|
||||
extern int64_t
|
||||
float_set(int32_t exponent, int64_t mantissa);
|
||||
|
||||
extern int64_t
|
||||
float_sign(int64_t float1);
|
||||
|
||||
extern int64_t
|
||||
float_sign_set(int64_t float1, uint32_t negative);
|
||||
|
||||
extern int64_t
|
||||
float_sto(
|
||||
uint32_t write_ptr,
|
||||
uint32_t write_len,
|
||||
uint32_t cread_ptr,
|
||||
uint32_t cread_len,
|
||||
uint32_t iread_ptr,
|
||||
uint32_t iread_len,
|
||||
int64_t float1,
|
||||
uint32_t field_code);
|
||||
|
||||
extern int64_t
|
||||
float_sto_set(uint32_t read_ptr, uint32_t read_len);
|
||||
|
||||
extern int64_t
|
||||
float_sum(int64_t float1, int64_t float2);
|
||||
|
||||
extern int64_t
|
||||
hook_account(uint32_t write_ptr, uint32_t write_len);
|
||||
|
||||
extern int64_t
|
||||
hook_again(void);
|
||||
|
||||
extern int64_t
|
||||
hook_hash(uint32_t write_ptr, uint32_t write_len, int32_t hook_no);
|
||||
|
||||
extern int64_t
|
||||
hook_param(
|
||||
uint32_t write_ptr,
|
||||
uint32_t write_len,
|
||||
uint32_t read_ptr,
|
||||
uint32_t read_len);
|
||||
|
||||
extern int64_t
|
||||
otxn_param(
|
||||
uint32_t write_ptr,
|
||||
uint32_t write_len,
|
||||
uint32_t read_ptr,
|
||||
uint32_t read_len);
|
||||
|
||||
extern int64_t
|
||||
hook_param_set(
|
||||
uint32_t read_ptr,
|
||||
uint32_t read_len,
|
||||
uint32_t kread_ptr,
|
||||
uint32_t kread_len,
|
||||
uint32_t hread_ptr,
|
||||
uint32_t hread_len);
|
||||
|
||||
extern int64_t
|
||||
hook_pos(void);
|
||||
|
||||
extern int64_t
|
||||
hook_skip(uint32_t read_ptr, uint32_t read_len, uint32_t flags);
|
||||
|
||||
extern int64_t
|
||||
ledger_keylet(
|
||||
uint32_t write_ptr,
|
||||
uint32_t write_len,
|
||||
uint32_t lread_ptr,
|
||||
uint32_t lread_len,
|
||||
uint32_t hread_ptr,
|
||||
uint32_t hread_len);
|
||||
|
||||
extern int64_t
|
||||
ledger_last_hash(uint32_t write_ptr, uint32_t write_len);
|
||||
|
||||
extern int64_t
|
||||
ledger_last_time(void);
|
||||
|
||||
extern int64_t
|
||||
ledger_nonce(uint32_t write_ptr, uint32_t write_len);
|
||||
|
||||
extern int64_t
|
||||
ledger_seq(void);
|
||||
|
||||
extern int64_t
|
||||
meta_slot(uint32_t slot_no);
|
||||
|
||||
extern int64_t
|
||||
otxn_burden(void);
|
||||
|
||||
extern int64_t
|
||||
otxn_field(uint32_t write_ptr, uint32_t write_len, uint32_t field_id);
|
||||
|
||||
extern int64_t
|
||||
otxn_field_txt(uint32_t write_ptr, uint32_t write_len, uint32_t field_id);
|
||||
|
||||
extern int64_t
|
||||
otxn_generation(void);
|
||||
|
||||
extern int64_t
|
||||
otxn_id(uint32_t write_ptr, uint32_t write_len, uint32_t flags);
|
||||
|
||||
extern int64_t
|
||||
otxn_slot(uint32_t slot_no);
|
||||
|
||||
extern int64_t
|
||||
otxn_type(void);
|
||||
|
||||
extern int64_t
|
||||
rollback(uint32_t read_ptr, uint32_t read_len, int64_t error_code);
|
||||
|
||||
extern int64_t
|
||||
slot(uint32_t write_ptr, uint32_t write_len, uint32_t slot);
|
||||
|
||||
extern int64_t
|
||||
slot_clear(uint32_t slot);
|
||||
|
||||
extern int64_t
|
||||
slot_count(uint32_t slot);
|
||||
|
||||
extern int64_t
|
||||
slot_float(uint32_t slot_no);
|
||||
|
||||
extern int64_t
|
||||
slot_id(uint32_t write_ptr, uint32_t write_len, uint32_t slot);
|
||||
|
||||
extern int64_t
|
||||
slot_set(uint32_t read_ptr, uint32_t read_len, uint32_t slot);
|
||||
|
||||
extern int64_t
|
||||
slot_size(uint32_t slot);
|
||||
|
||||
extern int64_t
|
||||
slot_subarray(uint32_t parent_slot, uint32_t array_id, uint32_t new_slot);
|
||||
|
||||
extern int64_t
|
||||
slot_subfield(uint32_t parent_slot, uint32_t field_id, uint32_t new_slot);
|
||||
|
||||
extern int64_t
|
||||
slot_type(uint32_t slot_no, uint32_t flags);
|
||||
|
||||
extern int64_t
|
||||
state(
|
||||
uint32_t write_ptr,
|
||||
uint32_t write_len,
|
||||
uint32_t kread_ptr,
|
||||
uint32_t kread_len);
|
||||
|
||||
extern int64_t
|
||||
state_foreign(
|
||||
uint32_t write_ptr,
|
||||
uint32_t write_len,
|
||||
uint32_t kread_ptr,
|
||||
uint32_t kread_len,
|
||||
uint32_t nread_ptr,
|
||||
uint32_t nread_len,
|
||||
uint32_t aread_ptr,
|
||||
uint32_t aread_len);
|
||||
|
||||
extern int64_t
|
||||
state_foreign_set(
|
||||
uint32_t read_ptr,
|
||||
uint32_t read_len,
|
||||
uint32_t kread_ptr,
|
||||
uint32_t kread_len,
|
||||
uint32_t nread_ptr,
|
||||
uint32_t nread_len,
|
||||
uint32_t aread_ptr,
|
||||
uint32_t aread_len);
|
||||
|
||||
extern int64_t
|
||||
state_set(
|
||||
uint32_t read_ptr,
|
||||
uint32_t read_len,
|
||||
uint32_t kread_ptr,
|
||||
uint32_t kread_len);
|
||||
|
||||
extern int64_t
|
||||
sto_emplace(
|
||||
uint32_t write_ptr,
|
||||
uint32_t write_len,
|
||||
uint32_t sread_ptr,
|
||||
uint32_t sread_len,
|
||||
uint32_t fread_ptr,
|
||||
uint32_t fread_len,
|
||||
uint32_t field_id);
|
||||
|
||||
extern int64_t
|
||||
sto_erase(
|
||||
uint32_t write_ptr,
|
||||
uint32_t write_len,
|
||||
uint32_t read_ptr,
|
||||
uint32_t read_len,
|
||||
uint32_t field_id);
|
||||
|
||||
extern int64_t
|
||||
sto_subarray(uint32_t read_ptr, uint32_t read_len, uint32_t array_id);
|
||||
|
||||
extern int64_t
|
||||
sto_subfield(uint32_t read_ptr, uint32_t read_len, uint32_t field_id);
|
||||
|
||||
extern int64_t
|
||||
sto_validate(uint32_t tread_ptr, uint32_t tread_len);
|
||||
|
||||
extern int64_t
|
||||
trace(
|
||||
uint32_t mread_ptr,
|
||||
uint32_t mread_len,
|
||||
uint32_t dread_ptr,
|
||||
uint32_t dread_len,
|
||||
uint32_t as_hex);
|
||||
|
||||
extern int64_t
|
||||
trace_float(uint32_t read_ptr, uint32_t read_len, int64_t float1);
|
||||
|
||||
extern int64_t
|
||||
trace_num(uint32_t read_ptr, uint32_t read_len, int64_t number);
|
||||
|
||||
extern int64_t
|
||||
trace_slot(uint32_t read_ptr, uint32_t read_len, uint32_t slot);
|
||||
|
||||
extern int64_t
|
||||
util_accid(
|
||||
uint32_t write_ptr,
|
||||
uint32_t write_len,
|
||||
uint32_t read_ptr,
|
||||
uint32_t read_len);
|
||||
|
||||
extern int64_t
|
||||
util_keylet(
|
||||
uint32_t write_ptr,
|
||||
uint32_t write_len,
|
||||
uint32_t keylet_type,
|
||||
uint32_t a,
|
||||
uint32_t b,
|
||||
uint32_t c,
|
||||
uint32_t d,
|
||||
uint32_t e,
|
||||
uint32_t f);
|
||||
|
||||
extern int64_t
|
||||
util_raddr(
|
||||
uint32_t write_ptr,
|
||||
uint32_t write_len,
|
||||
uint32_t read_ptr,
|
||||
uint32_t read_len);
|
||||
|
||||
extern int64_t
|
||||
util_sha512h(
|
||||
uint32_t write_ptr,
|
||||
uint32_t write_len,
|
||||
uint32_t read_ptr,
|
||||
uint32_t read_len);
|
||||
|
||||
extern int64_t
|
||||
util_verify(
|
||||
uint32_t dread_ptr,
|
||||
uint32_t dread_len,
|
||||
uint32_t sread_ptr,
|
||||
uint32_t sread_len,
|
||||
uint32_t kread_ptr,
|
||||
uint32_t kread_len);
|
||||
|
||||
extern int64_t xpop_slot(uint32_t, uint32_t);
|
||||
#define HOOK_EXTERN
|
||||
#endif // HOOK_EXTERN
|
||||
@@ -1,50 +0,0 @@
|
||||
/**
|
||||
* Hook API include file
|
||||
*
|
||||
* Note to the reader:
|
||||
* This include defines two types of things: external functions and macros
|
||||
* Functions are used sparingly because a non-inlining compiler may produce
|
||||
* undesirable output.
|
||||
*
|
||||
* Find documentation here: https://xrpl-hooks.readme.io/reference/
|
||||
*/
|
||||
|
||||
#ifndef HOOKAPI_INCLUDED
|
||||
#define HOOKAPI_INCLUDED 1
|
||||
|
||||
#define KEYLET_HOOK 1
|
||||
#define KEYLET_HOOK_STATE 2
|
||||
#define KEYLET_ACCOUNT 3
|
||||
#define KEYLET_AMENDMENTS 4
|
||||
#define KEYLET_CHILD 5
|
||||
#define KEYLET_SKIP 6
|
||||
#define KEYLET_FEES 7
|
||||
#define KEYLET_NEGATIVE_UNL 8
|
||||
#define KEYLET_LINE 9
|
||||
#define KEYLET_OFFER 10
|
||||
#define KEYLET_QUALITY 11
|
||||
#define KEYLET_EMITTED_DIR 12
|
||||
#define KEYLET_TICKET 13
|
||||
#define KEYLET_SIGNERS 14
|
||||
#define KEYLET_CHECK 15
|
||||
#define KEYLET_DEPOSIT_PREAUTH 16
|
||||
#define KEYLET_UNCHECKED 17
|
||||
#define KEYLET_OWNER_DIR 18
|
||||
#define KEYLET_PAGE 19
|
||||
#define KEYLET_ESCROW 20
|
||||
#define KEYLET_PAYCHAN 21
|
||||
#define KEYLET_EMITTED 22
|
||||
#define KEYLET_NFT_OFFER 23
|
||||
#define KEYLET_HOOK_DEFINITION 24
|
||||
|
||||
#define COMPARE_EQUAL 1U
|
||||
#define COMPARE_LESS 2U
|
||||
#define COMPARE_GREATER 4U
|
||||
|
||||
#include "error.h"
|
||||
#include "extern.h"
|
||||
#include "sfcodes.h"
|
||||
#include "macro.h"
|
||||
#include "types.h"
|
||||
|
||||
#endif
|
||||
@@ -1,668 +0,0 @@
|
||||
/**
|
||||
* These are helper macros for writing hooks, all of them are optional as is including hookmacro.h at all
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include "hookapi.h"
|
||||
#include "sfcodes.h"
|
||||
|
||||
#ifndef HOOKMACROS_INCLUDED
|
||||
#define HOOKMACROS_INCLUDED 1
|
||||
|
||||
|
||||
#ifdef NDEBUG
|
||||
#define DEBUG 0
|
||||
#else
|
||||
#define DEBUG 1
|
||||
#endif
|
||||
|
||||
#define TRACEVAR(v) if (DEBUG) trace_num((uint32_t)(#v), (uint32_t)(sizeof(#v) - 1), (int64_t)v);
|
||||
#define TRACEHEX(v) if (DEBUG) trace((uint32_t)(#v), (uint32_t)(sizeof(#v) - 1), (uint32_t)(v), (uint32_t)(sizeof(v)), 1);
|
||||
#define TRACEXFL(v) if (DEBUG) trace_float((uint32_t)(#v), (uint32_t)(sizeof(#v) - 1), (int64_t)v);
|
||||
#define TRACESTR(v) if (DEBUG) trace((uint32_t)(#v), (uint32_t)(sizeof(#v) - 1), (uint32_t)(v), sizeof(v), 0);
|
||||
|
||||
// hook developers should use this guard macro, simply GUARD(<maximum iterations>)
|
||||
#define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1)
|
||||
#define GUARDM(maxiter, n) _g(( (1ULL << 31U) + (__LINE__ << 16) + n), (maxiter)+1)
|
||||
|
||||
#define SBUF(str) (uint32_t)(str), sizeof(str)
|
||||
|
||||
#define REQUIRE(cond, str)\
|
||||
{\
|
||||
if (!(cond))\
|
||||
rollback(SBUF(str), __LINE__);\
|
||||
}
|
||||
|
||||
// make a report buffer as a c-string
|
||||
// provide a name for a buffer to declare (buf)
|
||||
// provide a static string
|
||||
// provide an integer to print after the string
|
||||
#define RBUF(buf, out_len, str, num)\
|
||||
unsigned char buf[sizeof(str) + 21];\
|
||||
int out_len = 0;\
|
||||
{\
|
||||
int i = 0;\
|
||||
for (; GUARDM(sizeof(str),1),i < sizeof(str); ++i)\
|
||||
(buf)[i] = str[i];\
|
||||
if ((buf)[sizeof(str)-1] == 0) i--;\
|
||||
if ((num) < 0) (buf)[i++] = '-';\
|
||||
uint64_t unsigned_num = (uint64_t)( (num) < 0 ? (num) * -1 : (num) );\
|
||||
uint64_t j = 10000000000000000000ULL;\
|
||||
int start = 1;\
|
||||
for (; GUARDM(20,2), unsigned_num > 0 && j > 0; j /= 10)\
|
||||
{\
|
||||
unsigned char digit = ( unsigned_num / j ) % 10;\
|
||||
if (digit == 0 && start)\
|
||||
continue;\
|
||||
start = 0;\
|
||||
(buf)[i++] = '0' + digit;\
|
||||
}\
|
||||
(buf)[i] = '\0';\
|
||||
out_len = i;\
|
||||
}
|
||||
|
||||
#define RBUF2(buff, out_len, str, num, str2, num2)\
|
||||
unsigned char buff[sizeof(str) + sizeof(str2) + 42];\
|
||||
int out_len = 0;\
|
||||
{\
|
||||
unsigned char* buf = buff;\
|
||||
int i = 0;\
|
||||
for (; GUARDM(sizeof(str),1),i < sizeof(str); ++i)\
|
||||
(buf)[i] = str[i];\
|
||||
if ((buf)[sizeof(str)-1] == 0) i--;\
|
||||
if ((num) < 0) (buf)[i++] = '-';\
|
||||
uint64_t unsigned_num = (uint64_t)( (num) < 0 ? (num) * -1 : (num) );\
|
||||
uint64_t j = 10000000000000000000ULL;\
|
||||
int start = 1;\
|
||||
for (; GUARDM(20,2), unsigned_num > 0 && j > 0; j /= 10)\
|
||||
{\
|
||||
unsigned char digit = ( unsigned_num / j ) % 10;\
|
||||
if (digit == 0 && start)\
|
||||
continue;\
|
||||
start = 0;\
|
||||
(buf)[i++] = '0' + digit;\
|
||||
}\
|
||||
buf += i;\
|
||||
out_len += i;\
|
||||
i = 0;\
|
||||
for (; GUARDM(sizeof(str2),3),i < sizeof(str2); ++i)\
|
||||
(buf)[i] = str2[i];\
|
||||
if ((buf)[sizeof(str2)-1] == 0) i--;\
|
||||
if ((num2) < 0) (buf)[i++] = '-';\
|
||||
unsigned_num = (uint64_t)( (num2) < 0 ? (num2) * -1 : (num2) );\
|
||||
j = 10000000000000000000ULL;\
|
||||
start = 1;\
|
||||
for (; GUARDM(20,4), unsigned_num > 0 && j > 0; j /= 10)\
|
||||
{\
|
||||
unsigned char digit = ( unsigned_num / j ) % 10;\
|
||||
if (digit == 0 && start)\
|
||||
continue;\
|
||||
start = 0;\
|
||||
(buf)[i++] = '0' + digit;\
|
||||
}\
|
||||
(buf)[i] = '\0';\
|
||||
out_len += i;\
|
||||
}
|
||||
|
||||
#define CLEARBUF(b)\
|
||||
{\
|
||||
for (int x = 0; GUARD(sizeof(b)), x < sizeof(b); ++x)\
|
||||
b[x] = 0;\
|
||||
}
|
||||
|
||||
// returns an in64_t, negative if error, non-negative if valid drops
|
||||
#define AMOUNT_TO_DROPS(amount_buffer)\
|
||||
(((amount_buffer)[0] >> 7) ? -2 : (\
|
||||
((((uint64_t)((amount_buffer)[0])) & 0xb00111111) << 56) +\
|
||||
(((uint64_t)((amount_buffer)[1])) << 48) +\
|
||||
(((uint64_t)((amount_buffer)[2])) << 40) +\
|
||||
(((uint64_t)((amount_buffer)[3])) << 32) +\
|
||||
(((uint64_t)((amount_buffer)[4])) << 24) +\
|
||||
(((uint64_t)((amount_buffer)[5])) << 16) +\
|
||||
(((uint64_t)((amount_buffer)[6])) << 8) +\
|
||||
(((uint64_t)((amount_buffer)[7])))))
|
||||
|
||||
#define SUB_OFFSET(x) ((int32_t)(x >> 32))
|
||||
#define SUB_LENGTH(x) ((int32_t)(x & 0xFFFFFFFFULL))
|
||||
|
||||
#define BUFFER_EQUAL_20(buf1, buf2)\
|
||||
(\
|
||||
*(((uint64_t*)(buf1)) + 0) == *(((uint64_t*)(buf2)) + 0) &&\
|
||||
*(((uint64_t*)(buf1)) + 1) == *(((uint64_t*)(buf2)) + 1) &&\
|
||||
*(((uint32_t*)(buf1)) + 4) == *(((uint32_t*)(buf2)) + 4))
|
||||
|
||||
#define BUFFER_EQUAL_32(buf1, buf2)\
|
||||
(\
|
||||
*(((uint64_t*)(buf1)) + 0) == *(((uint64_t*)(buf2)) + 0) &&\
|
||||
*(((uint64_t*)(buf1)) + 1) == *(((uint64_t*)(buf2)) + 1) &&\
|
||||
*(((uint64_t*)(buf1)) + 2) == *(((uint64_t*)(buf2)) + 2) &&\
|
||||
*(((uint64_t*)(buf1)) + 3) == *(((uint64_t*)(buf2)) + 3))
|
||||
|
||||
|
||||
// when using this macro buf1len may be dynamic but buf2len must be static
|
||||
// provide n >= 1 to indicate how many times the macro will be hit on the line of code
|
||||
// e.g. if it is in a loop that loops 10 times n = 10
|
||||
|
||||
#define BUFFER_EQUAL_GUARD(output, buf1, buf1len, buf2, buf2len, n)\
|
||||
{\
|
||||
output = ((buf1len) == (buf2len) ? 1 : 0);\
|
||||
for (int x = 0; GUARDM( (buf2len) * (n), 1 ), output && x < (buf2len);\
|
||||
++x)\
|
||||
output = *(((uint8_t*)(buf1)) + x) == *(((uint8_t*)(buf2)) + x);\
|
||||
}
|
||||
|
||||
#define BUFFER_SWAP(x,y)\
|
||||
{\
|
||||
uint8_t* z = x;\
|
||||
x = y;\
|
||||
y = z;\
|
||||
}
|
||||
|
||||
#define ACCOUNT_COMPARE(compare_result, buf1, buf2)\
|
||||
{\
|
||||
compare_result = 0;\
|
||||
for (int i = 0; GUARD(20), i < 20; ++i)\
|
||||
{\
|
||||
if (buf1[i] > buf2[i])\
|
||||
{\
|
||||
compare_result = 1;\
|
||||
break;\
|
||||
}\
|
||||
else if (buf1[i] < buf2[i])\
|
||||
{\
|
||||
compare_result = -1;\
|
||||
break;\
|
||||
}\
|
||||
}\
|
||||
}
|
||||
|
||||
#define BUFFER_EQUAL_STR_GUARD(output, buf1, buf1len, str, n)\
|
||||
BUFFER_EQUAL_GUARD(output, buf1, buf1len, str, (sizeof(str)-1), n)
|
||||
|
||||
#define BUFFER_EQUAL_STR(output, buf1, buf1len, str)\
|
||||
BUFFER_EQUAL_GUARD(output, buf1, buf1len, str, (sizeof(str)-1), 1)
|
||||
|
||||
#define BUFFER_EQUAL(output, buf1, buf2, compare_len)\
|
||||
BUFFER_EQUAL_GUARD(output, buf1, compare_len, buf2, compare_len, 1)
|
||||
|
||||
#define UINT16_TO_BUF(buf_raw, i)\
|
||||
{\
|
||||
unsigned char* buf = (unsigned char*)buf_raw;\
|
||||
buf[0] = (((uint64_t)i) >> 8) & 0xFFUL;\
|
||||
buf[1] = (((uint64_t)i) >> 0) & 0xFFUL;\
|
||||
}
|
||||
|
||||
#define UINT16_FROM_BUF(buf)\
|
||||
(((uint64_t)((buf)[0]) << 8) +\
|
||||
((uint64_t)((buf)[1]) << 0))
|
||||
|
||||
#define UINT32_TO_BUF(buf_raw, i)\
|
||||
{\
|
||||
unsigned char* buf = (unsigned char*)buf_raw;\
|
||||
buf[0] = (((uint64_t)i) >> 24) & 0xFFUL;\
|
||||
buf[1] = (((uint64_t)i) >> 16) & 0xFFUL;\
|
||||
buf[2] = (((uint64_t)i) >> 8) & 0xFFUL;\
|
||||
buf[3] = (((uint64_t)i) >> 0) & 0xFFUL;\
|
||||
}
|
||||
|
||||
|
||||
#define UINT32_FROM_BUF(buf)\
|
||||
(((uint64_t)((buf)[0]) << 24) +\
|
||||
((uint64_t)((buf)[1]) << 16) +\
|
||||
((uint64_t)((buf)[2]) << 8) +\
|
||||
((uint64_t)((buf)[3]) << 0))
|
||||
|
||||
#define UINT64_TO_BUF(buf_raw, i)\
|
||||
{\
|
||||
unsigned char* buf = (unsigned char*)buf_raw;\
|
||||
buf[0] = (((uint64_t)i) >> 56) & 0xFFUL;\
|
||||
buf[1] = (((uint64_t)i) >> 48) & 0xFFUL;\
|
||||
buf[2] = (((uint64_t)i) >> 40) & 0xFFUL;\
|
||||
buf[3] = (((uint64_t)i) >> 32) & 0xFFUL;\
|
||||
buf[4] = (((uint64_t)i) >> 24) & 0xFFUL;\
|
||||
buf[5] = (((uint64_t)i) >> 16) & 0xFFUL;\
|
||||
buf[6] = (((uint64_t)i) >> 8) & 0xFFUL;\
|
||||
buf[7] = (((uint64_t)i) >> 0) & 0xFFUL;\
|
||||
}
|
||||
|
||||
|
||||
#define UINT64_FROM_BUF(buf)\
|
||||
(((uint64_t)((buf)[0]) << 56) +\
|
||||
((uint64_t)((buf)[1]) << 48) +\
|
||||
((uint64_t)((buf)[2]) << 40) +\
|
||||
((uint64_t)((buf)[3]) << 32) +\
|
||||
((uint64_t)((buf)[4]) << 24) +\
|
||||
((uint64_t)((buf)[5]) << 16) +\
|
||||
((uint64_t)((buf)[6]) << 8) +\
|
||||
((uint64_t)((buf)[7]) << 0))
|
||||
|
||||
|
||||
#define INT64_FROM_BUF(buf)\
|
||||
((((uint64_t)((buf)[0] & 0x7FU) << 56) +\
|
||||
((uint64_t)((buf)[1]) << 48) +\
|
||||
((uint64_t)((buf)[2]) << 40) +\
|
||||
((uint64_t)((buf)[3]) << 32) +\
|
||||
((uint64_t)((buf)[4]) << 24) +\
|
||||
((uint64_t)((buf)[5]) << 16) +\
|
||||
((uint64_t)((buf)[6]) << 8) +\
|
||||
((uint64_t)((buf)[7]) << 0)) * (buf[0] & 0x80U ? -1 : 1))
|
||||
|
||||
#define INT64_TO_BUF(buf_raw, i)\
|
||||
{\
|
||||
unsigned char* buf = (unsigned char*)buf_raw;\
|
||||
buf[0] = (((uint64_t)i) >> 56) & 0x7FUL;\
|
||||
buf[1] = (((uint64_t)i) >> 48) & 0xFFUL;\
|
||||
buf[2] = (((uint64_t)i) >> 40) & 0xFFUL;\
|
||||
buf[3] = (((uint64_t)i) >> 32) & 0xFFUL;\
|
||||
buf[4] = (((uint64_t)i) >> 24) & 0xFFUL;\
|
||||
buf[5] = (((uint64_t)i) >> 16) & 0xFFUL;\
|
||||
buf[6] = (((uint64_t)i) >> 8) & 0xFFUL;\
|
||||
buf[7] = (((uint64_t)i) >> 0) & 0xFFUL;\
|
||||
if (i < 0) buf[0] |= 0x80U;\
|
||||
}
|
||||
|
||||
#define ttPAYMENT 0
|
||||
#define ttESCROW_CREATE 1
|
||||
#define ttESCROW_FINISH 2
|
||||
#define ttACCOUNT_SET 3
|
||||
#define ttESCROW_CANCEL 4
|
||||
#define ttREGULAR_KEY_SET 5
|
||||
#define ttOFFER_CREATE 7
|
||||
#define ttOFFER_CANCEL 8
|
||||
#define ttTICKET_CREATE 10
|
||||
#define ttSIGNER_LIST_SET 12
|
||||
#define ttPAYCHAN_CREATE 13
|
||||
#define ttPAYCHAN_FUND 14
|
||||
#define ttPAYCHAN_CLAIM 15
|
||||
#define ttCHECK_CREATE 16
|
||||
#define ttCHECK_CASH 17
|
||||
#define ttCHECK_CANCEL 18
|
||||
#define ttDEPOSIT_PREAUTH 19
|
||||
#define ttTRUST_SET 20
|
||||
#define ttACCOUNT_DELETE 21
|
||||
#define ttHOOK_SET 22
|
||||
#define ttNFTOKEN_MINT 25
|
||||
#define ttNFTOKEN_BURN 26
|
||||
#define ttNFTOKEN_CREATE_OFFER 27
|
||||
#define ttNFTOKEN_CANCEL_OFFER 28
|
||||
#define ttNFTOKEN_ACCEPT_OFFER 29
|
||||
#define ttURITOKEN_MINT 45
|
||||
#define ttURITOKEN_BURN 46
|
||||
#define ttURITOKEN_BUY 47
|
||||
#define ttURITOKEN_CREATE_SELL_OFFER 48
|
||||
#define ttURITOKEN_CANCEL_SELL_OFFER 49
|
||||
#define ttCLAIM_REWARD 98
|
||||
#define ttINVOKE 99
|
||||
#define ttAMENDMENT 100
|
||||
#define ttFEE 101
|
||||
#define ttUNL_MODIFY 102
|
||||
#define ttEMIT_FAILURE 103
|
||||
#define tfCANONICAL 0x80000000UL
|
||||
|
||||
#define atACCOUNT 1U
|
||||
#define atOWNER 2U
|
||||
#define atDESTINATION 3U
|
||||
#define atISSUER 4U
|
||||
#define atAUTHORIZE 5U
|
||||
#define atUNAUTHORIZE 6U
|
||||
#define atTARGET 7U
|
||||
#define atREGULARKEY 8U
|
||||
#define atPSEUDOCALLBACK 9U
|
||||
|
||||
#define amAMOUNT 1U
|
||||
#define amBALANCE 2U
|
||||
#define amLIMITAMOUNT 3U
|
||||
#define amTAKERPAYS 4U
|
||||
#define amTAKERGETS 5U
|
||||
#define amLOWLIMIT 6U
|
||||
#define amHIGHLIMIT 7U
|
||||
#define amFEE 8U
|
||||
#define amSENDMAX 9U
|
||||
#define amDELIVERMIN 10U
|
||||
#define amMINIMUMOFFER 16U
|
||||
#define amRIPPLEESCROW 17U
|
||||
#define amDELIVEREDAMOUNT 18U
|
||||
|
||||
/**
|
||||
* RH NOTE -- PAY ATTENTION
|
||||
*
|
||||
* ALL 'ENCODE' MACROS INCREMENT BUF_OUT
|
||||
* THIS IS TO MAKE CHAINING EASY
|
||||
* BUF_OUT IS A SACRIFICIAL POINTER
|
||||
*
|
||||
* 'ENCODE' MACROS WITH CONSTANTS HAVE
|
||||
* ALIASING TO ASSIST YOU WITH ORDER
|
||||
* _TYPECODE_FIELDCODE_ENCODE_MACRO
|
||||
* TO PRODUCE A SERIALIZED OBJECT
|
||||
* IN CANONICAL FORMAT YOU MUST ORDER
|
||||
* FIRST BY TYPE CODE THEN BY FIELD CODE
|
||||
*
|
||||
* ALL 'PREPARE' MACROS PRESERVE POINTERS
|
||||
*
|
||||
**/
|
||||
|
||||
|
||||
#define ENCODE_TL_SIZE 49
|
||||
#define ENCODE_TL(buf_out, tlamt, amount_type)\
|
||||
{\
|
||||
uint8_t uat = amount_type; \
|
||||
buf_out[0] = 0x60U +(uat & 0x0FU ); \
|
||||
for (int i = 1; GUARDM(48, 1), i < 49; ++i)\
|
||||
buf_out[i] = tlamt[i-1];\
|
||||
buf_out += ENCODE_TL_SIZE;\
|
||||
}
|
||||
#define _06_XX_ENCODE_TL(buf_out, drops, amount_type )\
|
||||
ENCODE_TL(buf_out, drops, amount_type );
|
||||
#define ENCODE_TL_AMOUNT(buf_out, drops )\
|
||||
ENCODE_TL(buf_out, drops, amAMOUNT );
|
||||
#define _06_01_ENCODE_TL_AMOUNT(buf_out, drops )\
|
||||
ENCODE_TL_AMOUNT(buf_out, drops );
|
||||
|
||||
|
||||
// Encode drops to serialization format
|
||||
// consumes 9 bytes
|
||||
#define ENCODE_DROPS_SIZE 9
|
||||
#define ENCODE_DROPS(buf_out, drops, amount_type ) \
|
||||
{\
|
||||
uint8_t uat = amount_type; \
|
||||
uint64_t udrops = drops; \
|
||||
buf_out[0] = 0x60U +(uat & 0x0FU ); \
|
||||
buf_out[1] = 0b01000000 + (( udrops >> 56 ) & 0b00111111 ); \
|
||||
buf_out[2] = (udrops >> 48) & 0xFFU; \
|
||||
buf_out[3] = (udrops >> 40) & 0xFFU; \
|
||||
buf_out[4] = (udrops >> 32) & 0xFFU; \
|
||||
buf_out[5] = (udrops >> 24) & 0xFFU; \
|
||||
buf_out[6] = (udrops >> 16) & 0xFFU; \
|
||||
buf_out[7] = (udrops >> 8) & 0xFFU; \
|
||||
buf_out[8] = (udrops >> 0) & 0xFFU; \
|
||||
buf_out += ENCODE_DROPS_SIZE; \
|
||||
}
|
||||
|
||||
#define _06_XX_ENCODE_DROPS(buf_out, drops, amount_type )\
|
||||
ENCODE_DROPS(buf_out, drops, amount_type );
|
||||
|
||||
#define ENCODE_DROPS_AMOUNT(buf_out, drops )\
|
||||
ENCODE_DROPS(buf_out, drops, amAMOUNT );
|
||||
#define _06_01_ENCODE_DROPS_AMOUNT(buf_out, drops )\
|
||||
ENCODE_DROPS_AMOUNT(buf_out, drops );
|
||||
|
||||
#define ENCODE_DROPS_FEE(buf_out, drops )\
|
||||
ENCODE_DROPS(buf_out, drops, amFEE );
|
||||
#define _06_08_ENCODE_DROPS_FEE(buf_out, drops )\
|
||||
ENCODE_DROPS_FEE(buf_out, drops );
|
||||
|
||||
#define ENCODE_TT_SIZE 3
|
||||
#define ENCODE_TT(buf_out, tt )\
|
||||
{\
|
||||
uint8_t utt = tt;\
|
||||
buf_out[0] = 0x12U;\
|
||||
buf_out[1] =(utt >> 8 ) & 0xFFU;\
|
||||
buf_out[2] =(utt >> 0 ) & 0xFFU;\
|
||||
buf_out += ENCODE_TT_SIZE; \
|
||||
}
|
||||
#define _01_02_ENCODE_TT(buf_out, tt)\
|
||||
ENCODE_TT(buf_out, tt);
|
||||
|
||||
|
||||
#define ENCODE_ACCOUNT_SIZE 22
|
||||
#define ENCODE_ACCOUNT(buf_out, account_id, account_type)\
|
||||
{\
|
||||
uint8_t uat = account_type;\
|
||||
buf_out[0] = 0x80U + uat;\
|
||||
buf_out[1] = 0x14U;\
|
||||
*(uint64_t*)(buf_out + 2) = *(uint64_t*)(account_id + 0);\
|
||||
*(uint64_t*)(buf_out + 10) = *(uint64_t*)(account_id + 8);\
|
||||
*(uint32_t*)(buf_out + 18) = *(uint32_t*)(account_id + 16);\
|
||||
buf_out += ENCODE_ACCOUNT_SIZE;\
|
||||
}
|
||||
#define _08_XX_ENCODE_ACCOUNT(buf_out, account_id, account_type)\
|
||||
ENCODE_ACCOUNT(buf_out, account_id, account_type);
|
||||
|
||||
#define ENCODE_ACCOUNT_SRC_SIZE 22
|
||||
#define ENCODE_ACCOUNT_SRC(buf_out, account_id)\
|
||||
ENCODE_ACCOUNT(buf_out, account_id, atACCOUNT);
|
||||
#define _08_01_ENCODE_ACCOUNT_SRC(buf_out, account_id)\
|
||||
ENCODE_ACCOUNT_SRC(buf_out, account_id);
|
||||
|
||||
#define ENCODE_ACCOUNT_DST_SIZE 22
|
||||
#define ENCODE_ACCOUNT_DST(buf_out, account_id)\
|
||||
ENCODE_ACCOUNT(buf_out, account_id, atDESTINATION);
|
||||
#define _08_03_ENCODE_ACCOUNT_DST(buf_out, account_id)\
|
||||
ENCODE_ACCOUNT_DST(buf_out, account_id);
|
||||
|
||||
#define ENCODE_ACCOUNT_OWNER_SIZE 22
|
||||
#define ENCODE_ACCOUNT_OWNER(buf_out, account_id) \
|
||||
ENCODE_ACCOUNT(buf_out, account_id, atOWNER);
|
||||
#define _08_02_ENCODE_ACCOUNT_OWNER(buf_out, account_id) \
|
||||
ENCODE_ACCOUNT_OWNER(buf_out, account_id);
|
||||
|
||||
#define ENCODE_UINT32_COMMON_SIZE 5U
|
||||
#define ENCODE_UINT32_COMMON(buf_out, i, field)\
|
||||
{\
|
||||
uint32_t ui = i; \
|
||||
uint8_t uf = field; \
|
||||
buf_out[0] = 0x20U +(uf & 0x0FU); \
|
||||
buf_out[1] =(ui >> 24 ) & 0xFFU; \
|
||||
buf_out[2] =(ui >> 16 ) & 0xFFU; \
|
||||
buf_out[3] =(ui >> 8 ) & 0xFFU; \
|
||||
buf_out[4] =(ui >> 0 ) & 0xFFU; \
|
||||
buf_out += ENCODE_UINT32_COMMON_SIZE; \
|
||||
}
|
||||
#define _02_XX_ENCODE_UINT32_COMMON(buf_out, i, field)\
|
||||
ENCODE_UINT32_COMMON(buf_out, i, field)\
|
||||
|
||||
#define ENCODE_UINT32_UNCOMMON_SIZE 6U
|
||||
#define ENCODE_UINT32_UNCOMMON(buf_out, i, field)\
|
||||
{\
|
||||
uint32_t ui = i; \
|
||||
uint8_t uf = field; \
|
||||
buf_out[0] = 0x20U; \
|
||||
buf_out[1] = uf; \
|
||||
buf_out[2] =(ui >> 24 ) & 0xFFU; \
|
||||
buf_out[3] =(ui >> 16 ) & 0xFFU; \
|
||||
buf_out[4] =(ui >> 8 ) & 0xFFU; \
|
||||
buf_out[5] =(ui >> 0 ) & 0xFFU; \
|
||||
buf_out += ENCODE_UINT32_UNCOMMON_SIZE; \
|
||||
}
|
||||
#define _02_XX_ENCODE_UINT32_UNCOMMON(buf_out, i, field)\
|
||||
ENCODE_UINT32_UNCOMMON(buf_out, i, field)\
|
||||
|
||||
#define ENCODE_LLS_SIZE 6U
|
||||
#define ENCODE_LLS(buf_out, lls )\
|
||||
ENCODE_UINT32_UNCOMMON(buf_out, lls, 0x1B );
|
||||
#define _02_27_ENCODE_LLS(buf_out, lls )\
|
||||
ENCODE_LLS(buf_out, lls );
|
||||
|
||||
#define ENCODE_FLS_SIZE 6U
|
||||
#define ENCODE_FLS(buf_out, fls )\
|
||||
ENCODE_UINT32_UNCOMMON(buf_out, fls, 0x1A );
|
||||
#define _02_26_ENCODE_FLS(buf_out, fls )\
|
||||
ENCODE_FLS(buf_out, fls );
|
||||
|
||||
#define ENCODE_TAG_SRC_SIZE 5
|
||||
#define ENCODE_TAG_SRC(buf_out, tag )\
|
||||
ENCODE_UINT32_COMMON(buf_out, tag, 0x3U );
|
||||
#define _02_03_ENCODE_TAG_SRC(buf_out, tag )\
|
||||
ENCODE_TAG_SRC(buf_out, tag );
|
||||
|
||||
#define ENCODE_TAG_DST_SIZE 5
|
||||
#define ENCODE_TAG_DST(buf_out, tag )\
|
||||
ENCODE_UINT32_COMMON(buf_out, tag, 0xEU );
|
||||
#define _02_14_ENCODE_TAG_DST(buf_out, tag )\
|
||||
ENCODE_TAG_DST(buf_out, tag );
|
||||
|
||||
#define ENCODE_SEQUENCE_SIZE 5
|
||||
#define ENCODE_SEQUENCE(buf_out, sequence )\
|
||||
ENCODE_UINT32_COMMON(buf_out, sequence, 0x4U );
|
||||
#define _02_04_ENCODE_SEQUENCE(buf_out, sequence )\
|
||||
ENCODE_SEQUENCE(buf_out, sequence );
|
||||
|
||||
#define ENCODE_FLAGS_SIZE 5
|
||||
#define ENCODE_FLAGS(buf_out, tag )\
|
||||
ENCODE_UINT32_COMMON(buf_out, tag, 0x2U );
|
||||
#define _02_02_ENCODE_FLAGS(buf_out, tag )\
|
||||
ENCODE_FLAGS(buf_out, tag );
|
||||
|
||||
#define ENCODE_SIGNING_PUBKEY_SIZE 35
|
||||
#define ENCODE_SIGNING_PUBKEY(buf_out, pkey )\
|
||||
{\
|
||||
buf_out[0] = 0x73U;\
|
||||
buf_out[1] = 0x21U;\
|
||||
*(uint64_t*)(buf_out + 2) = *(uint64_t*)(pkey + 0);\
|
||||
*(uint64_t*)(buf_out + 10) = *(uint64_t*)(pkey + 8);\
|
||||
*(uint64_t*)(buf_out + 18) = *(uint64_t*)(pkey + 16);\
|
||||
*(uint64_t*)(buf_out + 26) = *(uint64_t*)(pkey + 24);\
|
||||
buf[34] = pkey[32];\
|
||||
buf_out += ENCODE_SIGNING_PUBKEY_SIZE;\
|
||||
}
|
||||
|
||||
#define _07_03_ENCODE_SIGNING_PUBKEY(buf_out, pkey )\
|
||||
ENCODE_SIGNING_PUBKEY(buf_out, pkey );
|
||||
|
||||
#define ENCODE_SIGNING_PUBKEY_NULL_SIZE 2
|
||||
#define ENCODE_SIGNING_PUBKEY_NULL(buf_out )\
|
||||
{\
|
||||
*buf_out++ = 0x73U;\
|
||||
*buf_out++ = 0x00U;\
|
||||
}
|
||||
|
||||
#define _07_03_ENCODE_SIGNING_PUBKEY_NULL(buf_out )\
|
||||
ENCODE_SIGNING_PUBKEY_NULL(buf_out );
|
||||
|
||||
|
||||
#define _0E_0E_ENCODE_HOOKOBJ(buf_out, hhash)\
|
||||
{\
|
||||
uint8_t* hook0 = (hhash);\
|
||||
*buf_out++ = 0xEEU; /* hook obj start */ \
|
||||
if (hook0 == 0) /* noop */\
|
||||
{\
|
||||
/* do nothing */ \
|
||||
}\
|
||||
else\
|
||||
{\
|
||||
*buf_out++ = 0x22U; /* flags = override */\
|
||||
*buf_out++ = 0x00U;\
|
||||
*buf_out++ = 0x00U;\
|
||||
*buf_out++ = 0x00U;\
|
||||
*buf_out++ = 0x01U;\
|
||||
if (hook0 == 0xFFFFFFFFUL) /* delete operation */ \
|
||||
{\
|
||||
*buf_out++ = 0x7BU; /* empty createcode */ \
|
||||
*buf_out++ = 0x00U;\
|
||||
}\
|
||||
else\
|
||||
{\
|
||||
*buf_out++ = 0x50U; /* HookHash */\
|
||||
*buf_out++ = 0x1FU;\
|
||||
uint64_t* d = (uint64_t*)buf_out;\
|
||||
uint64_t* s = (uint64_t*)hook0;\
|
||||
*d++ = *s++;\
|
||||
*d++ = *s++;\
|
||||
*d++ = *s++;\
|
||||
*d++ = *s++;\
|
||||
buf_out+=32;\
|
||||
}\
|
||||
}\
|
||||
*buf_out++ = 0xE1U;\
|
||||
}
|
||||
|
||||
#define PREPARE_HOOKSET(buf_out_master, maxlen, h, sizeout)\
|
||||
{\
|
||||
uint8_t* buf_out = (buf_out_master); \
|
||||
uint8_t acc[20]; \
|
||||
uint32_t cls = (uint32_t)ledger_seq(); \
|
||||
hook_account(SBUF(acc)); \
|
||||
_01_02_ENCODE_TT (buf_out, ttHOOK_SET ); \
|
||||
_02_02_ENCODE_FLAGS (buf_out, tfCANONICAL ); \
|
||||
_02_04_ENCODE_SEQUENCE (buf_out, 0 ); \
|
||||
_02_26_ENCODE_FLS (buf_out, cls + 1 ); \
|
||||
_02_27_ENCODE_LLS (buf_out, cls + 5 ); \
|
||||
uint8_t* fee_ptr = buf_out; \
|
||||
_06_08_ENCODE_DROPS_FEE (buf_out, 0 ); \
|
||||
_07_03_ENCODE_SIGNING_PUBKEY_NULL (buf_out ); \
|
||||
_08_01_ENCODE_ACCOUNT_SRC (buf_out, acc ); \
|
||||
uint32_t remaining_size = (maxlen) - (buf_out - (buf_out_master)); \
|
||||
int64_t edlen = etxn_details((uint32_t)buf_out, remaining_size); \
|
||||
buf_out += edlen; \
|
||||
*buf_out++ = 0xFBU; /* hook array start */ \
|
||||
_0E_0E_ENCODE_HOOKOBJ (buf_out, h[0]); \
|
||||
_0E_0E_ENCODE_HOOKOBJ (buf_out, h[1]); \
|
||||
_0E_0E_ENCODE_HOOKOBJ (buf_out, h[2]); \
|
||||
_0E_0E_ENCODE_HOOKOBJ (buf_out, h[3]); \
|
||||
_0E_0E_ENCODE_HOOKOBJ (buf_out, h[4]); \
|
||||
_0E_0E_ENCODE_HOOKOBJ (buf_out, h[5]); \
|
||||
_0E_0E_ENCODE_HOOKOBJ (buf_out, h[6]); \
|
||||
_0E_0E_ENCODE_HOOKOBJ (buf_out, h[7]); \
|
||||
_0E_0E_ENCODE_HOOKOBJ (buf_out, h[8]); \
|
||||
_0E_0E_ENCODE_HOOKOBJ (buf_out, h[9]); \
|
||||
*buf_out++ = 0xF1U; /* hook array end */ \
|
||||
sizeout = (buf_out - (buf_out_master)); \
|
||||
int64_t fee = etxn_fee_base(buf_out_master, sizeout); \
|
||||
_06_08_ENCODE_DROPS_FEE (fee_ptr, fee ); \
|
||||
}
|
||||
|
||||
#ifdef HAS_CALLBACK
|
||||
#define PREPARE_PAYMENT_SIMPLE_SIZE 270U
|
||||
#else
|
||||
#define PREPARE_PAYMENT_SIMPLE_SIZE 248U
|
||||
#endif
|
||||
|
||||
#define PREPARE_PAYMENT_SIMPLE(buf_out_master, drops_amount_raw, to_address, dest_tag_raw, src_tag_raw)\
|
||||
{\
|
||||
uint8_t* buf_out = buf_out_master;\
|
||||
uint8_t acc[20];\
|
||||
uint64_t drops_amount = (drops_amount_raw);\
|
||||
uint32_t dest_tag = (dest_tag_raw);\
|
||||
uint32_t src_tag = (src_tag_raw);\
|
||||
uint32_t cls = (uint32_t)ledger_seq();\
|
||||
hook_account(SBUF(acc));\
|
||||
_01_02_ENCODE_TT (buf_out, ttPAYMENT ); /* uint16 | size 3 */ \
|
||||
_02_02_ENCODE_FLAGS (buf_out, tfCANONICAL ); /* uint32 | size 5 */ \
|
||||
_02_03_ENCODE_TAG_SRC (buf_out, src_tag ); /* uint32 | size 5 */ \
|
||||
_02_04_ENCODE_SEQUENCE (buf_out, 0 ); /* uint32 | size 5 */ \
|
||||
_02_14_ENCODE_TAG_DST (buf_out, dest_tag ); /* uint32 | size 5 */ \
|
||||
_02_26_ENCODE_FLS (buf_out, cls + 1 ); /* uint32 | size 6 */ \
|
||||
_02_27_ENCODE_LLS (buf_out, cls + 5 ); /* uint32 | size 6 */ \
|
||||
_06_01_ENCODE_DROPS_AMOUNT (buf_out, drops_amount ); /* amount | size 9 */ \
|
||||
uint8_t* fee_ptr = buf_out;\
|
||||
_06_08_ENCODE_DROPS_FEE (buf_out, 0 ); /* amount | size 9 */ \
|
||||
_07_03_ENCODE_SIGNING_PUBKEY_NULL (buf_out ); /* pk | size 35 */ \
|
||||
_08_01_ENCODE_ACCOUNT_SRC (buf_out, acc ); /* account | size 22 */ \
|
||||
_08_03_ENCODE_ACCOUNT_DST (buf_out, to_address ); /* account | size 22 */ \
|
||||
int64_t edlen = etxn_details((uint32_t)buf_out, PREPARE_PAYMENT_SIMPLE_SIZE); /* emitdet | size 1?? */ \
|
||||
int64_t fee = etxn_fee_base(buf_out_master, PREPARE_PAYMENT_SIMPLE_SIZE); \
|
||||
_06_08_ENCODE_DROPS_FEE (fee_ptr, fee ); \
|
||||
}
|
||||
|
||||
#ifdef HAS_CALLBACK
|
||||
#define PREPARE_PAYMENT_SIMPLE_TRUSTLINE_SIZE 309
|
||||
#else
|
||||
#define PREPARE_PAYMENT_SIMPLE_TRUSTLINE_SIZE 287
|
||||
#endif
|
||||
#define PREPARE_PAYMENT_SIMPLE_TRUSTLINE(buf_out_master, tlamt, to_address, dest_tag_raw, src_tag_raw)\
|
||||
{\
|
||||
uint8_t* buf_out = buf_out_master;\
|
||||
uint8_t acc[20];\
|
||||
uint32_t dest_tag = (dest_tag_raw);\
|
||||
uint32_t src_tag = (src_tag_raw);\
|
||||
uint32_t cls = (uint32_t)ledger_seq();\
|
||||
hook_account(SBUF(acc));\
|
||||
_01_02_ENCODE_TT (buf_out, ttPAYMENT ); /* uint16 | size 3 */ \
|
||||
_02_02_ENCODE_FLAGS (buf_out, tfCANONICAL ); /* uint32 | size 5 */ \
|
||||
_02_03_ENCODE_TAG_SRC (buf_out, src_tag ); /* uint32 | size 5 */ \
|
||||
_02_04_ENCODE_SEQUENCE (buf_out, 0 ); /* uint32 | size 5 */ \
|
||||
_02_14_ENCODE_TAG_DST (buf_out, dest_tag ); /* uint32 | size 5 */ \
|
||||
_02_26_ENCODE_FLS (buf_out, cls + 1 ); /* uint32 | size 6 */ \
|
||||
_02_27_ENCODE_LLS (buf_out, cls + 5 ); /* uint32 | size 6 */ \
|
||||
_06_01_ENCODE_TL_AMOUNT (buf_out, tlamt ); /* amount | size 48 */ \
|
||||
uint8_t* fee_ptr = buf_out;\
|
||||
_06_08_ENCODE_DROPS_FEE (buf_out, 0 ); /* amount | size 9 */ \
|
||||
_07_03_ENCODE_SIGNING_PUBKEY_NULL (buf_out ); /* pk | size 35 */ \
|
||||
_08_01_ENCODE_ACCOUNT_SRC (buf_out, acc ); /* account | size 22 */ \
|
||||
_08_03_ENCODE_ACCOUNT_DST (buf_out, to_address ); /* account | size 22 */ \
|
||||
etxn_details((uint32_t)buf_out, PREPARE_PAYMENT_SIMPLE_TRUSTLINE_SIZE); /* emitdet | size 1?? */ \
|
||||
int64_t fee = etxn_fee_base(buf_out_master, PREPARE_PAYMENT_SIMPLE_TRUSTLINE_SIZE); \
|
||||
_06_08_ENCODE_DROPS_FEE (fee_ptr, fee ); \
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,215 +0,0 @@
|
||||
// For documentation please see: https://xrpl-hooks.readme.io/reference/
|
||||
// Generated using generate_sfcodes.sh
|
||||
#define sfCloseResolution ((16U << 16U) + 1U)
|
||||
#define sfMethod ((16U << 16U) + 2U)
|
||||
#define sfTransactionResult ((16U << 16U) + 3U)
|
||||
#define sfTickSize ((16U << 16U) + 16U)
|
||||
#define sfUNLModifyDisabling ((16U << 16U) + 17U)
|
||||
#define sfHookResult ((16U << 16U) + 18U)
|
||||
#define sfLedgerEntryType ((1U << 16U) + 1U)
|
||||
#define sfTransactionType ((1U << 16U) + 2U)
|
||||
#define sfSignerWeight ((1U << 16U) + 3U)
|
||||
#define sfTransferFee ((1U << 16U) + 4U)
|
||||
#define sfVersion ((1U << 16U) + 16U)
|
||||
#define sfHookStateChangeCount ((1U << 16U) + 17U)
|
||||
#define sfHookEmitCount ((1U << 16U) + 18U)
|
||||
#define sfHookExecutionIndex ((1U << 16U) + 19U)
|
||||
#define sfHookApiVersion ((1U << 16U) + 20U)
|
||||
#define sfNetworkID ((2U << 16U) + 1U)
|
||||
#define sfFlags ((2U << 16U) + 2U)
|
||||
#define sfSourceTag ((2U << 16U) + 3U)
|
||||
#define sfSequence ((2U << 16U) + 4U)
|
||||
#define sfPreviousTxnLgrSeq ((2U << 16U) + 5U)
|
||||
#define sfLedgerSequence ((2U << 16U) + 6U)
|
||||
#define sfCloseTime ((2U << 16U) + 7U)
|
||||
#define sfParentCloseTime ((2U << 16U) + 8U)
|
||||
#define sfSigningTime ((2U << 16U) + 9U)
|
||||
#define sfExpiration ((2U << 16U) + 10U)
|
||||
#define sfTransferRate ((2U << 16U) + 11U)
|
||||
#define sfWalletSize ((2U << 16U) + 12U)
|
||||
#define sfOwnerCount ((2U << 16U) + 13U)
|
||||
#define sfDestinationTag ((2U << 16U) + 14U)
|
||||
#define sfHighQualityIn ((2U << 16U) + 16U)
|
||||
#define sfHighQualityOut ((2U << 16U) + 17U)
|
||||
#define sfLowQualityIn ((2U << 16U) + 18U)
|
||||
#define sfLowQualityOut ((2U << 16U) + 19U)
|
||||
#define sfQualityIn ((2U << 16U) + 20U)
|
||||
#define sfQualityOut ((2U << 16U) + 21U)
|
||||
#define sfStampEscrow ((2U << 16U) + 22U)
|
||||
#define sfBondAmount ((2U << 16U) + 23U)
|
||||
#define sfLoadFee ((2U << 16U) + 24U)
|
||||
#define sfOfferSequence ((2U << 16U) + 25U)
|
||||
#define sfFirstLedgerSequence ((2U << 16U) + 26U)
|
||||
#define sfLastLedgerSequence ((2U << 16U) + 27U)
|
||||
#define sfTransactionIndex ((2U << 16U) + 28U)
|
||||
#define sfOperationLimit ((2U << 16U) + 29U)
|
||||
#define sfReferenceFeeUnits ((2U << 16U) + 30U)
|
||||
#define sfReserveBase ((2U << 16U) + 31U)
|
||||
#define sfReserveIncrement ((2U << 16U) + 32U)
|
||||
#define sfSetFlag ((2U << 16U) + 33U)
|
||||
#define sfClearFlag ((2U << 16U) + 34U)
|
||||
#define sfSignerQuorum ((2U << 16U) + 35U)
|
||||
#define sfCancelAfter ((2U << 16U) + 36U)
|
||||
#define sfFinishAfter ((2U << 16U) + 37U)
|
||||
#define sfSignerListID ((2U << 16U) + 38U)
|
||||
#define sfSettleDelay ((2U << 16U) + 39U)
|
||||
#define sfTicketCount ((2U << 16U) + 40U)
|
||||
#define sfTicketSequence ((2U << 16U) + 41U)
|
||||
#define sfNFTokenTaxon ((2U << 16U) + 42U)
|
||||
#define sfMintedNFTokens ((2U << 16U) + 43U)
|
||||
#define sfBurnedNFTokens ((2U << 16U) + 44U)
|
||||
#define sfHookStateCount ((2U << 16U) + 45U)
|
||||
#define sfEmitGeneration ((2U << 16U) + 46U)
|
||||
#define sfLockCount ((2U << 16U) + 47U)
|
||||
#define sfRewardTime ((2U << 16U) + 98U)
|
||||
#define sfRewardLgrFirst ((2U << 16U) + 99U)
|
||||
#define sfRewardLgrLast ((2U << 16U) + 100U)
|
||||
#define sfIndexNext ((3U << 16U) + 1U)
|
||||
#define sfIndexPrevious ((3U << 16U) + 2U)
|
||||
#define sfBookNode ((3U << 16U) + 3U)
|
||||
#define sfOwnerNode ((3U << 16U) + 4U)
|
||||
#define sfBaseFee ((3U << 16U) + 5U)
|
||||
#define sfExchangeRate ((3U << 16U) + 6U)
|
||||
#define sfLowNode ((3U << 16U) + 7U)
|
||||
#define sfHighNode ((3U << 16U) + 8U)
|
||||
#define sfDestinationNode ((3U << 16U) + 9U)
|
||||
#define sfCookie ((3U << 16U) + 10U)
|
||||
#define sfServerVersion ((3U << 16U) + 11U)
|
||||
#define sfNFTokenOfferNode ((3U << 16U) + 12U)
|
||||
#define sfEmitBurden ((3U << 16U) + 13U)
|
||||
#define sfHookInstructionCount ((3U << 16U) + 17U)
|
||||
#define sfHookReturnCode ((3U << 16U) + 18U)
|
||||
#define sfReferenceCount ((3U << 16U) + 19U)
|
||||
#define sfRewardAccumulator ((3U << 16U) + 100U)
|
||||
#define sfEmailHash ((4U << 16U) + 1U)
|
||||
#define sfTakerPaysCurrency ((10U << 16U) + 1U)
|
||||
#define sfTakerPaysIssuer ((10U << 16U) + 2U)
|
||||
#define sfTakerGetsCurrency ((10U << 16U) + 3U)
|
||||
#define sfTakerGetsIssuer ((10U << 16U) + 4U)
|
||||
#define sfLedgerHash ((5U << 16U) + 1U)
|
||||
#define sfParentHash ((5U << 16U) + 2U)
|
||||
#define sfTransactionHash ((5U << 16U) + 3U)
|
||||
#define sfAccountHash ((5U << 16U) + 4U)
|
||||
#define sfPreviousTxnID ((5U << 16U) + 5U)
|
||||
#define sfLedgerIndex ((5U << 16U) + 6U)
|
||||
#define sfWalletLocator ((5U << 16U) + 7U)
|
||||
#define sfRootIndex ((5U << 16U) + 8U)
|
||||
#define sfAccountTxnID ((5U << 16U) + 9U)
|
||||
#define sfNFTokenID ((5U << 16U) + 10U)
|
||||
#define sfEmitParentTxnID ((5U << 16U) + 11U)
|
||||
#define sfEmitNonce ((5U << 16U) + 12U)
|
||||
#define sfEmitHookHash ((5U << 16U) + 13U)
|
||||
#define sfBookDirectory ((5U << 16U) + 16U)
|
||||
#define sfInvoiceID ((5U << 16U) + 17U)
|
||||
#define sfNickname ((5U << 16U) + 18U)
|
||||
#define sfAmendment ((5U << 16U) + 19U)
|
||||
#define sfHookOn ((5U << 16U) + 20U)
|
||||
#define sfDigest ((5U << 16U) + 21U)
|
||||
#define sfChannel ((5U << 16U) + 22U)
|
||||
#define sfConsensusHash ((5U << 16U) + 23U)
|
||||
#define sfCheckID ((5U << 16U) + 24U)
|
||||
#define sfValidatedHash ((5U << 16U) + 25U)
|
||||
#define sfPreviousPageMin ((5U << 16U) + 26U)
|
||||
#define sfNextPageMin ((5U << 16U) + 27U)
|
||||
#define sfNFTokenBuyOffer ((5U << 16U) + 28U)
|
||||
#define sfNFTokenSellOffer ((5U << 16U) + 29U)
|
||||
#define sfHookStateKey ((5U << 16U) + 30U)
|
||||
#define sfHookHash ((5U << 16U) + 31U)
|
||||
#define sfHookNamespace ((5U << 16U) + 32U)
|
||||
#define sfHookSetTxnID ((5U << 16U) + 33U)
|
||||
#define sfOfferID ((5U << 16U) + 34U)
|
||||
#define sfEscrowID ((5U << 16U) + 35U)
|
||||
#define sfURITokenID ((5U << 16U) + 36U)
|
||||
#define sfAmount ((6U << 16U) + 1U)
|
||||
#define sfBalance ((6U << 16U) + 2U)
|
||||
#define sfLimitAmount ((6U << 16U) + 3U)
|
||||
#define sfTakerPays ((6U << 16U) + 4U)
|
||||
#define sfTakerGets ((6U << 16U) + 5U)
|
||||
#define sfLowLimit ((6U << 16U) + 6U)
|
||||
#define sfHighLimit ((6U << 16U) + 7U)
|
||||
#define sfFee ((6U << 16U) + 8U)
|
||||
#define sfSendMax ((6U << 16U) + 9U)
|
||||
#define sfDeliverMin ((6U << 16U) + 10U)
|
||||
#define sfMinimumOffer ((6U << 16U) + 16U)
|
||||
#define sfRippleEscrow ((6U << 16U) + 17U)
|
||||
#define sfDeliveredAmount ((6U << 16U) + 18U)
|
||||
#define sfNFTokenBrokerFee ((6U << 16U) + 19U)
|
||||
#define sfHookCallbackFee ((6U << 16U) + 20U)
|
||||
#define sfLockedBalance ((6U << 16U) + 21U)
|
||||
#define sfPublicKey ((7U << 16U) + 1U)
|
||||
#define sfMessageKey ((7U << 16U) + 2U)
|
||||
#define sfSigningPubKey ((7U << 16U) + 3U)
|
||||
#define sfTxnSignature ((7U << 16U) + 4U)
|
||||
#define sfURI ((7U << 16U) + 5U)
|
||||
#define sfSignature ((7U << 16U) + 6U)
|
||||
#define sfDomain ((7U << 16U) + 7U)
|
||||
#define sfFundCode ((7U << 16U) + 8U)
|
||||
#define sfRemoveCode ((7U << 16U) + 9U)
|
||||
#define sfExpireCode ((7U << 16U) + 10U)
|
||||
#define sfCreateCode ((7U << 16U) + 11U)
|
||||
#define sfMemoType ((7U << 16U) + 12U)
|
||||
#define sfMemoData ((7U << 16U) + 13U)
|
||||
#define sfMemoFormat ((7U << 16U) + 14U)
|
||||
#define sfFulfillment ((7U << 16U) + 16U)
|
||||
#define sfCondition ((7U << 16U) + 17U)
|
||||
#define sfMasterSignature ((7U << 16U) + 18U)
|
||||
#define sfUNLModifyValidator ((7U << 16U) + 19U)
|
||||
#define sfValidatorToDisable ((7U << 16U) + 20U)
|
||||
#define sfValidatorToReEnable ((7U << 16U) + 21U)
|
||||
#define sfHookStateData ((7U << 16U) + 22U)
|
||||
#define sfHookReturnString ((7U << 16U) + 23U)
|
||||
#define sfHookParameterName ((7U << 16U) + 24U)
|
||||
#define sfHookParameterValue ((7U << 16U) + 25U)
|
||||
#define sfBlob ((7U << 16U) + 26U)
|
||||
#define sfAccount ((8U << 16U) + 1U)
|
||||
#define sfOwner ((8U << 16U) + 2U)
|
||||
#define sfDestination ((8U << 16U) + 3U)
|
||||
#define sfIssuer ((8U << 16U) + 4U)
|
||||
#define sfAuthorize ((8U << 16U) + 5U)
|
||||
#define sfUnauthorize ((8U << 16U) + 6U)
|
||||
#define sfRegularKey ((8U << 16U) + 8U)
|
||||
#define sfNFTokenMinter ((8U << 16U) + 9U)
|
||||
#define sfEmitCallback ((8U << 16U) + 10U)
|
||||
#define sfHookAccount ((8U << 16U) + 16U)
|
||||
#define sfIndexes ((19U << 16U) + 1U)
|
||||
#define sfHashes ((19U << 16U) + 2U)
|
||||
#define sfAmendments ((19U << 16U) + 3U)
|
||||
#define sfNFTokenOffers ((19U << 16U) + 4U)
|
||||
#define sfHookNamespaces ((19U << 16U) + 5U)
|
||||
#define sfPaths ((18U << 16U) + 1U)
|
||||
#define sfTransactionMetaData ((14U << 16U) + 2U)
|
||||
#define sfCreatedNode ((14U << 16U) + 3U)
|
||||
#define sfDeletedNode ((14U << 16U) + 4U)
|
||||
#define sfModifiedNode ((14U << 16U) + 5U)
|
||||
#define sfPreviousFields ((14U << 16U) + 6U)
|
||||
#define sfFinalFields ((14U << 16U) + 7U)
|
||||
#define sfNewFields ((14U << 16U) + 8U)
|
||||
#define sfTemplateEntry ((14U << 16U) + 9U)
|
||||
#define sfMemo ((14U << 16U) + 10U)
|
||||
#define sfSignerEntry ((14U << 16U) + 11U)
|
||||
#define sfNFToken ((14U << 16U) + 12U)
|
||||
#define sfEmitDetails ((14U << 16U) + 13U)
|
||||
#define sfHook ((14U << 16U) + 14U)
|
||||
#define sfSigner ((14U << 16U) + 16U)
|
||||
#define sfMajority ((14U << 16U) + 18U)
|
||||
#define sfDisabledValidator ((14U << 16U) + 19U)
|
||||
#define sfEmittedTxn ((14U << 16U) + 20U)
|
||||
#define sfHookExecution ((14U << 16U) + 21U)
|
||||
#define sfHookDefinition ((14U << 16U) + 22U)
|
||||
#define sfHookParameter ((14U << 16U) + 23U)
|
||||
#define sfHookGrant ((14U << 16U) + 24U)
|
||||
#define sfSigners ((15U << 16U) + 3U)
|
||||
#define sfSignerEntries ((15U << 16U) + 4U)
|
||||
#define sfTemplate ((15U << 16U) + 5U)
|
||||
#define sfNecessary ((15U << 16U) + 6U)
|
||||
#define sfSufficient ((15U << 16U) + 7U)
|
||||
#define sfAffectedNodes ((15U << 16U) + 8U)
|
||||
#define sfMemos ((15U << 16U) + 9U)
|
||||
#define sfNFTokens ((15U << 16U) + 10U)
|
||||
#define sfHooks ((15U << 16U) + 11U)
|
||||
#define sfMajorities ((15U << 16U) + 16U)
|
||||
#define sfDisabledValidators ((15U << 16U) + 17U)
|
||||
#define sfHookExecutions ((15U << 16U) + 18U)
|
||||
#define sfHookParameters ((15U << 16U) + 19U)
|
||||
#define sfHookGrants ((15U << 16U) + 20U)
|
||||
#define sfActiveValidators ((15U << 16U) + 95U)
|
||||
@@ -1,239 +0,0 @@
|
||||
#include <stdint.h>
|
||||
|
||||
// 8 byte-int = 1 bytes
|
||||
#define SFL_CLOSERESOLUTION 1
|
||||
#define SFL_METHOD 1
|
||||
#define SFL_TRANSACTIONRESULT 1
|
||||
#define SFL_TICKSIZE 1
|
||||
#define SFL_UNLMODIFYDISABLING 1
|
||||
#define SFL_HOOKRESULT 1
|
||||
// 16 byte-int = 2 bytes
|
||||
#define SFL_LEDGERENTRYTYPE 2
|
||||
#define SFL_TRANSACTIONTYPE 2
|
||||
#define SFL_SIGNERWEIGHT 2
|
||||
#define SFL_TRANSFERFEE 2
|
||||
#define SFL_VERSION 2
|
||||
#define SFL_HOOKSTATECHANGECOUNT 2
|
||||
#define SFL_HOOKEMITCOUNT 2
|
||||
#define SFL_HOOKEXECUTIONINDEX 2
|
||||
#define SFL_HOOKAPIVERSION 2
|
||||
// 32 byte-int = 4 bytes
|
||||
#define SFL_NETWORKID 4
|
||||
#define SFL_FLAGS 4
|
||||
#define SFL_SOURCETAG 4
|
||||
#define SFL_SEQUENCE 4
|
||||
#define SFL_PREVIOUSTXNLGRSEQ 4
|
||||
#define SFL_LEDGERSEQUENCE 4
|
||||
#define SFL_CLOSETIME 4
|
||||
#define SFL_PARENTCLOSETIME 4
|
||||
#define SFL_SIGNINGTIME 4
|
||||
#define SFL_EXPIRATION 4
|
||||
#define SFL_TRANSFERRATE 4
|
||||
#define SFL_WALLETSIZE 4
|
||||
#define SFL_OWNERCOUNT 4
|
||||
#define SFL_DESTINATIONTAG 4
|
||||
#define SFL_HIGHQUALITYIN 4
|
||||
#define SFL_HIGHQUALITYOUT 4
|
||||
#define SFL_LOWQUALITYIN 4
|
||||
#define SFL_LOWQUALITYOUT 4
|
||||
#define SFL_QUALITYIN 4
|
||||
#define SFL_QUALITYOUT 4
|
||||
#define SFL_STAMPESCROW 4
|
||||
#define SFL_BONDAMOUNT 4
|
||||
#define SFL_LOADFEE 4
|
||||
#define SFL_OFFERSEQUENCE 4
|
||||
#define SFL_FIRSTLEDGERSEQUENCE 4
|
||||
#define SFL_LASTLEDGERSEQUENCE 4
|
||||
#define SFL_TRANSACTIONINDEX 4
|
||||
#define SFL_OPERATIONLIMIT 4
|
||||
#define SFL_REFERENCEFEEUNITS 4
|
||||
#define SFL_RESERVEBASE 4
|
||||
#define SFL_RESERVEINCREMENT 4
|
||||
#define SFL_SETFLAG 4
|
||||
#define SFL_CLEARFLAG 4
|
||||
#define SFL_SIGNERQUORUM 4
|
||||
#define SFL_CANCELAFTER 4
|
||||
#define SFL_FINISHAFTER 4
|
||||
#define SFL_SIGNERLISTID 4
|
||||
#define SFL_SETTLEDELAY 4
|
||||
#define SFL_TICKETCOUNT 4
|
||||
#define SFL_TICKETSEQUENCE 4
|
||||
#define SFL_NFTOKENTAXON 4
|
||||
#define SFL_MINTEDNFTOKENS 4
|
||||
#define SFL_BURNEDNFTOKENS 4
|
||||
#define SFL_HOOKSTATECOUNT 4
|
||||
#define SFL_EMITGENERATION 4
|
||||
#define SFL_LOCKCOUNT 4
|
||||
#define SFL_REWARDTIME 4
|
||||
#define SFL_REWARDLGRFIRST 4
|
||||
#define SFL_REWARDLGRLAST 4
|
||||
#define SFL_FIRSTNFTOKENSEQUENCE 4
|
||||
// 64 byte-int = 8 bytes
|
||||
#define SFL_INDEX_NEXT 8
|
||||
#define SFL_INDEX_PREVIOUS 8
|
||||
#define SFL_BOOK_NODE 8
|
||||
#define SFL_OWNER_NODE 8
|
||||
#define SFL_BASE_FEE 8
|
||||
#define SFL_EXCHANGE_RATE 8
|
||||
#define SFL_LOW_NODE 8
|
||||
#define SFL_HIGH_NODE 8
|
||||
#define SFL_DESTINATION_NODE 8
|
||||
#define SFL_COOKIE 8
|
||||
#define SFL_SERVER_VERSION 8
|
||||
#define SFL_EMIT_BURDEN 8
|
||||
#define SFL_NFTOKEN_OFFER_NODE 8
|
||||
#define SFL_HOOK_INSTRUCTION_COUNT 8
|
||||
#define SFL_HOOK_RETURN_CODE 8
|
||||
#define SFL_REFERENCE_COUNT 8
|
||||
#define SFL_REWARD_ACCUMULATOR 8
|
||||
// 128 byte-int = 4 bytes
|
||||
#define SFL_EMAIL_HASH 128
|
||||
// 160 byte-int = 4 bytes
|
||||
#define SFL_TAKER_PAYS_CURRENCY 160
|
||||
#define SFL_TAKER_PAYS_ISSUER 160
|
||||
#define SFL_TAKER_GETS_CURRENCY 160
|
||||
#define SFL_TAKER_GETS_ISSUER 160
|
||||
// 256 byte-int = ??? bytes
|
||||
#define SFL_LEDGER_HASH 256
|
||||
#define SFL_PARENT_HASH 256
|
||||
#define SFL_TRANSACTION_HASH 256
|
||||
#define SFL_ACCOUNT_HASH 256
|
||||
#define SFL_HOOK_ON 256
|
||||
#define SFL_PREVIOUS_TXN_ID 256
|
||||
#define SFL_LEDGER_INDEX 256
|
||||
#define SFL_WALLET_LOCATOR 256
|
||||
#define SFL_ROOT_INDEX 256
|
||||
#define SFL_ACCOUNT_TXN_ID 256
|
||||
#define SFL_NFTOKEN_ID 256
|
||||
#define SFL_EMIT_PARENT_TXN_ID 256
|
||||
#define SFL_EMIT_NONCE 256
|
||||
#define SFL_EMIT_HOOK_HASH 256
|
||||
// 256 byte-int = ??? bytes
|
||||
#define SFL_BOOK_DIRECTORY 256
|
||||
#define SFL_INVOICE_ID 256
|
||||
#define SFL_NICKNAME 256
|
||||
#define SFL_AMENDMENT 256
|
||||
#define SFL_DIGEST 256
|
||||
#define SFL_CHANNEL 256
|
||||
#define SFL_CONSENSUS_HASH 256
|
||||
#define SFL_CHECK_ID 256
|
||||
#define SFL_VALIDATED_HASH 256
|
||||
#define SFL_PREVIOUS_PAGE_MIN 256
|
||||
#define SFL_NEXT_PAGE_MIN 256
|
||||
#define SFL_NFTOKEN_BUY_OFFER 256
|
||||
#define SFL_NFTOKEN_SELL_OFFER 256
|
||||
#define SFL_HOOK_STATE_KEY 256
|
||||
#define SFL_HOOK_HASH 256
|
||||
#define SFL_HOOK_NAMESPACE 256
|
||||
#define SFL_HOOK_SET_TXN_ID 256
|
||||
#define SFL_OFFER_ID 256
|
||||
#define SFL_ESCROW_ID 256
|
||||
#define SFL_URITOKEN_ID 256
|
||||
// 20 bytes
|
||||
#define SFL_AMOUNT 20
|
||||
#define SFL_BALANCE 20
|
||||
#define SFL_LIMIT_AMOUNT 20
|
||||
#define SFL_TAKER_PAYS 20
|
||||
#define SFL_TAKER_GETS 20
|
||||
#define SFL_LOW_LIMIT 20
|
||||
#define SFL_HIGH_LIMIT 20
|
||||
#define SFL_FEE 20
|
||||
#define SFL_SEND_MAX 20
|
||||
#define SFL_DELIVER_MIN 20
|
||||
#define SFL_LOCKED_BALANCE 20
|
||||
// Unimplemented
|
||||
#define SFL_AMOUNT_MINIMUM_OFFER 8
|
||||
#define SFL_AMOUNT_RIPPLE_ESCROW 8
|
||||
#define SFL_AMOUNT_DELIVERED_AMOUNT 8
|
||||
#define SFL_AMOUNT_NFTOKEN_BROKER_FEE 8
|
||||
#define SFL_AMOUNT_HOOK_CALLBACK_FEE 8
|
||||
#define SFL_AMOUNT_BASE_FEE_DROPS 8
|
||||
#define SFL_AMOUNT_RESERVE_BASE_DROPS 8
|
||||
#define SFL_AMOUNT_RESERVE_INCREMENT_DROPS 8
|
||||
// Unimplemented
|
||||
#define SFL_VL_PUBLIC_KEY 64
|
||||
#define SFL_VL_MESSAGE_KEY 64
|
||||
#define SFL_VL_SIGNING_PUB_KEY 64
|
||||
// Unimplemented
|
||||
#define SFL_VL_TXN_SIGNATURE 96
|
||||
// Unimplemented
|
||||
#define SFL_VL_URI 256
|
||||
// Unimplemented
|
||||
#define SFL_VL_SIGNATURE 96
|
||||
// Unimplemented
|
||||
#define SFL_VL_DOMAIN 256
|
||||
#define SFL_VL_FUND_CODE 256
|
||||
#define SFL_VL_REMOVE_CODE 256
|
||||
#define SFL_VL_EXPIRE_CODE 256
|
||||
#define SFL_VL_CREATE_CODE 256
|
||||
#define SFL_VL_MEMO_TYPE 256
|
||||
#define SFL_VL_MEMO_DATA 256
|
||||
#define SFL_VL_MEMO_FORMAT 256
|
||||
#define SFL_VL_FULFILLMENT 256
|
||||
#define SFL_VL_CONDITION 256
|
||||
// Unimplemented
|
||||
#define SFL_VL_MASTER_SIGNATURE 96
|
||||
// Unimplemented
|
||||
#define SFL_VL_UNL_MODIFY_VALIDATOR 256
|
||||
#define SFL_VL_VALIDATOR_TO_DISABLE 256
|
||||
#define SFL_VL_VALIDATOR_TO_RE_ENABLE 256
|
||||
#define SFL_VL_HOOK_STATE_DATA 256
|
||||
#define SFL_VL_HOOK_RETURN_STRING 256
|
||||
#define SFL_VL_HOOK_PARAMETER_NAME 256
|
||||
#define SFL_VL_HOOK_PARAMETER_VALUE 256
|
||||
#define SFL_VL_BLOB 256
|
||||
// 20 bytes
|
||||
#define SFL_ACCOUNT 20
|
||||
#define SFL_OWNER 20
|
||||
#define SFL_DESTINATION 20
|
||||
#define SFL_ISSUER 20
|
||||
#define SFL_AUTHORIZE 20
|
||||
#define SFL_UNAUTHORIZE 20
|
||||
#define SFL_REGULAR_KEY 20
|
||||
#define SFL_NFTOKEN_MINTER 20
|
||||
#define SFL_EMIT_CALLBACK 20
|
||||
#define SFL_HOOK_ACCOUNT 20
|
||||
#define SFL_NFTOKEN_MINTER 20
|
||||
// Unimplemented
|
||||
#define SFL_PATHS 1
|
||||
// Unimplemented
|
||||
#define SFL_VECTOR256_INDEXES 32
|
||||
#define SFL_VECTOR256_HASHES 32
|
||||
#define SFL_VECTOR256_AMENDMENTS 32
|
||||
#define SFL_VECTOR256_NFTOKEN_OFFERS 32
|
||||
#define SFL_VECTOR256_HOOK_NAMESPACES 32
|
||||
// Unimplemented
|
||||
#define SFL_TRANSACTION_META_DATA 1
|
||||
#define SFL_CREATED_NODE 1
|
||||
#define SFL_DELETED_NODE 1
|
||||
#define SFL_MODIFIED_NODE 1
|
||||
#define SFL_PREVIOUS_FIELDS 1
|
||||
#define SFL_FINAL_FIELDS 1
|
||||
#define SFL_NEW_FIELDS 1
|
||||
#define SFL_TEMPLATE_ENTRY 1
|
||||
#define SFL_MEMO 1
|
||||
#define SFL_SIGNER_ENTRY 1
|
||||
#define SFL_NFTOKEN 1
|
||||
#define SFL_EMIT_DETAILS 1
|
||||
#define SFL_HOOK 1
|
||||
#define SFL_SIGNER 1
|
||||
#define SFL_MAJORITY 1
|
||||
#define SFL_DISABLED_VALIDATOR 1
|
||||
#define SFL_EMITTED_TXN 1
|
||||
#define SFL_HOOK_EXECUTION 1
|
||||
#define SFL_HOOK_DEFINITION 1
|
||||
#define SFL_HOOK_PARAMETER 1
|
||||
#define SFL_HOOK_GRANT 1
|
||||
#define SFL_SIGNERS 1
|
||||
#define SFL_SIGNER_ENTRIES 1
|
||||
#define SFL_TEMPLATE 1
|
||||
#define SFL_NECESSARY 1
|
||||
#define SFL_SUFFICIENT 1
|
||||
#define SFL_AFFECTED_NODES 1
|
||||
#define SFL_MEMOS 1
|
||||
#define SFL_NFTOKENS 1
|
||||
#define SFL_HOOKS 1
|
||||
#define SFL_MAJORITIES 1
|
||||
#define SFL_DISABLED_VALIDATORS 1
|
||||
#define SFL_HOOK_EXECUTIONS 1
|
||||
#define SFL_HOOK_EXECUTION 1
|
||||
@@ -1,9 +1,9 @@
|
||||
all: reward govern mint
|
||||
accept:
|
||||
wasmcc accept.c -o accept.wasm -Oz -Wl,--allow-undefined -I./headers
|
||||
wasmcc accept.c -o accept.wasm -Oz -Wl,--allow-undefined -I../
|
||||
hook-cleaner accept.wasm
|
||||
reward:
|
||||
wasmcc reward.c -o reward.wasm -Oz -Wl,--allow-undefined -I./headers
|
||||
wasmcc reward.c -o reward.wasm -Oz -Wl,--allow-undefined -I../
|
||||
wasm-opt reward.wasm -o reward.wasm \
|
||||
--shrink-level=100000000 \
|
||||
--coalesce-locals-learning \
|
||||
@@ -58,7 +58,7 @@ reward:
|
||||
hook-cleaner reward.wasm
|
||||
guard_checker reward.wasm
|
||||
govern:
|
||||
wasmcc govern.c -o govern.wasm -Oz -Wl,--allow-undefined -I./headers
|
||||
wasmcc govern.c -o govern.wasm -Oz -Wl,--allow-undefined -I../
|
||||
wasm-opt govern.wasm -o govern.wasm \
|
||||
--shrink-level=100000000 \
|
||||
--coalesce-locals-learning \
|
||||
@@ -113,7 +113,7 @@ govern:
|
||||
hook-cleaner govern.wasm
|
||||
guard_checker govern.wasm
|
||||
mint:
|
||||
wasmcc mint.c -o mint.wasm -Oz -Wl,--allow-undefined -I./headers
|
||||
wasmcc mint.c -o mint.wasm -Oz -Wl,--allow-undefined -I../
|
||||
wasm-opt mint.wasm -o mint.wasm \
|
||||
--shrink-level=100000000 \
|
||||
--coalesce-locals-learning \
|
||||
@@ -142,5 +142,5 @@ mint:
|
||||
hook-cleaner mint.wasm
|
||||
guard_checker mint.wasm
|
||||
nftoken:
|
||||
wasmcc nftoken.c -o nftoken.wasm -Oz -Wl,--allow-undefined -I./headers
|
||||
wasmcc nftoken.c -o nftoken.wasm -Oz -Wl,--allow-undefined -I../
|
||||
hook-cleaner nftoken.wasm
|
||||
|
||||
@@ -49,7 +49,4 @@
|
||||
#include "macro.h"
|
||||
#include "tts.h"
|
||||
|
||||
#include "ls_flags.h"
|
||||
#include "tx_flags.h"
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
// Generated using generate_lsflags.sh
|
||||
|
||||
#ifndef HOOKLSFLAGS_INCLUDED
|
||||
#define HOOKLSFLAGS_INCLUDED 1
|
||||
|
||||
enum ltACCOUNT_ROOT {
|
||||
lsfPasswordSpent = 0x00010000,
|
||||
lsfRequireDestTag = 0x00020000,
|
||||
lsfRequireAuth = 0x00040000,
|
||||
lsfDisallowXRP = 0x00080000,
|
||||
lsfDisableMaster = 0x00100000,
|
||||
lsfNoFreeze = 0x00200000,
|
||||
lsfGlobalFreeze = 0x00400000,
|
||||
lsfDefaultRipple = 0x00800000,
|
||||
lsfDepositAuth = 0x01000000,
|
||||
lsfTshCollect = 0x02000000,
|
||||
lsfDisallowIncomingNFTokenOffer = 0x04000000,
|
||||
lsfDisallowIncomingCheck = 0x08000000,
|
||||
lsfDisallowIncomingPayChan = 0x10000000,
|
||||
lsfDisallowIncomingTrustline = 0x20000000,
|
||||
lsfURITokenIssuer = 0x40000000,
|
||||
lsfDisallowIncomingRemit = 0x80000000,
|
||||
lsfAllowTrustLineClawback = 0x00001000,
|
||||
};
|
||||
enum ltOFFER {
|
||||
lsfPassive = 0x00010000,
|
||||
lsfSell = 0x00020000,
|
||||
};
|
||||
enum ltRIPPLE_STATE {
|
||||
lsfLowReserve = 0x00010000,
|
||||
lsfHighReserve = 0x00020000,
|
||||
lsfLowAuth = 0x00040000,
|
||||
lsfHighAuth = 0x00080000,
|
||||
lsfLowNoRipple = 0x00100000,
|
||||
lsfHighNoRipple = 0x00200000,
|
||||
lsfLowFreeze = 0x00400000,
|
||||
lsfHighFreeze = 0x00800000,
|
||||
lsfLowDeepFreeze = 0x02000000,
|
||||
lsfHighDeepFreeze = 0x04000000,
|
||||
lsfAMMNode = 0x01000000,
|
||||
};
|
||||
enum ltSIGNER_LIST {
|
||||
lsfOneOwnerCount = 0x00010000,
|
||||
};
|
||||
enum ltDIR_NODE {
|
||||
lsfNFTokenBuyOffers = 0x00000001,
|
||||
lsfNFTokenSellOffers = 0x00000002,
|
||||
lsfEmittedDir = 0x00000004,
|
||||
};
|
||||
enum ltNFTOKEN_OFFER {
|
||||
lsfSellNFToken = 0x00000001,
|
||||
};
|
||||
enum ltURI_TOKEN {
|
||||
lsfBurnable = 0x00000001,
|
||||
};
|
||||
enum remarks {
|
||||
lsfImmutable = 1,
|
||||
};
|
||||
enum ltMPTOKEN_ISSUANCE {
|
||||
lsfMPTLocked = 0x00000001,
|
||||
lsfMPTCanLock = 0x00000002,
|
||||
lsfMPTRequireAuth = 0x00000004,
|
||||
lsfMPTCanEscrow = 0x00000008,
|
||||
lsfMPTCanTrade = 0x00000010,
|
||||
lsfMPTCanTransfer = 0x00000020,
|
||||
lsfMPTCanClawback = 0x00000040,
|
||||
};
|
||||
enum ltMPTOKEN {
|
||||
lsfMPTAuthorized = 0x00000002,
|
||||
};
|
||||
enum ltCREDENTIAL {
|
||||
lsfAccepted = 0x00010000,
|
||||
};
|
||||
|
||||
#endif // HOOKLSFLAGS_INCLUDED
|
||||
@@ -9,8 +9,6 @@
|
||||
#define sfUNLModifyDisabling ((16U << 16U) + 17U)
|
||||
#define sfHookResult ((16U << 16U) + 18U)
|
||||
#define sfWasLockingChainSend ((16U << 16U) + 19U)
|
||||
#define sfSidecarType ((16U << 16U) + 20U)
|
||||
#define sfEntropyTier ((16U << 16U) + 21U)
|
||||
#define sfLedgerEntryType ((1U << 16U) + 1U)
|
||||
#define sfTransactionType ((1U << 16U) + 2U)
|
||||
#define sfSignerWeight ((1U << 16U) + 3U)
|
||||
@@ -24,8 +22,6 @@
|
||||
#define sfHookApiVersion ((1U << 16U) + 20U)
|
||||
#define sfHookStateScale ((1U << 16U) + 21U)
|
||||
#define sfLedgerFixType ((1U << 16U) + 22U)
|
||||
#define sfHookExportCount ((1U << 16U) + 98U)
|
||||
#define sfEntropyCount ((1U << 16U) + 99U)
|
||||
#define sfNetworkID ((2U << 16U) + 1U)
|
||||
#define sfFlags ((2U << 16U) + 2U)
|
||||
#define sfSourceTag ((2U << 16U) + 3U)
|
||||
@@ -84,7 +80,6 @@
|
||||
#define sfRewardTime ((2U << 16U) + 98U)
|
||||
#define sfRewardLgrFirst ((2U << 16U) + 99U)
|
||||
#define sfRewardLgrLast ((2U << 16U) + 100U)
|
||||
#define sfCancelTicketSequence ((2U << 16U) + 101U)
|
||||
#define sfIndexNext ((3U << 16U) + 1U)
|
||||
#define sfIndexPrevious ((3U << 16U) + 2U)
|
||||
#define sfBookNode ((3U << 16U) + 3U)
|
||||
@@ -164,7 +159,6 @@
|
||||
#define sfEmittedTxnID ((5U << 16U) + 97U)
|
||||
#define sfGovernanceMarks ((5U << 16U) + 98U)
|
||||
#define sfGovernanceFlags ((5U << 16U) + 99U)
|
||||
#define sfEntropyDigest ((5U << 16U) + 100U)
|
||||
#define sfNumber ((9U << 16U) + 1U)
|
||||
#define sfAmount ((6U << 16U) + 1U)
|
||||
#define sfBalance ((6U << 16U) + 2U)
|
||||
@@ -195,7 +189,6 @@
|
||||
#define sfSignatureReward ((6U << 16U) + 29U)
|
||||
#define sfMinAccountCreateAmount ((6U << 16U) + 30U)
|
||||
#define sfLPTokenBalance ((6U << 16U) + 31U)
|
||||
#define sfTrustLineRewardAccumulator ((6U << 16U) + 99U)
|
||||
#define sfPublicKey ((7U << 16U) + 1U)
|
||||
#define sfMessageKey ((7U << 16U) + 2U)
|
||||
#define sfSigningPubKey ((7U << 16U) + 3U)
|
||||
@@ -227,7 +220,7 @@
|
||||
#define sfProvider ((7U << 16U) + 30U)
|
||||
#define sfMPTokenMetadata ((7U << 16U) + 31U)
|
||||
#define sfCredentialType ((7U << 16U) + 32U)
|
||||
#define sfHookName ((7U << 16U) + 97U)
|
||||
#define sfJsonTxBody ((7U << 16U) + 33U)
|
||||
#define sfRemarkValue ((7U << 16U) + 98U)
|
||||
#define sfRemarkName ((7U << 16U) + 99U)
|
||||
#define sfAccount ((8U << 16U) + 1U)
|
||||
@@ -263,7 +256,6 @@
|
||||
#define sfIssuingChainIssue ((24U << 16U) + 2U)
|
||||
#define sfAsset ((24U << 16U) + 3U)
|
||||
#define sfAsset2 ((24U << 16U) + 4U)
|
||||
#define sfClaimCurrency ((24U << 16U) + 5U)
|
||||
#define sfXChainBridge ((25U << 16U) + 1U)
|
||||
#define sfTransactionMetaData ((14U << 16U) + 2U)
|
||||
#define sfCreatedNode ((14U << 16U) + 3U)
|
||||
@@ -283,6 +275,7 @@
|
||||
#define sfDisabledValidator ((14U << 16U) + 19U)
|
||||
#define sfEmittedTxn ((14U << 16U) + 20U)
|
||||
#define sfHookExecution ((14U << 16U) + 21U)
|
||||
#define sfHookDefinition ((14U << 16U) + 22U)
|
||||
#define sfHookParameter ((14U << 16U) + 23U)
|
||||
#define sfHookGrant ((14U << 16U) + 24U)
|
||||
#define sfVoteEntry ((14U << 16U) + 25U)
|
||||
@@ -294,7 +287,6 @@
|
||||
#define sfXChainCreateAccountAttestationCollectionElement ((14U << 16U) + 31U)
|
||||
#define sfPriceData ((14U << 16U) + 32U)
|
||||
#define sfCredential ((14U << 16U) + 33U)
|
||||
#define sfExportedTxn ((14U << 16U) + 90U)
|
||||
#define sfAmountEntry ((14U << 16U) + 91U)
|
||||
#define sfMintURIToken ((14U << 16U) + 92U)
|
||||
#define sfHookEmission ((14U << 16U) + 93U)
|
||||
@@ -302,9 +294,6 @@
|
||||
#define sfActiveValidator ((14U << 16U) + 95U)
|
||||
#define sfGenesisMint ((14U << 16U) + 96U)
|
||||
#define sfRemark ((14U << 16U) + 97U)
|
||||
#define sfHighReward ((14U << 16U) + 98U)
|
||||
#define sfLowReward ((14U << 16U) + 99U)
|
||||
#define sfExportResult ((14U << 16U) + 100U)
|
||||
#define sfSigners ((15U << 16U) + 3U)
|
||||
#define sfSignerEntries ((15U << 16U) + 4U)
|
||||
#define sfTemplate ((15U << 16U) + 5U)
|
||||
|
||||
@@ -61,7 +61,6 @@
|
||||
#define ttNFTOKEN_MODIFY 70
|
||||
#define ttPERMISSIONED_DOMAIN_SET 71
|
||||
#define ttPERMISSIONED_DOMAIN_DELETE 72
|
||||
#define ttEXPORT 91
|
||||
#define ttCRON 92
|
||||
#define ttCRON_SET 93
|
||||
#define ttREMARKS_SET 94
|
||||
@@ -75,4 +74,3 @@
|
||||
#define ttUNL_MODIFY 102
|
||||
#define ttEMIT_FAILURE 103
|
||||
#define ttUNL_REPORT 104
|
||||
#define ttCONSENSUS_ENTROPY 105
|
||||
|
||||
122
hook/tx_flags.h
122
hook/tx_flags.h
@@ -1,122 +0,0 @@
|
||||
// Generated using generate_txflags.sh
|
||||
#include "ls_flags.h"
|
||||
#include <stdint.h>
|
||||
|
||||
enum UniversalFlags : uint32_t {
|
||||
tfFullyCanonicalSig = 0x80000000,
|
||||
};
|
||||
|
||||
enum AccountSetFlags : uint32_t {
|
||||
tfRequireDestTag = 0x00010000,
|
||||
tfOptionalDestTag = 0x00020000,
|
||||
tfRequireAuth = 0x00040000,
|
||||
tfOptionalAuth = 0x00080000,
|
||||
tfDisallowXRP = 0x00100000,
|
||||
tfAllowXRP = 0x00200000,
|
||||
};
|
||||
|
||||
enum AccountFlags : uint32_t {
|
||||
asfRequireDest = 1,
|
||||
asfRequireAuth = 2,
|
||||
asfDisallowXRP = 3,
|
||||
asfDisableMaster = 4,
|
||||
asfAccountTxnID = 5,
|
||||
asfNoFreeze = 6,
|
||||
asfGlobalFreeze = 7,
|
||||
asfDefaultRipple = 8,
|
||||
asfDepositAuth = 9,
|
||||
asfAuthorizedNFTokenMinter = 10,
|
||||
asfTshCollect = 11,
|
||||
asfDisallowIncomingNFTokenOffer = 12,
|
||||
asfDisallowIncomingCheck = 13,
|
||||
asfDisallowIncomingPayChan = 14,
|
||||
asfDisallowIncomingTrustline = 15,
|
||||
asfDisallowIncomingRemit = 16,
|
||||
asfAllowTrustLineClawback = 17,
|
||||
};
|
||||
|
||||
enum OfferCreateFlags : uint32_t {
|
||||
tfPassive = 0x00010000,
|
||||
tfImmediateOrCancel = 0x00020000,
|
||||
tfFillOrKill = 0x00040000,
|
||||
tfSell = 0x00080000,
|
||||
};
|
||||
|
||||
enum PaymentFlags : uint32_t {
|
||||
tfNoRippleDirect = 0x00010000,
|
||||
tfPartialPayment = 0x00020000,
|
||||
tfLimitQuality = 0x00040000,
|
||||
};
|
||||
|
||||
enum TrustSetFlags : uint32_t {
|
||||
tfSetfAuth = 0x00010000,
|
||||
tfSetNoRipple = 0x00020000,
|
||||
tfClearNoRipple = 0x00040000,
|
||||
tfSetFreeze = 0x00100000,
|
||||
tfClearFreeze = 0x00200000,
|
||||
tfSetDeepFreeze = 0x00400000,
|
||||
tfClearDeepFreeze = 0x00800000
|
||||
};
|
||||
|
||||
enum EnableAmendmentFlags : uint32_t {
|
||||
tfGotMajority = 0x00010000,
|
||||
tfLostMajority = 0x00020000,
|
||||
tfTestSuite = 0x80000000,
|
||||
};
|
||||
|
||||
enum PaymentChannelClaimFlags : uint32_t {
|
||||
tfRenew = 0x00010000,
|
||||
tfClose = 0x00020000,
|
||||
};
|
||||
|
||||
enum NFTokenMintFlags : uint32_t {
|
||||
tfBurnable = 0x00000001,
|
||||
tfOnlyXRP = 0x00000002,
|
||||
tfTrustLine = 0x00000004,
|
||||
tfTransferable = 0x00000008,
|
||||
tfMutable = 0x00000010,
|
||||
tfStrongTSH = 0x00008000,
|
||||
};
|
||||
|
||||
enum MPTokenIssuanceCreateFlags : uint32_t {
|
||||
tfMPTCanLock = lsfMPTCanLock,
|
||||
tfMPTRequireAuth = lsfMPTRequireAuth,
|
||||
tfMPTCanEscrow = lsfMPTCanEscrow,
|
||||
tfMPTCanTrade = lsfMPTCanTrade,
|
||||
tfMPTCanTransfer = lsfMPTCanTransfer,
|
||||
tfMPTCanClawback = lsfMPTCanClawback,
|
||||
};
|
||||
|
||||
enum MPTokenAuthorizeFlags : uint32_t {
|
||||
tfMPTUnauthorize = 0x00000001,
|
||||
};
|
||||
|
||||
enum MPTokenIssuanceSetFlags : uint32_t {
|
||||
tfMPTLock = 0x00000001,
|
||||
tfMPTUnlock = 0x00000002,
|
||||
};
|
||||
|
||||
enum NFTokenCreateOfferFlags : uint32_t {
|
||||
tfSellNFToken = 0x00000001,
|
||||
};
|
||||
|
||||
enum ClaimRewardFlags : uint32_t {
|
||||
tfOptOut = 0x00000001,
|
||||
};
|
||||
|
||||
enum CronSetFlags : uint32_t {
|
||||
tfCronUnset = 0x00000001,
|
||||
};
|
||||
|
||||
enum AMMClawbackFlags : uint32_t {
|
||||
tfClawTwoAssets = 0x00000001,
|
||||
};
|
||||
|
||||
enum BridgeModifyFlags : uint32_t {
|
||||
tfClearAccountCreateAmount = 0x00010000,
|
||||
};
|
||||
|
||||
enum ConsensusEntropyFlags : uint32_t {
|
||||
tfEntropyCommit = 0x00000001, // entry is a commitment in commitSet
|
||||
tfEntropyReveal = 0x00000002, // entry is a reveal in entropySet
|
||||
};
|
||||
@@ -15,10 +15,7 @@
|
||||
#define uint256 std::string
|
||||
#define featureHooksUpdate1 "1"
|
||||
#define featureHooksUpdate2 "1"
|
||||
#define featureExport "1"
|
||||
#define featureConsensusEntropy "1"
|
||||
#define fix20250131 "1"
|
||||
#define fixGuardDepth32 "1"
|
||||
namespace hook_api {
|
||||
struct Rules
|
||||
{
|
||||
@@ -322,7 +319,7 @@ namespace compare_mode {
|
||||
enum compare_mode : uint32_t { EQUAL = 1, LESS = 2, GREATER = 4 };
|
||||
}
|
||||
|
||||
enum class hook_return_code : int64_t {
|
||||
enum hook_return_code : int64_t {
|
||||
SUCCESS =
|
||||
0, // return codes > 0 are reserved for hook apis to return "success"
|
||||
OUT_OF_BOUNDS =
|
||||
@@ -386,13 +383,10 @@ enum class hook_return_code : int64_t {
|
||||
MEM_OVERLAP = -43, // one or more specified buffers are the same memory
|
||||
TOO_MANY_STATE_MODIFICATIONS = -44, // more than 5000 modified state
|
||||
// entires in the combined hook chains
|
||||
TOO_MANY_NAMESPACES = -45,
|
||||
EXPORT_FAILURE = -46,
|
||||
TOO_MANY_EXPORTED_TXN = -47,
|
||||
TOO_LITTLE_ENTROPY = -48,
|
||||
TOO_MANY_NAMESPACES = -45
|
||||
};
|
||||
|
||||
enum class ExitType : uint8_t {
|
||||
enum ExitType : uint8_t {
|
||||
UNSET = 0,
|
||||
WASM_ERROR = 1,
|
||||
ROLLBACK = 2,
|
||||
@@ -403,7 +397,6 @@ const uint16_t max_state_modifications = 256;
|
||||
const uint8_t max_slots = 255;
|
||||
const uint8_t max_nonce = 255;
|
||||
const uint8_t max_emit = 255;
|
||||
const uint8_t max_export = 2;
|
||||
const uint8_t max_params = 16;
|
||||
const double fee_base_multiplier = 1.1f;
|
||||
|
||||
@@ -444,9 +437,12 @@ getImportWhitelist(Rules const& rules)
|
||||
return whitelist;
|
||||
}
|
||||
|
||||
#undef HOOK_API_DEFINITION
|
||||
#undef I32
|
||||
#undef I64
|
||||
|
||||
enum GuardRulesVersion : uint64_t {
|
||||
GuardRuleFix20250131 = 0x00000001,
|
||||
GuardRuleDepth32 = 0x00000002,
|
||||
};
|
||||
|
||||
inline uint64_t
|
||||
@@ -455,8 +451,6 @@ getGuardRulesVersion(Rules const& rules)
|
||||
uint64_t version = 0;
|
||||
if (rules.enabled(fix20250131))
|
||||
version |= GuardRuleFix20250131;
|
||||
if (rules.enabled(fixGuardDepth32))
|
||||
version |= GuardRuleDepth32;
|
||||
return version;
|
||||
}
|
||||
|
||||
|
||||
@@ -204,13 +204,9 @@ struct WasmBlkInf
|
||||
}
|
||||
// compute worst case execution time
|
||||
inline uint64_t
|
||||
compute_wce(
|
||||
const WasmBlkInf* blk,
|
||||
int level,
|
||||
int max_level,
|
||||
bool* recursion_limit_reached)
|
||||
compute_wce(const WasmBlkInf* blk, int level, bool* recursion_limit_reached)
|
||||
{
|
||||
if (level > max_level)
|
||||
if (level > 16)
|
||||
{
|
||||
*recursion_limit_reached = true;
|
||||
return 0;
|
||||
@@ -237,8 +233,8 @@ compute_wce(
|
||||
|
||||
if (blk->children.size() > 0)
|
||||
for (auto const& child : blk->children)
|
||||
worst_case_execution += compute_wce(
|
||||
child, level + 1, max_level, recursion_limit_reached);
|
||||
worst_case_execution +=
|
||||
compute_wce(child, level + 1, recursion_limit_reached);
|
||||
|
||||
if (parent == 0 ||
|
||||
parent->iteration_bound ==
|
||||
@@ -792,17 +788,12 @@ check_guard(
|
||||
}
|
||||
|
||||
bool recursion_limit_reached = false;
|
||||
int max_level = 16;
|
||||
if (rulesVersion & hook_api::GuardRuleDepth32)
|
||||
max_level = 32;
|
||||
uint64_t wce =
|
||||
compute_wce(&(*root), 0, max_level, &recursion_limit_reached);
|
||||
uint64_t wce = compute_wce(&(*root), 0, &recursion_limit_reached);
|
||||
if (recursion_limit_reached)
|
||||
{
|
||||
GUARDLOG(hook::log::NESTING_LIMIT)
|
||||
<< "GuardCheck "
|
||||
<< "Maximum allowable depth of blocks reached (" << max_level
|
||||
<< " levels). Flatten "
|
||||
<< "Maximum allowable depth of blocks reached (16 levels). Flatten "
|
||||
"your loops and conditions!.\n";
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -89,69 +89,58 @@
|
||||
|
||||
#define WASM_VAL_TYPE(T, b) CAT2(TYP_, T)
|
||||
|
||||
#define UNSIGNED_TYPE(T) std::make_unsigned_t<T>
|
||||
|
||||
#define DECLARE_HOOK_FUNCTION(R, F, ...) \
|
||||
std::variant<UNSIGNED_TYPE(R), hook_api::hook_return_code> F( \
|
||||
hook::HookContext& hookCtx, \
|
||||
WasmEdge_CallingFrameContext const& frameCtx __VA_OPT__( \
|
||||
COMMA __VA_ARGS__)); \
|
||||
extern WasmEdge_Result WasmFunction##F( \
|
||||
void* data_ptr, \
|
||||
const WasmEdge_CallingFrameContext* frameCtx, \
|
||||
const WasmEdge_Value* in, \
|
||||
WasmEdge_Value* out); \
|
||||
extern WasmEdge_ValType WasmFunctionParams##F[]; \
|
||||
extern WasmEdge_ValType WasmFunctionResult##F[]; \
|
||||
extern WasmEdge_FunctionTypeContext* WasmFunctionType##F; \
|
||||
#define DECLARE_HOOK_FUNCTION(R, F, ...) \
|
||||
R F(hook::HookContext& hookCtx, \
|
||||
WasmEdge_CallingFrameContext const& frameCtx __VA_OPT__( \
|
||||
COMMA __VA_ARGS__)); \
|
||||
extern WasmEdge_Result WasmFunction##F( \
|
||||
void* data_ptr, \
|
||||
const WasmEdge_CallingFrameContext* frameCtx, \
|
||||
const WasmEdge_Value* in, \
|
||||
WasmEdge_Value* out); \
|
||||
extern WasmEdge_ValType WasmFunctionParams##F[]; \
|
||||
extern WasmEdge_ValType WasmFunctionResult##F[]; \
|
||||
extern WasmEdge_FunctionTypeContext* WasmFunctionType##F; \
|
||||
extern WasmEdge_String WasmFunctionName##F;
|
||||
|
||||
#define DEFINE_HOOK_FUNCTION(R, F, ...) \
|
||||
WasmEdge_Result hook_api::WasmFunction##F( \
|
||||
void* data_ptr, \
|
||||
const WasmEdge_CallingFrameContext* frameCtx, \
|
||||
const WasmEdge_Value* in, \
|
||||
WasmEdge_Value* out) \
|
||||
{ \
|
||||
__VA_OPT__(int _stack = 0;) \
|
||||
__VA_OPT__(FOR_VARS(VAR_ASSIGN, 2, __VA_ARGS__);) \
|
||||
hook::HookContext* hookCtx = \
|
||||
reinterpret_cast<hook::HookContext*>(data_ptr); \
|
||||
auto const& return_code = hook_api::F( \
|
||||
*hookCtx, \
|
||||
*const_cast<WasmEdge_CallingFrameContext*>(frameCtx) \
|
||||
__VA_OPT__(COMMA STRIP_TYPES(__VA_ARGS__))); \
|
||||
if (std::holds_alternative<hook_api::hook_return_code>(return_code) && \
|
||||
(std::get<hook_api::hook_return_code>(return_code) == \
|
||||
RC_ROLLBACK || \
|
||||
std::get<hook_api::hook_return_code>(return_code) == RC_ACCEPT)) \
|
||||
return WasmEdge_Result_Terminate; \
|
||||
out[0] = RET_ASSIGN( \
|
||||
R, \
|
||||
std::holds_alternative<UNSIGNED_TYPE(R)>(return_code) \
|
||||
? std::get<UNSIGNED_TYPE(R)>(return_code) \
|
||||
: R(std::get<hook_api::hook_return_code>(return_code))); \
|
||||
return WasmEdge_Result_Success; \
|
||||
}; \
|
||||
WasmEdge_ValType hook_api::WasmFunctionParams##F[] = { \
|
||||
__VA_OPT__(FOR_VARS(WASM_VAL_TYPE, 0, __VA_ARGS__))}; \
|
||||
WasmEdge_ValType hook_api::WasmFunctionResult##F[1] = { \
|
||||
WASM_VAL_TYPE(R, dummy)}; \
|
||||
WasmEdge_FunctionTypeContext* hook_api::WasmFunctionType##F = \
|
||||
WasmEdge_FunctionTypeCreate( \
|
||||
WasmFunctionParams##F, \
|
||||
VA_NARGS(NULL __VA_OPT__(, __VA_ARGS__)), \
|
||||
WasmFunctionResult##F, \
|
||||
1); \
|
||||
WasmEdge_String hook_api::WasmFunctionName##F = \
|
||||
WasmEdge_StringCreateByCString(#F); \
|
||||
std::variant<UNSIGNED_TYPE(R), hook_api::hook_return_code> hook_api::F( \
|
||||
hook::HookContext& hookCtx, \
|
||||
WasmEdge_CallingFrameContext const& frameCtx __VA_OPT__( \
|
||||
#define DEFINE_HOOK_FUNCTION(R, F, ...) \
|
||||
WasmEdge_Result hook_api::WasmFunction##F( \
|
||||
void* data_ptr, \
|
||||
const WasmEdge_CallingFrameContext* frameCtx, \
|
||||
const WasmEdge_Value* in, \
|
||||
WasmEdge_Value* out) \
|
||||
{ \
|
||||
__VA_OPT__(int _stack = 0;) \
|
||||
__VA_OPT__(FOR_VARS(VAR_ASSIGN, 2, __VA_ARGS__);) \
|
||||
hook::HookContext* hookCtx = \
|
||||
reinterpret_cast<hook::HookContext*>(data_ptr); \
|
||||
R return_code = hook_api::F( \
|
||||
*hookCtx, \
|
||||
*const_cast<WasmEdge_CallingFrameContext*>(frameCtx) \
|
||||
__VA_OPT__(COMMA STRIP_TYPES(__VA_ARGS__))); \
|
||||
if (return_code == RC_ROLLBACK || return_code == RC_ACCEPT) \
|
||||
return WasmEdge_Result_Terminate; \
|
||||
out[0] = RET_ASSIGN(R, return_code); \
|
||||
return WasmEdge_Result_Success; \
|
||||
}; \
|
||||
WasmEdge_ValType hook_api::WasmFunctionParams##F[] = { \
|
||||
__VA_OPT__(FOR_VARS(WASM_VAL_TYPE, 0, __VA_ARGS__))}; \
|
||||
WasmEdge_ValType hook_api::WasmFunctionResult##F[1] = { \
|
||||
WASM_VAL_TYPE(R, dummy)}; \
|
||||
WasmEdge_FunctionTypeContext* hook_api::WasmFunctionType##F = \
|
||||
WasmEdge_FunctionTypeCreate( \
|
||||
WasmFunctionParams##F, \
|
||||
VA_NARGS(NULL __VA_OPT__(, __VA_ARGS__)), \
|
||||
WasmFunctionResult##F, \
|
||||
1); \
|
||||
WasmEdge_String hook_api::WasmFunctionName##F = \
|
||||
WasmEdge_StringCreateByCString(#F); \
|
||||
R hook_api::F( \
|
||||
hook::HookContext& hookCtx, \
|
||||
WasmEdge_CallingFrameContext const& frameCtx __VA_OPT__( \
|
||||
COMMA __VA_ARGS__))
|
||||
|
||||
#define HOOK_SETUP() \
|
||||
using enum hook_api::hook_return_code; \
|
||||
try \
|
||||
{ \
|
||||
[[maybe_unused]] ApplyContext& applyCtx = hookCtx.applyCtx; \
|
||||
@@ -214,7 +203,7 @@
|
||||
host_memory_ptr, \
|
||||
guest_memory_length) \
|
||||
{ \
|
||||
uint64_t bytes_written = 0; \
|
||||
int64_t bytes_written = 0; \
|
||||
WRITE_WASM_MEMORY( \
|
||||
bytes_written, \
|
||||
guest_dst_ptr, \
|
||||
@@ -283,7 +272,7 @@
|
||||
data_ptr < (data_ptr_in)) \
|
||||
return INTERNAL_ERROR; \
|
||||
if (data_len == 0) \
|
||||
return 0ULL; \
|
||||
return 0; \
|
||||
if ((write_ptr_in) == 0) \
|
||||
return data_as_int64(data_ptr, data_len); \
|
||||
if (data_len > (write_len_in)) \
|
||||
|
||||
@@ -372,28 +372,3 @@ HOOK_API_DEFINITION(
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, prepare, (uint32_t, uint32_t, uint32_t, uint32_t),
|
||||
featureHooksUpdate2)
|
||||
|
||||
// int64_t xport_reserve(uint32_t count);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, xport_reserve, (uint32_t),
|
||||
featureExport)
|
||||
|
||||
// int64_t xport(uint32_t write_ptr, uint32_t write_len, uint32_t read_ptr, uint32_t read_len);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, xport, (uint32_t, uint32_t, uint32_t, uint32_t),
|
||||
featureExport)
|
||||
|
||||
// int64_t xport_cancel(uint32_t ticket_seq);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, xport_cancel, (uint32_t),
|
||||
featureExport)
|
||||
|
||||
// int64_t dice(uint32_t sides, uint32_t min_tier, uint32_t min_count);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, dice, (uint32_t, uint32_t, uint32_t),
|
||||
featureConsensusEntropy)
|
||||
|
||||
// int64_t random(uint32_t write_ptr, uint32_t write_len, uint32_t min_tier, uint32_t min_count);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, random, (uint32_t, uint32_t, uint32_t, uint32_t),
|
||||
featureConsensusEntropy)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,2 +0,0 @@
|
||||
---
|
||||
DisableFormat: true
|
||||
@@ -166,14 +166,6 @@ message TMProposeSet
|
||||
|
||||
// Number of hops traveled
|
||||
optional uint32 hops = 12 [deprecated=true];
|
||||
|
||||
// Export signatures for pending exports seen in the proposal set. The
|
||||
// proposal's ExtendedPosition includes a digest of this repeated field, so
|
||||
// these side-channel blobs are covered by the proposal signature.
|
||||
// Each entry is: txnHash (32 bytes) + validator pubkey (33 bytes)
|
||||
// + multisign signature (variable length). Validators attach these
|
||||
// so export quorum can be reached within the same consensus round.
|
||||
repeated bytes exportSignatures = 13;
|
||||
}
|
||||
|
||||
enum TxSetStatus
|
||||
@@ -392,3 +384,4 @@ message TMHaveTransactions
|
||||
{
|
||||
repeated bytes hashes = 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
#ifndef RIPPLE_PROTOCOL_ENTROPY_TIER_H_INCLUDED
|
||||
#define RIPPLE_PROTOCOL_ENTROPY_TIER_H_INCLUDED
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
/// Which gate the ledger's entropy passed. Stored in sfEntropyTier (UINT8)
|
||||
/// on the ttCONSENSUS_ENTROPY pseudo-transaction and the ConsensusEntropy
|
||||
/// ledger entry.
|
||||
///
|
||||
/// EntropyCount says how many validators contributed; EntropyTier says which
|
||||
/// gate the result passed. Values are strength-ordered so consumers can gate
|
||||
/// with a numeric comparison (tier >= required).
|
||||
enum EntropyTier : std::uint8_t {
|
||||
/// No usable entropy (reserved; a fresh ConsensusEntropy entry should
|
||||
/// always carry one of the tiers below).
|
||||
entropyTierNone = 0,
|
||||
|
||||
/// Consensus-bound deterministic fallback: derived from already-agreed
|
||||
/// round inputs (parent ledger hash, base tx set hash, sequence) under
|
||||
/// HashPrefix::entropyFallback when no agreed reveal set reaches either
|
||||
/// participant_aligned or validator_quorum. Unpredictable in practice but
|
||||
/// user-influenceable via transaction submission — never suitable for
|
||||
/// value-bearing outcomes.
|
||||
entropyTierConsensusFallback = 1,
|
||||
|
||||
/// Participant-aligned sub-quorum entropy: the agreed reveal set aligned at
|
||||
/// the tier-2 participant threshold — below the 80% validator quorum but at
|
||||
/// or above the equivocation-intersection floor over the original (pre-nUNL)
|
||||
/// view. Weaker than validator_quorum; opt-in for hooks via min_tier.
|
||||
entropyTierParticipantAligned = 2,
|
||||
|
||||
/// Validator commit/reveal entropy whose sidecar set passed the
|
||||
/// active-validator-view quorum alignment gate.
|
||||
entropyTierValidatorQuorum = 3,
|
||||
};
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
#endif
|
||||
@@ -1,33 +0,0 @@
|
||||
#ifndef RIPPLE_PROTOCOL_EXPORT_LIMITS_H_INCLUDED
|
||||
#define RIPPLE_PROTOCOL_EXPORT_LIMITS_H_INCLUDED
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
// Export system caps.
|
||||
//
|
||||
// These limits bound the DoS surface of the export signature system:
|
||||
// - Each pending export requires every validator to sign it every round
|
||||
// (sign-once, attach once via TMProposeSet)
|
||||
// - Inbound signature processing involves crypto verification per sig
|
||||
// - The open-ledger cap (maxPendingExports) is the root constraint;
|
||||
// signing throughput and inbound processing are transitively bounded by it
|
||||
struct ExportLimits
|
||||
{
|
||||
// Maximum exports a single hook execution may produce
|
||||
// (also enforced by hook_api::max_export in Enum.h)
|
||||
static constexpr std::uint8_t maxExportsPerHook = 2;
|
||||
|
||||
// Maximum pending export transactions in an open/apply ledger.
|
||||
// Hook-emitted export backlog drains into the open ledger at this cap.
|
||||
// This transitively caps:
|
||||
// - signatures per TMProposeSet message (1 per pending export)
|
||||
// - inbound proposal signature processing (clamped to this)
|
||||
// - validator signing work per round
|
||||
static constexpr std::uint8_t maxPendingExports = 8;
|
||||
};
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
#endif
|
||||
@@ -33,39 +33,35 @@
|
||||
*
|
||||
* Steps required to add new features to the code:
|
||||
*
|
||||
* 1) Add the appropriate XRPL_FEATURE or XRPL_FIX macro definition for the
|
||||
* feature to features.macro with the feature's name, `Supported::no`, and
|
||||
* `VoteBehavior::DefaultNo`.
|
||||
*
|
||||
* 2) Use the generated variable name as the parameter to `view.rules.enabled()`
|
||||
* to control flow into new code that this feature limits. (featureName or
|
||||
* fixName)
|
||||
*
|
||||
* 3) If the feature development is COMPLETE, and the feature is ready to be
|
||||
* SUPPORTED, change the macro parameter in features.macro to Supported::yes.
|
||||
*
|
||||
* 4) In general, any newly supported amendments (`Supported::yes`) should have
|
||||
* a `VoteBehavior::DefaultNo` indefinitely so that external governance can
|
||||
* make the decision on when to activate it. High priority bug fixes can be
|
||||
* an exception to this rule. In such cases, ensure the fix has been
|
||||
* clearly communicated to the community using appropriate channels,
|
||||
* then change the macro parameter in features.macro to
|
||||
* `VoteBehavior::DefaultYes`. The communication process is beyond
|
||||
* the scope of these instructions.
|
||||
*
|
||||
* 1) In this file, increment `numFeatures` and add a uint256 declaration
|
||||
* for the feature at the bottom
|
||||
* 2) Add a uint256 definition for the feature to the corresponding source
|
||||
* file (Feature.cpp). Use `registerFeature` to create the feature with
|
||||
* the feature's name, `Supported::no`, and `VoteBehavior::DefaultNo`. This
|
||||
* should be the only place the feature's name appears in code as a string.
|
||||
* 3) Use the uint256 as the parameter to `view.rules.enabled()` to
|
||||
* control flow into new code that this feature limits.
|
||||
* 4) If the feature development is COMPLETE, and the feature is ready to be
|
||||
* SUPPORTED, change the `registerFeature` parameter to Supported::yes.
|
||||
* 5) When the feature is ready to be ENABLED, change the `registerFeature`
|
||||
* parameter to `VoteBehavior::DefaultYes`.
|
||||
* In general, any newly supported amendments (`Supported::yes`) should have
|
||||
* a `VoteBehavior::DefaultNo` for at least one full release cycle. High
|
||||
* priority bug fixes can be an exception to this rule of thumb.
|
||||
*
|
||||
* When a feature has been enabled for several years, the conditional code
|
||||
* may be removed, and the feature "retired". To retire a feature:
|
||||
*
|
||||
* 1) MOVE the macro definition in features.macro to the "retired features"
|
||||
* section at the end of the file, and change the macro to XRPL_RETIRE.
|
||||
*
|
||||
* 1) Remove the uint256 declaration from this file.
|
||||
* 2) MOVE the uint256 definition in Feature.cpp to the "retired features"
|
||||
* section at the end of the file.
|
||||
* 3) CHANGE the name of the variable to start with "retired".
|
||||
* 4) CHANGE the parameters of the `registerFeature` call to `Supported::yes`
|
||||
* and `VoteBehavior::DefaultNo`.
|
||||
* The feature must remain registered and supported indefinitely because it
|
||||
* may exist in the Amendments object on ledger. There is no need to vote
|
||||
* for it because there's nothing to vote for. If the feature definition is
|
||||
* removed completely from the code, any instances running that code will get
|
||||
* amendment blocked. Removing the feature from the ledger is beyond the scope
|
||||
* of these instructions.
|
||||
* still exists in the ledger, but there is no need to vote for it because
|
||||
* there's nothing to vote for. If it is removed completely from the code, any
|
||||
* instances running that code will get amendment blocked. Removing the
|
||||
* feature from the ledger is beyond the scope of these instructions.
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -80,32 +76,11 @@ allAmendments();
|
||||
|
||||
namespace detail {
|
||||
|
||||
#pragma push_macro("XRPL_FEATURE")
|
||||
#undef XRPL_FEATURE
|
||||
#pragma push_macro("XRPL_FIX")
|
||||
#undef XRPL_FIX
|
||||
#pragma push_macro("XRPL_RETIRE")
|
||||
#undef XRPL_RETIRE
|
||||
|
||||
#define XRPL_FEATURE(name, supported, vote) +1
|
||||
#define XRPL_FIX(name, supported, vote) +1
|
||||
#define XRPL_RETIRE(name) +1
|
||||
|
||||
// This value SHOULD be equal to the number of amendments registered in
|
||||
// Feature.cpp. Because it's only used to reserve storage, and determine how
|
||||
// large to make the FeatureBitset, it MAY be larger. It MUST NOT be less than
|
||||
// the actual number of amendments. A LogicError on startup will verify this.
|
||||
static constexpr std::size_t numFeatures =
|
||||
(0 +
|
||||
#include <xrpl/protocol/detail/features.macro>
|
||||
);
|
||||
|
||||
#undef XRPL_RETIRE
|
||||
#pragma pop_macro("XRPL_RETIRE")
|
||||
#undef XRPL_FIX
|
||||
#pragma pop_macro("XRPL_FIX")
|
||||
#undef XRPL_FEATURE
|
||||
#pragma pop_macro("XRPL_FEATURE")
|
||||
static constexpr std::size_t numFeatures = 114;
|
||||
|
||||
/** Amendments that this server supports and the default voting behavior.
|
||||
Whether they are enabled depends on the Rules defined in the validated
|
||||
@@ -345,17 +320,12 @@ foreachFeature(FeatureBitset bs, F&& f)
|
||||
#undef XRPL_FEATURE
|
||||
#pragma push_macro("XRPL_FIX")
|
||||
#undef XRPL_FIX
|
||||
#pragma push_macro("XRPL_RETIRE")
|
||||
#undef XRPL_RETIRE
|
||||
|
||||
#define XRPL_FEATURE(name, supported, vote) extern uint256 const feature##name;
|
||||
#define XRPL_FIX(name, supported, vote) extern uint256 const fix##name;
|
||||
#define XRPL_RETIRE(name)
|
||||
|
||||
#include <xrpl/protocol/detail/features.macro>
|
||||
|
||||
#undef XRPL_RETIRE
|
||||
#pragma pop_macro("XRPL_RETIRE")
|
||||
#undef XRPL_FIX
|
||||
#pragma pop_macro("XRPL_FIX")
|
||||
#undef XRPL_FEATURE
|
||||
|
||||
@@ -96,15 +96,6 @@ enum class HashPrefix : std::uint32_t {
|
||||
|
||||
/** Credentials signature */
|
||||
credential = detail::make_hash_prefix('C', 'R', 'D'),
|
||||
|
||||
/** consensus extension sidecar object */
|
||||
sidecar = detail::make_hash_prefix('S', 'C', 'R'),
|
||||
|
||||
/** consensus-bound fallback entropy digest (Tier 1: derived from
|
||||
already-agreed round inputs when no agreed reveal set reaches an
|
||||
accepted validator-participant tier; never to be confused with
|
||||
validator entropy) */
|
||||
entropyFallback = detail::make_hash_prefix('E', 'F', 'B'),
|
||||
};
|
||||
|
||||
template <class Hasher>
|
||||
|
||||
@@ -62,9 +62,6 @@ emittedDir() noexcept;
|
||||
Keylet
|
||||
emittedTxn(uint256 const& id) noexcept;
|
||||
|
||||
Keylet
|
||||
shadowTicket(AccountID const& account, std::uint32_t ticketSeq) noexcept;
|
||||
|
||||
Keylet
|
||||
hookDefinition(uint256 const& hash) noexcept;
|
||||
|
||||
@@ -121,10 +118,6 @@ negativeUNL() noexcept;
|
||||
Keylet const&
|
||||
UNLReport() noexcept;
|
||||
|
||||
/** The (fixed) index of the object containing consensus-derived entropy. */
|
||||
Keylet const&
|
||||
consensusEntropy() noexcept;
|
||||
|
||||
/** The beginning of an order book */
|
||||
struct book_t
|
||||
{
|
||||
|
||||
81
include/xrpl/protocol/JsonTx.h
Normal file
81
include/xrpl/protocol/JsonTx.h
Normal file
@@ -0,0 +1,81 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2025 Ripple Labs Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef RIPPLE_PROTOCOL_JSONTX_H_INCLUDED
|
||||
#define RIPPLE_PROTOCOL_JSONTX_H_INCLUDED
|
||||
|
||||
#include <xrpl/basics/Blob.h>
|
||||
#include <xrpl/basics/Expected.h>
|
||||
#include <xrpl/basics/Slice.h>
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
|
||||
namespace ripple {
|
||||
namespace jsonTx {
|
||||
|
||||
/** Returns true iff the STObject declares a sfJsonTxBody field.
|
||||
|
||||
Used as a routing predicate: a transaction with this field present
|
||||
is claimed to use the "json-tx" signing scheme and must be validated
|
||||
through jsonTx::checkSignature / checkStructuralEquivalence. An
|
||||
empty body field still counts as "claimed json-tx" so the empty
|
||||
case is reported as a clean failure instead of silently falling
|
||||
back to the classical sig path.
|
||||
*/
|
||||
[[nodiscard]] bool
|
||||
hasBody(STObject const& obj) noexcept;
|
||||
|
||||
/** Borrow a Slice over the ASCII body bytes.
|
||||
|
||||
Returns an empty slice if sfJsonTxBody is not present. The slice is
|
||||
valid for as long as the STObject the field belongs to.
|
||||
*/
|
||||
[[nodiscard]] Slice
|
||||
body(STObject const& obj);
|
||||
|
||||
/** SHA-512-Half of the body bytes.
|
||||
|
||||
This is the deterministic "ASCII signing digest" used by json-tx:
|
||||
the bytes the client sees as their message are hashed with SHA-512
|
||||
and truncated to 256 bits, the same digest convention rippled uses
|
||||
elsewhere. Returns a zero-valued hash if sfJsonTxBody is absent.
|
||||
*/
|
||||
[[nodiscard]] uint256
|
||||
bodyHash(STObject const& obj);
|
||||
|
||||
/** Signature check only: verify sfTxnSignature against the raw bytes of
|
||||
sfJsonTxBody using sfSigningPubKey.
|
||||
|
||||
The classical signing payload is NOT used. This is the json-tx
|
||||
analogue of STTx::checkSingleSign and is intended to be called from
|
||||
the same code path (e.g. STTx::checkSign).
|
||||
|
||||
Precondition: `stx` carries a non-empty sfJsonTxBody.
|
||||
*/
|
||||
[[nodiscard]] Expected<void, std::string>
|
||||
checkSignature(STTx const& stx);
|
||||
|
||||
/** Structural-equivalence check: parse sfJsonTxBody as JSON and confirm
|
||||
it serialises to the same canonical binary as the other structural
|
||||
fields of `stx` (excluding sfTxnSignature and sfJsonTxBody).
|
||||
|
||||
This is a local-check style rule -- it should run alongside
|
||||
passesLocalChecks, not inside the signature verification path.
|
||||
|
||||
Precondition: `stx` carries a non-empty sfJsonTxBody.
|
||||
*/
|
||||
[[nodiscard]] Expected<void, std::string>
|
||||
checkStructuralEquivalence(STTx const& stx);
|
||||
|
||||
} // namespace jsonTx
|
||||
} // namespace ripple
|
||||
|
||||
#endif
|
||||
@@ -1,21 +0,0 @@
|
||||
#ifndef RIPPLE_PROTOCOL_SIDECAR_TYPE_H_INCLUDED
|
||||
#define RIPPLE_PROTOCOL_SIDECAR_TYPE_H_INCLUDED
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
/// Discriminator for sidecar set entries (SHAMap leaves used for
|
||||
/// consensus extension data: RNG commit/reveal, export signatures).
|
||||
///
|
||||
/// Stored in sfSidecarType (UINT8) on each STObject entry.
|
||||
/// Makes sidecar sets self-describing — no content-sniffing needed.
|
||||
enum SidecarType : std::uint8_t {
|
||||
sidecarRngCommit = 1,
|
||||
sidecarRngReveal = 2,
|
||||
sidecarExportSig = 3,
|
||||
};
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
#endif
|
||||
@@ -68,7 +68,6 @@ enum TELcodes : TERUnderlyingType {
|
||||
telNON_LOCAL_EMITTED_TXN,
|
||||
telIMPORT_VL_KEY_NOT_RECOGNISED,
|
||||
telCAN_NOT_QUEUE_IMPORT,
|
||||
telSHADOW_TICKET_REQUIRED,
|
||||
telENV_RPC_FAILED,
|
||||
};
|
||||
|
||||
@@ -138,7 +137,6 @@ enum TEMcodes : TERUnderlyingType {
|
||||
temXCHAIN_BRIDGE_NONDOOR_OWNER,
|
||||
temXCHAIN_BRIDGE_BAD_MIN_ACCOUNT_CREATE_AMOUNT,
|
||||
temXCHAIN_BRIDGE_BAD_REWARD_AMOUNT,
|
||||
temXCHAIN_TOO_MANY_ATTESTATIONS, // RESERVED - not used
|
||||
|
||||
temHOOK_DATA_TOO_LARGE,
|
||||
temEMPTY_DID,
|
||||
@@ -235,10 +233,8 @@ enum TERcodes : TERUnderlyingType {
|
||||
terQUEUED, // Transaction is being held in TxQ until fee drops
|
||||
terPRE_TICKET, // Ticket is not yet in ledger but might be on its way
|
||||
terNO_AMM, // AMM doesn't exist for the asset pair
|
||||
terNO_HOOK, // Transaction requires a non-existent hook definition
|
||||
terNO_HOOK // Transaction requires a non-existent hook definition
|
||||
// (referenced by sfHookHash)
|
||||
terRETRY_EXPORT // Export does not yet have enough validator signatures.
|
||||
// Retained in retriable set for next ledger.
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -366,7 +362,6 @@ enum TECcodes : TERUnderlyingType {
|
||||
tecARRAY_TOO_LARGE = 197,
|
||||
tecLOCKED = 198,
|
||||
tecBAD_CREDENTIALS = 199,
|
||||
tecEXPORT_EXPIRED = 200,
|
||||
tecLAST_POSSIBLE_ENTRY = 255,
|
||||
};
|
||||
|
||||
|
||||
@@ -132,7 +132,7 @@ constexpr std::uint32_t tfTrustSetMask =
|
||||
tfClearFreeze | tfSetDeepFreeze | tfClearDeepFreeze);
|
||||
|
||||
// EnableAmendment flags:
|
||||
enum EnableAmendmentFlags : uint32_t {
|
||||
enum EnableAmendmentFlags : std::uint32_t {
|
||||
tfGotMajority = 0x00010000,
|
||||
tfLostMajority = 0x00020000,
|
||||
tfTestSuite = 0x80000000,
|
||||
@@ -274,13 +274,6 @@ enum BridgeModifyFlags : uint32_t {
|
||||
tfClearAccountCreateAmount = 0x00010000,
|
||||
};
|
||||
constexpr std::uint32_t tfBridgeModifyMask = ~(tfUniversal | tfClearAccountCreateAmount);
|
||||
|
||||
// ConsensusEntropy flags (used on ttCONSENSUS_ENTROPY SHAMap entries):
|
||||
enum ConsensusEntropyFlags : uint32_t {
|
||||
tfEntropyCommit = 0x00000001, // entry is a commitment in commitSet
|
||||
tfEntropyReveal = 0x00000002, // entry is a reveal in entropySet
|
||||
};
|
||||
// flag=0 (no tfEntropyCommit/tfEntropyReveal) = final injected pseudo-tx
|
||||
// clang-format on
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
@@ -140,12 +140,6 @@ public:
|
||||
mHookEmissions = hookEmissions;
|
||||
}
|
||||
|
||||
void
|
||||
setExportResult(STObject const& exportResult)
|
||||
{
|
||||
mExportResult = exportResult;
|
||||
}
|
||||
|
||||
bool
|
||||
hasHookExecutions() const
|
||||
{
|
||||
@@ -158,12 +152,6 @@ public:
|
||||
return static_cast<bool>(mHookEmissions);
|
||||
}
|
||||
|
||||
bool
|
||||
hasExportResult() const
|
||||
{
|
||||
return static_cast<bool>(mExportResult);
|
||||
}
|
||||
|
||||
STAmount
|
||||
getDeliveredAmount() const
|
||||
{
|
||||
@@ -188,7 +176,6 @@ private:
|
||||
std::optional<STAmount> mDelivered;
|
||||
std::optional<STArray> mHookExecutions;
|
||||
std::optional<STArray> mHookEmissions;
|
||||
std::optional<STObject> mExportResult;
|
||||
|
||||
STArray mNodes;
|
||||
};
|
||||
|
||||
@@ -23,9 +23,6 @@
|
||||
#if !defined(XRPL_FIX)
|
||||
#error "undefined macro: XRPL_FIX"
|
||||
#endif
|
||||
#if !defined(XRPL_RETIRE)
|
||||
#error "undefined macro: XRPL_RETIRE"
|
||||
#endif
|
||||
|
||||
// clang-format off
|
||||
|
||||
@@ -34,11 +31,7 @@
|
||||
// If you add an amendment here, then do not forget to increment `numFeatures`
|
||||
// in include/xrpl/protocol/Feature.h.
|
||||
|
||||
XRPL_FIX (GuardDepth32, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(NamedHooks, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(IOURewardClaim, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (IOULockedBalanceInvariant, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (ImportIssuer, Supported::yes, VoteBehavior::DefaultYes)
|
||||
XRPL_FEATURE(JsonTx, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(HookAPISerializedType240, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(PermissionedDomains, Supported::no, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(DynamicNFT, Supported::no, VoteBehavior::DefaultNo)
|
||||
@@ -62,18 +55,16 @@ XRPL_FIX (DisallowIncomingV1, Supported::yes, VoteBehavior::DefaultYe
|
||||
XRPL_FEATURE(XChainBridge, Supported::no, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(AMM, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (ReducedOffersV1, Supported::yes, VoteBehavior::DefaultYes)
|
||||
XRPL_FEATURE(HooksUpdate2, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(HookOnV2, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(Export, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(ConsensusEntropy, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (HookAPI20251128, Supported::yes, VoteBehavior::DefaultYes)
|
||||
XRPL_FIX (CronStacking, Supported::yes, VoteBehavior::DefaultYes)
|
||||
XRPL_FEATURE(ExtendedHookState, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (InvalidTxFlags, Supported::yes, VoteBehavior::DefaultYes)
|
||||
XRPL_FEATURE(Cron, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(IOUIssuerWeakTSH, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(DeepFreeze, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (ProvisionalDoubleThreading, Supported::yes, VoteBehavior::DefaultYes)
|
||||
XRPL_FEATURE(HooksUpdate2, Supported::yes, VoteBehavior::DefaultNo);
|
||||
XRPL_FEATURE(HookOnV2, Supported::yes, VoteBehavior::DefaultNo);
|
||||
XRPL_FIX (HookAPI20251128, Supported::yes, VoteBehavior::DefaultYes);
|
||||
XRPL_FIX (CronStacking, Supported::yes, VoteBehavior::DefaultYes);
|
||||
XRPL_FEATURE(ExtendedHookState, Supported::yes, VoteBehavior::DefaultNo);
|
||||
XRPL_FIX (InvalidTxFlags, Supported::yes, VoteBehavior::DefaultYes);
|
||||
XRPL_FEATURE(Cron, Supported::yes, VoteBehavior::DefaultNo);
|
||||
XRPL_FEATURE(IOUIssuerWeakTSH, Supported::yes, VoteBehavior::DefaultNo);
|
||||
XRPL_FEATURE(DeepFreeze, Supported::yes, VoteBehavior::DefaultNo);
|
||||
XRPL_FIX (ProvisionalDoubleThreading, Supported::yes, VoteBehavior::DefaultYes);
|
||||
XRPL_FEATURE(Clawback, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (RewardClaimFlags, Supported::yes, VoteBehavior::DefaultYes)
|
||||
XRPL_FEATURE(HookCanEmit, Supported::yes, VoteBehavior::DefaultNo)
|
||||
@@ -157,24 +148,4 @@ XRPL_FIX (NFTokenDirV1, Supported::yes, VoteBehavior::Obsolete)
|
||||
XRPL_FEATURE(NonFungibleTokensV1, Supported::yes, VoteBehavior::Obsolete)
|
||||
XRPL_FEATURE(CryptoConditionsSuite, Supported::yes, VoteBehavior::Obsolete)
|
||||
|
||||
// The following amendments have been active for at least two years. Their
|
||||
// pre-amendment code has been removed and the identifiers are deprecated.
|
||||
// All known amendments and amendments that may appear in a validated
|
||||
// ledger must be registered either here or above with the "active" amendments
|
||||
XRPL_RETIRE(MultiSign)
|
||||
XRPL_RETIRE(TrustSetAuth)
|
||||
XRPL_RETIRE(FeeEscalation)
|
||||
XRPL_RETIRE(PayChan)
|
||||
XRPL_RETIRE(CryptoConditions)
|
||||
XRPL_RETIRE(TickSize)
|
||||
XRPL_RETIRE(fix1368)
|
||||
XRPL_RETIRE(Escrow)
|
||||
XRPL_RETIRE(fix1373)
|
||||
XRPL_RETIRE(EnforceInvariants)
|
||||
XRPL_RETIRE(SortedDirectories)
|
||||
XRPL_RETIRE(fix1201)
|
||||
XRPL_RETIRE(fix1512)
|
||||
XRPL_RETIRE(fix1523)
|
||||
XRPL_RETIRE(fix1528)
|
||||
|
||||
// clang-format on
|
||||
|
||||
@@ -223,21 +223,6 @@ LEDGER_ENTRY(ltURI_TOKEN, 0x0055, URIToken, uri_token, ({
|
||||
{sfPreviousTxnLgrSeq, soeREQUIRED},
|
||||
}))
|
||||
|
||||
/** The ledger object which stores consensus-derived entropy.
|
||||
|
||||
\note This is a singleton: only one such object exists in the ledger.
|
||||
|
||||
\sa keylet::consensusEntropy
|
||||
*/
|
||||
LEDGER_ENTRY_DUPLICATE(ltCONSENSUS_ENTROPY, 0x0058, ConsensusEntropy, consensus_entropy, ({
|
||||
{sfDigest, soeREQUIRED},
|
||||
{sfEntropyCount, soeREQUIRED},
|
||||
{sfEntropyTier, soeREQUIRED},
|
||||
{sfLedgerSequence, soeREQUIRED},
|
||||
{sfPreviousTxnID, soeREQUIRED},
|
||||
{sfPreviousTxnLgrSeq, soeREQUIRED},
|
||||
}))
|
||||
|
||||
/** A ledger object which describes an account.
|
||||
|
||||
\sa keylet::account
|
||||
@@ -411,8 +396,6 @@ LEDGER_ENTRY(ltRIPPLE_STATE, 0x0072, RippleState, state, ({
|
||||
{sfHighQualityOut, soeOPTIONAL},
|
||||
{sfLockedBalance, soeOPTIONAL},
|
||||
{sfLockCount, soeOPTIONAL},
|
||||
{sfHighReward, soeOPTIONAL},
|
||||
{sfLowReward, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
/** The ledger object which lists the network's fee settings.
|
||||
@@ -607,22 +590,6 @@ LEDGER_ENTRY(ltDID, 0x008D, DID, did, ({
|
||||
{sfPreviousTxnLgrSeq, soeREQUIRED},
|
||||
}))
|
||||
|
||||
//@@start shadow-ticket-ledger-entry
|
||||
/** A shadow ticket for export replay protection.
|
||||
|
||||
Created when a transaction is exported. Consumed when
|
||||
proof-of-execution is imported back. Account-owned (pays reserve).
|
||||
|
||||
\sa keylet::shadowTicket
|
||||
*/
|
||||
LEDGER_ENTRY(ltSHADOW_TICKET, 0x5374, ShadowTicket, shadow_ticket, ({
|
||||
{sfAccount, soeREQUIRED},
|
||||
{sfTicketSequence, soeREQUIRED},
|
||||
{sfTransactionHash, soeREQUIRED},
|
||||
{sfLedgerSequence, soeREQUIRED},
|
||||
{sfOwnerNode, soeREQUIRED},
|
||||
}))
|
||||
//@@end shadow-ticket-ledger-entry
|
||||
|
||||
#undef EXPAND
|
||||
#undef LEDGER_ENTRY_DUPLICATE
|
||||
|
||||
|
||||
@@ -42,8 +42,6 @@ TYPED_SFIELD(sfTickSize, UINT8, 16)
|
||||
TYPED_SFIELD(sfUNLModifyDisabling, UINT8, 17)
|
||||
TYPED_SFIELD(sfHookResult, UINT8, 18)
|
||||
TYPED_SFIELD(sfWasLockingChainSend, UINT8, 19)
|
||||
TYPED_SFIELD(sfSidecarType, UINT8, 20)
|
||||
TYPED_SFIELD(sfEntropyTier, UINT8, 21)
|
||||
|
||||
// 16-bit integers (common)
|
||||
TYPED_SFIELD(sfLedgerEntryType, UINT16, 1, SField::sMD_Never)
|
||||
@@ -61,8 +59,6 @@ TYPED_SFIELD(sfHookExecutionIndex, UINT16, 19)
|
||||
TYPED_SFIELD(sfHookApiVersion, UINT16, 20)
|
||||
TYPED_SFIELD(sfHookStateScale, UINT16, 21)
|
||||
TYPED_SFIELD(sfLedgerFixType, UINT16, 22)
|
||||
TYPED_SFIELD(sfHookExportCount, UINT16, 98)
|
||||
TYPED_SFIELD(sfEntropyCount, UINT16, 99)
|
||||
|
||||
// 32-bit integers (common)
|
||||
TYPED_SFIELD(sfNetworkID, UINT32, 1)
|
||||
@@ -127,7 +123,6 @@ TYPED_SFIELD(sfImportSequence, UINT32, 97)
|
||||
TYPED_SFIELD(sfRewardTime, UINT32, 98)
|
||||
TYPED_SFIELD(sfRewardLgrFirst, UINT32, 99)
|
||||
TYPED_SFIELD(sfRewardLgrLast, UINT32, 100)
|
||||
TYPED_SFIELD(sfCancelTicketSequence, UINT32, 101)
|
||||
|
||||
// 64-bit integers (common)
|
||||
TYPED_SFIELD(sfIndexNext, UINT64, 1)
|
||||
@@ -222,7 +217,6 @@ TYPED_SFIELD(sfHookCanEmit, UINT256, 96)
|
||||
TYPED_SFIELD(sfEmittedTxnID, UINT256, 97)
|
||||
TYPED_SFIELD(sfGovernanceMarks, UINT256, 98)
|
||||
TYPED_SFIELD(sfGovernanceFlags, UINT256, 99)
|
||||
TYPED_SFIELD(sfEntropyDigest, UINT256, 100)
|
||||
|
||||
// number (common)
|
||||
TYPED_SFIELD(sfNumber, NUMBER, 1)
|
||||
@@ -263,7 +257,6 @@ TYPED_SFIELD(sfPrice, AMOUNT, 28)
|
||||
TYPED_SFIELD(sfSignatureReward, AMOUNT, 29)
|
||||
TYPED_SFIELD(sfMinAccountCreateAmount, AMOUNT, 30)
|
||||
TYPED_SFIELD(sfLPTokenBalance, AMOUNT, 31)
|
||||
TYPED_SFIELD(sfTrustLineRewardAccumulator,AMOUNT, 99)
|
||||
|
||||
// variable length (common)
|
||||
TYPED_SFIELD(sfPublicKey, VL, 1)
|
||||
@@ -299,7 +292,10 @@ TYPED_SFIELD(sfAssetClass, VL, 29)
|
||||
TYPED_SFIELD(sfProvider, VL, 30)
|
||||
TYPED_SFIELD(sfMPTokenMetadata, VL, 31)
|
||||
TYPED_SFIELD(sfCredentialType, VL, 32)
|
||||
TYPED_SFIELD(sfHookName, VL, 97)
|
||||
// json-tx: the exact ASCII bytes the client signed; authoritative over
|
||||
// the classical signing payload when present. Not part of the classical
|
||||
// signing-payload computation (the bytes ARE the signing payload).
|
||||
TYPED_SFIELD(sfJsonTxBody, VL, 33, SField::sMD_Default, SField::notSigning)
|
||||
TYPED_SFIELD(sfRemarkValue, VL, 98)
|
||||
TYPED_SFIELD(sfRemarkName, VL, 99)
|
||||
|
||||
@@ -348,7 +344,6 @@ TYPED_SFIELD(sfLockingChainIssue, ISSUE, 1)
|
||||
TYPED_SFIELD(sfIssuingChainIssue, ISSUE, 2)
|
||||
TYPED_SFIELD(sfAsset, ISSUE, 3)
|
||||
TYPED_SFIELD(sfAsset2, ISSUE, 4)
|
||||
TYPED_SFIELD(sfClaimCurrency, ISSUE, 5)
|
||||
|
||||
// bridge
|
||||
TYPED_SFIELD(sfXChainBridge, XCHAIN_BRIDGE, 1)
|
||||
@@ -376,7 +371,7 @@ UNTYPED_SFIELD(sfMajority, OBJECT, 18)
|
||||
UNTYPED_SFIELD(sfDisabledValidator, OBJECT, 19)
|
||||
UNTYPED_SFIELD(sfEmittedTxn, OBJECT, 20)
|
||||
UNTYPED_SFIELD(sfHookExecution, OBJECT, 21)
|
||||
// 22 unused
|
||||
UNTYPED_SFIELD(sfHookDefinition, OBJECT, 22)
|
||||
UNTYPED_SFIELD(sfHookParameter, OBJECT, 23)
|
||||
UNTYPED_SFIELD(sfHookGrant, OBJECT, 24)
|
||||
UNTYPED_SFIELD(sfVoteEntry, OBJECT, 25)
|
||||
@@ -388,7 +383,6 @@ UNTYPED_SFIELD(sfXChainClaimAttestationCollectionElement, OBJECT, 30)
|
||||
UNTYPED_SFIELD(sfXChainCreateAccountAttestationCollectionElement, OBJECT, 31)
|
||||
UNTYPED_SFIELD(sfPriceData, OBJECT, 32)
|
||||
UNTYPED_SFIELD(sfCredential, OBJECT, 33)
|
||||
UNTYPED_SFIELD(sfExportedTxn, OBJECT, 90)
|
||||
UNTYPED_SFIELD(sfAmountEntry, OBJECT, 91)
|
||||
UNTYPED_SFIELD(sfMintURIToken, OBJECT, 92)
|
||||
UNTYPED_SFIELD(sfHookEmission, OBJECT, 93)
|
||||
@@ -396,9 +390,6 @@ UNTYPED_SFIELD(sfImportVLKey, OBJECT, 94)
|
||||
UNTYPED_SFIELD(sfActiveValidator, OBJECT, 95)
|
||||
UNTYPED_SFIELD(sfGenesisMint, OBJECT, 96)
|
||||
UNTYPED_SFIELD(sfRemark, OBJECT, 97)
|
||||
UNTYPED_SFIELD(sfHighReward, OBJECT, 98)
|
||||
UNTYPED_SFIELD(sfLowReward, OBJECT, 99)
|
||||
UNTYPED_SFIELD(sfExportResult, OBJECT, 100)
|
||||
|
||||
// array of objects (common)
|
||||
// ARRAY/1 is reserved for end of array
|
||||
|
||||
@@ -500,17 +500,6 @@ TRANSACTION(ttPERMISSIONED_DOMAIN_DELETE, 72, PermissionedDomainDelete, ({
|
||||
{sfDomainID, soeREQUIRED},
|
||||
}))
|
||||
|
||||
//@@start export-transaction-types
|
||||
/* User-submittable export: creates a cross-chain transaction for
|
||||
validator signing. Retries via terRETRY_EXPORT until quorum.
|
||||
Also supports shadow ticket cancellation via sfCancelTicketSequence.
|
||||
At least one of sfExportedTxn or sfCancelTicketSequence must be present. */
|
||||
TRANSACTION(ttEXPORT, 91, Export, ({
|
||||
{sfExportedTxn, soeOPTIONAL},
|
||||
{sfCancelTicketSequence, soeOPTIONAL},
|
||||
}))
|
||||
//@@end export-transaction-types
|
||||
|
||||
/* A pseudo-txn alarm signal for invoking a hook, emitted by validators after alarm set conditions are met */
|
||||
TRANSACTION(ttCRON, 92, Cron, ({
|
||||
{sfOwner, soeREQUIRED},
|
||||
@@ -561,7 +550,6 @@ TRANSACTION(ttIMPORT, 97, Import, ({
|
||||
* from a specified hook */
|
||||
TRANSACTION(ttCLAIM_REWARD, 98, ClaimReward, ({
|
||||
{sfIssuer, soeOPTIONAL},
|
||||
{sfClaimCurrency, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
/** This transaction invokes a hook, providing arbitrary data. Essentially as a 0 drop payment. **/
|
||||
@@ -617,11 +605,3 @@ TRANSACTION(ttUNL_REPORT, 104, UNLReport, ({
|
||||
{sfActiveValidator, soeOPTIONAL},
|
||||
{sfImportVLKey, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
TRANSACTION(ttCONSENSUS_ENTROPY, 105, ConsensusEntropy, ({
|
||||
{sfLedgerSequence, soeREQUIRED},
|
||||
{sfDigest, soeREQUIRED},
|
||||
{sfEntropyCount, soeREQUIRED},
|
||||
{sfEntropyTier, soeREQUIRED},
|
||||
{sfBlob, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
@@ -76,7 +76,6 @@ JSS(Holder); // field.
|
||||
JSS(HookApiVersion); // field
|
||||
JSS(HookCanEmit); // field
|
||||
JSS(HookHash); // field
|
||||
JSS(HookName); // field
|
||||
JSS(HookNamespace); // field
|
||||
JSS(HookOn); // field
|
||||
JSS(HookOnIncoming); // field
|
||||
|
||||
@@ -109,22 +109,14 @@ public:
|
||||
Consumer
|
||||
newInboundEndpoint(beast::IP::Endpoint const& address)
|
||||
{
|
||||
//@@start rng-local-testnet-resource-bucket
|
||||
// Inbound connections from the same IP normally share one
|
||||
// resource bucket (port stripped) for DoS protection. For
|
||||
// loopback addresses, preserve the port so local testnet nodes
|
||||
// each get their own bucket instead of all sharing one.
|
||||
auto const key = is_loopback(address) ? address : address.at_port(0);
|
||||
//@@end rng-local-testnet-resource-bucket
|
||||
|
||||
Entry* entry(nullptr);
|
||||
|
||||
{
|
||||
std::lock_guard _(lock_);
|
||||
auto [resultIt, resultInserted] = table_.emplace(
|
||||
std::piecewise_construct,
|
||||
std::make_tuple(kindInbound, key),
|
||||
std::make_tuple(m_clock.now()));
|
||||
std::make_tuple(kindInbound, address.at_port(0)), // Key
|
||||
std::make_tuple(m_clock.now())); // Entry
|
||||
|
||||
entry = &resultIt->second;
|
||||
entry->key = &resultIt->first;
|
||||
|
||||
20
json-tx-py/pyproject.toml
Normal file
20
json-tx-py/pyproject.toml
Normal file
@@ -0,0 +1,20 @@
|
||||
[project]
|
||||
name = "json-tx"
|
||||
version = "0.0.1"
|
||||
description = "Prototype: canonical packing of (tx_json_str, signature) using ripple-binary-codec output as an LZ dictionary"
|
||||
requires-python = ">=3.10"
|
||||
dependencies = [
|
||||
"xrpl-py>=4.0.0",
|
||||
]
|
||||
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
|
||||
[tool.hatch.build.targets.wheel]
|
||||
packages = ["src/json_tx"]
|
||||
|
||||
[tool.uv]
|
||||
dev-dependencies = [
|
||||
"pytest>=8.0",
|
||||
]
|
||||
24
json-tx-py/src/json_tx/__init__.py
Normal file
24
json-tx-py/src/json_tx/__init__.py
Normal file
@@ -0,0 +1,24 @@
|
||||
from json_tx import patch # noqa: F401 -- side-effect: register JsonTxCompressed
|
||||
from json_tx.codec import (
|
||||
JSON_TX_FIELD,
|
||||
TAGS,
|
||||
canonical_json,
|
||||
compress_stream,
|
||||
decompress_stream,
|
||||
pack,
|
||||
pack_wire,
|
||||
unpack,
|
||||
unpack_wire,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"JSON_TX_FIELD",
|
||||
"TAGS",
|
||||
"canonical_json",
|
||||
"compress_stream",
|
||||
"decompress_stream",
|
||||
"pack",
|
||||
"pack_wire",
|
||||
"unpack",
|
||||
"unpack_wire",
|
||||
]
|
||||
79
json-tx-py/src/json_tx/cli.py
Normal file
79
json-tx-py/src/json_tx/cli.py
Normal file
@@ -0,0 +1,79 @@
|
||||
"""Demo: compare classical binary, raw JSON+sig, and JsonTxCompressed wire."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
|
||||
from xrpl.core.binarycodec import encode, encode_for_signing
|
||||
from xrpl.core.keypairs import (
|
||||
derive_classic_address,
|
||||
derive_keypair,
|
||||
generate_seed,
|
||||
sign,
|
||||
)
|
||||
|
||||
from json_tx import canonical_json, compress_stream, pack_wire, unpack_wire
|
||||
|
||||
|
||||
SAMPLE_TX = {
|
||||
"TransactionType": "Payment",
|
||||
"Account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
|
||||
"Destination": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
||||
"Amount": "1000000",
|
||||
"Fee": "12",
|
||||
"Sequence": 1,
|
||||
"Flags": 2147483648,
|
||||
"SigningPubKey": "",
|
||||
}
|
||||
|
||||
|
||||
def _report(label: str, tx: dict, tx_json_str: str, priv: str) -> None:
|
||||
signature = bytes.fromhex(sign(tx_json_str.encode().hex(), priv))
|
||||
|
||||
# 1. Classical signed wire: full binary with TxnSignature (what xrpl does today).
|
||||
classical_signing = bytes.fromhex(encode_for_signing(tx))
|
||||
classical_signed_dict = dict(tx)
|
||||
classical_signed_dict["TxnSignature"] = signature.hex().upper()
|
||||
classical_wire = bytes.fromhex(encode(classical_signed_dict))
|
||||
|
||||
# 2. Naive JSON submission: tx_json_str + signature (what json-tx wants to replace).
|
||||
raw_json_plus_sig = len(tx_json_str) + len(signature)
|
||||
|
||||
# 3. json-tx wire: classical binary (ex TxnSignature) + JsonTxCompressed + TxnSignature.
|
||||
stream = compress_stream(tx_json_str, tx_json=tx)
|
||||
jsontx_wire = pack_wire(tx_json_str, signature)
|
||||
|
||||
print(f"\n== {label} ==")
|
||||
print(f" tx_json_str : {len(tx_json_str):5d} bytes")
|
||||
print(f" signature : {len(signature):5d} bytes")
|
||||
print(f" classical binary (signing payload): {len(classical_signing):5d} bytes")
|
||||
print(f" classical wire (binary + sig) : {len(classical_wire):5d} bytes <- today")
|
||||
print(f" raw JSON + sig (bytes sent) : {raw_json_plus_sig:5d} bytes <- naive json submit")
|
||||
print(f" JsonTxCompressed stream alone : {len(stream):5d} bytes [mode=0x{stream[0]:02x}]")
|
||||
print(f" json-tx wire (classical + stream) : {len(jsontx_wire):5d} bytes <- proposed")
|
||||
delta_vs_classical = len(jsontx_wire) - len(classical_wire)
|
||||
print(f" overhead vs classical wire : {delta_vs_classical:+5d} bytes")
|
||||
delta_vs_raw = len(jsontx_wire) - raw_json_plus_sig
|
||||
print(f" overhead vs raw JSON+sig : {delta_vs_raw:+5d} bytes")
|
||||
|
||||
recovered_tx, recovered_str, recovered_sig = unpack_wire(jsontx_wire)
|
||||
assert recovered_str == tx_json_str
|
||||
assert recovered_sig == signature
|
||||
assert recovered_tx == tx
|
||||
|
||||
|
||||
def main() -> None:
|
||||
seed = generate_seed()
|
||||
pub, priv = derive_keypair(seed)
|
||||
tx = dict(SAMPLE_TX)
|
||||
tx["Account"] = derive_classic_address(pub)
|
||||
tx["SigningPubKey"] = pub
|
||||
|
||||
_report("canonical tx_json_str (ordinal order, no whitespace)",
|
||||
tx, canonical_json(tx), priv)
|
||||
_report("non-canonical tx_json_str (insertion order + spaces)",
|
||||
tx, json.dumps(tx, separators=(", ", ": ")), priv)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
384
json-tx-py/src/json_tx/codec.py
Normal file
384
json-tx-py/src/json_tx/codec.py
Normal file
@@ -0,0 +1,384 @@
|
||||
"""
|
||||
json-tx: field-aware packer for (tx_json_str, signature).
|
||||
|
||||
The ripple binary codec already decomposes a transaction into ordered
|
||||
(field_name, canonical_bytes) pairs. We reuse that as the dictionary.
|
||||
|
||||
Opcode stream:
|
||||
OP_FIELD i -> render field i exactly as it appears in tx_json_str
|
||||
OP_TAG t -> emit a glue snippet from TAGS (',', ':', '"', ...)
|
||||
OP_RAW n <bytes> -> n bytes of literal passthrough
|
||||
OP_END -> terminator
|
||||
|
||||
The stream is what gets stored in the `JsonTxCompressed` Blob field on
|
||||
the wire. The TxnSignature field still carries the ed25519/secp256k1
|
||||
signature, but that signature is now over the ASCII `tx_json_str`, not
|
||||
the classical signing payload.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
||||
from xrpl.core.binarycodec.definitions.field_instance import FieldInstance
|
||||
from xrpl.core.binarycodec.types.st_object import STObject
|
||||
|
||||
# `patch` registers the JsonTxCompressed field. Imported for its side effect.
|
||||
from json_tx import patch as _patch # noqa: F401
|
||||
|
||||
OP_FIELD = 0x01 # emit `"<name>":<canonical-value>` (tight pair)
|
||||
OP_TAG = 0x02 # emit a structural glue byte
|
||||
OP_RAW = 0x03 # length-prefixed literal bytes
|
||||
OP_NAME = 0x04 # emit `"<name>"` for field i
|
||||
OP_VALUE = 0x05 # emit canonical rendering of field i's value
|
||||
OP_END = 0x00
|
||||
|
||||
# Mode byte at the head of every stream.
|
||||
MODE_CANONICAL = 0x00 # body is an OP_* stream; tx_json_str reconstructs via dictionary
|
||||
MODE_VERBATIM = 0x01 # body is raw UTF-8 tx_json_str, length-prefixed
|
||||
|
||||
JSON_TX_FIELD = _patch.FIELD_NAME
|
||||
|
||||
# Structural glue. INVARIANT: no tag may end in `"` -- otherwise it would
|
||||
# eat the leading `"` of a field NAME/FIELD rendering and prevent re-align.
|
||||
# Order: longest first so the greedy matcher picks the most specific glue.
|
||||
TAGS: list[bytes] = [
|
||||
# comma-based field separators
|
||||
b",\n ",
|
||||
b",\n\t",
|
||||
b",\n ",
|
||||
b",\n ",
|
||||
b",\n",
|
||||
b", ",
|
||||
# colon-based name:value separators
|
||||
b": ",
|
||||
b": ",
|
||||
# leading indent after `{`
|
||||
b"\n ",
|
||||
b"\n\t",
|
||||
b"\n ",
|
||||
b"\n ",
|
||||
# single chars
|
||||
b"{",
|
||||
b"}",
|
||||
b"[",
|
||||
b"]",
|
||||
b",",
|
||||
b":",
|
||||
b'"',
|
||||
b" ",
|
||||
b"\n",
|
||||
b"\t",
|
||||
]
|
||||
|
||||
|
||||
# ---------- varint (unsigned LEB128) ----------
|
||||
|
||||
def _vw(n: int) -> bytes:
|
||||
if n < 0:
|
||||
raise ValueError("varint must be non-negative")
|
||||
out = bytearray()
|
||||
while True:
|
||||
b = n & 0x7F
|
||||
n >>= 7
|
||||
if n:
|
||||
out.append(b | 0x80)
|
||||
else:
|
||||
out.append(b)
|
||||
return bytes(out)
|
||||
|
||||
|
||||
def _vr(buf: bytes, i: int) -> tuple[int, int]:
|
||||
n = 0
|
||||
shift = 0
|
||||
while True:
|
||||
b = buf[i]
|
||||
i += 1
|
||||
n |= (b & 0x7F) << shift
|
||||
if not (b & 0x80):
|
||||
return n, i
|
||||
shift += 7
|
||||
|
||||
|
||||
# ---------- canonical field extraction ----------
|
||||
|
||||
@dataclass
|
||||
class CanonField:
|
||||
name: str
|
||||
instance: FieldInstance
|
||||
canonical_bytes: bytes
|
||||
value: Any
|
||||
|
||||
|
||||
def _ordered_fields_from_dict(tx_json: dict, *, skip: set[str]) -> list[CanonField]:
|
||||
"""Serialize tx_json once through STObject, then re-parse to slice each field."""
|
||||
from xrpl.core.binarycodec.binary_wrappers.binary_parser import BinaryParser
|
||||
from xrpl.core.binarycodec.definitions import definitions
|
||||
|
||||
tx_for_enc = {k: v for k, v in tx_json.items() if k not in skip}
|
||||
st = STObject.from_value(tx_for_enc)
|
||||
blob = bytes(st)
|
||||
|
||||
parser = BinaryParser(blob.hex())
|
||||
total = len(parser)
|
||||
fields: list[CanonField] = []
|
||||
while not parser.is_end():
|
||||
start = total - len(parser)
|
||||
fi = parser.read_field()
|
||||
parser.read_field_value(fi)
|
||||
end = total - len(parser)
|
||||
fields.append(
|
||||
CanonField(
|
||||
name=fi.name,
|
||||
instance=definitions.get_field_instance(fi.name),
|
||||
canonical_bytes=blob[start:end],
|
||||
value=tx_json[fi.name],
|
||||
)
|
||||
)
|
||||
return fields
|
||||
|
||||
|
||||
def _render_name(name: str) -> bytes:
|
||||
"""Render `"Name"` including the enclosing double-quotes."""
|
||||
return json.dumps(name, separators=(",", ":")).encode()
|
||||
|
||||
|
||||
def _render_value(value: Any) -> bytes:
|
||||
"""Render the canonical JSON form of a value."""
|
||||
return json.dumps(value, separators=(",", ":")).encode()
|
||||
|
||||
|
||||
def _render_field_json(name: str, value: Any) -> bytes:
|
||||
"""Render one tight `"Name":<value>` pair -- no whitespace."""
|
||||
return _render_name(name) + b":" + _render_value(value)
|
||||
|
||||
|
||||
def canonical_json(tx_json: dict) -> str:
|
||||
"""Serialize tx_json with fields in canonical (ordinal) order.
|
||||
|
||||
The signed ASCII JSON must match this ordering so the opcode stream
|
||||
can walk fields in lock-step with the binary dictionary. Any field the
|
||||
codec does not recognize falls to the tail in insertion order.
|
||||
"""
|
||||
from xrpl.core.binarycodec.definitions import definitions
|
||||
|
||||
known, unknown = [], []
|
||||
for k, v in tx_json.items():
|
||||
try:
|
||||
fi = definitions.get_field_instance(k)
|
||||
known.append((fi.ordinal, k, v))
|
||||
except Exception:
|
||||
unknown.append((k, v))
|
||||
known.sort(key=lambda x: x[0])
|
||||
ordered = [(k, v) for _o, k, v in known] + unknown
|
||||
body = ",".join(
|
||||
f"{json.dumps(k, separators=(',', ':'))}:"
|
||||
f"{json.dumps(v, separators=(',', ':'))}"
|
||||
for k, v in ordered
|
||||
)
|
||||
return "{" + body + "}"
|
||||
|
||||
|
||||
# ---------- stream codec (opcode layer only) ----------
|
||||
|
||||
def compress_stream(tx_json_str: str, *, tx_json: dict | None = None) -> bytes:
|
||||
"""Encode tx_json_str using tx_json's fields as dictionary.
|
||||
|
||||
Opcodes (after the mode byte):
|
||||
OP_FIELD i -- `"Name":<canonical-value>` tight pair (no whitespace)
|
||||
OP_NAME i -- `"Name"` alone (enclosing quotes included)
|
||||
OP_VALUE i -- canonical rendering of field i's value
|
||||
OP_TAG t -- structural glue from TAGS
|
||||
OP_RAW n.. -- literal passthrough
|
||||
|
||||
The matcher at each cursor position tries, longest-match first:
|
||||
1. OP_FIELD against any unused field pair
|
||||
2. OP_NAME against any unused field name
|
||||
3. OP_VALUE against any unused field value
|
||||
4. OP_TAG
|
||||
5. OP_RAW (one byte, coalesced)
|
||||
|
||||
If the resulting OP stream is not shorter than a verbatim copy, we
|
||||
emit MODE_VERBATIM instead.
|
||||
"""
|
||||
if tx_json is None:
|
||||
tx_json = json.loads(tx_json_str)
|
||||
src = tx_json_str.encode()
|
||||
|
||||
fields = _ordered_fields_from_dict(
|
||||
tx_json, skip={"TxnSignature", JSON_TX_FIELD}
|
||||
)
|
||||
name_render = [_render_name(f.name) for f in fields]
|
||||
value_render = [_render_value(f.value) for f in fields]
|
||||
pair_render = [name_render[i] + b":" + value_render[i] for i in range(len(fields))]
|
||||
|
||||
unused_pair = set(range(len(fields)))
|
||||
unused_name = set(range(len(fields)))
|
||||
unused_value = set(range(len(fields)))
|
||||
|
||||
body = bytearray()
|
||||
raw_buf = bytearray()
|
||||
|
||||
def flush_raw() -> None:
|
||||
if raw_buf:
|
||||
body.append(OP_RAW)
|
||||
body.extend(_vw(len(raw_buf)))
|
||||
body.extend(raw_buf)
|
||||
raw_buf.clear()
|
||||
|
||||
def best_match(candidates: set[int], renders: list[bytes], at: int) -> tuple[int, int]:
|
||||
best_idx, best_len = -1, 0
|
||||
for idx in candidates:
|
||||
r = renders[idx]
|
||||
if len(r) > best_len and src.startswith(r, at):
|
||||
best_idx, best_len = idx, len(r)
|
||||
return best_idx, best_len
|
||||
|
||||
i = 0
|
||||
while i < len(src):
|
||||
# Try the tight FIELD match first -- cheapest per byte of output.
|
||||
idx, hit = best_match(unused_pair, pair_render, i)
|
||||
if idx >= 0:
|
||||
flush_raw()
|
||||
body.append(OP_FIELD)
|
||||
body.extend(_vw(idx))
|
||||
i += hit
|
||||
unused_pair.discard(idx)
|
||||
unused_name.discard(idx)
|
||||
unused_value.discard(idx)
|
||||
continue
|
||||
|
||||
# Then NAME (standalone), preferring longer names over shorter ones.
|
||||
idx, hit = best_match(unused_name, name_render, i)
|
||||
if idx >= 0:
|
||||
flush_raw()
|
||||
body.append(OP_NAME)
|
||||
body.extend(_vw(idx))
|
||||
i += hit
|
||||
unused_name.discard(idx)
|
||||
unused_pair.discard(idx) # no longer a "pair" candidate
|
||||
continue
|
||||
|
||||
# Then VALUE (standalone).
|
||||
idx, hit = best_match(unused_value, value_render, i)
|
||||
if idx >= 0:
|
||||
flush_raw()
|
||||
body.append(OP_VALUE)
|
||||
body.extend(_vw(idx))
|
||||
i += hit
|
||||
unused_value.discard(idx)
|
||||
unused_pair.discard(idx)
|
||||
continue
|
||||
|
||||
# Structural glue.
|
||||
tag_hit = -1
|
||||
for t_idx, tag in enumerate(TAGS):
|
||||
if src.startswith(tag, i):
|
||||
tag_hit = t_idx
|
||||
break
|
||||
if tag_hit >= 0:
|
||||
flush_raw()
|
||||
body.append(OP_TAG)
|
||||
body.extend(_vw(tag_hit))
|
||||
i += len(TAGS[tag_hit])
|
||||
continue
|
||||
|
||||
raw_buf.append(src[i])
|
||||
i += 1
|
||||
|
||||
flush_raw()
|
||||
body.append(OP_END)
|
||||
|
||||
op_form = bytes([MODE_CANONICAL]) + bytes(body)
|
||||
verbatim = bytes([MODE_VERBATIM]) + _vw(len(src)) + src
|
||||
return op_form if len(op_form) <= len(verbatim) else verbatim
|
||||
|
||||
|
||||
def decompress_stream(stream: bytes, tx_json_for_dict: dict) -> str:
|
||||
"""Rebuild tx_json_str from a json-tx stream + a parsed tx dict (dictionary source)."""
|
||||
mode = stream[0]
|
||||
i = 1
|
||||
if mode == MODE_VERBATIM:
|
||||
ln, i = _vr(stream, i)
|
||||
return stream[i : i + ln].decode()
|
||||
if mode != MODE_CANONICAL:
|
||||
raise ValueError(f"unknown json-tx stream mode 0x{mode:02x}")
|
||||
|
||||
fields = _ordered_fields_from_dict(
|
||||
tx_json_for_dict,
|
||||
skip={"TxnSignature", JSON_TX_FIELD},
|
||||
)
|
||||
name_render = [_render_name(f.name) for f in fields]
|
||||
value_render = [_render_value(f.value) for f in fields]
|
||||
pair_render = [name_render[i] + b":" + value_render[i] for i in range(len(fields))]
|
||||
|
||||
out = bytearray()
|
||||
while i < len(stream):
|
||||
op = stream[i]
|
||||
i += 1
|
||||
if op == OP_END:
|
||||
break
|
||||
if op == OP_FIELD:
|
||||
idx, i = _vr(stream, i)
|
||||
out += pair_render[idx]
|
||||
elif op == OP_NAME:
|
||||
idx, i = _vr(stream, i)
|
||||
out += name_render[idx]
|
||||
elif op == OP_VALUE:
|
||||
idx, i = _vr(stream, i)
|
||||
out += value_render[idx]
|
||||
elif op == OP_TAG:
|
||||
idx, i = _vr(stream, i)
|
||||
out += TAGS[idx]
|
||||
elif op == OP_RAW:
|
||||
ln, i = _vr(stream, i)
|
||||
out += stream[i : i + ln]
|
||||
i += ln
|
||||
else:
|
||||
raise ValueError(f"unknown opcode 0x{op:02x} at offset {i - 1}")
|
||||
return out.decode()
|
||||
|
||||
|
||||
# ---------- wire-tx pack/unpack (full binary with JsonTxCompressed) ----------
|
||||
|
||||
def pack_wire(tx_json_str: str, signature: bytes) -> bytes:
|
||||
"""Build the on-wire binary tx: canonical binary + JsonTxCompressed + TxnSignature.
|
||||
|
||||
`tx_json_str` is the exact ASCII bytes the client signed -- any field
|
||||
order / whitespace. `signature` is the raw signature over those bytes.
|
||||
"""
|
||||
from xrpl.core.binarycodec.main import encode
|
||||
|
||||
tx_json = json.loads(tx_json_str)
|
||||
stream = compress_stream(tx_json_str, tx_json=tx_json)
|
||||
wire_dict = dict(tx_json)
|
||||
wire_dict[JSON_TX_FIELD] = stream.hex().upper()
|
||||
wire_dict["TxnSignature"] = signature.hex().upper()
|
||||
return bytes.fromhex(encode(wire_dict))
|
||||
|
||||
|
||||
def unpack_wire(wire: bytes) -> tuple[dict, str, bytes]:
|
||||
"""Decode the wire tx back into (tx_json_dict, tx_json_str, signature)."""
|
||||
from xrpl.core.binarycodec.main import decode
|
||||
|
||||
decoded = decode(wire.hex().upper())
|
||||
stream_hex = decoded.pop(JSON_TX_FIELD)
|
||||
sig_hex = decoded.pop("TxnSignature")
|
||||
# The dictionary is the other fields of the tx, i.e. the decoded dict
|
||||
# minus the scaffolding keys (already removed above).
|
||||
tx_json_str = decompress_stream(bytes.fromhex(stream_hex), decoded)
|
||||
return json.loads(tx_json_str), tx_json_str, bytes.fromhex(sig_hex)
|
||||
|
||||
|
||||
# ---------- convenience ----------
|
||||
|
||||
def pack(tx_json: dict, signature: bytes) -> bytes:
|
||||
"""Alias for pack_wire for the common case."""
|
||||
return pack_wire(tx_json, signature)
|
||||
|
||||
|
||||
def unpack(wire: bytes) -> tuple[dict, bytes]:
|
||||
tx_json, _, sig = unpack_wire(wire)
|
||||
return tx_json, sig
|
||||
50
json-tx-py/src/json_tx/patch.py
Normal file
50
json-tx-py/src/json_tx/patch.py
Normal file
@@ -0,0 +1,50 @@
|
||||
"""Runtime monkey-patch: register a `JsonTxCompressed` Blob field.
|
||||
|
||||
Import this module (or call `register_json_tx_field()`) before using the
|
||||
binary codec so that a transaction dict containing `JsonTxCompressed`
|
||||
will serialize it as a Blob and parse it back out.
|
||||
|
||||
The field is intentionally `isSigningField=False` — the ASCII JSON is
|
||||
what TxnSignature signs, not the binary form, so this field must not
|
||||
participate in any classical signing payload.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from xrpl.core.binarycodec.definitions import definitions as _d
|
||||
from xrpl.core.binarycodec.definitions.field_header import FieldHeader
|
||||
from xrpl.core.binarycodec.definitions.field_info import FieldInfo
|
||||
|
||||
FIELD_NAME = "JsonTxCompressed"
|
||||
_TYPE_NAME = "Blob"
|
||||
|
||||
|
||||
def _pick_free_nth_for_type(type_name: str) -> int:
|
||||
"""Find an unused `nth` code within the given type so there's no header clash."""
|
||||
type_code = _d._TYPE_ORDINAL_MAP[type_name]
|
||||
taken = {
|
||||
h.field_code for h in _d._FIELD_HEADER_NAME_MAP if h.type_code == type_code
|
||||
}
|
||||
for n in range(1, 255):
|
||||
if n not in taken:
|
||||
return n
|
||||
raise RuntimeError(f"no free nth code for type {type_name}")
|
||||
|
||||
|
||||
def register_json_tx_field() -> None:
|
||||
if FIELD_NAME in _d._FIELD_INFO_MAP:
|
||||
return
|
||||
nth = _pick_free_nth_for_type(_TYPE_NAME)
|
||||
info = FieldInfo(
|
||||
nth=nth,
|
||||
is_variable_length_encoded=True,
|
||||
is_serialized=True,
|
||||
is_signing_field=False,
|
||||
type_name=_TYPE_NAME,
|
||||
)
|
||||
header = FieldHeader(_d._TYPE_ORDINAL_MAP[_TYPE_NAME], nth)
|
||||
_d._FIELD_INFO_MAP[FIELD_NAME] = info
|
||||
_d._FIELD_HEADER_NAME_MAP[header] = FIELD_NAME
|
||||
|
||||
|
||||
register_json_tx_field()
|
||||
63
json-tx-py/tests/test_roundtrip.py
Normal file
63
json-tx-py/tests/test_roundtrip.py
Normal file
@@ -0,0 +1,63 @@
|
||||
import json
|
||||
|
||||
from xrpl.core.keypairs import derive_classic_address, derive_keypair, generate_seed, sign
|
||||
|
||||
from json_tx import compress_stream, decompress_stream, pack_wire, unpack_wire
|
||||
|
||||
|
||||
def _signed(tx: dict) -> tuple[dict, str, bytes]:
|
||||
seed = generate_seed()
|
||||
pub, priv = derive_keypair(seed)
|
||||
tx = dict(tx)
|
||||
tx["Account"] = derive_classic_address(pub)
|
||||
tx["SigningPubKey"] = pub
|
||||
from json_tx import canonical_json
|
||||
tx_json_str = canonical_json(tx)
|
||||
sig = bytes.fromhex(sign(tx_json_str.encode().hex(), priv))
|
||||
return tx, tx_json_str, sig
|
||||
|
||||
|
||||
def test_wire_roundtrip():
|
||||
tx, tx_json_str, sig = _signed({
|
||||
"TransactionType": "Payment",
|
||||
"Destination": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
||||
"Amount": "1000000",
|
||||
"Fee": "12",
|
||||
"Sequence": 1,
|
||||
"Flags": 2147483648,
|
||||
})
|
||||
wire = pack_wire(tx_json_str, sig)
|
||||
recovered_tx, recovered_str, recovered_sig = unpack_wire(wire)
|
||||
assert recovered_str == tx_json_str
|
||||
assert recovered_sig == sig
|
||||
assert recovered_tx == tx
|
||||
|
||||
|
||||
def test_stream_roundtrip_direct():
|
||||
tx, tx_json_str, _ = _signed({
|
||||
"TransactionType": "Payment",
|
||||
"Destination": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
||||
"Amount": "2500000",
|
||||
"Fee": "15",
|
||||
"Sequence": 42,
|
||||
"Flags": 0,
|
||||
})
|
||||
stream = compress_stream(tx_json_str, tx_json=tx)
|
||||
rebuilt = decompress_stream(stream, tx)
|
||||
assert rebuilt == tx_json_str
|
||||
|
||||
|
||||
def test_raw_fallback_preserved():
|
||||
# Unusual whitespace -> RAW opcodes. Dictionary still reconstructs losslessly.
|
||||
tx = {
|
||||
"TransactionType": "Payment",
|
||||
"Account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
|
||||
"Destination": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
||||
"Amount": "1",
|
||||
"Fee": "12",
|
||||
"Sequence": 1,
|
||||
"SigningPubKey": "",
|
||||
}
|
||||
odd = '{ "TransactionType" : "Payment" }'
|
||||
stream = compress_stream(odd, tx_json=tx)
|
||||
assert decompress_stream(stream, tx) == odd
|
||||
476
json-tx-py/uv.lock
generated
Normal file
476
json-tx-py/uv.lock
generated
Normal file
@@ -0,0 +1,476 @@
|
||||
version = 1
|
||||
revision = 3
|
||||
requires-python = ">=3.10"
|
||||
|
||||
[[package]]
|
||||
name = "anyio"
|
||||
version = "4.13.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "exceptiongroup", marker = "python_full_version < '3.11'" },
|
||||
{ name = "idna" },
|
||||
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/19/14/2c5dd9f512b66549ae92767a9c7b330ae88e1932ca57876909410251fe13/anyio-4.13.0.tar.gz", hash = "sha256:334b70e641fd2221c1505b3890c69882fe4a2df910cba14d97019b90b24439dc", size = 231622, upload-time = "2026-03-24T12:59:09.671Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl", hash = "sha256:08b310f9e24a9594186fd75b4f73f4a4152069e3853f1ed8bfbf58369f4ad708", size = 114353, upload-time = "2026-03-24T12:59:08.246Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "base58"
|
||||
version = "2.1.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/7f/45/8ae61209bb9015f516102fa559a2914178da1d5868428bd86a1b4421141d/base58-2.1.1.tar.gz", hash = "sha256:c5d0cb3f5b6e81e8e35da5754388ddcc6d0d14b6c6a132cb93d69ed580a7278c", size = 6528, upload-time = "2021-10-30T22:12:17.858Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/4a/45/ec96b29162a402fc4c1c5512d114d7b3787b9d1c2ec241d9568b4816ee23/base58-2.1.1-py3-none-any.whl", hash = "sha256:11a36f4d3ce51dfc1043f3218591ac4eb1ceb172919cebe05b52a5bcc8d245c2", size = 5621, upload-time = "2021-10-30T22:12:16.658Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "certifi"
|
||||
version = "2026.4.22"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/25/ee/6caf7a40c36a1220410afe15a1cc64993a1f864871f698c0f93acb72842a/certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580", size = 137077, upload-time = "2026-04-22T11:26:11.191Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/22/30/7cd8fdcdfbc5b869528b079bfb76dcdf6056b1a2097a662e5e8c04f42965/certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a", size = 135707, upload-time = "2026-04-22T11:26:09.372Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "colorama"
|
||||
version = "0.4.6"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deprecated"
|
||||
version = "1.3.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "wrapt" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/49/85/12f0a49a7c4ffb70572b6c2ef13c90c88fd190debda93b23f026b25f9634/deprecated-1.3.1.tar.gz", hash = "sha256:b1b50e0ff0c1fddaa5708a2c6b0a6588bb09b892825ab2b214ac9ea9d92a5223", size = 2932523, upload-time = "2025-10-30T08:19:02.757Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/84/d0/205d54408c08b13550c733c4b85429e7ead111c7f0014309637425520a9a/deprecated-1.3.1-py2.py3-none-any.whl", hash = "sha256:597bfef186b6f60181535a29fbe44865ce137a5079f295b479886c82729d5f3f", size = 11298, upload-time = "2025-10-30T08:19:00.758Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ecpy"
|
||||
version = "1.2.5"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e0/48/3f8c1a252e3a46fd04e6fabc5e11c933b9c39cf84edd4e7c906e29c23750/ECPy-1.2.5.tar.gz", hash = "sha256:9635cffb9b6ecf7fd7f72aea1665829ac74a1d272006d0057d45a621aae20228", size = 38458, upload-time = "2020-10-26T11:56:16.313Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e8/35/4a113189f7138035a21bd255d30dc7bffc77c942c93b7948d2eac2e22429/ECPy-1.2.5-py3-none-any.whl", hash = "sha256:559c92e42406d9d1a6b2b8fc26e6ad7bc985f33903b72f426a56cb1073a25ce3", size = 43075, upload-time = "2020-10-26T11:56:13.613Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "exceptiongroup"
|
||||
version = "1.3.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "h11"
|
||||
version = "0.16.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "httpcore"
|
||||
version = "1.0.9"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "certifi" },
|
||||
{ name = "h11" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "httpx"
|
||||
version = "0.28.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "anyio" },
|
||||
{ name = "certifi" },
|
||||
{ name = "httpcore" },
|
||||
{ name = "idna" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "3.13"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ce/cc/762dfb036166873f0059f3b7de4565e1b5bc3d6f28a414c13da27e442f99/idna-3.13.tar.gz", hash = "sha256:585ea8fe5d69b9181ec1afba340451fba6ba764af97026f92a91d4eef164a242", size = 194210, upload-time = "2026-04-22T16:42:42.314Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/5d/13/ad7d7ca3808a898b4612b6fe93cde56b53f3034dcde235acb1f0e1df24c6/idna-3.13-py3-none-any.whl", hash = "sha256:892ea0cde124a99ce773decba204c5552b69c3c67ffd5f232eb7696135bc8bb3", size = 68629, upload-time = "2026-04-22T16:42:40.909Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iniconfig"
|
||||
version = "2.3.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "json-tx"
|
||||
version = "0.0.1"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "xrpl-py" },
|
||||
]
|
||||
|
||||
[package.dev-dependencies]
|
||||
dev = [
|
||||
{ name = "pytest" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [{ name = "xrpl-py", specifier = ">=4.0.0" }]
|
||||
|
||||
[package.metadata.requires-dev]
|
||||
dev = [{ name = "pytest", specifier = ">=8.0" }]
|
||||
|
||||
[[package]]
|
||||
name = "packaging"
|
||||
version = "26.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/df/de/0d2b39fb4af88a0258f3bac87dfcbb48e73fbdea4a2ed0e2213f9a4c2f9a/packaging-26.1.tar.gz", hash = "sha256:f042152b681c4bfac5cae2742a55e103d27ab2ec0f3d88037136b6bfe7c9c5de", size = 215519, upload-time = "2026-04-14T21:12:49.362Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/7a/c2/920ef838e2f0028c8262f16101ec09ebd5969864e5a64c4c05fad0617c56/packaging-26.1-py3-none-any.whl", hash = "sha256:5d9c0669c6285e491e0ced2eee587eaf67b670d94a19e94e3984a481aba6802f", size = 95831, upload-time = "2026-04-14T21:12:47.56Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pluggy"
|
||||
version = "1.6.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pycryptodome"
|
||||
version = "3.23.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/8e/a6/8452177684d5e906854776276ddd34eca30d1b1e15aa1ee9cefc289a33f5/pycryptodome-3.23.0.tar.gz", hash = "sha256:447700a657182d60338bab09fdb27518f8856aecd80ae4c6bdddb67ff5da44ef", size = 4921276, upload-time = "2025-05-17T17:21:45.242Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/04/5d/bdb09489b63cd34a976cc9e2a8d938114f7a53a74d3dd4f125ffa49dce82/pycryptodome-3.23.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:0011f7f00cdb74879142011f95133274741778abba114ceca229adbf8e62c3e4", size = 2495152, upload-time = "2025-05-17T17:20:20.833Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a7/ce/7840250ed4cc0039c433cd41715536f926d6e86ce84e904068eb3244b6a6/pycryptodome-3.23.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:90460fc9e088ce095f9ee8356722d4f10f86e5be06e2354230a9880b9c549aae", size = 1639348, upload-time = "2025-05-17T17:20:23.171Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ee/f0/991da24c55c1f688d6a3b5a11940567353f74590734ee4a64294834ae472/pycryptodome-3.23.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4764e64b269fc83b00f682c47443c2e6e85b18273712b98aa43bcb77f8570477", size = 2184033, upload-time = "2025-05-17T17:20:25.424Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/54/16/0e11882deddf00f68b68dd4e8e442ddc30641f31afeb2bc25588124ac8de/pycryptodome-3.23.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb8f24adb74984aa0e5d07a2368ad95276cf38051fe2dc6605cbcf482e04f2a7", size = 2270142, upload-time = "2025-05-17T17:20:27.808Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d5/fc/4347fea23a3f95ffb931f383ff28b3f7b1fe868739182cb76718c0da86a1/pycryptodome-3.23.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d97618c9c6684a97ef7637ba43bdf6663a2e2e77efe0f863cce97a76af396446", size = 2309384, upload-time = "2025-05-17T17:20:30.765Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6e/d9/c5261780b69ce66d8cfab25d2797bd6e82ba0241804694cd48be41add5eb/pycryptodome-3.23.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9a53a4fe5cb075075d515797d6ce2f56772ea7e6a1e5e4b96cf78a14bac3d265", size = 2183237, upload-time = "2025-05-17T17:20:33.736Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5a/6f/3af2ffedd5cfa08c631f89452c6648c4d779e7772dfc388c77c920ca6bbf/pycryptodome-3.23.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:763d1d74f56f031788e5d307029caef067febf890cd1f8bf61183ae142f1a77b", size = 2343898, upload-time = "2025-05-17T17:20:36.086Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9a/dc/9060d807039ee5de6e2f260f72f3d70ac213993a804f5e67e0a73a56dd2f/pycryptodome-3.23.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:954af0e2bd7cea83ce72243b14e4fb518b18f0c1649b576d114973e2073b273d", size = 2269197, upload-time = "2025-05-17T17:20:38.414Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f9/34/e6c8ca177cb29dcc4967fef73f5de445912f93bd0343c9c33c8e5bf8cde8/pycryptodome-3.23.0-cp313-cp313t-win32.whl", hash = "sha256:257bb3572c63ad8ba40b89f6fc9d63a2a628e9f9708d31ee26560925ebe0210a", size = 1768600, upload-time = "2025-05-17T17:20:40.688Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e4/1d/89756b8d7ff623ad0160f4539da571d1f594d21ee6d68be130a6eccb39a4/pycryptodome-3.23.0-cp313-cp313t-win_amd64.whl", hash = "sha256:6501790c5b62a29fcb227bd6b62012181d886a767ce9ed03b303d1f22eb5c625", size = 1799740, upload-time = "2025-05-17T17:20:42.413Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5d/61/35a64f0feaea9fd07f0d91209e7be91726eb48c0f1bfc6720647194071e4/pycryptodome-3.23.0-cp313-cp313t-win_arm64.whl", hash = "sha256:9a77627a330ab23ca43b48b130e202582e91cc69619947840ea4d2d1be21eb39", size = 1703685, upload-time = "2025-05-17T17:20:44.388Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/db/6c/a1f71542c969912bb0e106f64f60a56cc1f0fabecf9396f45accbe63fa68/pycryptodome-3.23.0-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:187058ab80b3281b1de11c2e6842a357a1f71b42cb1e15bce373f3d238135c27", size = 2495627, upload-time = "2025-05-17T17:20:47.139Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6e/4e/a066527e079fc5002390c8acdd3aca431e6ea0a50ffd7201551175b47323/pycryptodome-3.23.0-cp37-abi3-macosx_10_9_x86_64.whl", hash = "sha256:cfb5cd445280c5b0a4e6187a7ce8de5a07b5f3f897f235caa11f1f435f182843", size = 1640362, upload-time = "2025-05-17T17:20:50.392Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/50/52/adaf4c8c100a8c49d2bd058e5b551f73dfd8cb89eb4911e25a0c469b6b4e/pycryptodome-3.23.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67bd81fcbe34f43ad9422ee8fd4843c8e7198dd88dd3d40e6de42ee65fbe1490", size = 2182625, upload-time = "2025-05-17T17:20:52.866Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5f/e9/a09476d436d0ff1402ac3867d933c61805ec2326c6ea557aeeac3825604e/pycryptodome-3.23.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c8987bd3307a39bc03df5c8e0e3d8be0c4c3518b7f044b0f4c15d1aa78f52575", size = 2268954, upload-time = "2025-05-17T17:20:55.027Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f9/c5/ffe6474e0c551d54cab931918127c46d70cab8f114e0c2b5a3c071c2f484/pycryptodome-3.23.0-cp37-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa0698f65e5b570426fc31b8162ed4603b0c2841cbb9088e2b01641e3065915b", size = 2308534, upload-time = "2025-05-17T17:20:57.279Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/18/28/e199677fc15ecf43010f2463fde4c1a53015d1fe95fb03bca2890836603a/pycryptodome-3.23.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:53ecbafc2b55353edcebd64bf5da94a2a2cdf5090a6915bcca6eca6cc452585a", size = 2181853, upload-time = "2025-05-17T17:20:59.322Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ce/ea/4fdb09f2165ce1365c9eaefef36625583371ee514db58dc9b65d3a255c4c/pycryptodome-3.23.0-cp37-abi3-musllinux_1_2_i686.whl", hash = "sha256:156df9667ad9f2ad26255926524e1c136d6664b741547deb0a86a9acf5ea631f", size = 2342465, upload-time = "2025-05-17T17:21:03.83Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/22/82/6edc3fc42fe9284aead511394bac167693fb2b0e0395b28b8bedaa07ef04/pycryptodome-3.23.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:dea827b4d55ee390dc89b2afe5927d4308a8b538ae91d9c6f7a5090f397af1aa", size = 2267414, upload-time = "2025-05-17T17:21:06.72Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/59/fe/aae679b64363eb78326c7fdc9d06ec3de18bac68be4b612fc1fe8902693c/pycryptodome-3.23.0-cp37-abi3-win32.whl", hash = "sha256:507dbead45474b62b2bbe318eb1c4c8ee641077532067fec9c1aa82c31f84886", size = 1768484, upload-time = "2025-05-17T17:21:08.535Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/54/2f/e97a1b8294db0daaa87012c24a7bb714147c7ade7656973fd6c736b484ff/pycryptodome-3.23.0-cp37-abi3-win_amd64.whl", hash = "sha256:c75b52aacc6c0c260f204cbdd834f76edc9fb0d8e0da9fbf8352ef58202564e2", size = 1799636, upload-time = "2025-05-17T17:21:10.393Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/18/3d/f9441a0d798bf2b1e645adc3265e55706aead1255ccdad3856dbdcffec14/pycryptodome-3.23.0-cp37-abi3-win_arm64.whl", hash = "sha256:11eeeb6917903876f134b56ba11abe95c0b0fd5e3330def218083c7d98bbcb3c", size = 1703675, upload-time = "2025-05-17T17:21:13.146Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d9/12/e33935a0709c07de084d7d58d330ec3f4daf7910a18e77937affdb728452/pycryptodome-3.23.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ddb95b49df036ddd264a0ad246d1be5b672000f12d6961ea2c267083a5e19379", size = 1623886, upload-time = "2025-05-17T17:21:20.614Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/22/0b/aa8f9419f25870889bebf0b26b223c6986652bdf071f000623df11212c90/pycryptodome-3.23.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e95564beb8782abfd9e431c974e14563a794a4944c29d6d3b7b5ea042110b4", size = 1672151, upload-time = "2025-05-17T17:21:22.666Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d4/5e/63f5cbde2342b7f70a39e591dbe75d9809d6338ce0b07c10406f1a140cdc/pycryptodome-3.23.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14e15c081e912c4b0d75632acd8382dfce45b258667aa3c67caf7a4d4c13f630", size = 1664461, upload-time = "2025-05-17T17:21:25.225Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d6/92/608fbdad566ebe499297a86aae5f2a5263818ceeecd16733006f1600403c/pycryptodome-3.23.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a7fc76bf273353dc7e5207d172b83f569540fc9a28d63171061c42e361d22353", size = 1702440, upload-time = "2025-05-17T17:21:27.991Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/92/2eadd1341abd2989cce2e2740b4423608ee2014acb8110438244ee97d7ff/pycryptodome-3.23.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:45c69ad715ca1a94f778215a11e66b7ff989d792a4d63b68dc586a1da1392ff5", size = 1803005, upload-time = "2025-05-17T17:21:31.37Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pygments"
|
||||
version = "2.20.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pytest"
|
||||
version = "9.0.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||
{ name = "exceptiongroup", marker = "python_full_version < '3.11'" },
|
||||
{ name = "iniconfig" },
|
||||
{ name = "packaging" },
|
||||
{ name = "pluggy" },
|
||||
{ name = "pygments" },
|
||||
{ name = "tomli", marker = "python_full_version < '3.11'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/7d/0d/549bd94f1a0a402dc8cf64563a117c0f3765662e2e668477624baeec44d5/pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c", size = 1572165, upload-time = "2026-04-07T17:16:18.027Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9", size = 375249, upload-time = "2026-04-07T17:16:16.13Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tomli"
|
||||
version = "2.4.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/22/de/48c59722572767841493b26183a0d1cc411d54fd759c5607c4590b6563a6/tomli-2.4.1.tar.gz", hash = "sha256:7c7e1a961a0b2f2472c1ac5b69affa0ae1132c39adcb67aba98568702b9cc23f", size = 17543, upload-time = "2026-03-25T20:22:03.828Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/f4/11/db3d5885d8528263d8adc260bb2d28ebf1270b96e98f0e0268d32b8d9900/tomli-2.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f8f0fc26ec2cc2b965b7a3b87cd19c5c6b8c5e5f436b984e85f486d652285c30", size = 154704, upload-time = "2026-03-25T20:21:10.473Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6d/f7/675db52c7e46064a9aa928885a9b20f4124ecb9bc2e1ce74c9106648d202/tomli-2.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ab97e64ccda8756376892c53a72bd1f964e519c77236368527f758fbc36a53a", size = 149454, upload-time = "2026-03-25T20:21:12.036Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/61/71/81c50943cf953efa35bce7646caab3cf457a7d8c030b27cfb40d7235f9ee/tomli-2.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96481a5786729fd470164b47cdb3e0e58062a496f455ee41b4403be77cb5a076", size = 237561, upload-time = "2026-03-25T20:21:13.098Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/48/c1/f41d9cb618acccca7df82aaf682f9b49013c9397212cb9f53219e3abac37/tomli-2.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a881ab208c0baf688221f8cecc5401bd291d67e38a1ac884d6736cbcd8247e9", size = 243824, upload-time = "2026-03-25T20:21:14.569Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/22/e4/5a816ecdd1f8ca51fb756ef684b90f2780afc52fc67f987e3c61d800a46d/tomli-2.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47149d5bd38761ac8be13a84864bf0b7b70bc051806bc3669ab1cbc56216b23c", size = 242227, upload-time = "2026-03-25T20:21:15.712Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6b/49/2b2a0ef529aa6eec245d25f0c703e020a73955ad7edf73e7f54ddc608aa5/tomli-2.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ec9bfaf3ad2df51ace80688143a6a4ebc09a248f6ff781a9945e51937008fcbc", size = 247859, upload-time = "2026-03-25T20:21:17.001Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/83/bd/6c1a630eaca337e1e78c5903104f831bda934c426f9231429396ce3c3467/tomli-2.4.1-cp311-cp311-win32.whl", hash = "sha256:ff2983983d34813c1aeb0fa89091e76c3a22889ee83ab27c5eeb45100560c049", size = 97204, upload-time = "2026-03-25T20:21:18.079Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/42/59/71461df1a885647e10b6bb7802d0b8e66480c61f3f43079e0dcd315b3954/tomli-2.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:5ee18d9ebdb417e384b58fe414e8d6af9f4e7a0ae761519fb50f721de398dd4e", size = 108084, upload-time = "2026-03-25T20:21:18.978Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b8/83/dceca96142499c069475b790e7913b1044c1a4337e700751f48ed723f883/tomli-2.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:c2541745709bad0264b7d4705ad453b76ccd191e64aa6f0fc66b69a293a45ece", size = 95285, upload-time = "2026-03-25T20:21:20.309Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c1/ba/42f134a3fe2b370f555f44b1d72feebb94debcab01676bf918d0cb70e9aa/tomli-2.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c742f741d58a28940ce01d58f0ab2ea3ced8b12402f162f4d534dfe18ba1cd6a", size = 155924, upload-time = "2026-03-25T20:21:21.626Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/dc/c7/62d7a17c26487ade21c5422b646110f2162f1fcc95980ef7f63e73c68f14/tomli-2.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7f86fd587c4ed9dd76f318225e7d9b29cfc5a9d43de44e5754db8d1128487085", size = 150018, upload-time = "2026-03-25T20:21:23.002Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5c/05/79d13d7c15f13bdef410bdd49a6485b1c37d28968314eabee452c22a7fda/tomli-2.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff18e6a727ee0ab0388507b89d1bc6a22b138d1e2fa56d1ad494586d61d2eae9", size = 244948, upload-time = "2026-03-25T20:21:24.04Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/10/90/d62ce007a1c80d0b2c93e02cab211224756240884751b94ca72df8a875ca/tomli-2.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:136443dbd7e1dee43c68ac2694fde36b2849865fa258d39bf822c10e8068eac5", size = 253341, upload-time = "2026-03-25T20:21:25.177Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1a/7e/caf6496d60152ad4ed09282c1885cca4eea150bfd007da84aea07bcc0a3e/tomli-2.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5e262d41726bc187e69af7825504c933b6794dc3fbd5945e41a79bb14c31f585", size = 248159, upload-time = "2026-03-25T20:21:26.364Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/99/e7/c6f69c3120de34bbd882c6fba7975f3d7a746e9218e56ab46a1bc4b42552/tomli-2.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5cb41aa38891e073ee49d55fbc7839cfdb2bc0e600add13874d048c94aadddd1", size = 253290, upload-time = "2026-03-25T20:21:27.46Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d6/2f/4a3c322f22c5c66c4b836ec58211641a4067364f5dcdd7b974b4c5da300c/tomli-2.4.1-cp312-cp312-win32.whl", hash = "sha256:da25dc3563bff5965356133435b757a795a17b17d01dbc0f42fb32447ddfd917", size = 98141, upload-time = "2026-03-25T20:21:28.492Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/24/22/4daacd05391b92c55759d55eaee21e1dfaea86ce5c571f10083360adf534/tomli-2.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:52c8ef851d9a240f11a88c003eacb03c31fc1c9c4ec64a99a0f922b93874fda9", size = 108847, upload-time = "2026-03-25T20:21:29.386Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/68/fd/70e768887666ddd9e9f5d85129e84910f2db2796f9096aa02b721a53098d/tomli-2.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:f758f1b9299d059cc3f6546ae2af89670cb1c4d48ea29c3cacc4fe7de3058257", size = 95088, upload-time = "2026-03-25T20:21:30.677Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/07/06/b823a7e818c756d9a7123ba2cda7d07bc2dd32835648d1a7b7b7a05d848d/tomli-2.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:36d2bd2ad5fb9eaddba5226aa02c8ec3fa4f192631e347b3ed28186d43be6b54", size = 155866, upload-time = "2026-03-25T20:21:31.65Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/14/6f/12645cf7f08e1a20c7eb8c297c6f11d31c1b50f316a7e7e1e1de6e2e7b7e/tomli-2.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:eb0dc4e38e6a1fd579e5d50369aa2e10acfc9cace504579b2faabb478e76941a", size = 149887, upload-time = "2026-03-25T20:21:33.028Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5c/e0/90637574e5e7212c09099c67ad349b04ec4d6020324539297b634a0192b0/tomli-2.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7f2c7f2b9ca6bdeef8f0fa897f8e05085923eb091721675170254cbc5b02897", size = 243704, upload-time = "2026-03-25T20:21:34.51Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/10/8f/d3ddb16c5a4befdf31a23307f72828686ab2096f068eaf56631e136c1fdd/tomli-2.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f3c6818a1a86dd6dca7ddcaaf76947d5ba31aecc28cb1b67009a5877c9a64f3f", size = 251628, upload-time = "2026-03-25T20:21:36.012Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e3/f1/dbeeb9116715abee2485bf0a12d07a8f31af94d71608c171c45f64c0469d/tomli-2.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d312ef37c91508b0ab2cee7da26ec0b3ed2f03ce12bd87a588d771ae15dcf82d", size = 247180, upload-time = "2026-03-25T20:21:37.136Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d3/74/16336ffd19ed4da28a70959f92f506233bd7cfc2332b20bdb01591e8b1d1/tomli-2.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51529d40e3ca50046d7606fa99ce3956a617f9b36380da3b7f0dd3dd28e68cb5", size = 251674, upload-time = "2026-03-25T20:21:38.298Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/16/f9/229fa3434c590ddf6c0aa9af64d3af4b752540686cace29e6281e3458469/tomli-2.4.1-cp313-cp313-win32.whl", hash = "sha256:2190f2e9dd7508d2a90ded5ed369255980a1bcdd58e52f7fe24b8162bf9fedbd", size = 97976, upload-time = "2026-03-25T20:21:39.316Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6a/1e/71dfd96bcc1c775420cb8befe7a9d35f2e5b1309798f009dca17b7708c1e/tomli-2.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:8d65a2fbf9d2f8352685bc1364177ee3923d6baf5e7f43ea4959d7d8bc326a36", size = 108755, upload-time = "2026-03-25T20:21:40.248Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/83/7a/d34f422a021d62420b78f5c538e5b102f62bea616d1d75a13f0a88acb04a/tomli-2.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:4b605484e43cdc43f0954ddae319fb75f04cc10dd80d830540060ee7cd0243cd", size = 95265, upload-time = "2026-03-25T20:21:41.219Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3c/fb/9a5c8d27dbab540869f7c1f8eb0abb3244189ce780ba9cd73f3770662072/tomli-2.4.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fd0409a3653af6c147209d267a0e4243f0ae46b011aa978b1080359fddc9b6cf", size = 155726, upload-time = "2026-03-25T20:21:42.23Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/62/05/d2f816630cc771ad836af54f5001f47a6f611d2d39535364f148b6a92d6b/tomli-2.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a120733b01c45e9a0c34aeef92bf0cf1d56cfe81ed9d47d562f9ed591a9828ac", size = 149859, upload-time = "2026-03-25T20:21:43.386Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ce/48/66341bdb858ad9bd0ceab5a86f90eddab127cf8b046418009f2125630ecb/tomli-2.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:559db847dc486944896521f68d8190be1c9e719fced785720d2216fe7022b662", size = 244713, upload-time = "2026-03-25T20:21:44.474Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/df/6d/c5fad00d82b3c7a3ab6189bd4b10e60466f22cfe8a08a9394185c8a8111c/tomli-2.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01f520d4f53ef97964a240a035ec2a869fe1a37dde002b57ebc4417a27ccd853", size = 252084, upload-time = "2026-03-25T20:21:45.62Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/00/71/3a69e86f3eafe8c7a59d008d245888051005bd657760e96d5fbfb0b740c2/tomli-2.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7f94b27a62cfad8496c8d2513e1a222dd446f095fca8987fceef261225538a15", size = 247973, upload-time = "2026-03-25T20:21:46.937Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/67/50/361e986652847fec4bd5e4a0208752fbe64689c603c7ae5ea7cb16b1c0ca/tomli-2.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ede3e6487c5ef5d28634ba3f31f989030ad6af71edfb0055cbbd14189ff240ba", size = 256223, upload-time = "2026-03-25T20:21:48.467Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8c/9a/b4173689a9203472e5467217e0154b00e260621caa227b6fa01feab16998/tomli-2.4.1-cp314-cp314-win32.whl", hash = "sha256:3d48a93ee1c9b79c04bb38772ee1b64dcf18ff43085896ea460ca8dec96f35f6", size = 98973, upload-time = "2026-03-25T20:21:49.526Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/14/58/640ac93bf230cd27d002462c9af0d837779f8773bc03dee06b5835208214/tomli-2.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:88dceee75c2c63af144e456745e10101eb67361050196b0b6af5d717254dddf7", size = 109082, upload-time = "2026-03-25T20:21:50.506Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d5/2f/702d5e05b227401c1068f0d386d79a589bb12bf64c3d2c72ce0631e3bc49/tomli-2.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:b8c198f8c1805dc42708689ed6864951fd2494f924149d3e4bce7710f8eb5232", size = 96490, upload-time = "2026-03-25T20:21:51.474Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/45/4b/b877b05c8ba62927d9865dd980e34a755de541eb65fffba52b4cc495d4d2/tomli-2.4.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:d4d8fe59808a54658fcc0160ecfb1b30f9089906c50b23bcb4c69eddc19ec2b4", size = 164263, upload-time = "2026-03-25T20:21:52.543Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/24/79/6ab420d37a270b89f7195dec5448f79400d9e9c1826df982f3f8e97b24fd/tomli-2.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7008df2e7655c495dd12d2a4ad038ff878d4ca4b81fccaf82b714e07eae4402c", size = 160736, upload-time = "2026-03-25T20:21:53.674Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/02/e0/3630057d8eb170310785723ed5adcdfb7d50cb7e6455f85ba8a3deed642b/tomli-2.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1d8591993e228b0c930c4bb0db464bdad97b3289fb981255d6c9a41aedc84b2d", size = 270717, upload-time = "2026-03-25T20:21:55.129Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7a/b4/1613716072e544d1a7891f548d8f9ec6ce2faf42ca65acae01d76ea06bb0/tomli-2.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:734e20b57ba95624ecf1841e72b53f6e186355e216e5412de414e3c51e5e3c41", size = 278461, upload-time = "2026-03-25T20:21:56.228Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/05/38/30f541baf6a3f6df77b3df16b01ba319221389e2da59427e221ef417ac0c/tomli-2.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8a650c2dbafa08d42e51ba0b62740dae4ecb9338eefa093aa5c78ceb546fcd5c", size = 274855, upload-time = "2026-03-25T20:21:57.653Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/77/a3/ec9dd4fd2c38e98de34223b995a3b34813e6bdadf86c75314c928350ed14/tomli-2.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:504aa796fe0569bb43171066009ead363de03675276d2d121ac1a4572397870f", size = 283144, upload-time = "2026-03-25T20:21:59.089Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ef/be/605a6261cac79fba2ec0c9827e986e00323a1945700969b8ee0b30d85453/tomli-2.4.1-cp314-cp314t-win32.whl", hash = "sha256:b1d22e6e9387bf4739fbe23bfa80e93f6b0373a7f1b96c6227c32bef95a4d7a8", size = 108683, upload-time = "2026-03-25T20:22:00.214Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/12/64/da524626d3b9cc40c168a13da8335fe1c51be12c0a63685cc6db7308daae/tomli-2.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2c1c351919aca02858f740c6d33adea0c5deea37f9ecca1cc1ef9e884a619d26", size = 121196, upload-time = "2026-03-25T20:22:01.169Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5a/cd/e80b62269fc78fc36c9af5a6b89c835baa8af28ff5ad28c7028d60860320/tomli-2.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eab21f45c7f66c13f2a9e0e1535309cee140182a9cdae1e041d02e47291e8396", size = 100393, upload-time = "2026-03-25T20:22:02.137Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7b/61/cceae43728b7de99d9b847560c262873a1f6c98202171fd5ed62640b494b/tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe", size = 14583, upload-time = "2026-03-25T20:22:03.012Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "types-deprecated"
|
||||
version = "1.3.1.20260408"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/1a/db/076de3e81b106d3cec17aec9640ab1b2d02f29bad441de280459c161ce65/types_deprecated-1.3.1.20260408.tar.gz", hash = "sha256:62d6a86d0cc754c14bb2de31162d069b1c6a07ce11ee65e5258f8f75308eb3a3", size = 8524, upload-time = "2026-04-08T04:26:39.894Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/53/d0/d3258379deb749d949c3c72313981c9d2cceec518b87dcf506f022f5d49f/types_deprecated-1.3.1.20260408-py3-none-any.whl", hash = "sha256:b64e1eab560d4fa9394a27a3099211344b0e0f2f3ac8026d825c86e70d65cdd5", size = 9079, upload-time = "2026-04-08T04:26:38.752Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
version = "4.15.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "websockets"
|
||||
version = "16.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/04/24/4b2031d72e840ce4c1ccb255f693b15c334757fc50023e4db9537080b8c4/websockets-16.0.tar.gz", hash = "sha256:5f6261a5e56e8d5c42a4497b364ea24d94d9563e8fbd44e78ac40879c60179b5", size = 179346, upload-time = "2026-01-10T09:23:47.181Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/20/74/221f58decd852f4b59cc3354cccaf87e8ef695fede361d03dc9a7396573b/websockets-16.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:04cdd5d2d1dacbad0a7bf36ccbcd3ccd5a30ee188f2560b7a62a30d14107b31a", size = 177343, upload-time = "2026-01-10T09:22:21.28Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/19/0f/22ef6107ee52ab7f0b710d55d36f5a5d3ef19e8a205541a6d7ffa7994e5a/websockets-16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8ff32bb86522a9e5e31439a58addbb0166f0204d64066fb955265c4e214160f0", size = 175021, upload-time = "2026-01-10T09:22:22.696Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/10/40/904a4cb30d9b61c0e278899bf36342e9b0208eb3c470324a9ecbaac2a30f/websockets-16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:583b7c42688636f930688d712885cf1531326ee05effd982028212ccc13e5957", size = 175320, upload-time = "2026-01-10T09:22:23.94Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9d/2f/4b3ca7e106bc608744b1cdae041e005e446124bebb037b18799c2d356864/websockets-16.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7d837379b647c0c4c2355c2499723f82f1635fd2c26510e1f587d89bc2199e72", size = 183815, upload-time = "2026-01-10T09:22:25.469Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/86/26/d40eaa2a46d4302becec8d15b0fc5e45bdde05191e7628405a19cf491ccd/websockets-16.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:df57afc692e517a85e65b72e165356ed1df12386ecb879ad5693be08fac65dde", size = 185054, upload-time = "2026-01-10T09:22:27.101Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b0/ba/6500a0efc94f7373ee8fefa8c271acdfd4dca8bd49a90d4be7ccabfc397e/websockets-16.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:2b9f1e0d69bc60a4a87349d50c09a037a2607918746f07de04df9e43252c77a3", size = 184565, upload-time = "2026-01-10T09:22:28.293Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/04/b4/96bf2cee7c8d8102389374a2616200574f5f01128d1082f44102140344cc/websockets-16.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:335c23addf3d5e6a8633f9f8eda77efad001671e80b95c491dd0924587ece0b3", size = 183848, upload-time = "2026-01-10T09:22:30.394Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/02/8e/81f40fb00fd125357814e8c3025738fc4ffc3da4b6b4a4472a82ba304b41/websockets-16.0-cp310-cp310-win32.whl", hash = "sha256:37b31c1623c6605e4c00d466c9d633f9b812ea430c11c8a278774a1fde1acfa9", size = 178249, upload-time = "2026-01-10T09:22:32.083Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b4/5f/7e40efe8df57db9b91c88a43690ac66f7b7aa73a11aa6a66b927e44f26fa/websockets-16.0-cp310-cp310-win_amd64.whl", hash = "sha256:8e1dab317b6e77424356e11e99a432b7cb2f3ec8c5ab4dabbcee6add48f72b35", size = 178685, upload-time = "2026-01-10T09:22:33.345Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f2/db/de907251b4ff46ae804ad0409809504153b3f30984daf82a1d84a9875830/websockets-16.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:31a52addea25187bde0797a97d6fc3d2f92b6f72a9370792d65a6e84615ac8a8", size = 177340, upload-time = "2026-01-10T09:22:34.539Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f3/fa/abe89019d8d8815c8781e90d697dec52523fb8ebe308bf11664e8de1877e/websockets-16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:417b28978cdccab24f46400586d128366313e8a96312e4b9362a4af504f3bbad", size = 175022, upload-time = "2026-01-10T09:22:36.332Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/58/5d/88ea17ed1ded2079358b40d31d48abe90a73c9e5819dbcde1606e991e2ad/websockets-16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:af80d74d4edfa3cb9ed973a0a5ba2b2a549371f8a741e0800cb07becdd20f23d", size = 175319, upload-time = "2026-01-10T09:22:37.602Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d2/ae/0ee92b33087a33632f37a635e11e1d99d429d3d323329675a6022312aac2/websockets-16.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:08d7af67b64d29823fed316505a89b86705f2b7981c07848fb5e3ea3020c1abe", size = 184631, upload-time = "2026-01-10T09:22:38.789Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c8/c5/27178df583b6c5b31b29f526ba2da5e2f864ecc79c99dae630a85d68c304/websockets-16.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7be95cfb0a4dae143eaed2bcba8ac23f4892d8971311f1b06f3c6b78952ee70b", size = 185870, upload-time = "2026-01-10T09:22:39.893Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/87/05/536652aa84ddc1c018dbb7e2c4cbcd0db884580bf8e95aece7593fde526f/websockets-16.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d6297ce39ce5c2e6feb13c1a996a2ded3b6832155fcfc920265c76f24c7cceb5", size = 185361, upload-time = "2026-01-10T09:22:41.016Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6d/e2/d5332c90da12b1e01f06fb1b85c50cfc489783076547415bf9f0a659ec19/websockets-16.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1c1b30e4f497b0b354057f3467f56244c603a79c0d1dafce1d16c283c25f6e64", size = 184615, upload-time = "2026-01-10T09:22:42.442Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/77/fb/d3f9576691cae9253b51555f841bc6600bf0a983a461c79500ace5a5b364/websockets-16.0-cp311-cp311-win32.whl", hash = "sha256:5f451484aeb5cafee1ccf789b1b66f535409d038c56966d6101740c1614b86c6", size = 178246, upload-time = "2026-01-10T09:22:43.654Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/54/67/eaff76b3dbaf18dcddabc3b8c1dba50b483761cccff67793897945b37408/websockets-16.0-cp311-cp311-win_amd64.whl", hash = "sha256:8d7f0659570eefb578dacde98e24fb60af35350193e4f56e11190787bee77dac", size = 178684, upload-time = "2026-01-10T09:22:44.941Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/84/7b/bac442e6b96c9d25092695578dda82403c77936104b5682307bd4deb1ad4/websockets-16.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:71c989cbf3254fbd5e84d3bff31e4da39c43f884e64f2551d14bb3c186230f00", size = 177365, upload-time = "2026-01-10T09:22:46.787Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b0/fe/136ccece61bd690d9c1f715baaeefd953bb2360134de73519d5df19d29ca/websockets-16.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8b6e209ffee39ff1b6d0fa7bfef6de950c60dfb91b8fcead17da4ee539121a79", size = 175038, upload-time = "2026-01-10T09:22:47.999Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/40/1e/9771421ac2286eaab95b8575b0cb701ae3663abf8b5e1f64f1fd90d0a673/websockets-16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:86890e837d61574c92a97496d590968b23c2ef0aeb8a9bc9421d174cd378ae39", size = 175328, upload-time = "2026-01-10T09:22:49.809Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/18/29/71729b4671f21e1eaa5d6573031ab810ad2936c8175f03f97f3ff164c802/websockets-16.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9b5aca38b67492ef518a8ab76851862488a478602229112c4b0d58d63a7a4d5c", size = 184915, upload-time = "2026-01-10T09:22:51.071Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/97/bb/21c36b7dbbafc85d2d480cd65df02a1dc93bf76d97147605a8e27ff9409d/websockets-16.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e0334872c0a37b606418ac52f6ab9cfd17317ac26365f7f65e203e2d0d0d359f", size = 186152, upload-time = "2026-01-10T09:22:52.224Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4a/34/9bf8df0c0cf88fa7bfe36678dc7b02970c9a7d5e065a3099292db87b1be2/websockets-16.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a0b31e0b424cc6b5a04b8838bbaec1688834b2383256688cf47eb97412531da1", size = 185583, upload-time = "2026-01-10T09:22:53.443Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/47/88/4dd516068e1a3d6ab3c7c183288404cd424a9a02d585efbac226cb61ff2d/websockets-16.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:485c49116d0af10ac698623c513c1cc01c9446c058a4e61e3bf6c19dff7335a2", size = 184880, upload-time = "2026-01-10T09:22:55.033Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/91/d6/7d4553ad4bf1c0421e1ebd4b18de5d9098383b5caa1d937b63df8d04b565/websockets-16.0-cp312-cp312-win32.whl", hash = "sha256:eaded469f5e5b7294e2bdca0ab06becb6756ea86894a47806456089298813c89", size = 178261, upload-time = "2026-01-10T09:22:56.251Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c3/f0/f3a17365441ed1c27f850a80b2bc680a0fa9505d733fe152fdf5e98c1c0b/websockets-16.0-cp312-cp312-win_amd64.whl", hash = "sha256:5569417dc80977fc8c2d43a86f78e0a5a22fee17565d78621b6bb264a115d4ea", size = 178693, upload-time = "2026-01-10T09:22:57.478Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cc/9c/baa8456050d1c1b08dd0ec7346026668cbc6f145ab4e314d707bb845bf0d/websockets-16.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:878b336ac47938b474c8f982ac2f7266a540adc3fa4ad74ae96fea9823a02cc9", size = 177364, upload-time = "2026-01-10T09:22:59.333Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7e/0c/8811fc53e9bcff68fe7de2bcbe75116a8d959ac699a3200f4847a8925210/websockets-16.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:52a0fec0e6c8d9a784c2c78276a48a2bdf099e4ccc2a4cad53b27718dbfd0230", size = 175039, upload-time = "2026-01-10T09:23:01.171Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/aa/82/39a5f910cb99ec0b59e482971238c845af9220d3ab9fa76dd9162cda9d62/websockets-16.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e6578ed5b6981005df1860a56e3617f14a6c307e6a71b4fff8c48fdc50f3ed2c", size = 175323, upload-time = "2026-01-10T09:23:02.341Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bd/28/0a25ee5342eb5d5f297d992a77e56892ecb65e7854c7898fb7d35e9b33bd/websockets-16.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:95724e638f0f9c350bb1c2b0a7ad0e83d9cc0c9259f3ea94e40d7b02a2179ae5", size = 184975, upload-time = "2026-01-10T09:23:03.756Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f9/66/27ea52741752f5107c2e41fda05e8395a682a1e11c4e592a809a90c6a506/websockets-16.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c0204dc62a89dc9d50d682412c10b3542d748260d743500a85c13cd1ee4bde82", size = 186203, upload-time = "2026-01-10T09:23:05.01Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/37/e5/8e32857371406a757816a2b471939d51c463509be73fa538216ea52b792a/websockets-16.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:52ac480f44d32970d66763115edea932f1c5b1312de36df06d6b219f6741eed8", size = 185653, upload-time = "2026-01-10T09:23:06.301Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9b/67/f926bac29882894669368dc73f4da900fcdf47955d0a0185d60103df5737/websockets-16.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6e5a82b677f8f6f59e8dfc34ec06ca6b5b48bc4fcda346acd093694cc2c24d8f", size = 184920, upload-time = "2026-01-10T09:23:07.492Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3c/a1/3d6ccdcd125b0a42a311bcd15a7f705d688f73b2a22d8cf1c0875d35d34a/websockets-16.0-cp313-cp313-win32.whl", hash = "sha256:abf050a199613f64c886ea10f38b47770a65154dc37181bfaff70c160f45315a", size = 178255, upload-time = "2026-01-10T09:23:09.245Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6b/ae/90366304d7c2ce80f9b826096a9e9048b4bb760e44d3b873bb272cba696b/websockets-16.0-cp313-cp313-win_amd64.whl", hash = "sha256:3425ac5cf448801335d6fdc7ae1eb22072055417a96cc6b31b3861f455fbc156", size = 178689, upload-time = "2026-01-10T09:23:10.483Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f3/1d/e88022630271f5bd349ed82417136281931e558d628dd52c4d8621b4a0b2/websockets-16.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8cc451a50f2aee53042ac52d2d053d08bf89bcb31ae799cb4487587661c038a0", size = 177406, upload-time = "2026-01-10T09:23:12.178Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f2/78/e63be1bf0724eeb4616efb1ae1c9044f7c3953b7957799abb5915bffd38e/websockets-16.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:daa3b6ff70a9241cf6c7fc9e949d41232d9d7d26fd3522b1ad2b4d62487e9904", size = 175085, upload-time = "2026-01-10T09:23:13.511Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bb/f4/d3c9220d818ee955ae390cf319a7c7a467beceb24f05ee7aaaa2414345ba/websockets-16.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:fd3cb4adb94a2a6e2b7c0d8d05cb94e6f1c81a0cf9dc2694fb65c7e8d94c42e4", size = 175328, upload-time = "2026-01-10T09:23:14.727Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/63/bc/d3e208028de777087e6fb2b122051a6ff7bbcca0d6df9d9c2bf1dd869ae9/websockets-16.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:781caf5e8eee67f663126490c2f96f40906594cb86b408a703630f95550a8c3e", size = 185044, upload-time = "2026-01-10T09:23:15.939Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ad/6e/9a0927ac24bd33a0a9af834d89e0abc7cfd8e13bed17a86407a66773cc0e/websockets-16.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:caab51a72c51973ca21fa8a18bd8165e1a0183f1ac7066a182ff27107b71e1a4", size = 186279, upload-time = "2026-01-10T09:23:17.148Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b9/ca/bf1c68440d7a868180e11be653c85959502efd3a709323230314fda6e0b3/websockets-16.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:19c4dc84098e523fd63711e563077d39e90ec6702aff4b5d9e344a60cb3c0cb1", size = 185711, upload-time = "2026-01-10T09:23:18.372Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c4/f8/fdc34643a989561f217bb477cbc47a3a07212cbda91c0e4389c43c296ebf/websockets-16.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a5e18a238a2b2249c9a9235466b90e96ae4795672598a58772dd806edc7ac6d3", size = 184982, upload-time = "2026-01-10T09:23:19.652Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/dd/d1/574fa27e233764dbac9c52730d63fcf2823b16f0856b3329fc6268d6ae4f/websockets-16.0-cp314-cp314-win32.whl", hash = "sha256:a069d734c4a043182729edd3e9f247c3b2a4035415a9172fd0f1b71658a320a8", size = 177915, upload-time = "2026-01-10T09:23:21.458Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8a/f1/ae6b937bf3126b5134ce1f482365fde31a357c784ac51852978768b5eff4/websockets-16.0-cp314-cp314-win_amd64.whl", hash = "sha256:c0ee0e63f23914732c6d7e0cce24915c48f3f1512ec1d079ed01fc629dab269d", size = 178381, upload-time = "2026-01-10T09:23:22.715Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/06/9b/f791d1db48403e1f0a27577a6beb37afae94254a8c6f08be4a23e4930bc0/websockets-16.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:a35539cacc3febb22b8f4d4a99cc79b104226a756aa7400adc722e83b0d03244", size = 177737, upload-time = "2026-01-10T09:23:24.523Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bd/40/53ad02341fa33b3ce489023f635367a4ac98b73570102ad2cdd770dacc9a/websockets-16.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:b784ca5de850f4ce93ec85d3269d24d4c82f22b7212023c974c401d4980ebc5e", size = 175268, upload-time = "2026-01-10T09:23:25.781Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/74/9b/6158d4e459b984f949dcbbb0c5d270154c7618e11c01029b9bbd1bb4c4f9/websockets-16.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:569d01a4e7fba956c5ae4fc988f0d4e187900f5497ce46339c996dbf24f17641", size = 175486, upload-time = "2026-01-10T09:23:27.033Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e5/2d/7583b30208b639c8090206f95073646c2c9ffd66f44df967981a64f849ad/websockets-16.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:50f23cdd8343b984957e4077839841146f67a3d31ab0d00e6b824e74c5b2f6e8", size = 185331, upload-time = "2026-01-10T09:23:28.259Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/45/b0/cce3784eb519b7b5ad680d14b9673a31ab8dcb7aad8b64d81709d2430aa8/websockets-16.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:152284a83a00c59b759697b7f9e9cddf4e3c7861dd0d964b472b70f78f89e80e", size = 186501, upload-time = "2026-01-10T09:23:29.449Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/19/60/b8ebe4c7e89fb5f6cdf080623c9d92789a53636950f7abacfc33fe2b3135/websockets-16.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bc59589ab64b0022385f429b94697348a6a234e8ce22544e3681b2e9331b5944", size = 186062, upload-time = "2026-01-10T09:23:31.368Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/88/a8/a080593f89b0138b6cba1b28f8df5673b5506f72879322288b031337c0b8/websockets-16.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:32da954ffa2814258030e5a57bc73a3635463238e797c7375dc8091327434206", size = 185356, upload-time = "2026-01-10T09:23:32.627Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c2/b6/b9afed2afadddaf5ebb2afa801abf4b0868f42f8539bfe4b071b5266c9fe/websockets-16.0-cp314-cp314t-win32.whl", hash = "sha256:5a4b4cc550cb665dd8a47f868c8d04c8230f857363ad3c9caf7a0c3bf8c61ca6", size = 178085, upload-time = "2026-01-10T09:23:33.816Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9f/3e/28135a24e384493fa804216b79a6a6759a38cc4ff59118787b9fb693df93/websockets-16.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b14dc141ed6d2dde437cddb216004bcac6a1df0935d79656387bd41632ba0bbd", size = 178531, upload-time = "2026-01-10T09:23:35.016Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/72/07/c98a68571dcf256e74f1f816b8cc5eae6eb2d3d5cfa44d37f801619d9166/websockets-16.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:349f83cd6c9a415428ee1005cadb5c2c56f4389bc06a9af16103c3bc3dcc8b7d", size = 174947, upload-time = "2026-01-10T09:23:36.166Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7e/52/93e166a81e0305b33fe416338be92ae863563fe7bce446b0f687b9df5aea/websockets-16.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:4a1aba3340a8dca8db6eb5a7986157f52eb9e436b74813764241981ca4888f03", size = 175260, upload-time = "2026-01-10T09:23:37.409Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/56/0c/2dbf513bafd24889d33de2ff0368190a0e69f37bcfa19009ef819fe4d507/websockets-16.0-pp311-pypy311_pp73-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f4a32d1bd841d4bcbffdcb3d2ce50c09c3909fbead375ab28d0181af89fd04da", size = 176071, upload-time = "2026-01-10T09:23:39.158Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a5/8f/aea9c71cc92bf9b6cc0f7f70df8f0b420636b6c96ef4feee1e16f80f75dd/websockets-16.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0298d07ee155e2e9fda5be8a9042200dd2e3bb0b8a38482156576f863a9d457c", size = 176968, upload-time = "2026-01-10T09:23:41.031Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9a/3f/f70e03f40ffc9a30d817eef7da1be72ee4956ba8d7255c399a01b135902a/websockets-16.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:a653aea902e0324b52f1613332ddf50b00c06fdaf7e92624fbf8c77c78fa5767", size = 178735, upload-time = "2026-01-10T09:23:42.259Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6f/28/258ebab549c2bf3e64d2b0217b973467394a9cea8c42f70418ca2c5d0d2e/websockets-16.0-py3-none-any.whl", hash = "sha256:1637db62fad1dc833276dded54215f2c7fa46912301a24bd94d45d46a011ceec", size = 171598, upload-time = "2026-01-10T09:23:45.395Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wrapt"
|
||||
version = "2.1.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/2e/64/925f213fdcbb9baeb1530449ac71a4d57fc361c053d06bf78d0c5c7cd80c/wrapt-2.1.2.tar.gz", hash = "sha256:3996a67eecc2c68fd47b4e3c564405a5777367adfd9b8abb58387b63ee83b21e", size = 81678, upload-time = "2026-03-06T02:53:25.134Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/da/d2/387594fb592d027366645f3d7cc9b4d7ca7be93845fbaba6d835a912ef3c/wrapt-2.1.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4b7a86d99a14f76facb269dc148590c01aaf47584071809a70da30555228158c", size = 60669, upload-time = "2026-03-06T02:52:40.671Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c9/18/3f373935bc5509e7ac444c8026a56762e50c1183e7061797437ca96c12ce/wrapt-2.1.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a819e39017f95bf7aede768f75915635aa8f671f2993c036991b8d3bfe8dbb6f", size = 61603, upload-time = "2026-03-06T02:54:21.032Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c2/7a/32758ca2853b07a887a4574b74e28843919103194bb47001a304e24af62f/wrapt-2.1.2-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5681123e60aed0e64c7d44f72bbf8b4ce45f79d81467e2c4c728629f5baf06eb", size = 113632, upload-time = "2026-03-06T02:53:54.121Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1d/d5/eeaa38f670d462e97d978b3b0d9ce06d5b91e54bebac6fbed867809216e7/wrapt-2.1.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2b8b28e97a44d21836259739ae76284e180b18abbb4dcfdff07a415cf1016c3e", size = 115644, upload-time = "2026-03-06T02:54:53.33Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e3/09/2a41506cb17affb0bdf9d5e2129c8c19e192b388c4c01d05e1b14db23c00/wrapt-2.1.2-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cef91c95a50596fcdc31397eb6955476f82ae8a3f5a8eabdc13611b60ee380ba", size = 112016, upload-time = "2026-03-06T02:54:43.274Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/64/15/0e6c3f5e87caadc43db279724ee36979246d5194fa32fed489c73643ba59/wrapt-2.1.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:dad63212b168de8569b1c512f4eac4b57f2c6934b30df32d6ee9534a79f1493f", size = 114823, upload-time = "2026-03-06T02:54:29.392Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/56/b2/0ad17c8248f4e57bedf44938c26ec3ee194715f812d2dbbd9d7ff4be6c06/wrapt-2.1.2-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:d307aa6888d5efab2c1cde09843d48c843990be13069003184b67d426d145394", size = 111244, upload-time = "2026-03-06T02:54:02.149Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ff/04/bcdba98c26f2c6522c7c09a726d5d9229120163493620205b2f76bd13c01/wrapt-2.1.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c87cf3f0c85e27b3ac7d9ad95da166bf8739ca215a8b171e8404a2d739897a45", size = 113307, upload-time = "2026-03-06T02:54:12.428Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0e/1b/5e2883c6bc14143924e465a6fc5a92d09eeabe35310842a481fb0581f832/wrapt-2.1.2-cp310-cp310-win32.whl", hash = "sha256:d1c5fea4f9fe3762e2b905fdd67df51e4be7a73b7674957af2d2ade71a5c075d", size = 57986, upload-time = "2026-03-06T02:54:26.823Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/42/5a/4efc997bccadd3af5749c250b49412793bc41e13a83a486b2b54a33e240c/wrapt-2.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:d8f7740e1af13dff2684e4d56fe604a7e04d6c94e737a60568d8d4238b9a0c71", size = 60336, upload-time = "2026-03-06T02:54:18Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c1/f5/a2bb833e20181b937e87c242645ed5d5aa9c373006b0467bfe1a35c727d0/wrapt-2.1.2-cp310-cp310-win_arm64.whl", hash = "sha256:1c6cc827c00dc839350155f316f1f8b4b0c370f52b6a19e782e2bda89600c7dc", size = 58757, upload-time = "2026-03-06T02:53:51.545Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c7/81/60c4471fce95afa5922ca09b88a25f03c93343f759aae0f31fb4412a85c7/wrapt-2.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:96159a0ee2b0277d44201c3b5be479a9979cf154e8c82fa5df49586a8e7679bb", size = 60666, upload-time = "2026-03-06T02:52:58.934Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6b/be/80e80e39e7cb90b006a0eaf11c73ac3a62bbfb3068469aec15cc0bc795de/wrapt-2.1.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:98ba61833a77b747901e9012072f038795de7fc77849f1faa965464f3f87ff2d", size = 61601, upload-time = "2026-03-06T02:53:00.487Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b0/be/d7c88cd9293c859fc74b232abdc65a229bb953997995d6912fc85af18323/wrapt-2.1.2-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:767c0dbbe76cae2a60dd2b235ac0c87c9cccf4898aef8062e57bead46b5f6894", size = 114057, upload-time = "2026-03-06T02:52:44.08Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ea/25/36c04602831a4d685d45a93b3abea61eca7fe35dab6c842d6f5d570ef94a/wrapt-2.1.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c691a6bc752c0cc4711cc0c00896fcd0f116abc253609ef64ef930032821842", size = 116099, upload-time = "2026-03-06T02:54:56.74Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5c/4e/98a6eb417ef551dc277bec1253d5246b25003cf36fdf3913b65cb7657a56/wrapt-2.1.2-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f3b7d73012ea75aee5844de58c88f44cf62d0d62711e39da5a82824a7c4626a8", size = 112457, upload-time = "2026-03-06T02:53:52.842Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cb/a6/a6f7186a5297cad8ec53fd7578533b28f795fdf5372368c74bd7e6e9841c/wrapt-2.1.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:577dff354e7acd9d411eaf4bfe76b724c89c89c8fc9b7e127ee28c5f7bcb25b6", size = 115351, upload-time = "2026-03-06T02:53:32.684Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/97/6f/06e66189e721dbebd5cf20e138acc4d1150288ce118462f2fcbff92d38db/wrapt-2.1.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:3d7b6fd105f8b24e5bd23ccf41cb1d1099796524bcc6f7fbb8fe576c44befbc9", size = 111748, upload-time = "2026-03-06T02:53:08.455Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ef/43/4808b86f499a51370fbdbdfa6cb91e9b9169e762716456471b619fca7a70/wrapt-2.1.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:866abdbf4612e0b34764922ef8b1c5668867610a718d3053d59e24a5e5fcfc15", size = 113783, upload-time = "2026-03-06T02:53:02.02Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/91/2c/a3f28b8fa7ac2cefa01cfcaca3471f9b0460608d012b693998cd61ef43df/wrapt-2.1.2-cp311-cp311-win32.whl", hash = "sha256:5a0a0a3a882393095573344075189eb2d566e0fd205a2b6414e9997b1b800a8b", size = 57977, upload-time = "2026-03-06T02:53:27.844Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3f/c3/2b1c7bd07a27b1db885a2fab469b707bdd35bddf30a113b4917a7e2139d2/wrapt-2.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:64a07a71d2730ba56f11d1a4b91f7817dc79bc134c11516b75d1921a7c6fcda1", size = 60336, upload-time = "2026-03-06T02:54:28.104Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ec/5c/76ece7b401b088daa6503d6264dd80f9a727df3e6042802de9a223084ea2/wrapt-2.1.2-cp311-cp311-win_arm64.whl", hash = "sha256:b89f095fe98bc12107f82a9f7d570dc83a0870291aeb6b1d7a7d35575f55d98a", size = 58756, upload-time = "2026-03-06T02:53:16.319Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4c/b6/1db817582c49c7fcbb7df6809d0f515af29d7c2fbf57eb44c36e98fb1492/wrapt-2.1.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ff2aad9c4cda28a8f0653fc2d487596458c2a3f475e56ba02909e950a9efa6a9", size = 61255, upload-time = "2026-03-06T02:52:45.663Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a2/16/9b02a6b99c09227c93cd4b73acc3678114154ec38da53043c0ddc1fba0dc/wrapt-2.1.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6433ea84e1cfacf32021d2a4ee909554ade7fd392caa6f7c13f1f4bf7b8e8748", size = 61848, upload-time = "2026-03-06T02:53:48.728Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/af/aa/ead46a88f9ec3a432a4832dfedb84092fc35af2d0ba40cd04aea3889f247/wrapt-2.1.2-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c20b757c268d30d6215916a5fa8461048d023865d888e437fab451139cad6c8e", size = 121433, upload-time = "2026-03-06T02:54:40.328Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3a/9f/742c7c7cdf58b59085a1ee4b6c37b013f66ac33673a7ef4aaed5e992bc33/wrapt-2.1.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:79847b83eb38e70d93dc392c7c5b587efe65b3e7afcc167aa8abd5d60e8761c8", size = 123013, upload-time = "2026-03-06T02:53:26.58Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e8/44/2c3dd45d53236b7ed7c646fcf212251dc19e48e599debd3926b52310fafb/wrapt-2.1.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f8fba1bae256186a83d1875b2b1f4e2d1242e8fac0f58ec0d7e41b26967b965c", size = 117326, upload-time = "2026-03-06T02:53:11.547Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/74/e2/b17d66abc26bd96f89dec0ecd0ef03da4a1286e6ff793839ec431b9fae57/wrapt-2.1.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e3d3b35eedcf5f7d022291ecd7533321c4775f7b9cd0050a31a68499ba45757c", size = 121444, upload-time = "2026-03-06T02:54:09.5Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3c/62/e2977843fdf9f03daf1586a0ff49060b1b2fc7ff85a7ea82b6217c1ae36e/wrapt-2.1.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:6f2c5390460de57fa9582bc8a1b7a6c86e1a41dfad74c5225fc07044c15cc8d1", size = 116237, upload-time = "2026-03-06T02:54:03.884Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/88/dd/27fc67914e68d740bce512f11734aec08696e6b17641fef8867c00c949fc/wrapt-2.1.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7dfa9f2cf65d027b951d05c662cc99ee3bd01f6e4691ed39848a7a5fffc902b2", size = 120563, upload-time = "2026-03-06T02:53:20.412Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ec/9f/b750b3692ed2ef4705cb305bd68858e73010492b80e43d2a4faa5573cbe7/wrapt-2.1.2-cp312-cp312-win32.whl", hash = "sha256:eba8155747eb2cae4a0b913d9ebd12a1db4d860fc4c829d7578c7b989bd3f2f0", size = 58198, upload-time = "2026-03-06T02:53:37.732Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8e/b2/feecfe29f28483d888d76a48f03c4c4d8afea944dbee2b0cd3380f9df032/wrapt-2.1.2-cp312-cp312-win_amd64.whl", hash = "sha256:1c51c738d7d9faa0b3601708e7e2eda9bf779e1b601dce6c77411f2a1b324a63", size = 60441, upload-time = "2026-03-06T02:52:47.138Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/44/e1/e328f605d6e208547ea9fd120804fcdec68536ac748987a68c47c606eea8/wrapt-2.1.2-cp312-cp312-win_arm64.whl", hash = "sha256:c8e46ae8e4032792eb2f677dbd0d557170a8e5524d22acc55199f43efedd39bf", size = 58836, upload-time = "2026-03-06T02:53:22.053Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4c/7a/d936840735c828b38d26a854e85d5338894cda544cb7a85a9d5b8b9c4df7/wrapt-2.1.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:787fd6f4d67befa6fe2abdffcbd3de2d82dfc6fb8a6d850407c53332709d030b", size = 61259, upload-time = "2026-03-06T02:53:41.922Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5e/88/9a9b9a90ac8ca11c2fdb6a286cb3a1fc7dd774c00ed70929a6434f6bc634/wrapt-2.1.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4bdf26e03e6d0da3f0e9422fd36bcebf7bc0eeb55fdf9c727a09abc6b9fe472e", size = 61851, upload-time = "2026-03-06T02:52:48.672Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/03/a9/5b7d6a16fd6533fed2756900fc8fc923f678179aea62ada6d65c92718c00/wrapt-2.1.2-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bbac24d879aa22998e87f6b3f481a5216311e7d53c7db87f189a7a0266dafffb", size = 121446, upload-time = "2026-03-06T02:54:14.013Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/45/bb/34c443690c847835cfe9f892be78c533d4f32366ad2888972c094a897e39/wrapt-2.1.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:16997dfb9d67addc2e3f41b62a104341e80cac52f91110dece393923c0ebd5ca", size = 123056, upload-time = "2026-03-06T02:54:10.829Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/93/b9/ff205f391cb708f67f41ea148545f2b53ff543a7ac293b30d178af4d2271/wrapt-2.1.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:162e4e2ba7542da9027821cb6e7c5e068d64f9a10b5f15512ea28e954893a267", size = 117359, upload-time = "2026-03-06T02:53:03.623Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1f/3d/1ea04d7747825119c3c9a5e0874a40b33594ada92e5649347c457d982805/wrapt-2.1.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f29c827a8d9936ac320746747a016c4bc66ef639f5cd0d32df24f5eacbf9c69f", size = 121479, upload-time = "2026-03-06T02:53:45.844Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/78/cc/ee3a011920c7a023b25e8df26f306b2484a531ab84ca5c96260a73de76c0/wrapt-2.1.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:a9dd9813825f7ecb018c17fd147a01845eb330254dff86d3b5816f20f4d6aaf8", size = 116271, upload-time = "2026-03-06T02:54:46.356Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/98/fd/e5ff7ded41b76d802cf1191288473e850d24ba2e39a6ec540f21ae3b57cb/wrapt-2.1.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6f8dbdd3719e534860d6a78526aafc220e0241f981367018c2875178cf83a413", size = 120573, upload-time = "2026-03-06T02:52:50.163Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/47/c5/242cae3b5b080cd09bacef0591691ba1879739050cc7c801ff35c8886b66/wrapt-2.1.2-cp313-cp313-win32.whl", hash = "sha256:5c35b5d82b16a3bc6e0a04349b606a0582bc29f573786aebe98e0c159bc48db6", size = 58205, upload-time = "2026-03-06T02:53:47.494Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/12/69/c358c61e7a50f290958809b3c61ebe8b3838ea3e070d7aac9814f95a0528/wrapt-2.1.2-cp313-cp313-win_amd64.whl", hash = "sha256:f8bc1c264d8d1cf5b3560a87bbdd31131573eb25f9f9447bb6252b8d4c44a3a1", size = 60452, upload-time = "2026-03-06T02:53:30.038Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8e/66/c8a6fcfe321295fd8c0ab1bd685b5a01462a9b3aa2f597254462fc2bc975/wrapt-2.1.2-cp313-cp313-win_arm64.whl", hash = "sha256:3beb22f674550d5634642c645aba4c72a2c66fb185ae1aebe1e955fae5a13baf", size = 58842, upload-time = "2026-03-06T02:52:52.114Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/da/55/9c7052c349106e0b3f17ae8db4b23a691a963c334de7f9dbd60f8f74a831/wrapt-2.1.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0fc04bc8664a8bc4c8e00b37b5355cffca2535209fba1abb09ae2b7c76ddf82b", size = 63075, upload-time = "2026-03-06T02:53:19.108Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/09/a8/ce7b4006f7218248dd71b7b2b732d0710845a0e49213b18faef64811ffef/wrapt-2.1.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a9b9d50c9af998875a1482a038eb05755dfd6fe303a313f6a940bb53a83c3f18", size = 63719, upload-time = "2026-03-06T02:54:33.452Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e4/e5/2ca472e80b9e2b7a17f106bb8f9df1db11e62101652ce210f66935c6af67/wrapt-2.1.2-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2d3ff4f0024dd224290c0eabf0240f1bfc1f26363431505fb1b0283d3b08f11d", size = 152643, upload-time = "2026-03-06T02:52:42.721Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/36/42/30f0f2cefca9d9cbf6835f544d825064570203c3e70aa873d8ae12e23791/wrapt-2.1.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3278c471f4468ad544a691b31bb856374fbdefb7fee1a152153e64019379f015", size = 158805, upload-time = "2026-03-06T02:54:25.441Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bb/67/d08672f801f604889dcf58f1a0b424fe3808860ede9e03affc1876b295af/wrapt-2.1.2-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a8914c754d3134a3032601c6984db1c576e6abaf3fc68094bb8ab1379d75ff92", size = 145990, upload-time = "2026-03-06T02:53:57.456Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/68/a7/fd371b02e73babec1de6ade596e8cd9691051058cfdadbfd62a5898f3295/wrapt-2.1.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:ff95d4264e55839be37bafe1536db2ab2de19da6b65f9244f01f332b5286cfbf", size = 155670, upload-time = "2026-03-06T02:54:55.309Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/86/2d/9fe0095dfdb621009f40117dcebf41d7396c2c22dca6eac779f4c007b86c/wrapt-2.1.2-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:76405518ca4e1b76fbb1b9f686cff93aebae03920cc55ceeec48ff9f719c5f67", size = 144357, upload-time = "2026-03-06T02:54:24.092Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0e/b6/ec7b4a254abbe4cde9fa15c5d2cca4518f6b07d0f1b77d4ee9655e30280e/wrapt-2.1.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c0be8b5a74c5824e9359b53e7e58bef71a729bacc82e16587db1c4ebc91f7c5a", size = 150269, upload-time = "2026-03-06T02:53:31.268Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6e/6b/2fabe8ebf148f4ee3c782aae86a795cc68ffe7d432ef550f234025ce0cfa/wrapt-2.1.2-cp313-cp313t-win32.whl", hash = "sha256:f01277d9a5fc1862f26f7626da9cf443bebc0abd2f303f41c5e995b15887dabd", size = 59894, upload-time = "2026-03-06T02:54:15.391Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ca/fb/9ba66fc2dedc936de5f8073c0217b5d4484e966d87723415cc8262c5d9c2/wrapt-2.1.2-cp313-cp313t-win_amd64.whl", hash = "sha256:84ce8f1c2104d2f6daa912b1b5b039f331febfeee74f8042ad4e04992bd95c8f", size = 63197, upload-time = "2026-03-06T02:54:41.943Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c0/1c/012d7423c95d0e337117723eb8ecf73c622ce15a97847e84cf3f8f26cd7e/wrapt-2.1.2-cp313-cp313t-win_arm64.whl", hash = "sha256:a93cd767e37faeddbe07d8fc4212d5cba660af59bdb0f6372c93faaa13e6e679", size = 60363, upload-time = "2026-03-06T02:54:48.093Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/39/25/e7ea0b417db02bb796182a5316398a75792cd9a22528783d868755e1f669/wrapt-2.1.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:1370e516598854e5b4366e09ce81e08bfe94d42b0fd569b88ec46cc56d9164a9", size = 61418, upload-time = "2026-03-06T02:53:55.706Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ec/0f/fa539e2f6a770249907757eaeb9a5ff4deb41c026f8466c1c6d799088a9b/wrapt-2.1.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:6de1a3851c27e0bd6a04ca993ea6f80fc53e6c742ee1601f486c08e9f9b900a9", size = 61914, upload-time = "2026-03-06T02:52:53.37Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/53/37/02af1867f5b1441aaeda9c82deed061b7cd1372572ddcd717f6df90b5e93/wrapt-2.1.2-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:de9f1a2bbc5ac7f6012ec24525bdd444765a2ff64b5985ac6e0692144838542e", size = 120417, upload-time = "2026-03-06T02:54:30.74Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c3/b7/0138a6238c8ba7476c77cf786a807f871672b37f37a422970342308276e7/wrapt-2.1.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:970d57ed83fa040d8b20c52fe74a6ae7e3775ae8cff5efd6a81e06b19078484c", size = 122797, upload-time = "2026-03-06T02:54:51.539Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e1/ad/819ae558036d6a15b7ed290d5b14e209ca795dd4da9c58e50c067d5927b0/wrapt-2.1.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3969c56e4563c375861c8df14fa55146e81ac11c8db49ea6fb7f2ba58bc1ff9a", size = 117350, upload-time = "2026-03-06T02:54:37.651Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8b/2d/afc18dc57a4600a6e594f77a9ae09db54f55ba455440a54886694a84c71b/wrapt-2.1.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:57d7c0c980abdc5f1d98b11a2aa3bb159790add80258c717fa49a99921456d90", size = 121223, upload-time = "2026-03-06T02:54:35.221Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b9/5b/5ec189b22205697bc56eb3b62aed87a1e0423e9c8285d0781c7a83170d15/wrapt-2.1.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:776867878e83130c7a04237010463372e877c1c994d449ca6aaafeab6aab2586", size = 116287, upload-time = "2026-03-06T02:54:19.654Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f7/2d/f84939a7c9b5e6cdd8a8d0f6a26cabf36a0f7e468b967720e8b0cd2bdf69/wrapt-2.1.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:fab036efe5464ec3291411fabb80a7a39e2dd80bae9bcbeeca5087fdfa891e19", size = 119593, upload-time = "2026-03-06T02:54:16.697Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0b/fe/ccd22a1263159c4ac811ab9374c061bcb4a702773f6e06e38de5f81a1bdc/wrapt-2.1.2-cp314-cp314-win32.whl", hash = "sha256:e6ed62c82ddf58d001096ae84ce7f833db97ae2263bff31c9b336ba8cfe3f508", size = 58631, upload-time = "2026-03-06T02:53:06.498Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/65/0a/6bd83be7bff2e7efaac7b4ac9748da9d75a34634bbbbc8ad077d527146df/wrapt-2.1.2-cp314-cp314-win_amd64.whl", hash = "sha256:467e7c76315390331c67073073d00662015bb730c566820c9ca9b54e4d67fd04", size = 60875, upload-time = "2026-03-06T02:53:50.252Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6c/c0/0b3056397fe02ff80e5a5d72d627c11eb885d1ca78e71b1a5c1e8c7d45de/wrapt-2.1.2-cp314-cp314-win_arm64.whl", hash = "sha256:da1f00a557c66225d53b095a97eace0fc5349e3bfda28fa34ffae238978ee575", size = 59164, upload-time = "2026-03-06T02:53:59.128Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/71/ed/5d89c798741993b2371396eb9d4634f009ff1ad8a6c78d366fe2883ea7a6/wrapt-2.1.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:62503ffbc2d3a69891cf29beeaccdb4d5e0a126e2b6a851688d4777e01428dbb", size = 63163, upload-time = "2026-03-06T02:52:54.873Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c6/8c/05d277d182bf36b0a13d6bd393ed1dec3468a25b59d01fba2dd70fe4d6ae/wrapt-2.1.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c7e6cd120ef837d5b6f860a6ea3745f8763805c418bb2f12eeb1fa6e25f22d22", size = 63723, upload-time = "2026-03-06T02:52:56.374Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f4/27/6c51ec1eff4413c57e72d6106bb8dec6f0c7cdba6503d78f0fa98767bcc9/wrapt-2.1.2-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:3769a77df8e756d65fbc050333f423c01ae012b4f6731aaf70cf2bef61b34596", size = 152652, upload-time = "2026-03-06T02:53:23.79Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/db/4c/d7dd662d6963fc7335bfe29d512b02b71cdfa23eeca7ab3ac74a67505deb/wrapt-2.1.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a76d61a2e851996150ba0f80582dd92a870643fa481f3b3846f229de88caf044", size = 158807, upload-time = "2026-03-06T02:53:35.742Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b4/4d/1e5eea1a78d539d346765727422976676615814029522c76b87a95f6bcdd/wrapt-2.1.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6f97edc9842cf215312b75fe737ee7c8adda75a89979f8e11558dfff6343cc4b", size = 146061, upload-time = "2026-03-06T02:52:57.574Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/89/bc/62cabea7695cd12a288023251eeefdcb8465056ddaab6227cb78a2de005b/wrapt-2.1.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:4006c351de6d5007aa33a551f600404ba44228a89e833d2fadc5caa5de8edfbf", size = 155667, upload-time = "2026-03-06T02:53:39.422Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e9/99/6f2888cd68588f24df3a76572c69c2de28287acb9e1972bf0c83ce97dbc1/wrapt-2.1.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:a9372fc3639a878c8e7d87e1556fa209091b0a66e912c611e3f833e2c4202be2", size = 144392, upload-time = "2026-03-06T02:54:22.41Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/40/51/1dfc783a6c57971614c48e361a82ca3b6da9055879952587bc99fe1a7171/wrapt-2.1.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3144b027ff30cbd2fca07c0a87e67011adb717eb5f5bd8496325c17e454257a3", size = 150296, upload-time = "2026-03-06T02:54:07.848Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6c/38/cbb8b933a0201076c1f64fc42883b0023002bdc14a4964219154e6ff3350/wrapt-2.1.2-cp314-cp314t-win32.whl", hash = "sha256:3b8d15e52e195813efe5db8cec156eebe339aaf84222f4f4f051a6c01f237ed7", size = 60539, upload-time = "2026-03-06T02:54:00.594Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/82/dd/e5176e4b241c9f528402cebb238a36785a628179d7d8b71091154b3e4c9e/wrapt-2.1.2-cp314-cp314t-win_amd64.whl", hash = "sha256:08ffa54146a7559f5b8df4b289b46d963a8e74ed16ba3687f99896101a3990c5", size = 63969, upload-time = "2026-03-06T02:54:39Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5c/99/79f17046cf67e4a95b9987ea129632ba8bcec0bc81f3fb3d19bdb0bd60cd/wrapt-2.1.2-cp314-cp314t-win_arm64.whl", hash = "sha256:72aaa9d0d8e4ed0e2e98019cea47a21f823c9dd4b43c7b77bba6679ffcca6a00", size = 60554, upload-time = "2026-03-06T02:53:14.132Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1a/c7/8528ac2dfa2c1e6708f647df7ae144ead13f0a31146f43c7264b4942bf12/wrapt-2.1.2-py3-none-any.whl", hash = "sha256:b8fd6fa2b2c4e7621808f8c62e8317f4aae56e59721ad933bac5239d913cf0e8", size = 43993, upload-time = "2026-03-06T02:53:12.905Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xrpl-py"
|
||||
version = "4.5.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "base58" },
|
||||
{ name = "deprecated" },
|
||||
{ name = "ecpy" },
|
||||
{ name = "httpx" },
|
||||
{ name = "pycryptodome" },
|
||||
{ name = "types-deprecated" },
|
||||
{ name = "typing-extensions" },
|
||||
{ name = "websockets" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/51/e7/8faf71e5b9b314d329a13cdc7966ad565a6e52a875adeaee0f778b1b8ef1/xrpl_py-4.5.0.tar.gz", hash = "sha256:3ee25fcb748bdf6afe18aad8f74ba71ffa23bf681409fda3a9eb029e4381fc74", size = 175681, upload-time = "2026-02-12T23:41:52.176Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/1d/3c/65c853f906f5003c06c5e0cbc50e47bac2ecca17957a9d3a3d823efd8c38/xrpl_py-4.5.0-py3-none-any.whl", hash = "sha256:aa4720b5bf8070d8303346111f1095ec9afe13abdf49b2dc4b988d28ebc227ca", size = 314897, upload-time = "2026-02-12T23:41:50.609Z" },
|
||||
]
|
||||
@@ -11,11 +11,11 @@ echo "START BUILDING (HOST)"
|
||||
echo "Cleaning previously built binary"
|
||||
rm -f release-build/xahaud
|
||||
|
||||
BUILD_CORES=$(echo "scale=0 ; $(nproc) / 1.337" | bc)
|
||||
BUILD_CORES=$(echo "scale=0 ; `nproc` / 1.337" | bc)
|
||||
|
||||
if [[ "$GITHUB_REPOSITORY" == "" ]]; then
|
||||
#Default
|
||||
BUILD_CORES=${BUILD_CORES:-8}
|
||||
BUILD_CORES=${BUILD_CORES:-8}
|
||||
fi
|
||||
|
||||
# Ensure still works outside of GH Actions by setting these to /dev/null
|
||||
@@ -31,19 +31,21 @@ echo "-- GITHUB_SHA: $GITHUB_SHA"
|
||||
echo "-- GITHUB_RUN_NUMBER: $GITHUB_RUN_NUMBER"
|
||||
echo "-- CONTAINER_NAME: $CONTAINER_NAME"
|
||||
|
||||
which docker 2>/dev/null 2>/dev/null
|
||||
if [ "$?" -eq "1" ]; then
|
||||
which docker 2> /dev/null 2> /dev/null
|
||||
if [ "$?" -eq "1" ]
|
||||
then
|
||||
echo 'Docker not found. Install it first.'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
stat .git 2>/dev/null 2>/dev/null
|
||||
if [ "$?" -eq "1" ]; then
|
||||
stat .git 2> /dev/null 2> /dev/null
|
||||
if [ "$?" -eq "1" ]
|
||||
then
|
||||
echo 'Run this inside the source directory. (.git dir not found).'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
STATIC_CONTAINER=$(docker ps -a | grep $CONTAINER_NAME | wc -l)
|
||||
STATIC_CONTAINER=$(docker ps -a | grep $CONTAINER_NAME |wc -l)
|
||||
|
||||
CACHE_VOLUME_NAME="xahau-release-builder-cache"
|
||||
|
||||
@@ -55,14 +57,13 @@ if false; then
|
||||
docker stop $CONTAINER_NAME
|
||||
else
|
||||
echo "No static container, build on temp container"
|
||||
rm -rf release-build
|
||||
mkdir -p release-build
|
||||
rm -rf release-build;
|
||||
mkdir -p release-build;
|
||||
|
||||
docker volume create $CACHE_VOLUME_NAME
|
||||
|
||||
# Create inline Dockerfile with environment setup for build-full.sh
|
||||
DOCKERFILE_CONTENT=$(
|
||||
cat <<'DOCKERFILE_EOF'
|
||||
DOCKERFILE_CONTENT=$(cat <<'DOCKERFILE_EOF'
|
||||
FROM ghcr.io/phusion/holy-build-box:4.0.1-amd64
|
||||
|
||||
ARG BUILD_CORES=8
|
||||
@@ -217,7 +218,7 @@ RUN /hbb_exe/activate-exec bash -c "ccache -M 100G && \
|
||||
ln -s ../../bin/ccache /usr/lib64/ccache/c++"
|
||||
|
||||
DOCKERFILE_EOF
|
||||
)
|
||||
)
|
||||
|
||||
# Build custom Docker image
|
||||
IMAGE_NAME="xahaud-builder:latest"
|
||||
@@ -227,14 +228,14 @@ DOCKERFILE_EOF
|
||||
if [[ "$GITHUB_REPOSITORY" == "" ]]; then
|
||||
# Non GH, local building
|
||||
echo "Non-GH runner, local building, temp container"
|
||||
docker run -i --user 0:$(id -g) --rm -v /data/builds:/data/builds -v $(pwd):/io -v "$CACHE_VOLUME_NAME":/cache --network host "$IMAGE_NAME" /hbb_exe/activate-exec bash -c "source /opt/rh/gcc-toolset-11/enable && bash -x /io/build-full.sh '$GITHUB_REPOSITORY' '$GITHUB_SHA' '$BUILD_CORES' '$GITHUB_RUN_NUMBER'"
|
||||
docker run -i --user 0:$(id -g) --rm -v /data/builds:/data/builds -v `pwd`:/io -v "$CACHE_VOLUME_NAME":/cache --network host "$IMAGE_NAME" /hbb_exe/activate-exec bash -c "source /opt/rh/gcc-toolset-11/enable && bash -x /io/build-full.sh '$GITHUB_REPOSITORY' '$GITHUB_SHA' '$BUILD_CORES' '$GITHUB_RUN_NUMBER'"
|
||||
else
|
||||
# GH Action, runner
|
||||
echo "GH Action, runner, clean & re-create create persistent container"
|
||||
docker rm -f $CONTAINER_NAME
|
||||
echo "echo 'Stopping container: $CONTAINER_NAME'" >>"$JOB_CLEANUP_SCRIPT"
|
||||
echo "docker stop --time=15 \"$CONTAINER_NAME\" || echo 'Failed to stop container or container not running'" >>"$JOB_CLEANUP_SCRIPT"
|
||||
docker run -di --user 0:$(id -g) --name $CONTAINER_NAME -v /data/builds:/data/builds -v $(pwd):/io -v "$CACHE_VOLUME_NAME":/cache --network host "$IMAGE_NAME" /hbb_exe/activate-exec bash
|
||||
echo "echo 'Stopping container: $CONTAINER_NAME'" >> "$JOB_CLEANUP_SCRIPT"
|
||||
echo "docker stop --time=15 \"$CONTAINER_NAME\" || echo 'Failed to stop container or container not running'" >> "$JOB_CLEANUP_SCRIPT"
|
||||
docker run -di --user 0:$(id -g) --name $CONTAINER_NAME -v /data/builds:/data/builds -v `pwd`:/io -v "$CACHE_VOLUME_NAME":/cache --network host "$IMAGE_NAME" /hbb_exe/activate-exec bash
|
||||
docker exec -i $CONTAINER_NAME /hbb_exe/activate-exec bash -c "source /opt/rh/gcc-toolset-11/enable && bash -x /io/build-full.sh '$GITHUB_REPOSITORY' '$GITHUB_SHA' '$BUILD_CORES' '$GITHUB_RUN_NUMBER'"
|
||||
docker stop $CONTAINER_NAME
|
||||
fi
|
||||
|
||||
@@ -31,7 +31,6 @@
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
#include <ctime>
|
||||
#include <exception>
|
||||
#include <fstream>
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
@@ -352,18 +351,9 @@ Logs::format(
|
||||
|
||||
if (useLocalTime)
|
||||
{
|
||||
try
|
||||
{
|
||||
auto now = std::chrono::system_clock::now();
|
||||
auto local = date::make_zoned(date::current_zone(), now);
|
||||
output = date::format(fmt, local);
|
||||
}
|
||||
catch (std::exception const&)
|
||||
{
|
||||
// Enhanced logging should not make startup fatal if tzdb lookup is
|
||||
// unavailable or misconfigured. Fall back to UTC formatting.
|
||||
output = date::format(fmt, std::chrono::system_clock::now());
|
||||
}
|
||||
auto now = std::chrono::system_clock::now();
|
||||
auto local = date::make_zoned(date::current_zone(), now);
|
||||
output = date::format(fmt, local);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -66,7 +66,7 @@ invalidAMMAsset(
|
||||
Issue const& issue,
|
||||
std::optional<std::pair<Issue, Issue>> const& pair)
|
||||
{
|
||||
if (isBadCurrency(issue.currency))
|
||||
if (badCurrency() == issue.currency)
|
||||
return temBAD_CURRENCY;
|
||||
if (isXRP(issue) && issue.account.isNonZero())
|
||||
return temBAD_ISSUER;
|
||||
|
||||
@@ -250,9 +250,12 @@ FeatureCollections::registerFeature(
|
||||
Feature const* i = getByName(name);
|
||||
if (!i)
|
||||
{
|
||||
// If this check fails, and you just added a feature, increase the
|
||||
// numFeatures value in Feature.h
|
||||
check(
|
||||
features.size() < detail::numFeatures,
|
||||
"More features defined than allocated.");
|
||||
"More features defined than allocated. Adjust numFeatures in "
|
||||
"Feature.h.");
|
||||
|
||||
auto const f = sha512Half(Slice(name.data(), name.size()));
|
||||
|
||||
@@ -421,26 +424,45 @@ featureToName(uint256 const& f)
|
||||
#undef XRPL_FEATURE
|
||||
#pragma push_macro("XRPL_FIX")
|
||||
#undef XRPL_FIX
|
||||
#pragma push_macro("XRPL_RETIRE")
|
||||
#undef XRPL_RETIRE
|
||||
|
||||
#define XRPL_FEATURE(name, supported, vote) \
|
||||
uint256 const feature##name = registerFeature(#name, supported, vote);
|
||||
#define XRPL_FIX(name, supported, vote) \
|
||||
uint256 const fix##name = registerFeature("fix" #name, supported, vote);
|
||||
#define XRPL_RETIRE(name) \
|
||||
[[deprecated("The referenced amendment has been retired"), maybe_unused]] \
|
||||
uint256 const retired##name = retireFeature(#name);
|
||||
|
||||
#include <xrpl/protocol/detail/features.macro>
|
||||
|
||||
#undef XRPL_RETIRE
|
||||
#pragma pop_macro("XRPL_RETIRE")
|
||||
#undef XRPL_FIX
|
||||
#pragma pop_macro("XRPL_FIX")
|
||||
#undef XRPL_FEATURE
|
||||
#pragma pop_macro("XRPL_FEATURE")
|
||||
|
||||
// clang-format off
|
||||
|
||||
// The following amendments have been active for at least two years. Their
|
||||
// pre-amendment code has been removed and the identifiers are deprecated.
|
||||
// All known amendments and amendments that may appear in a validated
|
||||
// ledger must be registered either here or above with the "active" amendments
|
||||
[[deprecated("The referenced amendment has been retired"), maybe_unused]]
|
||||
uint256 const
|
||||
retiredMultiSign = retireFeature("MultiSign"),
|
||||
retiredTrustSetAuth = retireFeature("TrustSetAuth"),
|
||||
retiredFeeEscalation = retireFeature("FeeEscalation"),
|
||||
retiredPayChan = retireFeature("PayChan"),
|
||||
retiredCryptoConditions = retireFeature("CryptoConditions"),
|
||||
retiredTickSize = retireFeature("TickSize"),
|
||||
retiredFix1368 = retireFeature("fix1368"),
|
||||
retiredEscrow = retireFeature("Escrow"),
|
||||
retiredFix1373 = retireFeature("fix1373"),
|
||||
retiredEnforceInvariants = retireFeature("EnforceInvariants"),
|
||||
retiredSortedDirectories = retireFeature("SortedDirectories"),
|
||||
retiredFix1201 = retireFeature("fix1201"),
|
||||
retiredFix1512 = retireFeature("fix1512"),
|
||||
retiredFix1523 = retireFeature("fix1523"),
|
||||
retiredFix1528 = retireFeature("fix1528");
|
||||
|
||||
// clang-format on
|
||||
|
||||
// All of the features should now be registered, since variables in a cpp file
|
||||
// are initialized from top to bottom.
|
||||
//
|
||||
|
||||
@@ -31,8 +31,6 @@
|
||||
|
||||
namespace ripple {
|
||||
|
||||
#define LEDGER_NAMESPACE2(value1, value2) (uint16_t(value1) << 8) | value2
|
||||
|
||||
/** Type-specific prefix for calculating ledger indices.
|
||||
|
||||
The identifier for a given object within the ledger is calculated based
|
||||
@@ -74,7 +72,6 @@ enum class LedgerNameSpace : std::uint16_t {
|
||||
HOOK_DEFINITION = 'D',
|
||||
EMITTED_TXN = 'E',
|
||||
EMITTED_DIR = 'F',
|
||||
SHADOW_TICKET = 0x5374, // St
|
||||
NFTOKEN_OFFER = 'q',
|
||||
NFTOKEN_BUY_OFFERS = 'h',
|
||||
NFTOKEN_SELL_OFFERS = 'i',
|
||||
@@ -82,16 +79,15 @@ enum class LedgerNameSpace : std::uint16_t {
|
||||
IMPORT_VLSEQ = 'I',
|
||||
UNL_REPORT = 'R',
|
||||
CRON = 'L',
|
||||
CONSENSUS_ENTROPY = 'X',
|
||||
AMM = 'A',
|
||||
BRIDGE = LEDGER_NAMESPACE2(0x01, 'H'),
|
||||
BRIDGE = 'H',
|
||||
XCHAIN_CLAIM_ID = 'Q',
|
||||
XCHAIN_CREATE_ACCOUNT_CLAIM_ID = 'K',
|
||||
DID = LEDGER_NAMESPACE2(0x01, 'I'),
|
||||
ORACLE = LEDGER_NAMESPACE2(0x01, 'R'),
|
||||
DID = 'I',
|
||||
ORACLE = 'R',
|
||||
MPTOKEN_ISSUANCE = '~',
|
||||
MPTOKEN = 't',
|
||||
CREDENTIAL = LEDGER_NAMESPACE2(0x01, 'D'),
|
||||
CREDENTIAL = 'D',
|
||||
PERMISSIONED_DOMAIN = 'm',
|
||||
|
||||
// No longer used or supported. Left here to reserve the space
|
||||
@@ -190,15 +186,6 @@ emittedTxn(uint256 const& id) noexcept
|
||||
return {ltEMITTED_TXN, indexHash(LedgerNameSpace::EMITTED_TXN, id)};
|
||||
}
|
||||
|
||||
Keylet
|
||||
shadowTicket(AccountID const& account, std::uint32_t ticketSeq) noexcept
|
||||
{
|
||||
return {
|
||||
ltSHADOW_TICKET,
|
||||
indexHash(
|
||||
LedgerNameSpace::SHADOW_TICKET, account, std::uint32_t(ticketSeq))};
|
||||
}
|
||||
|
||||
Keylet
|
||||
hook(AccountID const& id) noexcept
|
||||
{
|
||||
@@ -557,14 +544,6 @@ cron(uint32_t timestamp, std::optional<AccountID> const& id)
|
||||
return {ltCRON, uint256::fromVoid(h)};
|
||||
}
|
||||
|
||||
Keylet const&
|
||||
consensusEntropy() noexcept
|
||||
{
|
||||
static Keylet const ret{
|
||||
ltCONSENSUS_ENTROPY, indexHash(LedgerNameSpace::CONSENSUS_ENTROPY)};
|
||||
return ret;
|
||||
}
|
||||
|
||||
Keylet
|
||||
amm(Asset const& issue1, Asset const& issue2) noexcept
|
||||
{
|
||||
|
||||
@@ -78,7 +78,6 @@ InnerObjectFormats::InnerObjectFormats()
|
||||
{sfHookExecutionIndex, soeREQUIRED},
|
||||
{sfHookStateChangeCount, soeREQUIRED},
|
||||
{sfHookEmitCount, soeREQUIRED},
|
||||
{sfHookExportCount, soeOPTIONAL},
|
||||
{sfFlags, soeOPTIONAL}});
|
||||
|
||||
add(sfHookEmission.jsonName,
|
||||
@@ -88,6 +87,19 @@ InnerObjectFormats::InnerObjectFormats()
|
||||
{sfEmittedTxnID, soeREQUIRED},
|
||||
{sfEmitNonce, soeOPTIONAL}});
|
||||
|
||||
add(sfHookDefinition.jsonName,
|
||||
sfHookDefinition.getCode(),
|
||||
{{sfCreateCode, soeREQUIRED},
|
||||
{sfHookNamespace, soeREQUIRED},
|
||||
{sfHookParameters, soeREQUIRED},
|
||||
{sfHookOn, soeOPTIONAL},
|
||||
{sfHookOnIncoming, soeOPTIONAL},
|
||||
{sfHookOnOutgoing, soeOPTIONAL},
|
||||
{sfHookCanEmit, soeOPTIONAL},
|
||||
{sfHookApiVersion, soeREQUIRED},
|
||||
{sfFlags, soeREQUIRED},
|
||||
{sfFee, soeREQUIRED}});
|
||||
|
||||
add(sfHook.jsonName,
|
||||
sfHook.getCode(),
|
||||
{{sfHookHash, soeOPTIONAL},
|
||||
@@ -100,7 +112,6 @@ InnerObjectFormats::InnerObjectFormats()
|
||||
{sfHookOnOutgoing, soeOPTIONAL},
|
||||
{sfHookCanEmit, soeOPTIONAL},
|
||||
{sfHookApiVersion, soeOPTIONAL},
|
||||
{sfHookName, soeOPTIONAL},
|
||||
{sfFlags, soeOPTIONAL}});
|
||||
|
||||
add(sfHookGrant.jsonName,
|
||||
@@ -255,24 +266,6 @@ InnerObjectFormats::InnerObjectFormats()
|
||||
{sfIssuer, soeREQUIRED},
|
||||
{sfCredentialType, soeREQUIRED},
|
||||
});
|
||||
|
||||
add(sfHighReward.jsonName,
|
||||
sfHighReward.getCode(),
|
||||
{
|
||||
{sfRewardLgrFirst, soeREQUIRED},
|
||||
{sfRewardLgrLast, soeREQUIRED},
|
||||
{sfRewardTime, soeREQUIRED},
|
||||
{sfTrustLineRewardAccumulator, soeREQUIRED},
|
||||
});
|
||||
|
||||
add(sfLowReward.jsonName,
|
||||
sfLowReward.getCode(),
|
||||
{
|
||||
{sfRewardLgrFirst, soeREQUIRED},
|
||||
{sfRewardLgrLast, soeREQUIRED},
|
||||
{sfRewardTime, soeREQUIRED},
|
||||
{sfTrustLineRewardAccumulator, soeREQUIRED},
|
||||
});
|
||||
}
|
||||
|
||||
InnerObjectFormats const&
|
||||
|
||||
@@ -111,7 +111,7 @@ issueFromJson(Json::Value const& v)
|
||||
}
|
||||
|
||||
auto const currency = to_currency(curStr.asString());
|
||||
if (isBadCurrency(currency) || currency == noCurrency())
|
||||
if (currency == badCurrency() || currency == noCurrency())
|
||||
{
|
||||
Throw<Json::error>("issueFromJson currency must be a valid currency");
|
||||
}
|
||||
|
||||
187
src/libxrpl/protocol/JsonTx.cpp
Normal file
187
src/libxrpl/protocol/JsonTx.cpp
Normal file
@@ -0,0 +1,187 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2025 Ripple Labs Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <xrpl/protocol/JsonTx.h>
|
||||
|
||||
#include <xrpl/basics/StringUtilities.h>
|
||||
#include <xrpl/basics/strHex.h>
|
||||
#include <xrpl/json/json_reader.h>
|
||||
#include <xrpl/protocol/PublicKey.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STBase.h>
|
||||
#include <xrpl/protocol/STBlob.h>
|
||||
#include <xrpl/protocol/STObject.h>
|
||||
#include <xrpl/protocol/STParsedJSON.h>
|
||||
#include <xrpl/protocol/Serializer.h>
|
||||
#include <xrpl/protocol/digest.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <initializer_list>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace ripple {
|
||||
namespace jsonTx {
|
||||
|
||||
namespace {
|
||||
|
||||
/** Canonical serialization of `obj` with the given fields removed.
|
||||
STObject's own serialization already sorts by field code, but we
|
||||
have to walk the fields ourselves to skip the json-tx wrapper
|
||||
entries rather than mutate the object. */
|
||||
Blob
|
||||
canonicalSerialization(
|
||||
STObject const& obj,
|
||||
std::initializer_list<SField const*> skip)
|
||||
{
|
||||
std::vector<STBase const*> fields;
|
||||
for (auto const& entry : obj)
|
||||
{
|
||||
if (entry.getSType() == STI_NOTPRESENT)
|
||||
continue;
|
||||
bool skipped = false;
|
||||
for (SField const* s : skip)
|
||||
if (entry.getFName() == *s)
|
||||
{
|
||||
skipped = true;
|
||||
break;
|
||||
}
|
||||
if (!skipped)
|
||||
fields.push_back(&entry);
|
||||
}
|
||||
std::sort(
|
||||
fields.begin(), fields.end(), [](STBase const* a, STBase const* b) {
|
||||
return a->getFName().fieldCode < b->getFName().fieldCode;
|
||||
});
|
||||
|
||||
Serializer s;
|
||||
for (STBase const* f : fields)
|
||||
{
|
||||
f->addFieldID(s);
|
||||
f->add(s);
|
||||
auto const sType = f->getSType();
|
||||
if (sType == STI_ARRAY || sType == STI_OBJECT)
|
||||
s.addFieldID(sType, 1);
|
||||
}
|
||||
return s.getData();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool
|
||||
hasBody(STObject const& obj) noexcept
|
||||
{
|
||||
try
|
||||
{
|
||||
return obj.isFieldPresent(sfJsonTxBody);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Slice
|
||||
body(STObject const& obj)
|
||||
{
|
||||
if (!obj.isFieldPresent(sfJsonTxBody))
|
||||
return Slice{};
|
||||
// peekAtField gives us a view into the STObject's owned storage;
|
||||
// STBlob::value() returns a Slice over that storage directly.
|
||||
auto const& field = obj.peekAtField(sfJsonTxBody);
|
||||
return static_cast<STBlob const&>(field).value();
|
||||
}
|
||||
|
||||
uint256
|
||||
bodyHash(STObject const& obj)
|
||||
{
|
||||
auto const s = body(obj);
|
||||
if (s.empty())
|
||||
return uint256{};
|
||||
return sha512Half(s);
|
||||
}
|
||||
|
||||
Expected<void, std::string>
|
||||
checkSignature(STTx const& stx)
|
||||
{
|
||||
if (!hasBody(stx))
|
||||
return Unexpected<std::string>("JsonTxBody field is missing.");
|
||||
|
||||
auto const bodySlice = body(stx);
|
||||
if (bodySlice.empty())
|
||||
return Unexpected<std::string>("JsonTxBody is empty.");
|
||||
|
||||
if (!stx.isFieldPresent(sfSigningPubKey))
|
||||
return Unexpected<std::string>("SigningPubKey is missing.");
|
||||
|
||||
Blob const spk = stx.getFieldVL(sfSigningPubKey);
|
||||
if (!publicKeyType(makeSlice(spk)))
|
||||
return Unexpected<std::string>("SigningPubKey is not a valid key.");
|
||||
|
||||
if (!stx.isFieldPresent(sfTxnSignature))
|
||||
return Unexpected<std::string>("TxnSignature is missing.");
|
||||
|
||||
Blob const sig = stx.getFieldVL(sfTxnSignature);
|
||||
if (sig.empty())
|
||||
return Unexpected<std::string>("TxnSignature is empty.");
|
||||
|
||||
if (!verify(PublicKey(makeSlice(spk)), bodySlice, makeSlice(sig)))
|
||||
return Unexpected<std::string>(
|
||||
"Signature over JsonTxBody failed verification.");
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
Expected<void, std::string>
|
||||
checkStructuralEquivalence(STTx const& stx)
|
||||
{
|
||||
if (!hasBody(stx))
|
||||
return Unexpected<std::string>("JsonTxBody field is missing.");
|
||||
|
||||
auto const bodySlice = body(stx);
|
||||
if (bodySlice.empty())
|
||||
return Unexpected<std::string>("JsonTxBody is empty.");
|
||||
|
||||
std::string const bodyStr(
|
||||
reinterpret_cast<char const*>(bodySlice.data()), bodySlice.size());
|
||||
|
||||
Json::Value parsed;
|
||||
Json::Reader reader;
|
||||
if (!reader.parse(bodyStr, parsed) || !parsed.isObject())
|
||||
return Unexpected<std::string>(
|
||||
"JsonTxBody is not a valid JSON object.");
|
||||
|
||||
STParsedJSONObject parsedObj("JsonTxBody", parsed);
|
||||
if (!parsedObj.object)
|
||||
return Unexpected<std::string>(
|
||||
"JsonTxBody does not parse into a valid STObject: " +
|
||||
(parsedObj.error.isMember(jss::error_message)
|
||||
? parsedObj.error[jss::error_message].asString()
|
||||
: std::string("unknown parse error")));
|
||||
|
||||
// The json-tx wrapper fields (TxnSignature, JsonTxBody) are excluded
|
||||
// from both sides: TxnSignature covers the body bytes (not the
|
||||
// binary), and JsonTxBody is the body itself.
|
||||
std::initializer_list<SField const*> const skip{
|
||||
&sfTxnSignature, &sfJsonTxBody};
|
||||
|
||||
if (canonicalSerialization(stx, skip) !=
|
||||
canonicalSerialization(*parsedObj.object, skip))
|
||||
return Unexpected<std::string>(
|
||||
"JsonTxBody content does not match the structural fields "
|
||||
"of the transaction.");
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace jsonTx
|
||||
} // namespace ripple
|
||||
@@ -103,7 +103,7 @@ currencyFromJson(SField const& name, Json::Value const& v)
|
||||
}
|
||||
|
||||
auto const currency = to_currency(v.asString());
|
||||
if (isBadCurrency(currency) || currency == noCurrency())
|
||||
if (currency == badCurrency() || currency == noCurrency())
|
||||
{
|
||||
Throw<std::runtime_error>(
|
||||
"currencyFromJson currency must be a valid currency");
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include <xrpl/json/to_string.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/HashPrefix.h>
|
||||
#include <xrpl/protocol/JsonTx.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
#include <xrpl/protocol/PublicKey.h>
|
||||
#include <xrpl/protocol/STAccount.h>
|
||||
@@ -216,6 +217,14 @@ STTx::checkSign(
|
||||
{
|
||||
try
|
||||
{
|
||||
// json-tx: when sfJsonTxBody is present and the amendment is
|
||||
// active, the signature covers the raw ASCII bytes of the body
|
||||
// instead of the classical signing payload. Structural
|
||||
// equivalence between the body and the other STTx fields is
|
||||
// enforced separately in passesLocalChecks.
|
||||
if (rules.enabled(featureJsonTx) && jsonTx::hasBody(*this))
|
||||
return jsonTx::checkSignature(*this);
|
||||
|
||||
// Determine whether we're single- or multi-signing by looking
|
||||
// at the SigningPubKey. If it's empty we must be
|
||||
// multi-signing. Otherwise we're single-signing.
|
||||
@@ -663,6 +672,21 @@ passesLocalChecks(STObject const& st, std::string& reason)
|
||||
return false;
|
||||
}
|
||||
|
||||
// json-tx: if the tx carries sfJsonTxBody, its parsed content must
|
||||
// match the other structural fields. We can only run this when the
|
||||
// object is actually an STTx -- passesLocalChecks is also called on
|
||||
// nested STObjects that don't participate in the json-tx scheme.
|
||||
if (auto const* stx = dynamic_cast<STTx const*>(&st);
|
||||
stx && jsonTx::hasBody(*stx))
|
||||
{
|
||||
if (auto const result = jsonTx::checkStructuralEquivalence(*stx);
|
||||
!result)
|
||||
{
|
||||
reason = result.error();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -684,8 +708,7 @@ isPseudoTx(STObject const& tx)
|
||||
|
||||
auto tt = safe_cast<TxType>(*t);
|
||||
return tt == ttAMENDMENT || tt == ttFEE || tt == ttUNL_MODIFY ||
|
||||
tt == ttEMIT_FAILURE || tt == ttUNL_REPORT || tt == ttCRON ||
|
||||
tt == ttCONSENSUS_ENTROPY;
|
||||
tt == ttEMIT_FAILURE || tt == ttUNL_REPORT || tt == ttCRON;
|
||||
}
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
@@ -124,7 +124,6 @@ transResults()
|
||||
MAKE_ERROR(tecARRAY_TOO_LARGE, "Array is too large."),
|
||||
MAKE_ERROR(tecLOCKED, "Fund is locked."),
|
||||
MAKE_ERROR(tecBAD_CREDENTIALS, "Bad credentials."),
|
||||
MAKE_ERROR(tecEXPORT_EXPIRED, "Export expired without reaching signature quorum."),
|
||||
|
||||
MAKE_ERROR(tefALREADY, "The exact transaction was already in this ledger."),
|
||||
MAKE_ERROR(tefBAD_ADD_AUTH, "Not authorized to add account."),
|
||||
@@ -172,7 +171,6 @@ transResults()
|
||||
MAKE_ERROR(telNON_LOCAL_EMITTED_TXN, "Emitted transaction cannot be applied because it was not generated locally."),
|
||||
MAKE_ERROR(telIMPORT_VL_KEY_NOT_RECOGNISED, "Import vl key was not recognized."),
|
||||
MAKE_ERROR(telCAN_NOT_QUEUE_IMPORT, "Import transaction was not able to be directly applied and cannot be queued."),
|
||||
MAKE_ERROR(telSHADOW_TICKET_REQUIRED, "The imported transaction uses a TicketSequence but no shadow ticket exists."),
|
||||
MAKE_ERROR(telENV_RPC_FAILED, "Unit test RPC failure."),
|
||||
|
||||
MAKE_ERROR(temMALFORMED, "Malformed transaction."),
|
||||
@@ -240,7 +238,6 @@ transResults()
|
||||
MAKE_ERROR(terPRE_TICKET, "Ticket is not yet in ledger."),
|
||||
MAKE_ERROR(terNO_HOOK, "No hook with that hash exists on the ledger."),
|
||||
MAKE_ERROR(terNO_AMM, "AMM doesn't exist for the asset pair."),
|
||||
MAKE_ERROR(terRETRY_EXPORT, "Export awaiting validator signatures."),
|
||||
|
||||
MAKE_ERROR(tesSUCCESS, "The transaction was applied. Only final in a validated ledger."),
|
||||
MAKE_ERROR(tesPARTIAL, "The transaction was applied but should be submitted again until returning tesSUCCESS."),
|
||||
|
||||
@@ -43,12 +43,12 @@ TxFormats::TxFormats()
|
||||
{sfSigningPubKey, soeREQUIRED},
|
||||
{sfTicketSequence, soeOPTIONAL},
|
||||
{sfTxnSignature, soeOPTIONAL},
|
||||
{sfSigners, soeOPTIONAL}, // submit_multisigned
|
||||
{sfJsonTxBody, soeOPTIONAL}, // json-tx: ASCII bytes that were signed
|
||||
{sfSigners, soeOPTIONAL}, // submit_multisigned
|
||||
{sfEmitDetails, soeOPTIONAL},
|
||||
{sfFirstLedgerSequence, soeOPTIONAL},
|
||||
{sfNetworkID, soeOPTIONAL},
|
||||
{sfHookParameters, soeOPTIONAL},
|
||||
{sfHookName, soeOPTIONAL},
|
||||
};
|
||||
|
||||
#pragma push_macro("UNWRAP")
|
||||
|
||||
@@ -49,11 +49,6 @@ TxMeta::TxMeta(
|
||||
|
||||
if (obj.isFieldPresent(sfHookEmissions))
|
||||
setHookEmissions(obj.getFieldArray(sfHookEmissions));
|
||||
|
||||
if (obj.isFieldPresent(sfExportResult))
|
||||
setExportResult(const_cast<STObject&>(obj)
|
||||
.getField(sfExportResult)
|
||||
.downcast<STObject>());
|
||||
}
|
||||
|
||||
TxMeta::TxMeta(uint256 const& txid, std::uint32_t ledger, STObject const& obj)
|
||||
@@ -80,11 +75,6 @@ TxMeta::TxMeta(uint256 const& txid, std::uint32_t ledger, STObject const& obj)
|
||||
|
||||
if (obj.isFieldPresent(sfHookEmissions))
|
||||
setHookEmissions(obj.getFieldArray(sfHookEmissions));
|
||||
|
||||
if (obj.isFieldPresent(sfExportResult))
|
||||
setExportResult(const_cast<STObject&>(obj)
|
||||
.getField(sfExportResult)
|
||||
.downcast<STObject>());
|
||||
}
|
||||
|
||||
TxMeta::TxMeta(uint256 const& txid, std::uint32_t ledger, Blob const& vec)
|
||||
@@ -255,14 +245,6 @@ TxMeta::getAsObject() const
|
||||
if (hasHookEmissions())
|
||||
metaData.setFieldArray(sfHookEmissions, getHookEmissions());
|
||||
|
||||
if (hasExportResult())
|
||||
{
|
||||
Serializer s;
|
||||
mExportResult->add(s);
|
||||
SerialIter sit(s.slice());
|
||||
metaData.emplace_back(STObject(sit, sfExportResult));
|
||||
}
|
||||
|
||||
return metaData;
|
||||
}
|
||||
|
||||
|
||||
1474
src/magic/magic_enum.h
Normal file
1474
src/magic/magic_enum.h
Normal file
File diff suppressed because it is too large
Load Diff
@@ -18,7 +18,6 @@
|
||||
//==============================================================================
|
||||
|
||||
#include <test/jtx.h>
|
||||
#include <test/jtx/AMM.h>
|
||||
#include <xrpld/app/ledger/LedgerMaster.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
@@ -27,14 +26,6 @@ namespace ripple {
|
||||
namespace test {
|
||||
struct ClaimReward_test : public beast::unit_test::suite
|
||||
{
|
||||
private:
|
||||
// helper
|
||||
void static overrideFlag(Json::Value& jv)
|
||||
{
|
||||
jv[jss::Flags] = hsfOVERRIDE;
|
||||
}
|
||||
|
||||
public:
|
||||
bool
|
||||
expectRewards(
|
||||
jtx::Env const& env,
|
||||
@@ -68,52 +59,6 @@ public:
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
expectRewardsIOU(
|
||||
jtx::Env const& env,
|
||||
jtx::Account const& acct,
|
||||
jtx::IOU const& iou,
|
||||
std::uint32_t ledgerFirst,
|
||||
std::uint32_t ledgerLast,
|
||||
STAmount accumulator,
|
||||
std::uint32_t time)
|
||||
{
|
||||
auto const sle = env.le(keylet::line(acct, iou.account, iou.currency));
|
||||
BEAST_EXPECT(!!sle);
|
||||
auto const& sfRewardField =
|
||||
std::minmax(acct.id(), iou.account.id()).first == acct.id()
|
||||
? sfLowReward
|
||||
: sfHighReward;
|
||||
|
||||
if (!sle->isFieldPresent(sfRewardField))
|
||||
return false;
|
||||
|
||||
auto const& reward =
|
||||
static_cast<STObject const&>(sle->peekAtField(sfRewardField));
|
||||
|
||||
if (!reward.isFieldPresent(sfRewardLgrFirst) ||
|
||||
reward.getFieldU32(sfRewardLgrFirst) != ledgerFirst)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!reward.isFieldPresent(sfRewardLgrLast) ||
|
||||
reward.getFieldU32(sfRewardLgrLast) != ledgerLast)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!reward.isFieldPresent(sfTrustLineRewardAccumulator) ||
|
||||
reward.getFieldAmount(sfTrustLineRewardAccumulator) != accumulator)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!reward.isFieldPresent(sfRewardTime) ||
|
||||
reward.getFieldU32(sfRewardTime) != time)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
expectNoRewards(jtx::Env const& env, jtx::Account const& acct)
|
||||
{
|
||||
@@ -137,24 +82,6 @@ public:
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
expectNoRewardsIOU(
|
||||
jtx::Env const& env,
|
||||
jtx::Account const& acct,
|
||||
jtx::IOU const& iou)
|
||||
{
|
||||
auto const sle = env.le(keylet::line(acct, iou.account, iou.currency));
|
||||
BEAST_EXPECT(!!sle);
|
||||
auto const& sfRewardField =
|
||||
std::minmax(acct.id(), iou.account.id()).first == acct.id()
|
||||
? sfLowReward
|
||||
: sfHighReward;
|
||||
|
||||
if (sle->isFieldPresent(sfRewardField))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
testEnabled(FeatureBitset features)
|
||||
{
|
||||
@@ -164,7 +91,7 @@ public:
|
||||
|
||||
// setup env
|
||||
auto const alice = Account("alice");
|
||||
auto const issuer = Account::master;
|
||||
auto const issuer = Account("issuer");
|
||||
|
||||
for (bool const withClaimReward : {false, true})
|
||||
{
|
||||
@@ -174,11 +101,7 @@ public:
|
||||
withClaimReward ? features : features - featureBalanceRewards;
|
||||
Env env{*this, amend};
|
||||
|
||||
env.fund(XRP(1000), alice);
|
||||
env.close();
|
||||
|
||||
env(hook(issuer, {{hso(jtx::genesis::AcceptHook)}}, 0),
|
||||
fee(XRP(1)));
|
||||
env.fund(XRP(1000), alice, issuer);
|
||||
env.close();
|
||||
|
||||
auto const txResult =
|
||||
@@ -195,10 +118,7 @@ public:
|
||||
.count();
|
||||
|
||||
// CLAIM
|
||||
env(reward::claim(alice),
|
||||
reward::issuer(issuer),
|
||||
fee(XRP(1)),
|
||||
txResult);
|
||||
env(reward::claim(alice), reward::issuer(issuer), txResult);
|
||||
env.close();
|
||||
|
||||
if (withClaimReward)
|
||||
@@ -274,19 +194,14 @@ public:
|
||||
Env env{*this, amend};
|
||||
|
||||
auto const alice = Account("alice");
|
||||
auto const issuer = Account::master;
|
||||
auto const issuer = Account("issuer");
|
||||
|
||||
env.fund(XRP(1000), alice);
|
||||
env.close();
|
||||
|
||||
env(hook(issuer, {{hso(jtx::genesis::AcceptHook)}}, 0),
|
||||
fee(XRP(1)));
|
||||
env.fund(XRP(1000), alice, issuer);
|
||||
env.close();
|
||||
|
||||
auto tx = reward::claim(alice);
|
||||
env(tx,
|
||||
reward::issuer(issuer),
|
||||
fee(XRP(1)),
|
||||
txflags(tfFullyCanonicalSig),
|
||||
withFixFlags ? ter(tesSUCCESS) : ter(temINVALID_FLAG));
|
||||
env.close();
|
||||
@@ -305,105 +220,6 @@ public:
|
||||
env(reward::claim(alice), reward::issuer(alice), ter(temMALFORMED));
|
||||
env.close();
|
||||
}
|
||||
|
||||
// featureIOURewardClaim
|
||||
|
||||
// temDISABLED
|
||||
// featureIOURewardClaim amendment is disabled
|
||||
{
|
||||
test::jtx::Env env{
|
||||
*this,
|
||||
network::makeNetworkConfig(21337),
|
||||
features - featureIOURewardClaim};
|
||||
|
||||
auto const alice = Account("alice");
|
||||
auto const bob = Account("bob");
|
||||
auto const gw = Account("gw");
|
||||
env.fund(XRP(1000), alice, bob, gw);
|
||||
env.close();
|
||||
|
||||
jtx::IOU const USD = gw["USD"];
|
||||
|
||||
env(reward::claim(alice),
|
||||
reward::issuer(bob),
|
||||
reward::claimCurrency(USD),
|
||||
ter(temDISABLED));
|
||||
env.close();
|
||||
}
|
||||
|
||||
// temMALFORMED
|
||||
// ClaimCurrency.account cannot be the source account.
|
||||
{
|
||||
test::jtx::Env env{*this, network::makeNetworkConfig(21337)};
|
||||
|
||||
auto const alice = Account("alice");
|
||||
auto const bob = Account("bob");
|
||||
env.fund(XRP(1000), alice);
|
||||
env.close();
|
||||
|
||||
jtx::IOU const USD = alice["USD"];
|
||||
|
||||
env(reward::claim(alice),
|
||||
reward::issuer(bob),
|
||||
reward::claimCurrency(USD),
|
||||
ter(temMALFORMED));
|
||||
env.close();
|
||||
}
|
||||
|
||||
// temMALFORMED
|
||||
// Issuer cannot be Genesis account if ClaimCurrency is set.
|
||||
{
|
||||
test::jtx::Env env{*this, network::makeNetworkConfig(21337)};
|
||||
|
||||
auto const alice = Account("alice");
|
||||
auto const gw = Account("gw");
|
||||
env.fund(XRP(1000), alice, gw);
|
||||
env.close();
|
||||
|
||||
jtx::IOU const USD = gw["USD"];
|
||||
|
||||
env(reward::claim(alice),
|
||||
reward::issuer(Account::master),
|
||||
reward::claimCurrency(USD),
|
||||
ter(temBAD_ISSUER));
|
||||
}
|
||||
|
||||
// MPT
|
||||
{
|
||||
// tested in testMPTInvalidInTx() at MPToken_test.cpp
|
||||
}
|
||||
|
||||
// XAH RewardClaim: Issuer must be the Genesis account if
|
||||
// featureXahauGenesis and featureIOURewardClaim are enabled.
|
||||
for (bool const withIOURewardClaim : {false, true})
|
||||
{
|
||||
auto const amend = withIOURewardClaim
|
||||
? features
|
||||
: features - featureIOURewardClaim;
|
||||
|
||||
auto const alice = Account("alice");
|
||||
auto const badIssuer = Account("gw");
|
||||
auto const issuer = Account::master;
|
||||
auto const USD = badIssuer["USD"];
|
||||
|
||||
Env env{*this, amend};
|
||||
env.fund(XRP(1000), alice, badIssuer);
|
||||
env.close();
|
||||
|
||||
env(hook(issuer, {{hso(jtx::genesis::AcceptHook)}}, 0),
|
||||
fee(XRP(1)));
|
||||
env.close();
|
||||
|
||||
env(reward::claim(alice),
|
||||
reward::issuer(badIssuer),
|
||||
fee(XRP(1)),
|
||||
withIOURewardClaim ? ter(temBAD_ISSUER) : ter(tesSUCCESS));
|
||||
|
||||
env(reward::claim(alice),
|
||||
reward::issuer(issuer),
|
||||
fee(XRP(1)),
|
||||
ter(tesSUCCESS));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
@@ -427,7 +243,6 @@ public:
|
||||
auto const alice = Account("alice");
|
||||
auto const issuer = Account("issuer");
|
||||
env.memoize(alice);
|
||||
auto USD = issuer["USD"];
|
||||
|
||||
env.fund(XRP(1000), issuer);
|
||||
env.close();
|
||||
@@ -435,10 +250,7 @@ public:
|
||||
auto tx = reward::claim(alice);
|
||||
tx[jss::Sequence] = 0;
|
||||
tx[jss::Fee] = 10;
|
||||
env(tx,
|
||||
reward::issuer(issuer),
|
||||
reward::claimCurrency(USD),
|
||||
ter(terNO_ACCOUNT));
|
||||
env(tx, reward::issuer(issuer), ter(terNO_ACCOUNT));
|
||||
env.close();
|
||||
}
|
||||
|
||||
@@ -448,9 +260,9 @@ public:
|
||||
test::jtx::Env env{*this, network::makeNetworkConfig(21337)};
|
||||
|
||||
auto const alice = Account("alice");
|
||||
auto const issuer = Account::master;
|
||||
auto const issuer = Account("issuer");
|
||||
|
||||
env.fund(XRP(1000), alice);
|
||||
env.fund(XRP(1000), alice, issuer);
|
||||
env.close();
|
||||
|
||||
env(reward::claim(alice),
|
||||
@@ -481,193 +293,13 @@ public:
|
||||
auto const issuer = Account("issuer");
|
||||
env.memoize(issuer);
|
||||
|
||||
auto USD = issuer["USD"];
|
||||
|
||||
env.fund(XRP(1000), alice);
|
||||
env.close();
|
||||
|
||||
auto tx = reward::claim(alice);
|
||||
env(tx,
|
||||
reward::issuer(issuer),
|
||||
reward::claimCurrency(USD),
|
||||
ter(tecNO_ISSUER));
|
||||
env(tx, reward::issuer(issuer), ter(tecNO_ISSUER));
|
||||
env.close();
|
||||
}
|
||||
|
||||
// tecNO_PERMISSION
|
||||
// issuer is an AMM account
|
||||
{
|
||||
test::jtx::Env env{*this, network::makeNetworkConfig(21337)};
|
||||
|
||||
auto const alice = Account("alice");
|
||||
auto const issuer = Account("issuer");
|
||||
auto const USD = issuer["USD"];
|
||||
|
||||
env.fund(XRP(1000), alice, issuer);
|
||||
env.close();
|
||||
|
||||
AMM amm(env, issuer, XRP(100), USD(100));
|
||||
|
||||
BEAST_EXPECT(amm.ammExists());
|
||||
|
||||
env(reward::claim(alice),
|
||||
reward::issuer(amm.ammAccount()),
|
||||
reward::claimCurrency(USD),
|
||||
ter(tecNO_PERMISSION));
|
||||
env.close();
|
||||
}
|
||||
|
||||
// tecNO_TARGET
|
||||
// no claim reward hook
|
||||
{
|
||||
Env env{*this};
|
||||
|
||||
auto const alice = Account("alice");
|
||||
auto const issuer = Account::master;
|
||||
|
||||
env.fund(XRP(1000), alice);
|
||||
env.close();
|
||||
|
||||
// Doesn't have hook
|
||||
{
|
||||
env(reward::claim(alice),
|
||||
reward::issuer(issuer),
|
||||
ter(tecNO_TARGET));
|
||||
env.close();
|
||||
}
|
||||
// Invalid HookOn
|
||||
{
|
||||
auto hookObj = hso(jtx::genesis::AcceptHook, overrideFlag);
|
||||
hookObj[jss::HookOn] = to_string(UINT256_BIT[ttCLAIM_REWARD]);
|
||||
env(hook(issuer, {{hookObj}}, 0), fee(XRP(1)));
|
||||
env.close();
|
||||
|
||||
env(reward::claim(alice),
|
||||
reward::issuer(issuer),
|
||||
ter(tecNO_TARGET));
|
||||
env.close();
|
||||
}
|
||||
// Invalid IncomingHookOn
|
||||
{
|
||||
auto hookObj = hso(jtx::genesis::AcceptHook, overrideFlag);
|
||||
hookObj.removeMember(jss::HookOn);
|
||||
hookObj[jss::HookOnIncoming] =
|
||||
to_string(UINT256_BIT[ttCLAIM_REWARD]);
|
||||
hookObj[jss::HookOnOutgoing] = to_string(uint256{});
|
||||
env(hook(issuer, {{hookObj}}, 0), fee(XRP(1)));
|
||||
env.close();
|
||||
|
||||
env(reward::claim(alice),
|
||||
reward::issuer(issuer),
|
||||
ter(tecNO_TARGET));
|
||||
}
|
||||
// Vaild HookOn
|
||||
{
|
||||
auto hookObj = hso(jtx::genesis::AcceptHook, overrideFlag);
|
||||
hookObj[jss::HookOn] = to_string(~UINT256_BIT[ttCLAIM_REWARD]);
|
||||
env(hook(issuer, {{hookObj}}, 0), fee(XRP(1)));
|
||||
env.close();
|
||||
|
||||
env(reward::claim(alice),
|
||||
reward::issuer(issuer),
|
||||
fee(XRP(1)),
|
||||
ter(tesSUCCESS));
|
||||
}
|
||||
// Vaild IncomingHookOn
|
||||
{
|
||||
auto hookObj = hso(jtx::genesis::AcceptHook, overrideFlag);
|
||||
hookObj.removeMember(jss::HookOn);
|
||||
hookObj[jss::HookOnIncoming] =
|
||||
to_string(~UINT256_BIT[ttCLAIM_REWARD]);
|
||||
hookObj[jss::HookOnOutgoing] = to_string(uint256{});
|
||||
env(hook(issuer, {{hookObj}}, 0), fee(XRP(1)));
|
||||
env.close();
|
||||
|
||||
env(reward::claim(alice),
|
||||
reward::issuer(issuer),
|
||||
fee(XRP(1)),
|
||||
ter(tesSUCCESS));
|
||||
}
|
||||
// Invalid Hooks Array
|
||||
{
|
||||
auto hookObj = hso(jtx::genesis::AcceptHook, overrideFlag);
|
||||
hookObj[jss::HookOn] = to_string(UINT256_BIT[ttCLAIM_REWARD]);
|
||||
env(hook(
|
||||
issuer,
|
||||
{{
|
||||
hookObj,
|
||||
hookObj,
|
||||
hookObj,
|
||||
hookObj,
|
||||
hookObj,
|
||||
hookObj,
|
||||
hookObj,
|
||||
hookObj,
|
||||
hookObj,
|
||||
hookObj,
|
||||
}},
|
||||
0),
|
||||
fee(XRP(1)));
|
||||
env.close();
|
||||
|
||||
env(reward::claim(alice),
|
||||
reward::issuer(issuer),
|
||||
fee(XRP(1)),
|
||||
ter(tecNO_TARGET));
|
||||
}
|
||||
// Vaild Hooks Array
|
||||
{
|
||||
auto hookObj = hso(jtx::genesis::AcceptHook, overrideFlag);
|
||||
hookObj[jss::HookOn] = to_string(UINT256_BIT[ttCLAIM_REWARD]);
|
||||
auto hookObj2 = hso(jtx::genesis::AcceptHook, overrideFlag);
|
||||
hookObj2[jss::HookOn] = to_string(~UINT256_BIT[ttCLAIM_REWARD]);
|
||||
env(hook(
|
||||
issuer,
|
||||
{{
|
||||
hookObj,
|
||||
hookObj,
|
||||
hookObj,
|
||||
hookObj,
|
||||
hookObj,
|
||||
hookObj,
|
||||
hookObj,
|
||||
hookObj,
|
||||
hookObj,
|
||||
hookObj2,
|
||||
}},
|
||||
0),
|
||||
fee(XRP(1)));
|
||||
env.close();
|
||||
|
||||
env(reward::claim(alice),
|
||||
reward::issuer(issuer),
|
||||
fee(XRP(1)),
|
||||
ter(tesSUCCESS));
|
||||
}
|
||||
}
|
||||
|
||||
// tecNO_LINE
|
||||
// trustline does not exist.
|
||||
{
|
||||
test::jtx::Env env{*this, network::makeNetworkConfig(21337)};
|
||||
|
||||
auto const alice = Account("alice");
|
||||
|
||||
auto const gw = Account("gw");
|
||||
env.fund(XRP(1000), alice, gw);
|
||||
env.close();
|
||||
|
||||
env(hook(gw, {{hso(jtx::genesis::AcceptHook)}}, 0), fee(XRP(1)));
|
||||
env.close();
|
||||
|
||||
jtx::IOU const USD = gw["USD"];
|
||||
|
||||
env(reward::claim(alice),
|
||||
reward::issuer(gw),
|
||||
reward::claimCurrency(USD),
|
||||
fee(XRP(1)),
|
||||
ter(tecNO_LINE));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
@@ -680,17 +312,9 @@ public:
|
||||
test::jtx::Env env{*this, network::makeNetworkConfig(21337)};
|
||||
|
||||
auto const alice = Account("alice");
|
||||
auto const bob = Account("bob");
|
||||
auto const gw = Account("gw");
|
||||
auto const issuer = Account("issuer");
|
||||
|
||||
env.fund(XRP(1000), alice, bob, gw, issuer);
|
||||
env.close();
|
||||
|
||||
env(hook(issuer, {{hso(jtx::genesis::AcceptHook)}}, 0), fee(XRP(1)));
|
||||
env.close();
|
||||
env(hook(Account::master, {{hso(jtx::genesis::AcceptHook)}}, 0),
|
||||
fee(XRP(1)));
|
||||
env.fund(XRP(1000), alice, issuer);
|
||||
env.close();
|
||||
|
||||
// test claim rewards - no opt out
|
||||
@@ -705,7 +329,7 @@ public:
|
||||
.count();
|
||||
|
||||
auto tx = reward::claim(alice);
|
||||
env(tx, reward::issuer(Account::master), fee(XRP(1)), ter(tesSUCCESS));
|
||||
env(tx, reward::issuer(issuer), ter(tesSUCCESS));
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(
|
||||
@@ -718,51 +342,6 @@ public:
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(expectNoRewards(env, alice) == true);
|
||||
|
||||
// test iou claim rewards
|
||||
{
|
||||
// set trustline
|
||||
env(trust(bob, gw["USD"](10000)));
|
||||
env.close();
|
||||
|
||||
// opt in
|
||||
auto const currentLedger = env.current()->seq();
|
||||
auto const currentTime =
|
||||
std::chrono::duration_cast<std::chrono::seconds>(
|
||||
env.app()
|
||||
.getLedgerMaster()
|
||||
.getValidatedLedger()
|
||||
->info()
|
||||
.parentCloseTime.time_since_epoch())
|
||||
.count();
|
||||
|
||||
auto tx = reward::claim(bob);
|
||||
env(tx,
|
||||
reward::issuer(issuer),
|
||||
reward::claimCurrency(gw["USD"]),
|
||||
fee(XRP(1)),
|
||||
ter(tesSUCCESS));
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(
|
||||
expectRewardsIOU(
|
||||
env,
|
||||
bob,
|
||||
gw["USD"],
|
||||
currentLedger,
|
||||
currentLedger,
|
||||
gw["USD"](0),
|
||||
currentTime) == true);
|
||||
|
||||
// opt out
|
||||
env(reward::claim(bob),
|
||||
reward::claimCurrency(gw["USD"]),
|
||||
txflags(tfOptOut),
|
||||
ter(tesSUCCESS));
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(expectNoRewardsIOU(env, bob, gw["USD"]) == true);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
@@ -773,20 +352,16 @@ public:
|
||||
using namespace std::literals::chrono_literals;
|
||||
Env env{*this, features};
|
||||
auto const alice = Account("alice");
|
||||
auto const issuer = Account::master;
|
||||
env.fund(XRP(10000), alice);
|
||||
auto const issuer = Account("issuer");
|
||||
env.fund(XRP(10000), alice, issuer);
|
||||
std::uint32_t aliceTicketSeq{env.seq(alice) + 1};
|
||||
env(ticket::create(alice, 10));
|
||||
std::uint32_t const aliceSeq{env.seq(alice)};
|
||||
env.require(owners(alice, 10));
|
||||
|
||||
env(hook(issuer, {{hso(jtx::genesis::AcceptHook)}}, 0), fee(XRP(1)));
|
||||
env.close();
|
||||
|
||||
env(reward::claim(alice),
|
||||
reward::issuer(issuer),
|
||||
ticket::use(aliceTicketSeq++),
|
||||
fee(XRP(1)),
|
||||
ter(tesSUCCESS));
|
||||
|
||||
env.require(tickets(alice, env.seq(alice) - aliceTicketSeq));
|
||||
@@ -794,317 +369,6 @@ public:
|
||||
env.require(owners(alice, 9));
|
||||
}
|
||||
|
||||
void
|
||||
testBalanceChanges(FeatureBitset features)
|
||||
{
|
||||
testcase("balance changes");
|
||||
using namespace jtx;
|
||||
using namespace std::literals::chrono_literals;
|
||||
|
||||
auto const getCurrentTime = [&](Env& env) {
|
||||
return std::chrono::duration_cast<std::chrono::seconds>(
|
||||
env.app()
|
||||
.getLedgerMaster()
|
||||
.getValidatedLedger()
|
||||
->info()
|
||||
.parentCloseTime.time_since_epoch())
|
||||
.count();
|
||||
};
|
||||
|
||||
// Native Reward Claim
|
||||
{
|
||||
Env env{*this, features};
|
||||
auto const alice = Account("alice");
|
||||
auto const gw = Account("gw");
|
||||
|
||||
auto const issuer = Account::master;
|
||||
env.fund(XRP(10001), alice, gw);
|
||||
env.close();
|
||||
|
||||
env(hook(issuer, {{hso(jtx::genesis::AcceptHook)}}, 0),
|
||||
fee(XRP(1)));
|
||||
env.close();
|
||||
|
||||
auto const currentTime = getCurrentTime(env);
|
||||
auto const currentLedger = env.current()->seq();
|
||||
|
||||
env(reward::claim(alice), reward::issuer(issuer), fee(XRP(1)));
|
||||
env.close();
|
||||
|
||||
env(fset(alice, 0));
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(
|
||||
expectRewards(
|
||||
env,
|
||||
alice,
|
||||
currentLedger,
|
||||
currentLedger + 1,
|
||||
10000, // 10000 XAH * time 1
|
||||
currentTime) == true);
|
||||
}
|
||||
|
||||
// IOU Reward Claim
|
||||
for (bool const fromHighAccount : {true, false})
|
||||
{
|
||||
Env env{*this, features};
|
||||
auto const alice = Account("alice");
|
||||
auto const bob = Account("bob");
|
||||
auto const issuer = Account("issuer");
|
||||
|
||||
auto const user = fromHighAccount ? alice : bob;
|
||||
auto const gw = fromHighAccount ? bob : alice;
|
||||
|
||||
if (fromHighAccount)
|
||||
BEAST_EXPECT(user.id() < gw.id());
|
||||
else
|
||||
BEAST_EXPECT(user.id() > gw.id());
|
||||
|
||||
env.fund(XRP(10000), user, gw, issuer);
|
||||
env(fset(gw, asfDefaultRipple));
|
||||
|
||||
auto hookObj = hso(jtx::genesis::AcceptHook);
|
||||
hookObj[jss::HookOn] = to_string(~UINT256_BIT[ttCLAIM_REWARD]);
|
||||
env(hook(gw, {{hookObj}}, 0), fee(XRP(1)));
|
||||
env.close();
|
||||
|
||||
env(trust(user, gw["USD"](1000000)), fee(XRP(1)));
|
||||
env.close();
|
||||
env(pay(gw, user, gw["USD"](10000)));
|
||||
env.close();
|
||||
|
||||
auto currentTime = getCurrentTime(env);
|
||||
auto currentLedger = env.current()->seq();
|
||||
|
||||
env(reward::claim(user),
|
||||
reward::issuer(gw),
|
||||
reward::claimCurrency(gw["USD"]),
|
||||
fee(XRP(1)));
|
||||
env.close();
|
||||
|
||||
env(pay(user, gw, gw["USD"](10000)));
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(
|
||||
expectRewardsIOU(
|
||||
env,
|
||||
user,
|
||||
gw["USD"],
|
||||
currentLedger,
|
||||
currentLedger + 1,
|
||||
user["USD"](10000), // 10000 USD * time 1
|
||||
currentTime) == true);
|
||||
|
||||
env(pay(gw, user, gw["USD"](1)));
|
||||
env.close();
|
||||
|
||||
// check Balance == 0
|
||||
BEAST_EXPECT(
|
||||
expectRewardsIOU(
|
||||
env,
|
||||
user,
|
||||
gw["USD"],
|
||||
currentLedger,
|
||||
currentLedger + 2,
|
||||
user["USD"](10000), // 10000 USD * time 1 + 0 USD * time 1
|
||||
currentTime) == true);
|
||||
}
|
||||
|
||||
// Check Balance minus -> plus, plus -> minus
|
||||
for (bool const fromHighAccount : {true, false})
|
||||
{
|
||||
Env env{*this, features};
|
||||
auto const alice = Account("alice");
|
||||
auto const bob = Account("bob");
|
||||
auto const issuer = Account("issuer");
|
||||
|
||||
auto const user = fromHighAccount ? alice : bob;
|
||||
auto const gw = fromHighAccount ? bob : alice;
|
||||
|
||||
if (fromHighAccount)
|
||||
BEAST_EXPECT(user.id() < gw.id());
|
||||
else
|
||||
BEAST_EXPECT(user.id() > gw.id());
|
||||
|
||||
env.fund(XRP(10000), user, gw, issuer);
|
||||
env(fset(gw, asfDefaultRipple));
|
||||
env.close();
|
||||
|
||||
auto hookObj = hso(jtx::genesis::AcceptHook);
|
||||
hookObj[jss::HookOn] = to_string(~UINT256_BIT[ttCLAIM_REWARD]);
|
||||
env(hook(gw, {{hookObj}}, 0), fee(XRP(1)));
|
||||
env.close();
|
||||
|
||||
env(trust(user, gw["USD"](1000000)));
|
||||
env.close();
|
||||
env(trust(gw, user["USD"](1000000)));
|
||||
env(pay(gw, user, gw["USD"](10000)));
|
||||
env.close();
|
||||
|
||||
auto currentTime = getCurrentTime(env);
|
||||
auto currentLedger = env.current()->seq();
|
||||
|
||||
env(reward::claim(user),
|
||||
reward::issuer(gw),
|
||||
reward::claimCurrency(gw["USD"]),
|
||||
fee(XRP(1)));
|
||||
env.close();
|
||||
|
||||
env(pay(user, gw, gw["USD"](20000)));
|
||||
env.close();
|
||||
|
||||
env(pay(user, gw, gw["USD"](1)));
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(
|
||||
expectRewardsIOU(
|
||||
env,
|
||||
user,
|
||||
gw["USD"],
|
||||
currentLedger,
|
||||
currentLedger + 2,
|
||||
user["USD"](10000), // 10000 USD * time 1 + 0 USD * time 1
|
||||
currentTime) == true);
|
||||
}
|
||||
|
||||
// test with escrow (locked balance)
|
||||
for (bool const fromHighAccount : {true, false})
|
||||
{
|
||||
for (bool const hasEscrow : {true, false})
|
||||
{
|
||||
Env env{*this, features};
|
||||
auto const alice = Account("alice");
|
||||
auto const bob = Account("bob");
|
||||
auto const issuer = Account("issuer");
|
||||
|
||||
auto const user = fromHighAccount ? alice : bob;
|
||||
auto const gw = fromHighAccount ? bob : alice;
|
||||
|
||||
if (fromHighAccount)
|
||||
BEAST_EXPECT(user.id() < gw.id());
|
||||
else
|
||||
BEAST_EXPECT(user.id() > gw.id());
|
||||
|
||||
env.fund(XRP(10000), user, gw, issuer);
|
||||
env(fset(gw, asfDefaultRipple));
|
||||
|
||||
auto hookObj = hso(jtx::genesis::AcceptHook);
|
||||
hookObj[jss::HookOn] = to_string(~UINT256_BIT[ttCLAIM_REWARD]);
|
||||
env(hook(gw, {{hookObj}}, 0), fee(XRP(1)));
|
||||
env.close();
|
||||
|
||||
env(trust(user, gw["USD"](1000000)), fee(XRP(1)));
|
||||
env.close();
|
||||
env(pay(gw, user, gw["USD"](10000)));
|
||||
env.close();
|
||||
|
||||
if (hasEscrow)
|
||||
{
|
||||
env(escrow(user, user, gw["USD"](2000)),
|
||||
finish_time(env.now() + 1s),
|
||||
fee(XRP(1)));
|
||||
env.close();
|
||||
}
|
||||
|
||||
auto currentTime = getCurrentTime(env);
|
||||
auto currentLedger = env.current()->seq();
|
||||
|
||||
env(reward::claim(user),
|
||||
reward::issuer(gw),
|
||||
reward::claimCurrency(gw["USD"]),
|
||||
fee(XRP(1)));
|
||||
env.close();
|
||||
env(pay(user, gw, gw["USD"](5000)));
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(
|
||||
expectRewardsIOU(
|
||||
env,
|
||||
user,
|
||||
gw["USD"],
|
||||
currentLedger,
|
||||
currentLedger + 1,
|
||||
user["USD"](10000), // 10000 USD * time 1
|
||||
currentTime) == true);
|
||||
|
||||
env(pay(gw, user, gw["USD"](1)));
|
||||
env.close();
|
||||
|
||||
// check Balance == 0
|
||||
BEAST_EXPECT(
|
||||
expectRewardsIOU(
|
||||
env,
|
||||
user,
|
||||
gw["USD"],
|
||||
currentLedger,
|
||||
currentLedger + 2,
|
||||
user["USD"](
|
||||
15000), // 10000 USD * time 1 + 5000 USD * time 1
|
||||
currentTime) == true);
|
||||
}
|
||||
}
|
||||
|
||||
// STAmount overflow in reward accumulation should not cause
|
||||
// transaction failure (tefEXCEPTION). The overflow should be
|
||||
// gracefully skipped via try-catch.
|
||||
for (bool const fromHighAccount : {true, false})
|
||||
{
|
||||
Env env{*this, features};
|
||||
auto const alice = Account("alice");
|
||||
auto const bob = Account("bob");
|
||||
auto const issuer = Account("issuer");
|
||||
|
||||
auto const user = fromHighAccount ? alice : bob;
|
||||
auto const gw = fromHighAccount ? bob : alice;
|
||||
|
||||
env.fund(XRP(10000), user, gw, issuer);
|
||||
env(fset(gw, asfDefaultRipple));
|
||||
env.close();
|
||||
|
||||
auto hookObj = hso(jtx::genesis::AcceptHook);
|
||||
hookObj[jss::HookOn] = to_string(~UINT256_BIT[ttCLAIM_REWARD]);
|
||||
env(hook(gw, {{hookObj}}, 0), fee(XRP(1)));
|
||||
env.close();
|
||||
|
||||
// Use a near-max IOU balance at exponent 80. When
|
||||
// multiply(balance, STAmount(lgrElapsed), issue) is called
|
||||
// with lgrElapsed >= 2, the result exponent exceeds
|
||||
// cMaxOffset(80), causing IOUAmount::normalize to throw
|
||||
// std::overflow_error("value overflow").
|
||||
auto const bigUSD = STAmount{
|
||||
gw["USD"].issue(), std::uint64_t(5000000000000000ull), 80};
|
||||
// Payment amount must be large enough to register a
|
||||
// balance change given STAmount's 16-digit precision.
|
||||
auto const payBackUSD = STAmount{
|
||||
gw["USD"].issue(), std::uint64_t(1000000000000000ull), 80};
|
||||
|
||||
env(trust(user, bigUSD));
|
||||
env.close();
|
||||
env(pay(gw, user, bigUSD));
|
||||
env.close();
|
||||
|
||||
// Claim IOU reward to initialize reward tracking
|
||||
env(reward::claim(user),
|
||||
reward::issuer(gw),
|
||||
reward::claimCurrency(gw["USD"]),
|
||||
fee(XRP(1)));
|
||||
env.close();
|
||||
|
||||
// Advance ledger so lgrElapsed >= 2. With lgrElapsed=1
|
||||
// the multiply result is exactly at cMaxOffset boundary
|
||||
// (no overflow). With lgrElapsed >= 2, the result exponent
|
||||
// exceeds cMaxOffset and triggers the overflow.
|
||||
env.close();
|
||||
|
||||
// This payment modifies the trustline balance, triggering
|
||||
// reward accumulation in Transactor. Without the try-catch
|
||||
// fix, multiply() throws std::overflow_error("value overflow")
|
||||
// and the transaction fails with tefEXCEPTION.
|
||||
env(pay(user, gw, payBackUSD), ter(tesSUCCESS));
|
||||
env.close();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testWithFeats(FeatureBitset features)
|
||||
{
|
||||
@@ -1113,7 +377,6 @@ public:
|
||||
testInvalidPreclaim(features);
|
||||
testValidNoHook(features);
|
||||
testUsingTickets(features);
|
||||
testBalanceChanges(features);
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
@@ -1,502 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2026 XRPL Labs
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <test/app/ConsensusEntropy_test_hooks.h>
|
||||
#include <test/jtx.h>
|
||||
#include <test/jtx/hook.h>
|
||||
#include <xrpl/beast/unit_test.h>
|
||||
#include <xrpl/hook/Enum.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
|
||||
using TestHook = std::vector<uint8_t> const&;
|
||||
|
||||
#define BEAST_REQUIRE(x) \
|
||||
{ \
|
||||
BEAST_EXPECT(!!(x)); \
|
||||
if (!(x)) \
|
||||
return; \
|
||||
}
|
||||
|
||||
#define HSFEE fee(100'000'000)
|
||||
#define M(m) memo(m, "", "")
|
||||
|
||||
class ConsensusEntropy_test : public beast::unit_test::suite
|
||||
{
|
||||
static void
|
||||
overrideFlag(Json::Value& jv)
|
||||
{
|
||||
jv[jss::Flags] = hsfOVERRIDE;
|
||||
}
|
||||
|
||||
void
|
||||
testSLECreated()
|
||||
{
|
||||
testcase("SLE created on ledger close");
|
||||
using namespace jtx;
|
||||
|
||||
Env env{
|
||||
*this,
|
||||
envconfig(),
|
||||
supported_amendments() | featureConsensusEntropy,
|
||||
nullptr};
|
||||
|
||||
BEAST_EXPECT(!env.le(keylet::consensusEntropy()));
|
||||
|
||||
env.close();
|
||||
|
||||
auto const sle = env.le(keylet::consensusEntropy());
|
||||
BEAST_REQUIRE(sle);
|
||||
|
||||
auto const digest = sle->getFieldH256(sfDigest);
|
||||
BEAST_EXPECT(digest != uint256{});
|
||||
|
||||
auto const count = sle->getFieldU16(sfEntropyCount);
|
||||
BEAST_EXPECT(count >= 5);
|
||||
|
||||
auto const sleSeq = sle->getFieldU32(sfLedgerSequence);
|
||||
BEAST_EXPECT(sleSeq == env.closed()->seq());
|
||||
}
|
||||
|
||||
void
|
||||
testSLEUpdatedOnSubsequentClose()
|
||||
{
|
||||
testcase("SLE updated on subsequent ledger close");
|
||||
using namespace jtx;
|
||||
|
||||
Env env{
|
||||
*this,
|
||||
envconfig(),
|
||||
supported_amendments() | featureConsensusEntropy,
|
||||
nullptr};
|
||||
|
||||
env.close();
|
||||
auto const sle1 = env.le(keylet::consensusEntropy());
|
||||
BEAST_REQUIRE(sle1);
|
||||
|
||||
auto const digest1 = sle1->getFieldH256(sfDigest);
|
||||
auto const seq1 = sle1->getFieldU32(sfLedgerSequence);
|
||||
|
||||
env.close();
|
||||
|
||||
auto const sle2 = env.le(keylet::consensusEntropy());
|
||||
BEAST_REQUIRE(sle2);
|
||||
|
||||
auto const digest2 = sle2->getFieldH256(sfDigest);
|
||||
auto const seq2 = sle2->getFieldU32(sfLedgerSequence);
|
||||
|
||||
BEAST_EXPECT(digest2 != digest1);
|
||||
BEAST_EXPECT(seq2 == seq1 + 1);
|
||||
}
|
||||
|
||||
void
|
||||
testNoSLEWithoutAmendment()
|
||||
{
|
||||
testcase("No SLE without amendment");
|
||||
using namespace jtx;
|
||||
|
||||
Env env{*this};
|
||||
|
||||
env.close();
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(!env.le(keylet::consensusEntropy()));
|
||||
}
|
||||
|
||||
void
|
||||
testDice()
|
||||
{
|
||||
testcase("Hook dice() API");
|
||||
using namespace jtx;
|
||||
|
||||
Env env{
|
||||
*this,
|
||||
envconfig(),
|
||||
supported_amendments() | featureConsensusEntropy,
|
||||
nullptr};
|
||||
|
||||
auto const alice = Account{"alice"};
|
||||
env.fund(XRP(10000), alice);
|
||||
env.close();
|
||||
|
||||
// Entropy SLE must exist before hook can use dice()
|
||||
BEAST_REQUIRE(env.le(keylet::consensusEntropy()));
|
||||
|
||||
// Set the hook
|
||||
TestHook hook = consensusentropy_test_wasm[R"[test.hook](
|
||||
#include <stdint.h>
|
||||
extern int32_t _g(uint32_t, uint32_t);
|
||||
extern int64_t accept(uint32_t read_ptr, uint32_t read_len, int64_t error_code);
|
||||
extern int64_t rollback(uint32_t read_ptr, uint32_t read_len, int64_t error_code);
|
||||
extern int64_t dice(uint32_t sides, uint32_t min_tier, uint32_t min_count);
|
||||
#define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1)
|
||||
|
||||
int64_t hook(uint32_t r)
|
||||
{
|
||||
_g(1,1);
|
||||
|
||||
// dice(6) should return 0..5
|
||||
int64_t result = dice(6, 3, 5);
|
||||
|
||||
// negative means error
|
||||
if (result < 0)
|
||||
rollback(0, 0, result);
|
||||
|
||||
if (result >= 6)
|
||||
rollback(0, 0, -1);
|
||||
|
||||
// return the dice result as the accept code
|
||||
return accept(0, 0, result);
|
||||
}
|
||||
)[test.hook]"];
|
||||
|
||||
env(ripple::test::jtx::hook(alice, {{hso(hook, overrideFlag)}}, 0),
|
||||
M("set dice hook"),
|
||||
HSFEE);
|
||||
env.close();
|
||||
|
||||
// Invoke the hook
|
||||
Json::Value invoke;
|
||||
invoke[jss::TransactionType] = "Invoke";
|
||||
invoke[jss::Account] = alice.human();
|
||||
env(invoke, M("test dice"), fee(XRP(1)));
|
||||
|
||||
auto meta = env.meta();
|
||||
BEAST_REQUIRE(meta);
|
||||
BEAST_REQUIRE(meta->isFieldPresent(sfHookExecutions));
|
||||
|
||||
auto const hookExecutions = meta->getFieldArray(sfHookExecutions);
|
||||
BEAST_REQUIRE(hookExecutions.size() == 1);
|
||||
|
||||
auto const returnCode = hookExecutions[0].getFieldU64(sfHookReturnCode);
|
||||
std::cerr << " dice(6) returnCode = " << returnCode << " (hex 0x"
|
||||
<< std::hex << returnCode << std::dec << ")\n";
|
||||
// dice(6) returns 0..5
|
||||
BEAST_EXPECT(returnCode <= 5);
|
||||
|
||||
// Result should be 3 (accept)
|
||||
BEAST_EXPECT(hookExecutions[0].getFieldU8(sfHookResult) == 3);
|
||||
}
|
||||
|
||||
void
|
||||
testRandom()
|
||||
{
|
||||
testcase("Hook random() API");
|
||||
using namespace jtx;
|
||||
|
||||
Env env{
|
||||
*this,
|
||||
envconfig(),
|
||||
supported_amendments() | featureConsensusEntropy,
|
||||
nullptr};
|
||||
|
||||
auto const alice = Account{"alice"};
|
||||
env.fund(XRP(10000), alice);
|
||||
env.close();
|
||||
|
||||
BEAST_REQUIRE(env.le(keylet::consensusEntropy()));
|
||||
|
||||
// Hook calls random() to fill a 32-byte buffer, then checks
|
||||
// the buffer is not all zeroes.
|
||||
TestHook hook = consensusentropy_test_wasm[R"[test.hook](
|
||||
#include <stdint.h>
|
||||
extern int32_t _g(uint32_t, uint32_t);
|
||||
extern int64_t accept(uint32_t read_ptr, uint32_t read_len, int64_t error_code);
|
||||
extern int64_t rollback(uint32_t read_ptr, uint32_t read_len, int64_t error_code);
|
||||
extern int64_t random(uint32_t write_ptr, uint32_t write_len, uint32_t min_tier, uint32_t min_count);
|
||||
#define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1)
|
||||
|
||||
int64_t hook(uint32_t r)
|
||||
{
|
||||
_g(1,1);
|
||||
|
||||
uint8_t buf[32];
|
||||
for (int i = 0; GUARD(32), i < 32; ++i)
|
||||
buf[i] = 0;
|
||||
|
||||
int64_t result = random((uint32_t)buf, 32, 3, 5);
|
||||
|
||||
// Should return 32 (bytes written)
|
||||
if (result != 32)
|
||||
rollback(0, 0, result);
|
||||
|
||||
// Verify buffer is not all zeroes
|
||||
int nonzero = 0;
|
||||
for (int i = 0; GUARD(32), i < 32; ++i)
|
||||
if (buf[i] != 0) nonzero = 1;
|
||||
|
||||
if (!nonzero)
|
||||
rollback(0, 0, -2);
|
||||
|
||||
return accept(0, 0, 0);
|
||||
}
|
||||
)[test.hook]"];
|
||||
|
||||
env(ripple::test::jtx::hook(alice, {{hso(hook, overrideFlag)}}, 0),
|
||||
M("set random hook"),
|
||||
HSFEE);
|
||||
env.close();
|
||||
|
||||
Json::Value invoke;
|
||||
invoke[jss::TransactionType] = "Invoke";
|
||||
invoke[jss::Account] = alice.human();
|
||||
env(invoke, M("test random"), fee(XRP(1)));
|
||||
|
||||
auto meta = env.meta();
|
||||
BEAST_REQUIRE(meta);
|
||||
BEAST_REQUIRE(meta->isFieldPresent(sfHookExecutions));
|
||||
|
||||
auto const hookExecutions = meta->getFieldArray(sfHookExecutions);
|
||||
BEAST_REQUIRE(hookExecutions.size() == 1);
|
||||
|
||||
// Return code 0 = all checks passed in the hook
|
||||
BEAST_EXPECT(hookExecutions[0].getFieldU64(sfHookReturnCode) == 0);
|
||||
BEAST_EXPECT(hookExecutions[0].getFieldU8(sfHookResult) == 3);
|
||||
}
|
||||
|
||||
void
|
||||
testDiceConsecutiveCallsDiffer()
|
||||
{
|
||||
testcase("Hook dice() consecutive calls return different values");
|
||||
using namespace jtx;
|
||||
|
||||
Env env{
|
||||
*this,
|
||||
envconfig(),
|
||||
supported_amendments() | featureConsensusEntropy,
|
||||
nullptr};
|
||||
|
||||
auto const alice = Account{"alice"};
|
||||
env.fund(XRP(10000), alice);
|
||||
env.close();
|
||||
|
||||
BEAST_REQUIRE(env.le(keylet::consensusEntropy()));
|
||||
|
||||
// dice(1000000) twice — large range makes collision near-impossible
|
||||
// encode r1 in low 20 bits, r2 in high bits
|
||||
TestHook hook = consensusentropy_test_wasm[R"[test.hook](
|
||||
#include <stdint.h>
|
||||
extern int32_t _g(uint32_t, uint32_t);
|
||||
extern int64_t accept(uint32_t read_ptr, uint32_t read_len, int64_t error_code);
|
||||
extern int64_t rollback(uint32_t read_ptr, uint32_t read_len, int64_t error_code);
|
||||
extern int64_t dice(uint32_t sides, uint32_t min_tier, uint32_t min_count);
|
||||
|
||||
int64_t hook(uint32_t r)
|
||||
{
|
||||
_g(1,1);
|
||||
int64_t r1 = dice(1000000, 3, 5);
|
||||
if (r1 < 0)
|
||||
rollback(0, 0, r1);
|
||||
|
||||
int64_t r2 = dice(1000000, 3, 5);
|
||||
if (r2 < 0)
|
||||
rollback(0, 0, r2);
|
||||
|
||||
// consecutive calls should differ (rngCallCounter)
|
||||
if (r1 == r2)
|
||||
rollback(0, 0, -1);
|
||||
|
||||
return accept(0, 0, r1 | (r2 << 20));
|
||||
}
|
||||
)[test.hook]"];
|
||||
|
||||
env(ripple::test::jtx::hook(alice, {{hso(hook, overrideFlag)}}, 0),
|
||||
M("set dice hook"),
|
||||
HSFEE);
|
||||
env.close();
|
||||
|
||||
Json::Value invoke;
|
||||
invoke[jss::TransactionType] = "Invoke";
|
||||
invoke[jss::Account] = alice.human();
|
||||
env(invoke, M("test dice consecutive"), fee(XRP(1)));
|
||||
|
||||
auto meta = env.meta();
|
||||
BEAST_REQUIRE(meta);
|
||||
BEAST_REQUIRE(meta->isFieldPresent(sfHookExecutions));
|
||||
|
||||
auto const hookExecutions = meta->getFieldArray(sfHookExecutions);
|
||||
BEAST_REQUIRE(hookExecutions.size() == 1);
|
||||
|
||||
auto const rc = hookExecutions[0].getFieldU64(sfHookReturnCode);
|
||||
auto const r1 = rc & 0xFFFFF;
|
||||
auto const r2 = (rc >> 20) & 0xFFFFF;
|
||||
|
||||
std::cerr << " two-call dice(1000000): returnCode=" << rc << " hex=0x"
|
||||
<< std::hex << rc << std::dec << " r1=" << r1 << " r2=" << r2
|
||||
<< "\n";
|
||||
|
||||
// hookResult 3 = accept (would be 1 if r1==r2 triggered rollback)
|
||||
BEAST_EXPECT(hookExecutions[0].getFieldU8(sfHookResult) == 3);
|
||||
BEAST_EXPECT(r1 < 1000000);
|
||||
BEAST_EXPECT(r2 < 1000000);
|
||||
BEAST_EXPECT(r1 != r2);
|
||||
}
|
||||
|
||||
void
|
||||
testDiceZeroSides()
|
||||
{
|
||||
testcase("Hook dice(0) returns INVALID_ARGUMENT");
|
||||
using namespace jtx;
|
||||
|
||||
Env env{
|
||||
*this,
|
||||
envconfig(),
|
||||
supported_amendments() | featureConsensusEntropy,
|
||||
nullptr};
|
||||
|
||||
auto const alice = Account{"alice"};
|
||||
env.fund(XRP(10000), alice);
|
||||
env.close();
|
||||
|
||||
BEAST_REQUIRE(env.le(keylet::consensusEntropy()));
|
||||
|
||||
// Hook calls dice(0) and returns whatever dice returns.
|
||||
// dice(0) should return INVALID_ARGUMENT (-7).
|
||||
TestHook hook = consensusentropy_test_wasm[R"[test.hook](
|
||||
#include <stdint.h>
|
||||
extern int32_t _g(uint32_t, uint32_t);
|
||||
extern int64_t accept(uint32_t read_ptr, uint32_t read_len, int64_t error_code);
|
||||
extern int64_t dice(uint32_t sides, uint32_t min_tier, uint32_t min_count);
|
||||
|
||||
int64_t hook(uint32_t r)
|
||||
{
|
||||
_g(1,1);
|
||||
int64_t result = dice(0, 3, 5);
|
||||
// dice(0) should return negative error code, pass it through
|
||||
return accept(0, 0, result);
|
||||
}
|
||||
)[test.hook]"];
|
||||
|
||||
env(ripple::test::jtx::hook(alice, {{hso(hook, overrideFlag)}}, 0),
|
||||
M("set dice0 hook"),
|
||||
HSFEE);
|
||||
env.close();
|
||||
|
||||
Json::Value invoke;
|
||||
invoke[jss::TransactionType] = "Invoke";
|
||||
invoke[jss::Account] = alice.human();
|
||||
env(invoke, M("test dice(0)"), fee(XRP(1)));
|
||||
|
||||
auto meta = env.meta();
|
||||
BEAST_REQUIRE(meta);
|
||||
BEAST_REQUIRE(meta->isFieldPresent(sfHookExecutions));
|
||||
|
||||
auto const hookExecutions = meta->getFieldArray(sfHookExecutions);
|
||||
BEAST_REQUIRE(hookExecutions.size() == 1);
|
||||
|
||||
// INVALID_ARGUMENT = -7, encoded as 0x8000000000000000 + abs(code)
|
||||
// (see applyHook.cpp unsigned_exit_code encoding)
|
||||
auto const rawCode = hookExecutions[0].getFieldU64(sfHookReturnCode);
|
||||
int64_t returnCode = (rawCode & 0x8000000000000000ULL)
|
||||
? -static_cast<int64_t>(rawCode & 0x7FFFFFFFFFFFFFFFULL)
|
||||
: static_cast<int64_t>(rawCode);
|
||||
std::cerr << " dice(0) returnCode = " << returnCode << " (raw 0x"
|
||||
<< std::hex << rawCode << std::dec << ")\n";
|
||||
BEAST_EXPECT(returnCode == -7);
|
||||
BEAST_EXPECT(hookExecutions[0].getFieldU8(sfHookResult) == 3);
|
||||
}
|
||||
|
||||
void
|
||||
testDiceRequirementNotMet()
|
||||
{
|
||||
testcase("Hook dice() returns TOO_LITTLE_ENTROPY below requirement");
|
||||
using namespace jtx;
|
||||
|
||||
Env env{
|
||||
*this,
|
||||
envconfig(),
|
||||
supported_amendments() | featureConsensusEntropy,
|
||||
nullptr};
|
||||
|
||||
auto const alice = Account{"alice"};
|
||||
env.fund(XRP(10000), alice);
|
||||
env.close();
|
||||
|
||||
BEAST_REQUIRE(env.le(keylet::consensusEntropy()));
|
||||
|
||||
// Standalone entropy carries EntropyCount=20 / tier validator_quorum.
|
||||
// A hook demanding min_count=21 states a requirement this ledger
|
||||
// cannot meet, so dice must fail closed with TOO_LITTLE_ENTROPY (-48)
|
||||
// rather than silently serving weaker entropy.
|
||||
TestHook hook = consensusentropy_test_wasm[R"[test.hook](
|
||||
#include <stdint.h>
|
||||
extern int32_t _g(uint32_t, uint32_t);
|
||||
extern int64_t accept(uint32_t read_ptr, uint32_t read_len, int64_t error_code);
|
||||
extern int64_t dice(uint32_t sides, uint32_t min_tier, uint32_t min_count);
|
||||
|
||||
int64_t hook(uint32_t r)
|
||||
{
|
||||
_g(1,1);
|
||||
int64_t result = dice(6, 3, 21);
|
||||
// requirement unmet: pass the error code through
|
||||
return accept(0, 0, result);
|
||||
}
|
||||
)[test.hook]"];
|
||||
|
||||
env(ripple::test::jtx::hook(alice, {{hso(hook, overrideFlag)}}, 0),
|
||||
M("set dice-requirement hook"),
|
||||
HSFEE);
|
||||
env.close();
|
||||
|
||||
Json::Value invoke;
|
||||
invoke[jss::TransactionType] = "Invoke";
|
||||
invoke[jss::Account] = alice.human();
|
||||
env(invoke, M("test dice min_count unmet"), fee(XRP(1)));
|
||||
|
||||
auto meta = env.meta();
|
||||
BEAST_REQUIRE(meta);
|
||||
BEAST_REQUIRE(meta->isFieldPresent(sfHookExecutions));
|
||||
|
||||
auto const hookExecutions = meta->getFieldArray(sfHookExecutions);
|
||||
BEAST_REQUIRE(hookExecutions.size() == 1);
|
||||
|
||||
auto const rawCode = hookExecutions[0].getFieldU64(sfHookReturnCode);
|
||||
int64_t returnCode = (rawCode & 0x8000000000000000ULL)
|
||||
? -static_cast<int64_t>(rawCode & 0x7FFFFFFFFFFFFFFFULL)
|
||||
: static_cast<int64_t>(rawCode);
|
||||
std::cerr << " dice(6,3,21) returnCode = " << returnCode << " (raw 0x"
|
||||
<< std::hex << rawCode << std::dec << ")\n";
|
||||
BEAST_EXPECT(returnCode == -48); // TOO_LITTLE_ENTROPY
|
||||
BEAST_EXPECT(hookExecutions[0].getFieldU8(sfHookResult) == 3);
|
||||
}
|
||||
|
||||
void
|
||||
run() override
|
||||
{
|
||||
testSLECreated();
|
||||
testSLEUpdatedOnSubsequentClose();
|
||||
testNoSLEWithoutAmendment();
|
||||
testDice();
|
||||
testDiceZeroSides();
|
||||
testDiceRequirementNotMet();
|
||||
testRandom();
|
||||
testDiceConsecutiveCallsDiffer();
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(ConsensusEntropy, app, ripple);
|
||||
|
||||
} // namespace test
|
||||
} // namespace ripple
|
||||
@@ -1,256 +0,0 @@
|
||||
|
||||
// This file is generated by hookz build-test-hooks
|
||||
#ifndef CONSENSUSENTROPY_TEST_WASM_INCLUDED
|
||||
#define CONSENSUSENTROPY_TEST_WASM_INCLUDED
|
||||
#include <map>
|
||||
#include <stdint.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
inline std::map<std::string, std::vector<uint8_t>> consensusentropy_test_wasm =
|
||||
{
|
||||
/* ==== WASM: 0 ==== */
|
||||
{R"[test.hook](
|
||||
#include <stdint.h>
|
||||
extern int32_t _g(uint32_t, uint32_t);
|
||||
extern int64_t accept(uint32_t read_ptr, uint32_t read_len, int64_t error_code);
|
||||
extern int64_t rollback(uint32_t read_ptr, uint32_t read_len, int64_t error_code);
|
||||
extern int64_t dice(uint32_t sides, uint32_t min_tier, uint32_t min_count);
|
||||
#define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1)
|
||||
|
||||
int64_t hook(uint32_t r)
|
||||
{
|
||||
_g(1,1);
|
||||
|
||||
// dice(6) should return 0..5
|
||||
int64_t result = dice(6, 3, 5);
|
||||
|
||||
// negative means error
|
||||
if (result < 0)
|
||||
rollback(0, 0, result);
|
||||
|
||||
if (result >= 6)
|
||||
rollback(0, 0, -1);
|
||||
|
||||
// return the dice result as the accept code
|
||||
return accept(0, 0, result);
|
||||
}
|
||||
)[test.hook]",
|
||||
{
|
||||
0x00U, 0x61U, 0x73U, 0x6DU, 0x01U, 0x00U, 0x00U, 0x00U, 0x01U,
|
||||
0x1AU, 0x04U, 0x60U, 0x03U, 0x7FU, 0x7FU, 0x7EU, 0x01U, 0x7EU,
|
||||
0x60U, 0x02U, 0x7FU, 0x7FU, 0x01U, 0x7FU, 0x60U, 0x03U, 0x7FU,
|
||||
0x7FU, 0x7FU, 0x01U, 0x7EU, 0x60U, 0x01U, 0x7FU, 0x01U, 0x7EU,
|
||||
0x02U, 0x31U, 0x04U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x02U, 0x5FU,
|
||||
0x67U, 0x00U, 0x01U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x04U, 0x64U,
|
||||
0x69U, 0x63U, 0x65U, 0x00U, 0x02U, 0x03U, 0x65U, 0x6EU, 0x76U,
|
||||
0x08U, 0x72U, 0x6FU, 0x6CU, 0x6CU, 0x62U, 0x61U, 0x63U, 0x6BU,
|
||||
0x00U, 0x00U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x06U, 0x61U, 0x63U,
|
||||
0x63U, 0x65U, 0x70U, 0x74U, 0x00U, 0x00U, 0x03U, 0x02U, 0x01U,
|
||||
0x03U, 0x05U, 0x03U, 0x01U, 0x00U, 0x01U, 0x07U, 0x08U, 0x01U,
|
||||
0x04U, 0x68U, 0x6FU, 0x6FU, 0x6BU, 0x00U, 0x04U, 0x0AU, 0x40U,
|
||||
0x01U, 0x3EU, 0x01U, 0x02U, 0x7EU, 0x41U, 0x01U, 0x41U, 0x01U,
|
||||
0x10U, 0x00U, 0x1AU, 0x41U, 0x06U, 0x41U, 0x03U, 0x41U, 0x05U,
|
||||
0x10U, 0x01U, 0x22U, 0x01U, 0x21U, 0x02U, 0x02U, 0x40U, 0x20U,
|
||||
0x01U, 0x42U, 0x00U, 0x59U, 0x04U, 0x40U, 0x42U, 0x7FU, 0x21U,
|
||||
0x02U, 0x20U, 0x01U, 0x42U, 0x06U, 0x54U, 0x0DU, 0x01U, 0x0BU,
|
||||
0x41U, 0x00U, 0x41U, 0x00U, 0x20U, 0x02U, 0x10U, 0x02U, 0x1AU,
|
||||
0x0BU, 0x41U, 0x00U, 0x41U, 0x00U, 0x20U, 0x01U, 0x10U, 0x03U,
|
||||
0x0BU,
|
||||
}},
|
||||
|
||||
/* ==== WASM: 1 ==== */
|
||||
{R"[test.hook](
|
||||
#include <stdint.h>
|
||||
extern int32_t _g(uint32_t, uint32_t);
|
||||
extern int64_t accept(uint32_t read_ptr, uint32_t read_len, int64_t error_code);
|
||||
extern int64_t rollback(uint32_t read_ptr, uint32_t read_len, int64_t error_code);
|
||||
extern int64_t random(uint32_t write_ptr, uint32_t write_len, uint32_t min_tier, uint32_t min_count);
|
||||
#define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1)
|
||||
|
||||
int64_t hook(uint32_t r)
|
||||
{
|
||||
_g(1,1);
|
||||
|
||||
uint8_t buf[32];
|
||||
for (int i = 0; GUARD(32), i < 32; ++i)
|
||||
buf[i] = 0;
|
||||
|
||||
int64_t result = random((uint32_t)buf, 32, 3, 5);
|
||||
|
||||
// Should return 32 (bytes written)
|
||||
if (result != 32)
|
||||
rollback(0, 0, result);
|
||||
|
||||
// Verify buffer is not all zeroes
|
||||
int nonzero = 0;
|
||||
for (int i = 0; GUARD(32), i < 32; ++i)
|
||||
if (buf[i] != 0) nonzero = 1;
|
||||
|
||||
if (!nonzero)
|
||||
rollback(0, 0, -2);
|
||||
|
||||
return accept(0, 0, 0);
|
||||
}
|
||||
)[test.hook]",
|
||||
{
|
||||
0x00U, 0x61U, 0x73U, 0x6DU, 0x01U, 0x00U, 0x00U, 0x00U, 0x01U,
|
||||
0x1BU, 0x04U, 0x60U, 0x03U, 0x7FU, 0x7FU, 0x7EU, 0x01U, 0x7EU,
|
||||
0x60U, 0x02U, 0x7FU, 0x7FU, 0x01U, 0x7FU, 0x60U, 0x04U, 0x7FU,
|
||||
0x7FU, 0x7FU, 0x7FU, 0x01U, 0x7EU, 0x60U, 0x01U, 0x7FU, 0x01U,
|
||||
0x7EU, 0x02U, 0x33U, 0x04U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x02U,
|
||||
0x5FU, 0x67U, 0x00U, 0x01U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x06U,
|
||||
0x72U, 0x61U, 0x6EU, 0x64U, 0x6FU, 0x6DU, 0x00U, 0x02U, 0x03U,
|
||||
0x65U, 0x6EU, 0x76U, 0x08U, 0x72U, 0x6FU, 0x6CU, 0x6CU, 0x62U,
|
||||
0x61U, 0x63U, 0x6BU, 0x00U, 0x00U, 0x03U, 0x65U, 0x6EU, 0x76U,
|
||||
0x06U, 0x61U, 0x63U, 0x63U, 0x65U, 0x70U, 0x74U, 0x00U, 0x00U,
|
||||
0x03U, 0x02U, 0x01U, 0x03U, 0x05U, 0x03U, 0x01U, 0x00U, 0x01U,
|
||||
0x06U, 0x08U, 0x01U, 0x7FU, 0x01U, 0x41U, 0x80U, 0x80U, 0x04U,
|
||||
0x0BU, 0x07U, 0x08U, 0x01U, 0x04U, 0x68U, 0x6FU, 0x6FU, 0x6BU,
|
||||
0x00U, 0x04U, 0x0AU, 0xD0U, 0x01U, 0x01U, 0xCDU, 0x01U, 0x02U,
|
||||
0x03U, 0x7FU, 0x01U, 0x7EU, 0x23U, 0x00U, 0x41U, 0x20U, 0x6BU,
|
||||
0x22U, 0x01U, 0x24U, 0x00U, 0x41U, 0x01U, 0x41U, 0x01U, 0x10U,
|
||||
0x00U, 0x1AU, 0x41U, 0x8EU, 0x80U, 0x80U, 0x80U, 0x78U, 0x41U,
|
||||
0x21U, 0x10U, 0x00U, 0x1AU, 0x41U, 0x00U, 0x21U, 0x00U, 0x03U,
|
||||
0x40U, 0x41U, 0x8EU, 0x80U, 0x80U, 0x80U, 0x78U, 0x41U, 0x21U,
|
||||
0x10U, 0x00U, 0x1AU, 0x20U, 0x00U, 0x20U, 0x01U, 0x6AU, 0x41U,
|
||||
0x00U, 0x3AU, 0x00U, 0x00U, 0x01U, 0x01U, 0x01U, 0x01U, 0x01U,
|
||||
0x01U, 0x01U, 0x01U, 0x01U, 0x01U, 0x01U, 0x20U, 0x00U, 0x41U,
|
||||
0x01U, 0x6AU, 0x22U, 0x00U, 0x41U, 0x20U, 0x47U, 0x0DU, 0x00U,
|
||||
0x0BU, 0x20U, 0x01U, 0x41U, 0x20U, 0x41U, 0x03U, 0x41U, 0x05U,
|
||||
0x10U, 0x01U, 0x22U, 0x04U, 0x42U, 0x20U, 0x52U, 0x04U, 0x40U,
|
||||
0x41U, 0x00U, 0x41U, 0x00U, 0x20U, 0x04U, 0x10U, 0x02U, 0x1AU,
|
||||
0x0BU, 0x41U, 0x99U, 0x80U, 0x80U, 0x80U, 0x78U, 0x41U, 0x21U,
|
||||
0x10U, 0x00U, 0x1AU, 0x41U, 0x00U, 0x21U, 0x00U, 0x03U, 0x40U,
|
||||
0x41U, 0x99U, 0x80U, 0x80U, 0x80U, 0x78U, 0x41U, 0x21U, 0x10U,
|
||||
0x00U, 0x1AU, 0x20U, 0x00U, 0x20U, 0x01U, 0x6AU, 0x2DU, 0x00U,
|
||||
0x00U, 0x21U, 0x03U, 0x41U, 0x01U, 0x20U, 0x02U, 0x20U, 0x03U,
|
||||
0x1BU, 0x21U, 0x02U, 0x20U, 0x00U, 0x41U, 0x01U, 0x6AU, 0x22U,
|
||||
0x00U, 0x41U, 0x20U, 0x47U, 0x0DU, 0x00U, 0x0BU, 0x20U, 0x02U,
|
||||
0x45U, 0x04U, 0x40U, 0x41U, 0x00U, 0x41U, 0x00U, 0x42U, 0x7EU,
|
||||
0x10U, 0x02U, 0x1AU, 0x0BU, 0x41U, 0x00U, 0x41U, 0x00U, 0x42U,
|
||||
0x00U, 0x10U, 0x03U, 0x21U, 0x04U, 0x20U, 0x01U, 0x41U, 0x20U,
|
||||
0x6AU, 0x24U, 0x00U, 0x20U, 0x04U, 0x0BU,
|
||||
}},
|
||||
|
||||
/* ==== WASM: 2 ==== */
|
||||
{R"[test.hook](
|
||||
#include <stdint.h>
|
||||
extern int32_t _g(uint32_t, uint32_t);
|
||||
extern int64_t accept(uint32_t read_ptr, uint32_t read_len, int64_t error_code);
|
||||
extern int64_t rollback(uint32_t read_ptr, uint32_t read_len, int64_t error_code);
|
||||
extern int64_t dice(uint32_t sides, uint32_t min_tier, uint32_t min_count);
|
||||
|
||||
int64_t hook(uint32_t r)
|
||||
{
|
||||
_g(1,1);
|
||||
int64_t r1 = dice(1000000, 3, 5);
|
||||
if (r1 < 0)
|
||||
rollback(0, 0, r1);
|
||||
|
||||
int64_t r2 = dice(1000000, 3, 5);
|
||||
if (r2 < 0)
|
||||
rollback(0, 0, r2);
|
||||
|
||||
// consecutive calls should differ (rngCallCounter)
|
||||
if (r1 == r2)
|
||||
rollback(0, 0, -1);
|
||||
|
||||
return accept(0, 0, r1 | (r2 << 20));
|
||||
}
|
||||
)[test.hook]",
|
||||
{
|
||||
0x00U, 0x61U, 0x73U, 0x6DU, 0x01U, 0x00U, 0x00U, 0x00U, 0x01U,
|
||||
0x1AU, 0x04U, 0x60U, 0x03U, 0x7FU, 0x7FU, 0x7EU, 0x01U, 0x7EU,
|
||||
0x60U, 0x02U, 0x7FU, 0x7FU, 0x01U, 0x7FU, 0x60U, 0x03U, 0x7FU,
|
||||
0x7FU, 0x7FU, 0x01U, 0x7EU, 0x60U, 0x01U, 0x7FU, 0x01U, 0x7EU,
|
||||
0x02U, 0x31U, 0x04U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x02U, 0x5FU,
|
||||
0x67U, 0x00U, 0x01U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x04U, 0x64U,
|
||||
0x69U, 0x63U, 0x65U, 0x00U, 0x02U, 0x03U, 0x65U, 0x6EU, 0x76U,
|
||||
0x08U, 0x72U, 0x6FU, 0x6CU, 0x6CU, 0x62U, 0x61U, 0x63U, 0x6BU,
|
||||
0x00U, 0x00U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x06U, 0x61U, 0x63U,
|
||||
0x63U, 0x65U, 0x70U, 0x74U, 0x00U, 0x00U, 0x03U, 0x02U, 0x01U,
|
||||
0x03U, 0x05U, 0x03U, 0x01U, 0x00U, 0x01U, 0x07U, 0x08U, 0x01U,
|
||||
0x04U, 0x68U, 0x6FU, 0x6FU, 0x6BU, 0x00U, 0x04U, 0x0AU, 0x62U,
|
||||
0x01U, 0x60U, 0x01U, 0x02U, 0x7EU, 0x41U, 0x01U, 0x41U, 0x01U,
|
||||
0x10U, 0x00U, 0x1AU, 0x41U, 0xC0U, 0x84U, 0x3DU, 0x41U, 0x03U,
|
||||
0x41U, 0x05U, 0x10U, 0x01U, 0x22U, 0x01U, 0x42U, 0x00U, 0x53U,
|
||||
0x04U, 0x40U, 0x41U, 0x00U, 0x41U, 0x00U, 0x20U, 0x01U, 0x10U,
|
||||
0x02U, 0x1AU, 0x0BU, 0x41U, 0xC0U, 0x84U, 0x3DU, 0x41U, 0x03U,
|
||||
0x41U, 0x05U, 0x10U, 0x01U, 0x22U, 0x02U, 0x42U, 0x00U, 0x53U,
|
||||
0x04U, 0x40U, 0x41U, 0x00U, 0x41U, 0x00U, 0x20U, 0x02U, 0x10U,
|
||||
0x02U, 0x1AU, 0x0BU, 0x20U, 0x01U, 0x20U, 0x02U, 0x51U, 0x04U,
|
||||
0x40U, 0x41U, 0x00U, 0x41U, 0x00U, 0x42U, 0x7FU, 0x10U, 0x02U,
|
||||
0x1AU, 0x0BU, 0x41U, 0x00U, 0x41U, 0x00U, 0x20U, 0x02U, 0x42U,
|
||||
0x14U, 0x86U, 0x20U, 0x01U, 0x84U, 0x10U, 0x03U, 0x0BU,
|
||||
}},
|
||||
|
||||
/* ==== WASM: 3 ==== */
|
||||
{R"[test.hook](
|
||||
#include <stdint.h>
|
||||
extern int32_t _g(uint32_t, uint32_t);
|
||||
extern int64_t accept(uint32_t read_ptr, uint32_t read_len, int64_t error_code);
|
||||
extern int64_t dice(uint32_t sides, uint32_t min_tier, uint32_t min_count);
|
||||
|
||||
int64_t hook(uint32_t r)
|
||||
{
|
||||
_g(1,1);
|
||||
int64_t result = dice(0, 3, 5);
|
||||
// dice(0) should return negative error code, pass it through
|
||||
return accept(0, 0, result);
|
||||
}
|
||||
)[test.hook]",
|
||||
{
|
||||
0x00U, 0x61U, 0x73U, 0x6DU, 0x01U, 0x00U, 0x00U, 0x00U, 0x01U,
|
||||
0x1AU, 0x04U, 0x60U, 0x02U, 0x7FU, 0x7FU, 0x01U, 0x7FU, 0x60U,
|
||||
0x03U, 0x7FU, 0x7FU, 0x7FU, 0x01U, 0x7EU, 0x60U, 0x03U, 0x7FU,
|
||||
0x7FU, 0x7EU, 0x01U, 0x7EU, 0x60U, 0x01U, 0x7FU, 0x01U, 0x7EU,
|
||||
0x02U, 0x22U, 0x03U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x02U, 0x5FU,
|
||||
0x67U, 0x00U, 0x00U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x04U, 0x64U,
|
||||
0x69U, 0x63U, 0x65U, 0x00U, 0x01U, 0x03U, 0x65U, 0x6EU, 0x76U,
|
||||
0x06U, 0x61U, 0x63U, 0x63U, 0x65U, 0x70U, 0x74U, 0x00U, 0x02U,
|
||||
0x03U, 0x02U, 0x01U, 0x03U, 0x05U, 0x03U, 0x01U, 0x00U, 0x01U,
|
||||
0x07U, 0x08U, 0x01U, 0x04U, 0x68U, 0x6FU, 0x6FU, 0x6BU, 0x00U,
|
||||
0x03U, 0x0AU, 0x19U, 0x01U, 0x17U, 0x00U, 0x41U, 0x01U, 0x41U,
|
||||
0x01U, 0x10U, 0x00U, 0x1AU, 0x41U, 0x00U, 0x41U, 0x00U, 0x41U,
|
||||
0x00U, 0x41U, 0x03U, 0x41U, 0x05U, 0x10U, 0x01U, 0x10U, 0x02U,
|
||||
0x0BU,
|
||||
}},
|
||||
|
||||
/* ==== WASM: 4 ==== */
|
||||
{R"[test.hook](
|
||||
#include <stdint.h>
|
||||
extern int32_t _g(uint32_t, uint32_t);
|
||||
extern int64_t accept(uint32_t read_ptr, uint32_t read_len, int64_t error_code);
|
||||
extern int64_t dice(uint32_t sides, uint32_t min_tier, uint32_t min_count);
|
||||
|
||||
int64_t hook(uint32_t r)
|
||||
{
|
||||
_g(1,1);
|
||||
int64_t result = dice(6, 3, 21);
|
||||
// requirement unmet: pass the error code through
|
||||
return accept(0, 0, result);
|
||||
}
|
||||
)[test.hook]",
|
||||
{
|
||||
0x00U, 0x61U, 0x73U, 0x6DU, 0x01U, 0x00U, 0x00U, 0x00U, 0x01U,
|
||||
0x1AU, 0x04U, 0x60U, 0x02U, 0x7FU, 0x7FU, 0x01U, 0x7FU, 0x60U,
|
||||
0x03U, 0x7FU, 0x7FU, 0x7FU, 0x01U, 0x7EU, 0x60U, 0x03U, 0x7FU,
|
||||
0x7FU, 0x7EU, 0x01U, 0x7EU, 0x60U, 0x01U, 0x7FU, 0x01U, 0x7EU,
|
||||
0x02U, 0x22U, 0x03U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x02U, 0x5FU,
|
||||
0x67U, 0x00U, 0x00U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x04U, 0x64U,
|
||||
0x69U, 0x63U, 0x65U, 0x00U, 0x01U, 0x03U, 0x65U, 0x6EU, 0x76U,
|
||||
0x06U, 0x61U, 0x63U, 0x63U, 0x65U, 0x70U, 0x74U, 0x00U, 0x02U,
|
||||
0x03U, 0x02U, 0x01U, 0x03U, 0x05U, 0x03U, 0x01U, 0x00U, 0x01U,
|
||||
0x07U, 0x08U, 0x01U, 0x04U, 0x68U, 0x6FU, 0x6FU, 0x6BU, 0x00U,
|
||||
0x03U, 0x0AU, 0x19U, 0x01U, 0x17U, 0x00U, 0x41U, 0x01U, 0x41U,
|
||||
0x01U, 0x10U, 0x00U, 0x1AU, 0x41U, 0x00U, 0x41U, 0x00U, 0x41U,
|
||||
0x06U, 0x41U, 0x03U, 0x41U, 0x15U, 0x10U, 0x01U, 0x10U, 0x02U,
|
||||
0x0BU,
|
||||
}},
|
||||
|
||||
};
|
||||
}
|
||||
} // namespace ripple
|
||||
#endif
|
||||
@@ -1,221 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <xrpld/app/tx/detail/ExportResultBuilder.h>
|
||||
#include <xrpl/beast/unit_test.h>
|
||||
#include <xrpl/protocol/HashPrefix.h>
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
#include <xrpl/protocol/STArray.h>
|
||||
#include <xrpl/protocol/STObject.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/SecretKey.h>
|
||||
#include <xrpl/protocol/Sign.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
#include <xrpl/protocol/TxFormats.h>
|
||||
#include <xrpl/protocol/digest.h>
|
||||
|
||||
#include <cstring>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
namespace {
|
||||
|
||||
uint256
|
||||
makeHash(char const* label)
|
||||
{
|
||||
return sha512Half(Slice(label, std::strlen(label)));
|
||||
}
|
||||
|
||||
STTx
|
||||
makeSTTx(STObject const& obj)
|
||||
{
|
||||
Serializer s;
|
||||
obj.add(s);
|
||||
SerialIter sit{s.slice()};
|
||||
return STTx{std::ref(sit)};
|
||||
}
|
||||
|
||||
STTx
|
||||
makeExportedPayment(AccountID const& src, AccountID const& dst)
|
||||
{
|
||||
STObject obj(sfExportedTxn);
|
||||
obj.setFieldU16(sfTransactionType, ttPAYMENT);
|
||||
obj.setFieldU32(sfFlags, tfFullyCanonicalSig);
|
||||
obj.setFieldU32(sfSequence, 0);
|
||||
obj.setFieldU32(sfTicketSequence, 1);
|
||||
obj.setFieldU32(sfFirstLedgerSequence, 2);
|
||||
obj.setFieldU32(sfLastLedgerSequence, 6);
|
||||
obj.setFieldAmount(sfAmount, XRPAmount{1000000});
|
||||
obj.setFieldAmount(sfFee, XRPAmount{10});
|
||||
obj.setFieldVL(sfSigningPubKey, Blob{});
|
||||
obj.setAccountID(sfAccount, src);
|
||||
obj.setAccountID(sfDestination, dst);
|
||||
return makeSTTx(obj);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class ExportResultBuilder_test : public beast::unit_test::suite
|
||||
{
|
||||
public:
|
||||
void
|
||||
testAssemblesSignedMetadata()
|
||||
{
|
||||
testcase("assembles signed metadata");
|
||||
|
||||
auto const signerA = randomKeyPair(KeyType::secp256k1);
|
||||
auto const signerB = randomKeyPair(KeyType::secp256k1);
|
||||
auto const innerTx = makeExportedPayment(
|
||||
calcAccountID(signerA.first), calcAccountID(signerB.first));
|
||||
auto const exportTxHash = makeHash("outer-export-tx");
|
||||
|
||||
ExportResultBuilder::SignatureSnapshot signatures;
|
||||
signatures.emplace(
|
||||
signerA.first,
|
||||
ExportResultBuilder::signExportedTxn(
|
||||
innerTx, signerA.first, signerA.second));
|
||||
signatures.emplace(
|
||||
signerB.first,
|
||||
ExportResultBuilder::signExportedTxn(
|
||||
innerTx, signerB.first, signerB.second));
|
||||
|
||||
auto assembled = ExportResultBuilder::assemble(
|
||||
innerTx, signatures, 123, exportTxHash);
|
||||
|
||||
BEAST_EXPECT(assembled.signerCount == 2);
|
||||
BEAST_EXPECT(assembled.metadata.getFieldU32(sfLedgerSequence) == 123);
|
||||
BEAST_EXPECT(
|
||||
assembled.metadata.getFieldH256(sfTransactionHash) == exportTxHash);
|
||||
|
||||
auto const& multiSigned =
|
||||
assembled.metadata.peekAtField(sfExportedTxn).downcast<STObject>();
|
||||
BEAST_EXPECT(multiSigned.getFieldVL(sfSigningPubKey).empty());
|
||||
BEAST_EXPECT(multiSigned.isFieldPresent(sfSigners));
|
||||
|
||||
auto const& signers = multiSigned.getFieldArray(sfSigners);
|
||||
BEAST_EXPECT(signers.size() == 2);
|
||||
if (signers.size() == 2)
|
||||
{
|
||||
BEAST_EXPECT(
|
||||
signers[0].getAccountID(sfAccount) <
|
||||
signers[1].getAccountID(sfAccount));
|
||||
}
|
||||
|
||||
for (auto const& signer : signers)
|
||||
{
|
||||
auto const pkVL = signer.getFieldVL(sfSigningPubKey);
|
||||
PublicKey const pk{makeSlice(pkVL)};
|
||||
auto const sigVL = signer.getFieldVL(sfTxnSignature);
|
||||
auto const signerAcctID = signer.getAccountID(sfAccount);
|
||||
auto const sigData = buildMultiSigningData(innerTx, signerAcctID);
|
||||
BEAST_EXPECT(verify(pk, sigData.slice(), makeSlice(sigVL)));
|
||||
}
|
||||
|
||||
BEAST_EXPECT(
|
||||
assembled.signedTxHash ==
|
||||
multiSigned.getHash(HashPrefix::transactionID));
|
||||
|
||||
Serializer serialized;
|
||||
multiSigned.add(serialized);
|
||||
SerialIter sit(serialized.slice());
|
||||
STTx signedTx{std::ref(sit)};
|
||||
BEAST_EXPECT(signedTx.getTransactionID() == assembled.signedTxHash);
|
||||
}
|
||||
|
||||
void
|
||||
testSkipsEmptySignatures()
|
||||
{
|
||||
testcase("skips empty signatures");
|
||||
|
||||
auto const signer = randomKeyPair(KeyType::secp256k1);
|
||||
auto const dst = randomKeyPair(KeyType::secp256k1);
|
||||
auto const innerTx = makeExportedPayment(
|
||||
calcAccountID(signer.first), calcAccountID(dst.first));
|
||||
|
||||
ExportResultBuilder::SignatureSnapshot signatures;
|
||||
signatures.emplace(signer.first, Buffer{});
|
||||
|
||||
auto assembled = ExportResultBuilder::assemble(
|
||||
innerTx, signatures, 456, makeHash("empty-sig-export"));
|
||||
|
||||
BEAST_EXPECT(assembled.signerCount == 0);
|
||||
|
||||
auto const& multiSigned =
|
||||
assembled.metadata.peekAtField(sfExportedTxn).downcast<STObject>();
|
||||
BEAST_EXPECT(multiSigned.getFieldVL(sfSigningPubKey).empty());
|
||||
BEAST_EXPECT(!multiSigned.isFieldPresent(sfSigners));
|
||||
BEAST_EXPECT(
|
||||
assembled.signedTxHash ==
|
||||
multiSigned.getHash(HashPrefix::transactionID));
|
||||
}
|
||||
|
||||
void
|
||||
testBuildMultiSignedExportedTxnDirect()
|
||||
{
|
||||
testcase("builds multisigned exported transaction directly");
|
||||
|
||||
auto const signerA = randomKeyPair(KeyType::secp256k1);
|
||||
auto const signerB = randomKeyPair(KeyType::secp256k1);
|
||||
auto const dst = randomKeyPair(KeyType::secp256k1);
|
||||
auto const innerTx = makeExportedPayment(
|
||||
calcAccountID(signerA.first), calcAccountID(dst.first));
|
||||
|
||||
ExportResultBuilder::SignatureSnapshot signatures;
|
||||
signatures.emplace(signerB.first, Buffer{});
|
||||
signatures.emplace(
|
||||
signerA.first,
|
||||
ExportResultBuilder::signExportedTxn(
|
||||
innerTx, signerA.first, signerA.second));
|
||||
|
||||
auto multiSigned = ExportResultBuilder::buildMultiSignedExportedTxn(
|
||||
innerTx, signatures);
|
||||
BEAST_EXPECT(multiSigned.getFieldVL(sfSigningPubKey).empty());
|
||||
BEAST_EXPECT(multiSigned.isFieldPresent(sfSigners));
|
||||
|
||||
auto const& signers = multiSigned.getFieldArray(sfSigners);
|
||||
BEAST_EXPECT(signers.size() == 1);
|
||||
if (signers.size() == 1)
|
||||
{
|
||||
BEAST_EXPECT(
|
||||
signers[0].getAccountID(sfAccount) ==
|
||||
calcAccountID(signerA.first));
|
||||
BEAST_EXPECT(
|
||||
makeSlice(signers[0].getFieldVL(sfSigningPubKey)) ==
|
||||
signerA.first.slice());
|
||||
}
|
||||
|
||||
ExportResultBuilder::SignatureSnapshot none;
|
||||
auto unsignedMulti =
|
||||
ExportResultBuilder::buildMultiSignedExportedTxn(innerTx, none);
|
||||
BEAST_EXPECT(unsignedMulti.getFieldVL(sfSigningPubKey).empty());
|
||||
BEAST_EXPECT(!unsignedMulti.isFieldPresent(sfSigners));
|
||||
}
|
||||
|
||||
void
|
||||
run() override
|
||||
{
|
||||
testAssemblesSignedMetadata();
|
||||
testSkipsEmptySignatures();
|
||||
testBuildMultiSignedExportedTxnDirect();
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(ExportResultBuilder, app, ripple);
|
||||
|
||||
} // namespace test
|
||||
} // namespace ripple
|
||||
@@ -1,299 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <xrpld/app/misc/ExportSigCollector.h>
|
||||
#include <xrpl/basics/StringUtilities.h>
|
||||
#include <xrpl/beast/unit_test.h>
|
||||
#include <xrpl/protocol/SecretKey.h>
|
||||
#include <xrpl/protocol/digest.h>
|
||||
#include <cstring>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
|
||||
namespace {
|
||||
|
||||
uint256
|
||||
makeHash(char const* label)
|
||||
{
|
||||
return sha512Half(Slice(label, std::strlen(label)));
|
||||
}
|
||||
|
||||
PublicKey
|
||||
makePublicKey(char const* hex)
|
||||
{
|
||||
auto const raw = strUnHex(hex);
|
||||
return PublicKey{makeSlice(*raw)};
|
||||
}
|
||||
|
||||
Buffer
|
||||
makeSignature(std::uint8_t seed)
|
||||
{
|
||||
std::uint8_t bytes[] = {
|
||||
seed,
|
||||
static_cast<std::uint8_t>(seed + 1),
|
||||
static_cast<std::uint8_t>(seed + 2)};
|
||||
return Buffer(bytes, sizeof(bytes));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class ExportSigCollector_test : public beast::unit_test::suite
|
||||
{
|
||||
PublicKey const validator_ = makePublicKey(
|
||||
"0388935426E0D08083314842EDFBB2D517BD47699F9A4527318A8E10468C97C05"
|
||||
"2");
|
||||
|
||||
public:
|
||||
void
|
||||
testCleanupUsesFirstSeenSeq()
|
||||
{
|
||||
testcase("cleanup uses first seen sequence");
|
||||
|
||||
ExportSigCollector collector;
|
||||
auto const tx = makeHash("cleanup-verified");
|
||||
auto const sig = makeSignature(1);
|
||||
|
||||
collector.addVerifiedSignature(tx, validator_, sig, 10);
|
||||
BEAST_EXPECT(collector.signatureCount(tx) == 1);
|
||||
|
||||
collector.cleanupStale(266);
|
||||
BEAST_EXPECT(collector.signatureCount(tx) == 1);
|
||||
|
||||
collector.cleanupStale(267);
|
||||
BEAST_EXPECT(collector.signatureCount(tx) == 0);
|
||||
}
|
||||
|
||||
void
|
||||
testUpgradeSetsFirstSeenSeq()
|
||||
{
|
||||
testcase("upgrade sets first seen sequence");
|
||||
|
||||
ExportSigCollector collector;
|
||||
auto const tx = makeHash("cleanup-upgraded");
|
||||
auto const sig = makeSignature(5);
|
||||
|
||||
collector.addUnverifiedSignature(tx, validator_, sig);
|
||||
BEAST_EXPECT(collector.hasUnverifiedSignatures());
|
||||
|
||||
collector.upgradeSignature(tx, validator_, sig, 10);
|
||||
BEAST_EXPECT(!collector.hasUnverifiedSignatures());
|
||||
BEAST_EXPECT(collector.signatureCount(tx) == 1);
|
||||
|
||||
collector.cleanupStale(266);
|
||||
BEAST_EXPECT(collector.signatureCount(tx) == 1);
|
||||
|
||||
collector.cleanupStale(267);
|
||||
BEAST_EXPECT(collector.signatureCount(tx) == 0);
|
||||
}
|
||||
|
||||
void
|
||||
testRemoveInvalidUnverifiedSignature()
|
||||
{
|
||||
testcase("remove invalid unverified signature");
|
||||
|
||||
ExportSigCollector collector;
|
||||
auto const tx = makeHash("remove-invalid");
|
||||
auto const sig = makeSignature(9);
|
||||
auto const otherSig = makeSignature(10);
|
||||
|
||||
collector.addUnverifiedSignature(tx, validator_, sig, 10);
|
||||
BEAST_EXPECT(collector.hasUnverifiedSignatures());
|
||||
|
||||
BEAST_EXPECT(!collector.removeSignature(tx, validator_, otherSig));
|
||||
BEAST_EXPECT(collector.hasUnverifiedSignatures());
|
||||
|
||||
BEAST_EXPECT(collector.removeSignature(tx, validator_, sig));
|
||||
BEAST_EXPECT(!collector.hasUnverifiedSignatures());
|
||||
BEAST_EXPECT(collector.signatureCount(tx) == 0);
|
||||
}
|
||||
|
||||
void
|
||||
testSnapshotsAndFilteredCounts()
|
||||
{
|
||||
testcase("snapshots and filtered counts use verified signatures only");
|
||||
|
||||
auto const other = randomKeyPair(KeyType::secp256k1).first;
|
||||
ExportSigCollector collector;
|
||||
auto const tx = makeHash("snapshot-filtered");
|
||||
auto const verifiedSig = makeSignature(20);
|
||||
auto const unverifiedSig = makeSignature(30);
|
||||
|
||||
BEAST_EXPECT(!collector.hasVerifiedSignature(tx, validator_));
|
||||
BEAST_EXPECT(collector.unverifiedSignatures(tx).empty());
|
||||
BEAST_EXPECT(!collector.checkQuorumAndSnapshot(tx, 1));
|
||||
|
||||
collector.addVerifiedSignature(tx, validator_, verifiedSig, 10);
|
||||
collector.addUnverifiedSignature(tx, other, unverifiedSig, 11);
|
||||
|
||||
BEAST_EXPECT(collector.hasVerifiedSignature(tx, validator_));
|
||||
BEAST_EXPECT(!collector.hasVerifiedSignature(tx, other));
|
||||
BEAST_EXPECT(collector.signatureCount(tx) == 1);
|
||||
BEAST_EXPECT(collector.signatureCount(tx, [&](PublicKey const& pk) {
|
||||
return pk == validator_;
|
||||
}) == 1);
|
||||
BEAST_EXPECT(collector.signatureCount(tx, [&](PublicKey const& pk) {
|
||||
return pk == other;
|
||||
}) == 0);
|
||||
|
||||
auto unverified = collector.unverifiedSignatures(tx);
|
||||
BEAST_EXPECT(unverified.size() == 1);
|
||||
BEAST_EXPECT(unverified.count(other) == 1);
|
||||
|
||||
auto snapshot = collector.snapshot();
|
||||
BEAST_EXPECT(snapshot.size() == 1);
|
||||
BEAST_EXPECT(snapshot[tx].count(validator_) == 1);
|
||||
BEAST_EXPECT(snapshot[tx].count(other) == 0);
|
||||
|
||||
auto sigSnapshot = collector.snapshotWithSigs();
|
||||
BEAST_EXPECT(sigSnapshot[tx].size() == 1);
|
||||
BEAST_EXPECT(sigSnapshot[tx][validator_] == verifiedSig);
|
||||
|
||||
auto filteredSnapshot = collector.snapshotWithSigs(
|
||||
[&](PublicKey const& pk) { return pk == other; });
|
||||
BEAST_EXPECT(filteredSnapshot.empty());
|
||||
|
||||
BEAST_EXPECT(!collector.checkQuorumAndSnapshot(tx, 2));
|
||||
auto quorum = collector.checkQuorumAndSnapshot(tx, 1);
|
||||
BEAST_EXPECT(quorum.has_value());
|
||||
if (quorum)
|
||||
{
|
||||
BEAST_EXPECT(quorum->size() == 1);
|
||||
BEAST_EXPECT((*quorum)[validator_] == verifiedSig);
|
||||
}
|
||||
|
||||
collector.upgradeSignature(tx, other, makeSignature(31), 12);
|
||||
BEAST_EXPECT(collector.signatureCount(tx) == 1);
|
||||
|
||||
collector.upgradeSignature(tx, other, unverifiedSig, 12);
|
||||
BEAST_EXPECT(!collector.hasUnverifiedSignatures());
|
||||
BEAST_EXPECT(collector.signatureCount(tx) == 2);
|
||||
|
||||
auto filteredQuorum = collector.checkQuorumAndSnapshot(
|
||||
tx, 1, [&](PublicKey const& pk) { return pk == other; });
|
||||
BEAST_EXPECT(filteredQuorum.has_value());
|
||||
if (filteredQuorum)
|
||||
BEAST_EXPECT((*filteredQuorum)[other] == unverifiedSig);
|
||||
|
||||
collector.clear(tx);
|
||||
BEAST_EXPECT(collector.signatureCount(tx) == 0);
|
||||
BEAST_EXPECT(collector.snapshot().empty());
|
||||
}
|
||||
|
||||
void
|
||||
testStandaloneAndRoundState()
|
||||
{
|
||||
testcase("standalone signatures and round state");
|
||||
|
||||
ExportSigCollector collector;
|
||||
auto const tx = makeHash("standalone-round");
|
||||
|
||||
collector.addStandaloneSignature(tx, validator_, 10);
|
||||
BEAST_EXPECT(collector.hasVerifiedSignature(tx, validator_));
|
||||
BEAST_EXPECT(collector.signatureCount(tx) == 1);
|
||||
BEAST_EXPECT(!collector.hasUnverifiedSignatures());
|
||||
|
||||
auto snapshot = collector.snapshot();
|
||||
BEAST_EXPECT(snapshot.size() == 1);
|
||||
BEAST_EXPECT(snapshot[tx].count(validator_) == 1);
|
||||
|
||||
auto sigSnapshot = collector.snapshotWithSigs();
|
||||
BEAST_EXPECT(sigSnapshot.size() == 1);
|
||||
BEAST_EXPECT(sigSnapshot[tx].count(validator_) == 1);
|
||||
BEAST_EXPECT(sigSnapshot[tx][validator_].empty());
|
||||
|
||||
BEAST_EXPECT(collector.markSent(tx));
|
||||
BEAST_EXPECT(!collector.markSent(tx));
|
||||
collector.clearRound();
|
||||
BEAST_EXPECT(collector.markSent(tx));
|
||||
}
|
||||
|
||||
void
|
||||
testClearAll()
|
||||
{
|
||||
testcase("clear all signatures and round state");
|
||||
|
||||
ExportSigCollector collector;
|
||||
auto const verifiedTx = makeHash("clear-all-verified");
|
||||
auto const unverifiedTx = makeHash("clear-all-unverified");
|
||||
auto const sig = makeSignature(12);
|
||||
|
||||
collector.addVerifiedSignature(verifiedTx, validator_, sig, 10);
|
||||
collector.addUnverifiedSignature(unverifiedTx, validator_, sig, 10);
|
||||
BEAST_EXPECT(collector.signatureCount(verifiedTx) == 1);
|
||||
BEAST_EXPECT(collector.hasUnverifiedSignatures());
|
||||
BEAST_EXPECT(collector.markSent(verifiedTx));
|
||||
BEAST_EXPECT(!collector.markSent(verifiedTx));
|
||||
|
||||
collector.clearAll();
|
||||
|
||||
BEAST_EXPECT(collector.signatureCount(verifiedTx) == 0);
|
||||
BEAST_EXPECT(!collector.hasUnverifiedSignatures());
|
||||
BEAST_EXPECT(collector.markSent(verifiedTx));
|
||||
}
|
||||
|
||||
void
|
||||
testDefensiveNoOps()
|
||||
{
|
||||
testcase("defensive no-op paths");
|
||||
|
||||
ExportSigCollector collector;
|
||||
auto const missingTx = makeHash("missing-defensive");
|
||||
auto const standaloneTx = makeHash("standalone-defensive");
|
||||
auto const sig = makeSignature(40);
|
||||
|
||||
collector.upgradeSignature(missingTx, validator_, sig, 10);
|
||||
BEAST_EXPECT(collector.signatureCount(missingTx) == 0);
|
||||
BEAST_EXPECT(!collector.removeSignature(missingTx, validator_, sig));
|
||||
BEAST_EXPECT(!collector.checkQuorumAndSnapshot(missingTx, 1));
|
||||
BEAST_EXPECT(collector.signatureCount(missingTx, [](PublicKey const&) {
|
||||
return true;
|
||||
}) == 0);
|
||||
|
||||
collector.addStandaloneSignature(standaloneTx, validator_, 10);
|
||||
collector.upgradeSignature(standaloneTx, validator_, Buffer{}, 11);
|
||||
BEAST_EXPECT(collector.signatureCount(standaloneTx) == 1);
|
||||
BEAST_EXPECT(collector.snapshotWithSigs()
|
||||
.at(standaloneTx)
|
||||
.at(validator_)
|
||||
.empty());
|
||||
|
||||
auto filtered =
|
||||
collector.snapshotWithSigs([](PublicKey const&) { return false; });
|
||||
BEAST_EXPECT(filtered.empty());
|
||||
BEAST_EXPECT(!collector.checkQuorumAndSnapshot(
|
||||
standaloneTx, 1, [](PublicKey const&) { return false; }));
|
||||
}
|
||||
|
||||
void
|
||||
run() override
|
||||
{
|
||||
testCleanupUsesFirstSeenSeq();
|
||||
testUpgradeSetsFirstSeenSeq();
|
||||
testRemoveInvalidUnverifiedSignature();
|
||||
testSnapshotsAndFilteredCounts();
|
||||
testStandaloneAndRoundState();
|
||||
testClearAll();
|
||||
testDefensiveNoOps();
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(ExportSigCollector, app, ripple);
|
||||
|
||||
} // namespace test
|
||||
} // namespace ripple
|
||||
@@ -1,400 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <test/unit_test/SuiteJournal.h>
|
||||
#include <xrpld/app/consensus/ExportSignatureHarvester.h>
|
||||
#include <xrpl/basics/StringUtilities.h>
|
||||
#include <xrpl/beast/unit_test.h>
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
#include <xrpl/protocol/STObject.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/SecretKey.h>
|
||||
#include <xrpl/protocol/Sign.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
#include <xrpl/protocol/TxFormats.h>
|
||||
#include <xrpl/protocol/digest.h>
|
||||
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
|
||||
namespace {
|
||||
|
||||
uint256
|
||||
makeHash(char const* label)
|
||||
{
|
||||
return sha512Half(Slice(label, std::strlen(label)));
|
||||
}
|
||||
|
||||
STTx
|
||||
makeSTTx(STObject const& obj)
|
||||
{
|
||||
Serializer s;
|
||||
obj.add(s);
|
||||
SerialIter sit{s.slice()};
|
||||
return STTx{std::ref(sit)};
|
||||
}
|
||||
|
||||
STObject
|
||||
makeExportedPayment(AccountID const& src, AccountID const& dst)
|
||||
{
|
||||
STObject obj(sfExportedTxn);
|
||||
obj.setFieldU16(sfTransactionType, ttPAYMENT);
|
||||
obj.setFieldU32(sfFlags, tfFullyCanonicalSig);
|
||||
obj.setFieldU32(sfSequence, 0);
|
||||
obj.setFieldU32(sfTicketSequence, 1);
|
||||
obj.setFieldU32(sfFirstLedgerSequence, 2);
|
||||
obj.setFieldU32(sfLastLedgerSequence, 6);
|
||||
obj.setFieldAmount(sfAmount, XRPAmount{1000000});
|
||||
obj.setFieldAmount(sfFee, XRPAmount{10});
|
||||
obj.setFieldVL(sfSigningPubKey, Blob{});
|
||||
obj.setAccountID(sfAccount, src);
|
||||
obj.setAccountID(sfDestination, dst);
|
||||
return obj;
|
||||
}
|
||||
|
||||
std::shared_ptr<STTx const>
|
||||
makeExportTx(STObject const& inner, AccountID const& account)
|
||||
{
|
||||
STObject exportObj(sfGeneric);
|
||||
exportObj.setFieldU16(sfTransactionType, ttEXPORT);
|
||||
exportObj.setAccountID(sfAccount, account);
|
||||
exportObj.setFieldU32(sfSequence, 0);
|
||||
exportObj.setFieldVL(sfSigningPubKey, Blob{});
|
||||
exportObj.setFieldU32(sfFirstLedgerSequence, 2);
|
||||
exportObj.setFieldU32(sfLastLedgerSequence, 6);
|
||||
exportObj.setFieldAmount(sfFee, XRPAmount{0});
|
||||
exportObj.set(std::make_unique<STObject>(inner));
|
||||
|
||||
return std::make_shared<STTx const>(makeSTTx(exportObj));
|
||||
}
|
||||
|
||||
std::shared_ptr<STTx const>
|
||||
makeExportTxWithoutInner(AccountID const& account)
|
||||
{
|
||||
STObject exportObj(sfGeneric);
|
||||
exportObj.setFieldU16(sfTransactionType, ttEXPORT);
|
||||
exportObj.setAccountID(sfAccount, account);
|
||||
exportObj.setFieldU32(sfSequence, 0);
|
||||
exportObj.setFieldVL(sfSigningPubKey, Blob{});
|
||||
exportObj.setFieldU32(sfFirstLedgerSequence, 2);
|
||||
exportObj.setFieldU32(sfLastLedgerSequence, 6);
|
||||
exportObj.setFieldAmount(sfFee, XRPAmount{0});
|
||||
|
||||
return std::make_shared<STTx const>(makeSTTx(exportObj));
|
||||
}
|
||||
|
||||
std::string
|
||||
makeBlob(uint256 const& txHash, PublicKey const& pk, Slice sig)
|
||||
{
|
||||
std::string blob;
|
||||
blob.append(reinterpret_cast<char const*>(txHash.data()), txHash.size());
|
||||
blob.append(reinterpret_cast<char const*>(pk.data()), pk.size());
|
||||
blob.append(reinterpret_cast<char const*>(sig.data()), sig.size());
|
||||
return blob;
|
||||
}
|
||||
|
||||
std::string
|
||||
makeBlob(uint256 const& txHash, PublicKey const& pk, Buffer const& sig)
|
||||
{
|
||||
return makeBlob(txHash, pk, Slice(sig.data(), sig.size()));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class ExportSignatureHarvester_test : public beast::unit_test::suite
|
||||
{
|
||||
SuiteJournal journal_{"ExportSignatureHarvester_test", *this};
|
||||
std::pair<PublicKey, SecretKey> const sender_ =
|
||||
randomKeyPair(KeyType::secp256k1);
|
||||
std::pair<PublicKey, SecretKey> const other_ =
|
||||
randomKeyPair(KeyType::secp256k1);
|
||||
uint256 const prevLedger_ = makeHash("export-harvester-prev-ledger");
|
||||
char const* source_ = "unit-test";
|
||||
|
||||
ExportSignatureHarvestInput
|
||||
makeInput(
|
||||
std::vector<std::string> const& blobs,
|
||||
ExportTxnLookup const& exportTxns,
|
||||
bool active = true,
|
||||
std::optional<uint256> sourceLedgerHash = std::nullopt,
|
||||
PublicKey const* sender = nullptr,
|
||||
std::size_t maxEntries = 2) const
|
||||
{
|
||||
return ExportSignatureHarvestInput{
|
||||
sender ? *sender : sender_.first,
|
||||
prevLedger_,
|
||||
blobs,
|
||||
sourceLedgerHash,
|
||||
[active](PublicKey const&) { return active; },
|
||||
exportTxns,
|
||||
42,
|
||||
source_,
|
||||
maxEntries};
|
||||
}
|
||||
|
||||
beast::Journal&
|
||||
journal()
|
||||
{
|
||||
return journal_;
|
||||
}
|
||||
|
||||
public:
|
||||
void
|
||||
testRejectsTooManyEntries()
|
||||
{
|
||||
testcase("rejects too many entries");
|
||||
|
||||
auto const txHash = makeHash("too-many");
|
||||
auto const blob = makeBlob(txHash, sender_.first, Slice("sig", 3));
|
||||
std::vector<std::string> const blobs{blob, blob, blob};
|
||||
ExportTxnLookup lookup;
|
||||
ExportSigCollector collector;
|
||||
|
||||
auto input = makeInput(blobs, lookup);
|
||||
BEAST_EXPECT(harvestExportSignatures(input, collector, journal()) == 0);
|
||||
BEAST_EXPECT(!collector.hasUnverifiedSignatures());
|
||||
BEAST_EXPECT(collector.signatureCount(txHash) == 0);
|
||||
}
|
||||
|
||||
void
|
||||
testEmptyInputAndDirectVerification()
|
||||
{
|
||||
testcase("empty input and direct verification");
|
||||
|
||||
std::vector<std::string> const empty;
|
||||
ExportTxnLookup lookup;
|
||||
ExportSigCollector collector;
|
||||
|
||||
auto input = makeInput(empty, lookup, true, prevLedger_);
|
||||
BEAST_EXPECT(harvestExportSignatures(input, collector, journal()) == 0);
|
||||
|
||||
auto const senderAccount = calcAccountID(sender_.first);
|
||||
auto const dstAccount = calcAccountID(other_.first);
|
||||
auto const innerObj = makeExportedPayment(senderAccount, dstAccount);
|
||||
auto const innerTx = makeSTTx(innerObj);
|
||||
auto const sigData = buildMultiSigningData(innerTx, senderAccount);
|
||||
auto const sig = sign(sender_.first, sender_.second, sigData.slice());
|
||||
auto const exportTx = makeExportTx(innerObj, senderAccount);
|
||||
auto const txHash = exportTx->getTransactionID();
|
||||
|
||||
BEAST_EXPECT(verifyExportSignatureAgainstTx(
|
||||
*exportTx,
|
||||
sender_.first,
|
||||
Slice(sig.data(), sig.size()),
|
||||
txHash,
|
||||
journal(),
|
||||
source_));
|
||||
|
||||
BEAST_EXPECT(!verifyExportSignatureAgainstTx(
|
||||
*exportTx,
|
||||
sender_.first,
|
||||
Slice("bad-sig", 7),
|
||||
txHash,
|
||||
journal(),
|
||||
source_));
|
||||
|
||||
auto const noInner = makeExportTxWithoutInner(senderAccount);
|
||||
BEAST_EXPECT(!verifyExportSignatureAgainstTx(
|
||||
*noInner,
|
||||
sender_.first,
|
||||
Slice(sig.data(), sig.size()),
|
||||
noInner->getTransactionID(),
|
||||
journal(),
|
||||
source_));
|
||||
}
|
||||
|
||||
void
|
||||
testIgnoresEmptyAndMalformedEntries()
|
||||
{
|
||||
testcase("ignores empty and malformed entries");
|
||||
|
||||
auto const txHash = makeHash("malformed");
|
||||
std::string invalidPubkeyBlob;
|
||||
invalidPubkeyBlob.append(
|
||||
reinterpret_cast<char const*>(txHash.data()), txHash.size());
|
||||
invalidPubkeyBlob.append(33, '\0');
|
||||
invalidPubkeyBlob.append("sig", 3);
|
||||
|
||||
std::vector<std::string> const blobs{
|
||||
"",
|
||||
std::string(64, 'x'),
|
||||
makeBlob(txHash, sender_.first, Slice{}),
|
||||
invalidPubkeyBlob};
|
||||
ExportTxnLookup lookup;
|
||||
ExportSigCollector collector;
|
||||
|
||||
auto input =
|
||||
makeInput(blobs, lookup, true, prevLedger_, nullptr, blobs.size());
|
||||
BEAST_EXPECT(harvestExportSignatures(input, collector, journal()) == 0);
|
||||
BEAST_EXPECT(!collector.hasUnverifiedSignatures());
|
||||
BEAST_EXPECT(collector.signatureCount(txHash) == 0);
|
||||
}
|
||||
|
||||
void
|
||||
testRejectsInactiveOrWrongParent()
|
||||
{
|
||||
testcase("rejects inactive or wrong-parent senders");
|
||||
|
||||
auto const txHash = makeHash("inactive");
|
||||
std::vector<std::string> const blobs{
|
||||
makeBlob(txHash, sender_.first, Slice("sig", 3))};
|
||||
ExportTxnLookup lookup;
|
||||
ExportSigCollector collector;
|
||||
|
||||
auto inactive = makeInput(blobs, lookup, false);
|
||||
BEAST_EXPECT(
|
||||
harvestExportSignatures(inactive, collector, journal()) == 0);
|
||||
BEAST_EXPECT(!collector.hasUnverifiedSignatures());
|
||||
|
||||
auto wrongParent =
|
||||
makeInput(blobs, lookup, true, makeHash("different-parent"));
|
||||
BEAST_EXPECT(
|
||||
harvestExportSignatures(wrongParent, collector, journal()) == 0);
|
||||
BEAST_EXPECT(!collector.hasUnverifiedSignatures());
|
||||
}
|
||||
|
||||
void
|
||||
testRejectsPubkeyMismatchAtomically()
|
||||
{
|
||||
testcase("rejects embedded pubkey mismatch atomically");
|
||||
|
||||
auto const txHash = makeHash("mismatch");
|
||||
std::vector<std::string> const blobs{
|
||||
makeBlob(txHash, sender_.first, Slice("sig-a", 5)),
|
||||
makeBlob(txHash, other_.first, Slice("sig-b", 5))};
|
||||
ExportTxnLookup lookup;
|
||||
ExportSigCollector collector;
|
||||
|
||||
auto input = makeInput(blobs, lookup, true, prevLedger_);
|
||||
BEAST_EXPECT(harvestExportSignatures(input, collector, journal()) == 0);
|
||||
BEAST_EXPECT(!collector.hasUnverifiedSignatures());
|
||||
BEAST_EXPECT(collector.signatureCount(txHash) == 0);
|
||||
}
|
||||
|
||||
void
|
||||
testMissingTxStoresUnverified()
|
||||
{
|
||||
testcase("missing tx stores unverified");
|
||||
|
||||
auto const txHash = makeHash("missing-tx");
|
||||
std::vector<std::string> const blobs{
|
||||
makeBlob(txHash, sender_.first, Slice("sig", 3))};
|
||||
ExportTxnLookup lookup;
|
||||
ExportSigCollector collector;
|
||||
|
||||
auto input = makeInput(blobs, lookup, true, prevLedger_);
|
||||
BEAST_EXPECT(harvestExportSignatures(input, collector, journal()) == 1);
|
||||
BEAST_EXPECT(collector.hasUnverifiedSignatures());
|
||||
BEAST_EXPECT(collector.signatureCount(txHash) == 0);
|
||||
}
|
||||
|
||||
void
|
||||
testOpenLedgerTxStoresVerifiedAndSkipsDuplicate()
|
||||
{
|
||||
testcase("open-ledger tx stores verified and skips duplicate");
|
||||
|
||||
auto const senderAccount = calcAccountID(sender_.first);
|
||||
auto const dstAccount = calcAccountID(other_.first);
|
||||
auto const innerObj = makeExportedPayment(senderAccount, dstAccount);
|
||||
auto const innerTx = makeSTTx(innerObj);
|
||||
auto const sigData = buildMultiSigningData(innerTx, senderAccount);
|
||||
auto const sig = sign(sender_.first, sender_.second, sigData.slice());
|
||||
auto const exportTx = makeExportTx(innerObj, senderAccount);
|
||||
auto const txHash = exportTx->getTransactionID();
|
||||
|
||||
ExportTxnLookup lookup;
|
||||
lookup.emplace(txHash, exportTx);
|
||||
std::vector<std::string> const blobs{
|
||||
makeBlob(txHash, sender_.first, sig)};
|
||||
ExportSigCollector collector;
|
||||
|
||||
auto input = makeInput(blobs, lookup, true, prevLedger_);
|
||||
BEAST_EXPECT(harvestExportSignatures(input, collector, journal()) == 1);
|
||||
BEAST_EXPECT(!collector.hasUnverifiedSignatures());
|
||||
BEAST_EXPECT(collector.signatureCount(txHash) == 1);
|
||||
|
||||
BEAST_EXPECT(harvestExportSignatures(input, collector, journal()) == 0);
|
||||
BEAST_EXPECT(collector.signatureCount(txHash) == 1);
|
||||
}
|
||||
|
||||
void
|
||||
testRejectsInvalidOpenLedgerSignatures()
|
||||
{
|
||||
testcase("rejects invalid open-ledger signatures");
|
||||
|
||||
auto const senderAccount = calcAccountID(sender_.first);
|
||||
auto const dstAccount = calcAccountID(other_.first);
|
||||
auto const innerObj = makeExportedPayment(senderAccount, dstAccount);
|
||||
auto const exportTx = makeExportTx(innerObj, senderAccount);
|
||||
auto const txHash = exportTx->getTransactionID();
|
||||
|
||||
ExportTxnLookup lookup;
|
||||
lookup.emplace(txHash, exportTx);
|
||||
std::vector<std::string> const blobs{
|
||||
makeBlob(txHash, sender_.first, Slice("bad-sig", 7))};
|
||||
ExportSigCollector collector;
|
||||
|
||||
auto input = makeInput(blobs, lookup, true, prevLedger_);
|
||||
BEAST_EXPECT(harvestExportSignatures(input, collector, journal()) == 0);
|
||||
BEAST_EXPECT(!collector.hasUnverifiedSignatures());
|
||||
BEAST_EXPECT(collector.signatureCount(txHash) == 0);
|
||||
}
|
||||
|
||||
void
|
||||
testRejectsOpenLedgerTxWithoutExportedTxn()
|
||||
{
|
||||
testcase("rejects open-ledger tx without exported transaction");
|
||||
|
||||
auto const senderAccount = calcAccountID(sender_.first);
|
||||
auto const exportTx = makeExportTxWithoutInner(senderAccount);
|
||||
auto const txHash = exportTx->getTransactionID();
|
||||
|
||||
ExportTxnLookup lookup;
|
||||
lookup.emplace(txHash, exportTx);
|
||||
std::vector<std::string> const blobs{
|
||||
makeBlob(txHash, sender_.first, Slice("sig", 3))};
|
||||
ExportSigCollector collector;
|
||||
|
||||
auto input = makeInput(blobs, lookup, true, prevLedger_);
|
||||
BEAST_EXPECT(harvestExportSignatures(input, collector, journal()) == 0);
|
||||
BEAST_EXPECT(!collector.hasUnverifiedSignatures());
|
||||
BEAST_EXPECT(collector.signatureCount(txHash) == 0);
|
||||
}
|
||||
|
||||
void
|
||||
run() override
|
||||
{
|
||||
testRejectsTooManyEntries();
|
||||
testEmptyInputAndDirectVerification();
|
||||
testIgnoresEmptyAndMalformedEntries();
|
||||
testRejectsInactiveOrWrongParent();
|
||||
testRejectsPubkeyMismatchAtomically();
|
||||
testMissingTxStoresUnverified();
|
||||
testOpenLedgerTxStoresVerifiedAndSkipsDuplicate();
|
||||
testRejectsInvalidOpenLedgerSignatures();
|
||||
testRejectsOpenLedgerTxWithoutExportedTxn();
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(ExportSignatureHarvester, app, ripple);
|
||||
|
||||
} // namespace test
|
||||
} // namespace ripple
|
||||
@@ -1,210 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <xrpld/app/tx/detail/ExportResultBuilder.h>
|
||||
#include <xrpld/app/tx/detail/ExportSignatureUpgrader.h>
|
||||
#include <xrpl/beast/unit_test.h>
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
#include <xrpl/protocol/STObject.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/Serializer.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
#include <xrpl/protocol/TxFormats.h>
|
||||
#include <xrpl/protocol/digest.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <set>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
namespace {
|
||||
|
||||
uint256
|
||||
makeHash(char const* label)
|
||||
{
|
||||
return sha512Half(Slice(label, std::strlen(label)));
|
||||
}
|
||||
|
||||
STTx
|
||||
makeSTTx(STObject const& obj)
|
||||
{
|
||||
Serializer s;
|
||||
obj.add(s);
|
||||
SerialIter sit{s.slice()};
|
||||
return STTx{std::ref(sit)};
|
||||
}
|
||||
|
||||
STTx
|
||||
makeExportedPayment(AccountID const& src, AccountID const& dst)
|
||||
{
|
||||
STObject obj(sfExportedTxn);
|
||||
obj.setFieldU16(sfTransactionType, ttPAYMENT);
|
||||
obj.setFieldU32(sfFlags, tfFullyCanonicalSig);
|
||||
obj.setFieldU32(sfSequence, 0);
|
||||
obj.setFieldU32(sfTicketSequence, 1);
|
||||
obj.setFieldU32(sfFirstLedgerSequence, 2);
|
||||
obj.setFieldU32(sfLastLedgerSequence, 6);
|
||||
obj.setFieldAmount(sfAmount, XRPAmount{1000000});
|
||||
obj.setFieldAmount(sfFee, XRPAmount{10});
|
||||
obj.setFieldVL(sfSigningPubKey, Blob{});
|
||||
obj.setAccountID(sfAccount, src);
|
||||
obj.setAccountID(sfDestination, dst);
|
||||
return makeSTTx(obj);
|
||||
}
|
||||
|
||||
Buffer
|
||||
makeInvalidSignature(std::uint8_t first = 1)
|
||||
{
|
||||
std::uint8_t bytes[] = {
|
||||
first,
|
||||
static_cast<std::uint8_t>(first + 1),
|
||||
static_cast<std::uint8_t>(first + 2),
|
||||
static_cast<std::uint8_t>(first + 3),
|
||||
static_cast<std::uint8_t>(first + 4)};
|
||||
return Buffer(bytes, sizeof(bytes));
|
||||
}
|
||||
|
||||
beast::Journal
|
||||
nullJournal()
|
||||
{
|
||||
return beast::Journal{beast::Journal::getNullSink()};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class ExportSignatureUpgrader_test : public beast::unit_test::suite
|
||||
{
|
||||
public:
|
||||
void
|
||||
testUpgradeFiltersAndRemovesInvalid()
|
||||
{
|
||||
testcase("upgrade filters and removes invalid signatures");
|
||||
|
||||
auto const validSigner = randomKeyPair(KeyType::secp256k1);
|
||||
auto const invalidSigner = randomKeyPair(KeyType::secp256k1);
|
||||
auto const inactiveSigner = randomKeyPair(KeyType::secp256k1);
|
||||
auto const dst = randomKeyPair(KeyType::secp256k1);
|
||||
auto const innerTx = makeExportedPayment(
|
||||
calcAccountID(validSigner.first), calcAccountID(dst.first));
|
||||
auto const txHash = makeHash("export-upgrade");
|
||||
|
||||
auto validSig = ExportResultBuilder::signExportedTxn(
|
||||
innerTx, validSigner.first, validSigner.second);
|
||||
auto invalidSig = makeInvalidSignature();
|
||||
auto inactiveSig = ExportResultBuilder::signExportedTxn(
|
||||
innerTx, inactiveSigner.first, inactiveSigner.second);
|
||||
|
||||
ExportSigCollector collector;
|
||||
collector.addUnverifiedSignature(
|
||||
txHash, validSigner.first, validSig, 7);
|
||||
collector.addUnverifiedSignature(
|
||||
txHash, invalidSigner.first, invalidSig, 7);
|
||||
collector.addUnverifiedSignature(
|
||||
txHash, inactiveSigner.first, inactiveSig, 7);
|
||||
|
||||
std::set<PublicKey> active{
|
||||
validSigner.first,
|
||||
invalidSigner.first,
|
||||
};
|
||||
auto stats = ExportSignatureUpgrader::upgradeUnverifiedSignatures(
|
||||
collector,
|
||||
innerTx,
|
||||
txHash,
|
||||
12,
|
||||
[&active](PublicKey const& pk) { return active.count(pk) > 0; },
|
||||
nullJournal());
|
||||
|
||||
BEAST_EXPECT(stats.inspected == 3);
|
||||
BEAST_EXPECT(stats.inactiveSkipped == 1);
|
||||
BEAST_EXPECT(stats.upgraded == 1);
|
||||
BEAST_EXPECT(stats.removedInvalid == 1);
|
||||
|
||||
BEAST_EXPECT(collector.hasVerifiedSignature(txHash, validSigner.first));
|
||||
BEAST_EXPECT(
|
||||
!collector.hasVerifiedSignature(txHash, invalidSigner.first));
|
||||
BEAST_EXPECT(
|
||||
!collector.hasVerifiedSignature(txHash, inactiveSigner.first));
|
||||
|
||||
auto const unverified = collector.unverifiedSignatures(txHash);
|
||||
BEAST_EXPECT(!unverified.contains(invalidSigner.first));
|
||||
BEAST_EXPECT(unverified.contains(inactiveSigner.first));
|
||||
BEAST_EXPECT(collector.signatureCount(txHash) == 1);
|
||||
}
|
||||
|
||||
void
|
||||
testInvalidRemovalRequiresStoredBufferMatch()
|
||||
{
|
||||
testcase("invalid removal requires stored buffer match");
|
||||
|
||||
auto const invalidSigner = randomKeyPair(KeyType::secp256k1);
|
||||
auto const dst = randomKeyPair(KeyType::secp256k1);
|
||||
auto const innerTx = makeExportedPayment(
|
||||
calcAccountID(invalidSigner.first), calcAccountID(dst.first));
|
||||
auto const txHash = makeHash("export-upgrade-race");
|
||||
|
||||
auto invalidSig = makeInvalidSignature();
|
||||
auto replacementSig = makeInvalidSignature(20);
|
||||
|
||||
ExportSigCollector collector;
|
||||
collector.addUnverifiedSignature(
|
||||
txHash, invalidSigner.first, invalidSig, 7);
|
||||
|
||||
bool mutated = false;
|
||||
auto stats = ExportSignatureUpgrader::upgradeUnverifiedSignatures(
|
||||
collector,
|
||||
innerTx,
|
||||
txHash,
|
||||
12,
|
||||
[&](PublicKey const& pk) {
|
||||
if (pk == invalidSigner.first && !mutated)
|
||||
{
|
||||
mutated = true;
|
||||
collector.addUnverifiedSignature(
|
||||
txHash, invalidSigner.first, replacementSig, 12);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
nullJournal());
|
||||
|
||||
BEAST_EXPECT(mutated);
|
||||
BEAST_EXPECT(stats.inspected == 1);
|
||||
BEAST_EXPECT(stats.upgraded == 0);
|
||||
BEAST_EXPECT(stats.removedInvalid == 0);
|
||||
BEAST_EXPECT(
|
||||
!collector.hasVerifiedSignature(txHash, invalidSigner.first));
|
||||
|
||||
auto const unverified = collector.unverifiedSignatures(txHash);
|
||||
auto const it = unverified.find(invalidSigner.first);
|
||||
BEAST_EXPECT(it != unverified.end());
|
||||
if (it != unverified.end())
|
||||
BEAST_EXPECT(it->second == replacementSig);
|
||||
}
|
||||
|
||||
void
|
||||
run() override
|
||||
{
|
||||
testUpgradeFiltersAndRemovesInvalid();
|
||||
testInvalidRemovalRequiresStoredBufferMatch();
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(ExportSignatureUpgrader, app, ripple);
|
||||
|
||||
} // namespace test
|
||||
} // namespace ripple
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user