From cb52c9af001316a906665a10283f6f5dd271ebb3 Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Fri, 5 Sep 2025 14:08:17 +0100 Subject: [PATCH 01/18] fix: Remove extra @ in notify-clio.yml (#5771) --- .github/workflows/notify-clio.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/notify-clio.yml b/.github/workflows/notify-clio.yml index b0e5f35787..0c85bc9302 100644 --- a/.github/workflows/notify-clio.yml +++ b/.github/workflows/notify-clio.yml @@ -53,7 +53,7 @@ jobs: - name: Calculate conan reference id: conan_ref run: | - echo "conan_ref=${{ steps.generate.outputs.version }}@${{ steps.generate.outputs.user }}/@${{ steps.generate.outputs.channel }}" >> "${GITHUB_OUTPUT}" + echo "conan_ref=${{ steps.generate.outputs.version }}@${{ steps.generate.outputs.user }}/${{ steps.generate.outputs.channel }}" >> "${GITHUB_OUTPUT}" - name: Add Conan remote run: | echo "Adding Conan remote '${{ inputs.conan_remote_name }}' at ${{ inputs.conan_remote_url }}." From 9fe0a154f15df0e6314317b1b5952353470c0765 Mon Sep 17 00:00:00 2001 From: tzchenxixi Date: Mon, 8 Sep 2025 21:13:32 +0800 Subject: [PATCH 02/18] chore: remove redundant word in comment (#5752) --- src/xrpld/app/paths/Pathfinder.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/xrpld/app/paths/Pathfinder.h b/src/xrpld/app/paths/Pathfinder.h index ea3928dff4..b6c8bb8b2d 100644 --- a/src/xrpld/app/paths/Pathfinder.h +++ b/src/xrpld/app/paths/Pathfinder.h @@ -166,7 +166,7 @@ private: int addFlags, std::function const& continueCallback); - // Compute the liquidity for a path. Return tesSUCCESS if it has has enough + // Compute the liquidity for a path. Return tesSUCCESS if it has enough // liquidity to be worth keeping, otherwise an error. TER getPathLiquidity( From 6d40b882a4684d6926d0b23ad0e68c847e19e1ef Mon Sep 17 00:00:00 2001 From: Bronek Kozicki Date: Mon, 8 Sep 2025 14:54:50 +0100 Subject: [PATCH 03/18] Switch on-trigger to minimal build (#5773) --- .github/workflows/on-trigger.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/on-trigger.yml b/.github/workflows/on-trigger.yml index dcd592a1f7..d005d43d33 100644 --- a/.github/workflows/on-trigger.yml +++ b/.github/workflows/on-trigger.yml @@ -112,7 +112,7 @@ jobs: dependencies_force_build: ${{ needs.generate-outputs.outputs.dependencies_force_build == 'true' }} dependencies_force_upload: ${{ needs.generate-outputs.outputs.dependencies_force_upload == 'true' }} os: ${{ matrix.os }} - strategy_matrix: "all" + strategy_matrix: "minimal" secrets: codecov_token: ${{ secrets.CODECOV_TOKEN }} conan_remote_username: ${{ secrets.CONAN_REMOTE_USERNAME }} From 9ebeb413e45b64b738c9c2ee1367b1e7674859be Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Mon, 8 Sep 2025 16:15:59 +0100 Subject: [PATCH 04/18] feat: Implement separate upload workflow (#5762) * feat: Implement separate upload workflow * Use cleanup-workspace * Name some workflows reusable * Add dependencies --- .github/actions/build-deps/action.yml | 31 +------- .github/actions/build-test/action.yml | 1 + .github/actions/setup-conan/action.yml | 43 ++++++++++ .github/scripts/strategy-matrix/generate.py | 0 .github/workflows/build-test.yml | 62 ++------------- .github/workflows/notify-clio.yml | 19 +++-- .github/workflows/on-pr.yml | 26 +------ .github/workflows/on-trigger.yml | 36 --------- .../workflows/reusable-strategy-matrix.yml | 38 +++++++++ .../reusable-upload-conan-deps-os.yml | 78 +++++++++++++++++++ .github/workflows/upload-conan-deps.yml | 62 +++++++++++++++ 11 files changed, 243 insertions(+), 153 deletions(-) create mode 100644 .github/actions/setup-conan/action.yml mode change 100644 => 100755 .github/scripts/strategy-matrix/generate.py create mode 100644 .github/workflows/reusable-strategy-matrix.yml create mode 100644 .github/workflows/reusable-upload-conan-deps-os.yml create mode 100644 .github/workflows/upload-conan-deps.yml diff --git a/.github/actions/build-deps/action.yml b/.github/actions/build-deps/action.yml index ba4f4e9e2f..c3b405e70f 100644 --- a/.github/actions/build-deps/action.yml +++ b/.github/actions/build-deps/action.yml @@ -1,7 +1,5 @@ -# This action installs and optionally uploads Conan dependencies to a remote -# repository. The dependencies will only be uploaded if the credentials are -# provided. name: Build Conan dependencies +description: "Install Conan dependencies, optionally forcing a rebuild of all dependencies." # Note that actions do not support 'type' and all inputs are strings, see # https://docs.github.com/en/actions/reference/workflows-and-actions/metadata-syntax#inputs. @@ -12,28 +10,10 @@ inputs: build_type: description: 'The build type to use ("Debug", "Release").' required: true - conan_remote_name: - description: "The name of the Conan remote to use." - required: true - conan_remote_url: - description: "The URL of the Conan endpoint to use." - required: true - conan_remote_username: - description: "The username for logging into the Conan remote. If not provided, the dependencies will not be uploaded." - required: false - default: "" - conan_remote_password: - description: "The password for logging into the Conan remote. If not provided, the dependencies will not be uploaded." - required: false - default: "" force_build: description: 'Force building of all dependencies ("true", "false").' required: false default: "false" - force_upload: - description: 'Force uploading of all dependencies ("true", "false").' - required: false - default: "false" runs: using: composite @@ -51,12 +31,3 @@ runs: --options:host '&:xrpld=True' \ --settings:all build_type=${{ inputs.build_type }} \ --format=json .. - - name: Upload Conan dependencies - if: ${{ inputs.conan_remote_username != '' && inputs.conan_remote_password != '' }} - shell: bash - working-directory: ${{ inputs.build_dir }} - run: | - echo "Logging into Conan remote '${{ inputs.conan_remote_name }}' at ${{ inputs.conan_remote_url }}." - conan remote login ${{ inputs.conan_remote_name }} "${{ inputs.conan_remote_username }}" --password "${{ inputs.conan_remote_password }}" - echo 'Uploading dependencies.' - conan upload '*' --confirm --check ${{ inputs.force_upload == 'true' && '--force' || '' }} --remote=${{ inputs.conan_remote_name }} diff --git a/.github/actions/build-test/action.yml b/.github/actions/build-test/action.yml index ee945dcf38..cf1bac16f7 100644 --- a/.github/actions/build-test/action.yml +++ b/.github/actions/build-test/action.yml @@ -1,6 +1,7 @@ # This action build and tests the binary. The Conan dependencies must have # already been installed (see the build-deps action). name: Build and Test +description: "Build and test the binary." # Note that actions do not support 'type' and all inputs are strings, see # https://docs.github.com/en/actions/reference/workflows-and-actions/metadata-syntax#inputs. diff --git a/.github/actions/setup-conan/action.yml b/.github/actions/setup-conan/action.yml new file mode 100644 index 0000000000..d31809dc94 --- /dev/null +++ b/.github/actions/setup-conan/action.yml @@ -0,0 +1,43 @@ +name: Setup Conan +description: "Set up Conan configuration, profile, and remote." + +inputs: + conan_remote_name: + description: "The name of the Conan remote to use." + required: false + default: xrplf + conan_remote_url: + description: "The URL of the Conan endpoint to use." + required: false + default: https://conan.ripplex.io + +runs: + using: composite + + steps: + - name: Set up Conan configuration + shell: bash + run: | + echo 'Installing configuration.' + cat conan/global.conf ${{ runner.os == 'Linux' && '>>' || '>' }} $(conan config home)/global.conf + + echo 'Conan configuration:' + conan config show '*' + + - name: Set up Conan profile + shell: bash + run: | + echo 'Installing profile.' + conan config install conan/profiles/default -tf $(conan config home)/profiles/ + + echo 'Conan profile:' + conan profile show + + - name: Set up Conan remote + shell: bash + run: | + echo "Adding Conan remote '${{ inputs.conan_remote_name }}' at ${{ inputs.conan_remote_url }}." + conan remote add --index 0 --force ${{ inputs.conan_remote_name }} ${{ inputs.conan_remote_url }} + + echo 'Listing Conan remotes.' + conan remote list diff --git a/.github/scripts/strategy-matrix/generate.py b/.github/scripts/strategy-matrix/generate.py old mode 100644 new mode 100755 diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 40399539b8..69ff986f98 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -13,14 +13,6 @@ on: required: false type: string default: ".build" - conan_remote_name: - description: "The name of the Conan remote to use." - required: true - type: string - conan_remote_url: - description: "The URL of the Conan endpoint to use." - required: true - type: string dependencies_force_build: description: "Force building of all dependencies." required: false @@ -45,12 +37,6 @@ on: codecov_token: description: "The Codecov token to use for uploading coverage reports." required: false - conan_remote_username: - description: "The username for logging into the Conan remote. If not provided, the dependencies will not be uploaded." - required: false - conan_remote_password: - description: "The password for logging into the Conan remote. If not provided, the dependencies will not be uploaded." - required: false concurrency: group: ${{ github.workflow }}-${{ github.ref }}-${{ inputs.os }} @@ -63,20 +49,10 @@ defaults: jobs: # Generate the strategy matrix to be used by the following job. generate-matrix: - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 - - name: Set up Python - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 - with: - python-version: 3.13 - - name: Generate strategy matrix - working-directory: .github/scripts/strategy-matrix - id: generate - run: python generate.py ${{ inputs.strategy_matrix == 'all' && '--all' || '' }} --config=${{ inputs.os }}.json >> "${GITHUB_OUTPUT}" - outputs: - matrix: ${{ steps.generate.outputs.matrix }} + uses: ./.github/workflows/reusable-strategy-matrix.yml + with: + os: ${{ inputs.os }} + strategy_matrix: ${{ inputs.strategy_matrix }} # Build and test the binary. build-test: @@ -148,40 +124,16 @@ jobs: echo 'Checking nproc version.' nproc --version - - name: Set up Conan configuration - run: | - echo 'Installing configuration.' - cat conan/global.conf ${{ inputs.os == 'linux' && '>>' || '>' }} $(conan config home)/global.conf - - echo 'Conan configuration:' - conan config show '*' - - name: Set up Conan profile - run: | - echo 'Installing profile.' - conan config install conan/profiles/default -tf $(conan config home)/profiles/ - - echo 'Conan profile:' - conan profile show - - name: Set up Conan remote - shell: bash - run: | - echo "Adding Conan remote '${{ inputs.conan_remote_name }}' at ${{ inputs.conan_remote_url }}." - conan remote add --index 0 --force ${{ inputs.conan_remote_name }} ${{ inputs.conan_remote_url }} - - echo 'Listing Conan remotes.' - conan remote list + - name: Setup Conan + uses: ./.github/actions/setup-conan - name: Build dependencies uses: ./.github/actions/build-deps with: build_dir: ${{ inputs.build_dir }} build_type: ${{ matrix.build_type }} - conan_remote_name: ${{ inputs.conan_remote_name }} - conan_remote_url: ${{ inputs.conan_remote_url }} - conan_remote_username: ${{ secrets.conan_remote_username }} - conan_remote_password: ${{ secrets.conan_remote_password }} force_build: ${{ inputs.dependencies_force_build }} - force_upload: ${{ inputs.dependencies_force_upload }} + - name: Build and test binary uses: ./.github/actions/build-test with: diff --git a/.github/workflows/notify-clio.yml b/.github/workflows/notify-clio.yml index 0c85bc9302..692904ff12 100644 --- a/.github/workflows/notify-clio.yml +++ b/.github/workflows/notify-clio.yml @@ -9,12 +9,14 @@ on: inputs: conan_remote_name: description: "The name of the Conan remote to use." - required: true + required: false type: string + default: xrplf conan_remote_url: description: "The URL of the Conan endpoint to use." - required: true + required: false type: string + default: https://conan.ripplex.io secrets: clio_notify_token: description: "The GitHub token to notify Clio about new versions." @@ -54,12 +56,13 @@ jobs: id: conan_ref run: | echo "conan_ref=${{ steps.generate.outputs.version }}@${{ steps.generate.outputs.user }}/${{ steps.generate.outputs.channel }}" >> "${GITHUB_OUTPUT}" - - name: Add Conan remote - run: | - echo "Adding Conan remote '${{ inputs.conan_remote_name }}' at ${{ inputs.conan_remote_url }}." - conan remote add --index 0 --force ${{ inputs.conan_remote_name }} ${{ inputs.conan_remote_url }} - echo 'Listing Conan remotes.' - conan remote list + + - name: Set up Conan + uses: ./.github/actions/setup-conan + with: + conan_remote_name: ${{ inputs.conan_remote_name }} + conan_remote_url: ${{ inputs.conan_remote_url }} + - name: Log into Conan remote run: conan remote login ${{ inputs.conan_remote_name }} "${{ secrets.conan_remote_username }}" --password "${{ secrets.conan_remote_password }}" - name: Upload package diff --git a/.github/workflows/on-pr.yml b/.github/workflows/on-pr.yml index 8008aebcbb..f72b8a9121 100644 --- a/.github/workflows/on-pr.yml +++ b/.github/workflows/on-pr.yml @@ -23,10 +23,6 @@ defaults: run: shell: bash -env: - CONAN_REMOTE_NAME: xrplf - CONAN_REMOTE_URL: https://conan.ripplex.io - jobs: # This job determines whether the rest of the workflow should run. It runs # when the PR is not a draft (which should also cover merge-group) or @@ -105,40 +101,22 @@ jobs: if: needs.should-run.outputs.go == 'true' uses: ./.github/workflows/check-levelization.yml - # This job works around the limitation that GitHub Actions does not support - # using environment variables as inputs for reusable workflows. - generate-outputs: - needs: should-run - if: needs.should-run.outputs.go == 'true' - runs-on: ubuntu-latest - steps: - - name: No-op - run: true - outputs: - conan_remote_name: ${{ env.CONAN_REMOTE_NAME }} - conan_remote_url: ${{ env.CONAN_REMOTE_URL }} - build-test: - needs: generate-outputs + needs: should-run uses: ./.github/workflows/build-test.yml strategy: matrix: os: [linux, macos, windows] with: - conan_remote_name: ${{ needs.generate-outputs.outputs.conan_remote_name }} - conan_remote_url: ${{ needs.generate-outputs.outputs.conan_remote_url }} os: ${{ matrix.os }} secrets: codecov_token: ${{ secrets.CODECOV_TOKEN }} notify-clio: needs: - - generate-outputs + - should-run - build-test uses: ./.github/workflows/notify-clio.yml - with: - conan_remote_name: ${{ needs.generate-outputs.outputs.conan_remote_name }} - conan_remote_url: ${{ needs.generate-outputs.outputs.conan_remote_url }} secrets: clio_notify_token: ${{ secrets.CLIO_NOTIFY_TOKEN }} conan_remote_username: ${{ secrets.CONAN_REMOTE_USERNAME }} diff --git a/.github/workflows/on-trigger.yml b/.github/workflows/on-trigger.yml index d005d43d33..14884391ef 100644 --- a/.github/workflows/on-trigger.yml +++ b/.github/workflows/on-trigger.yml @@ -66,54 +66,18 @@ defaults: run: shell: bash -env: - CONAN_REMOTE_NAME: xrplf - CONAN_REMOTE_URL: https://conan.ripplex.io - jobs: check-missing-commits: if: ${{ github.event_name == 'push' && github.ref_type == 'branch' && contains(fromJSON('["develop", "release"]'), github.ref_name) }} uses: ./.github/workflows/check-missing-commits.yml - # This job works around the limitation that GitHub Actions does not support - # using environment variables as inputs for reusable workflows. It also sets - # outputs that depend on the event that triggered the workflow. - generate-outputs: - runs-on: ubuntu-latest - steps: - - name: Check inputs and set outputs - id: generate - run: | - if [[ '${{ github.event_name }}' == 'push' ]]; then - echo 'dependencies_force_build=false' >> "${GITHUB_OUTPUT}" - echo 'dependencies_force_upload=false' >> "${GITHUB_OUTPUT}" - elif [[ '${{ github.event_name }}' == 'schedule' ]]; then - echo 'dependencies_force_build=true' >> "${GITHUB_OUTPUT}" - echo 'dependencies_force_upload=false' >> "${GITHUB_OUTPUT}" - else - echo 'dependencies_force_build=${{ inputs.dependencies_force_build }}' >> "${GITHUB_OUTPUT}" - echo 'dependencies_force_upload=${{ inputs.dependencies_force_upload }}' >> "${GITHUB_OUTPUT}" - fi - outputs: - conan_remote_name: ${{ env.CONAN_REMOTE_NAME }} - conan_remote_url: ${{ env.CONAN_REMOTE_URL }} - dependencies_force_build: ${{ steps.generate.outputs.dependencies_force_build }} - dependencies_force_upload: ${{ steps.generate.outputs.dependencies_force_upload }} - build-test: - needs: generate-outputs uses: ./.github/workflows/build-test.yml strategy: matrix: os: [linux, macos, windows] with: - conan_remote_name: ${{ needs.generate-outputs.outputs.conan_remote_name }} - conan_remote_url: ${{ needs.generate-outputs.outputs.conan_remote_url }} - dependencies_force_build: ${{ needs.generate-outputs.outputs.dependencies_force_build == 'true' }} - dependencies_force_upload: ${{ needs.generate-outputs.outputs.dependencies_force_upload == 'true' }} os: ${{ matrix.os }} strategy_matrix: "minimal" secrets: codecov_token: ${{ secrets.CODECOV_TOKEN }} - conan_remote_username: ${{ secrets.CONAN_REMOTE_USERNAME }} - conan_remote_password: ${{ secrets.CONAN_REMOTE_PASSWORD }} diff --git a/.github/workflows/reusable-strategy-matrix.yml b/.github/workflows/reusable-strategy-matrix.yml new file mode 100644 index 0000000000..5c84b51de1 --- /dev/null +++ b/.github/workflows/reusable-strategy-matrix.yml @@ -0,0 +1,38 @@ +name: Generate strategy matrix + +on: + workflow_call: + inputs: + os: + description: 'The operating system to use for the build ("linux", "macos", "windows").' + required: true + type: string + strategy_matrix: + # TODO: Support additional strategies, e.g. "ubuntu" for generating all Ubuntu configurations. + description: 'The strategy matrix to use for generating the configurations ("minimal", "all").' + required: false + type: string + default: "minimal" + outputs: + matrix: + description: "The generated strategy matrix." + value: ${{ jobs.generate-matrix.outputs.matrix }} + +jobs: + generate-matrix: + runs-on: ubuntu-latest + outputs: + matrix: ${{ steps.generate.outputs.matrix }} + steps: + - name: Checkout repository + uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + + - name: Set up Python + uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 + with: + python-version: 3.13 + + - name: Generate strategy matrix + working-directory: .github/scripts/strategy-matrix + id: generate + run: ./generate.py ${{ inputs.strategy_matrix == 'all' && '--all' || '' }} --config=${{ inputs.os }}.json >> "${GITHUB_OUTPUT}" diff --git a/.github/workflows/reusable-upload-conan-deps-os.yml b/.github/workflows/reusable-upload-conan-deps-os.yml new file mode 100644 index 0000000000..787cf6a39e --- /dev/null +++ b/.github/workflows/reusable-upload-conan-deps-os.yml @@ -0,0 +1,78 @@ +name: Upload Conan Dependencies + +on: + workflow_call: + inputs: + build_dir: + description: "The directory where to build." + required: false + type: string + default: ".build" + conan_remote_name: + description: "The name of the Conan remote to use." + required: false + type: string + default: xrplf + + os: + description: 'The operating system to use for the build ("linux", "macos", "windows").' + required: true + type: string + force_source_build: + description: "Force source build of all dependencies" + required: true + type: boolean + force_upload: + description: "Force upload of all dependencies" + required: true + type: boolean + secrets: + CONAN_USERNAME: + required: true + CONAN_PASSWORD: + required: true + +jobs: + generate-matrix: + uses: ./.github/workflows/reusable-strategy-matrix.yml + with: + os: ${{ inputs.os }} + strategy_matrix: all + + upload-conan-deps: + needs: + - generate-matrix + strategy: + fail-fast: false + matrix: ${{ fromJson(needs.generate-matrix.outputs.matrix) }} + runs-on: ${{ matrix.architecture.runner }} + container: ${{ inputs.os == 'linux' && format('ghcr.io/xrplf/ci/{0}-{1}:{2}-{3}', matrix.os.distro_name, matrix.os.distro_version, matrix.os.compiler_name, matrix.os.compiler_version) || null }} + + steps: + - name: Cleanup workspace + if: ${{ runner.os == 'macOS' }} + uses: XRPLF/actions/.github/actions/cleanup-workspace@3f044c7478548e3c32ff68980eeb36ece02b364e + + - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + - name: Prepare runner + uses: XRPLF/actions/.github/actions/prepare-runner@638e0dc11ea230f91bd26622fb542116bb5254d5 + with: + disable_ccache: false + + - name: Setup Conan + uses: ./.github/actions/setup-conan + + - name: Build dependencies + uses: ./.github/actions/build-deps + with: + build_dir: ${{ inputs.build_dir }} + build_type: ${{ matrix.build_type }} + force_build: ${{ inputs.force_source_build }} + + - name: Login to Conan + if: github.repository_owner == 'XRPLF' && github.event_name != 'pull_request' + run: conan remote login -p ${{ secrets.CONAN_PASSWORD }} ${{ inputs.conan_remote_name }} ${{ secrets.CONAN_USERNAME }} + + - name: Upload Conan packages + if: github.repository_owner == 'XRPLF' && github.event_name != 'pull_request' && github.event_name != 'schedule' + run: conan upload "*" -r=${{ inputs.conan_remote_name }} --confirm ${{ inputs.force_upload == 'true' && '--force' || '' }} diff --git a/.github/workflows/upload-conan-deps.yml b/.github/workflows/upload-conan-deps.yml new file mode 100644 index 0000000000..5a6adc99be --- /dev/null +++ b/.github/workflows/upload-conan-deps.yml @@ -0,0 +1,62 @@ +name: Upload Conan Dependencies + +on: + schedule: + - cron: "0 9 * * 1-5" + workflow_dispatch: + inputs: + force_source_build: + description: "Force source build of all dependencies" + required: false + default: false + type: boolean + force_upload: + description: "Force upload of all dependencies" + required: false + default: false + type: boolean + pull_request: + branches: [develop] + paths: + - .github/workflows/upload-conan-deps.yml + + - .github/workflows/reusable-strategy-matrix.yml + - .github/workflows/reusable-upload-conan-deps-os.yml + + - .github/actions/build-deps/action.yml + - ".github/scripts/strategy-matrix/**" + + - conanfile.py + - conan.lock + push: + branches: [develop] + paths: + - .github/workflows/upload-conan-deps.yml + + - .github/workflows/reusable-strategy-matrix.yml + - .github/workflows/reusable-upload-conan-deps-os.yml + + - .github/actions/build-deps/action.yml + - ".github/scripts/strategy-matrix/**" + + - conanfile.py + - conan.lock + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + run-upload-conan-deps: + strategy: + fail-fast: true + matrix: + os: ["linux", "macos", "windows"] + uses: ./.github/workflows/reusable-upload-conan-deps-os.yml + with: + force_source_build: ${{ github.event_name == 'schedule' || github.event.inputs.force_source_build == 'true' }} + force_upload: ${{ github.event.inputs.force_upload == 'true' }} + os: ${{ matrix.os }} + secrets: + CONAN_USERNAME: ${{ secrets.CONAN_REMOTE_USERNAME }} + CONAN_PASSWORD: ${{ secrets.CONAN_REMOTE_PASSWORD }} From bcde2790a412e647f15bb8e4df6c778c4050bd67 Mon Sep 17 00:00:00 2001 From: Wo Jake Date: Tue, 9 Sep 2025 02:03:20 +0800 Subject: [PATCH 05/18] Update old links & descriptions in README.md (#4701) --- README.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index fe7daa38bc..dbc5ab078e 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ The [XRP Ledger](https://xrpl.org/) is a decentralized cryptographic ledger powe ## XRP -[XRP](https://xrpl.org/xrp.html) is a public, counterparty-free asset native to the XRP Ledger, and is designed to bridge the many different currencies in use worldwide. XRP is traded on the open-market and is available for anyone to access. The XRP Ledger was created in 2012 with a finite supply of 100 billion units of XRP. +[XRP](https://xrpl.org/xrp.html) is a public, counterparty-free crypto-asset native to the XRP Ledger, and is designed as a gas token for network services and to bridge different currencies. XRP is traded on the open-market and is available for anyone to access. The XRP Ledger was created in 2012 with a finite supply of 100 billion units of XRP. ## rippled @@ -23,19 +23,19 @@ If you are interested in running an **API Server** (including a **Full History S - **[Censorship-Resistant Transaction Processing][]:** No single party decides which transactions succeed or fail, and no one can "roll back" a transaction after it completes. As long as those who choose to participate in the network keep it healthy, they can settle transactions in seconds. - **[Fast, Efficient Consensus Algorithm][]:** The XRP Ledger's consensus algorithm settles transactions in 4 to 5 seconds, processing at a throughput of up to 1500 transactions per second. These properties put XRP at least an order of magnitude ahead of other top digital assets. -- **[Finite XRP Supply][]:** When the XRP Ledger began, 100 billion XRP were created, and no more XRP will ever be created. The available supply of XRP decreases slowly over time as small amounts are destroyed to pay transaction costs. -- **[Responsible Software Governance][]:** A team of full-time, world-class developers at Ripple maintain and continually improve the XRP Ledger's underlying software with contributions from the open-source community. Ripple acts as a steward for the technology and an advocate for its interests, and builds constructive relationships with governments and financial institutions worldwide. +- **[Finite XRP Supply][]:** When the XRP Ledger began, 100 billion XRP were created, and no more XRP will ever be created. The available supply of XRP decreases slowly over time as small amounts are destroyed to pay transaction fees. +- **[Responsible Software Governance][]:** A team of full-time developers at Ripple & other organizations maintain and continually improve the XRP Ledger's underlying software with contributions from the open-source community. Ripple acts as a steward for the technology and an advocate for its interests. - **[Secure, Adaptable Cryptography][]:** The XRP Ledger relies on industry standard digital signature systems like ECDSA (the same scheme used by Bitcoin) but also supports modern, efficient algorithms like Ed25519. The extensible nature of the XRP Ledger's software makes it possible to add and disable algorithms as the state of the art in cryptography advances. -- **[Modern Features for Smart Contracts][]:** Features like Escrow, Checks, and Payment Channels support cutting-edge financial applications including the [Interledger Protocol](https://interledger.org/). This toolbox of advanced features comes with safety features like a process for amending the network and separate checks against invariant constraints. +- **[Modern Features][]:** Features like Escrow, Checks, and Payment Channels support financial applications atop of the XRP Ledger. This toolbox of advanced features comes with safety features like a process for amending the network and separate checks against invariant constraints. - **[On-Ledger Decentralized Exchange][]:** In addition to all the features that make XRP useful on its own, the XRP Ledger also has a fully-functional accounting system for tracking and trading obligations denominated in any way users want, and an exchange built into the protocol. The XRP Ledger can settle long, cross-currency payment paths and exchanges of multiple currencies in atomic transactions, bridging gaps of trust with XRP. -[Censorship-Resistant Transaction Processing]: https://xrpl.org/xrp-ledger-overview.html#censorship-resistant-transaction-processing -[Fast, Efficient Consensus Algorithm]: https://xrpl.org/xrp-ledger-overview.html#fast-efficient-consensus-algorithm -[Finite XRP Supply]: https://xrpl.org/xrp-ledger-overview.html#finite-xrp-supply -[Responsible Software Governance]: https://xrpl.org/xrp-ledger-overview.html#responsible-software-governance -[Secure, Adaptable Cryptography]: https://xrpl.org/xrp-ledger-overview.html#secure-adaptable-cryptography -[Modern Features for Smart Contracts]: https://xrpl.org/xrp-ledger-overview.html#modern-features-for-smart-contracts -[On-Ledger Decentralized Exchange]: https://xrpl.org/xrp-ledger-overview.html#on-ledger-decentralized-exchange +[Censorship-Resistant Transaction Processing]: https://xrpl.org/transaction-censorship-detection.html#transaction-censorship-detection +[Fast, Efficient Consensus Algorithm]: https://xrpl.org/consensus-research.html#consensus-research +[Finite XRP Supply]: https://xrpl.org/what-is-xrp.html +[Responsible Software Governance]: https://xrpl.org/contribute-code.html#contribute-code-to-the-xrp-ledger +[Secure, Adaptable Cryptography]: https://xrpl.org/cryptographic-keys.html#cryptographic-keys +[Modern Features]: https://xrpl.org/use-specialized-payment-types.html +[On-Ledger Decentralized Exchange]: https://xrpl.org/decentralized-exchange.html#decentralized-exchange ## Source Code From da4c8c9550157dd91950aa774d13659104609e2a Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Tue, 9 Sep 2025 16:25:41 +0100 Subject: [PATCH 06/18] ci: Only run build-test/notify-clio if should-run indicates to (#5777) - Fixes an issue introduced by #5762 which removed the transitive `should-run` check from these two jobs. --- .github/workflows/on-pr.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/on-pr.yml b/.github/workflows/on-pr.yml index f72b8a9121..4aa9ca5869 100644 --- a/.github/workflows/on-pr.yml +++ b/.github/workflows/on-pr.yml @@ -103,6 +103,7 @@ jobs: build-test: needs: should-run + if: needs.should-run.outputs.go == 'true' uses: ./.github/workflows/build-test.yml strategy: matrix: @@ -116,6 +117,7 @@ jobs: needs: - should-run - build-test + if: needs.should-run.outputs.go == 'true' uses: ./.github/workflows/notify-clio.yml secrets: clio_notify_token: ${{ secrets.CLIO_NOTIFY_TOKEN }} From f1eaa6a2641238cf1c4098f4a006e5ab04a3afee Mon Sep 17 00:00:00 2001 From: yinyiqian1 Date: Tue, 9 Sep 2025 11:57:28 -0400 Subject: [PATCH 07/18] enable fixAMMClawbackRounding (#5750) --- include/xrpl/protocol/detail/features.macro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/xrpl/protocol/detail/features.macro b/include/xrpl/protocol/detail/features.macro index e2725d1fc0..264fad7fc2 100644 --- a/include/xrpl/protocol/detail/features.macro +++ b/include/xrpl/protocol/detail/features.macro @@ -34,7 +34,7 @@ XRPL_FIX (PriceOracleOrder, Supported::no, VoteBehavior::DefaultNo) XRPL_FIX (MPTDeliveredAmount, Supported::no, VoteBehavior::DefaultNo) -XRPL_FIX (AMMClawbackRounding, Supported::no, VoteBehavior::DefaultNo) +XRPL_FIX (AMMClawbackRounding, Supported::yes, VoteBehavior::DefaultNo) XRPL_FEATURE(TokenEscrow, Supported::yes, VoteBehavior::DefaultNo) XRPL_FIX (EnforceNFTokenTrustlineV2, Supported::yes, VoteBehavior::DefaultNo) XRPL_FIX (AMMv1_3, Supported::yes, VoteBehavior::DefaultNo) From 148f669a2562a1870503969946286885a5981835 Mon Sep 17 00:00:00 2001 From: Ed Hennis Date: Tue, 9 Sep 2025 14:07:04 -0400 Subject: [PATCH 08/18] chore: "passed" fails if any previous jobs fail or are cancelled (#5776) For the purposes of being able to merge a PR, Github Actions jobs count as passed if they ran and passed, or were skipped. With this change, if any of the jobs that "passed" depends on fail or are cancelled, then "passed" will fail. If they all succeed or are skipped, then "passed" is skipped, which does not prevent a merge. This saves spinning up a runner in the usual case where things work, and will simplify our branch protection rules, so that only "passed" will need to be checked. --- .github/workflows/on-pr.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/on-pr.yml b/.github/workflows/on-pr.yml index 4aa9ca5869..f194bd1e37 100644 --- a/.github/workflows/on-pr.yml +++ b/.github/workflows/on-pr.yml @@ -125,11 +125,12 @@ jobs: conan_remote_password: ${{ secrets.CONAN_REMOTE_PASSWORD }} passed: + if: failure() || cancelled() needs: - build-test - check-format - check-levelization runs-on: ubuntu-latest steps: - - name: No-op - run: true + - name: Fail + run: false From e67e0395df2d7e3ead98163920112b7772bed325 Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Tue, 9 Sep 2025 20:47:06 +0100 Subject: [PATCH 09/18] ci: Limit number of parallel jobs in "upload-conan-deps" (#5781) - This should prevent Artifactory from being overloaded by too many requests at a time. - Uses "max-parallel" to limit the build job to 10 simultaneous instances. - Only run the minimal matrix on PRs. --- .github/scripts/strategy-matrix/generate.py | 43 +++++++--- .../workflows/reusable-strategy-matrix.yml | 4 +- .../reusable-upload-conan-deps-os.yml | 78 ------------------- .github/workflows/upload-conan-deps.yml | 56 +++++++++---- 4 files changed, 75 insertions(+), 106 deletions(-) delete mode 100644 .github/workflows/reusable-upload-conan-deps-os.yml diff --git a/.github/scripts/strategy-matrix/generate.py b/.github/scripts/strategy-matrix/generate.py index 9743d5a4e3..b6f6601291 100755 --- a/.github/scripts/strategy-matrix/generate.py +++ b/.github/scripts/strategy-matrix/generate.py @@ -2,7 +2,17 @@ import argparse import itertools import json -import re +from pathlib import Path +from dataclasses import dataclass + +THIS_DIR = Path(__file__).parent.resolve() + +@dataclass +class Config: + architecture: list[dict] + os: list[dict] + build_type: list[str] + cmake_args: list[str] ''' Generate a strategy matrix for GitHub Actions CI. @@ -18,9 +28,9 @@ We will further set additional CMake arguments as follows: - Certain Debian Bookworm configurations will change the reference fee, enable codecov, and enable voidstar in PRs. ''' -def generate_strategy_matrix(all: bool, architecture: list[dict], os: list[dict], build_type: list[str], cmake_args: list[str]) -> dict: +def generate_strategy_matrix(all: bool, config: Config) -> list: configurations = [] - for architecture, os, build_type, cmake_args in itertools.product(architecture, os, build_type, cmake_args): + for architecture, os, build_type, cmake_args in itertools.product(config.architecture, config.os, config.build_type, config.cmake_args): # The default CMake target is 'all' for Linux and MacOS and 'install' # for Windows, but it can get overridden for certain configurations. cmake_target = 'install' if os["distro_name"] == 'windows' else 'all' @@ -158,21 +168,30 @@ def generate_strategy_matrix(all: bool, architecture: list[dict], os: list[dict] 'architecture': architecture, }) - return {'include': configurations} + return configurations + + +def read_config(file: Path) -> Config: + config = json.loads(file.read_text()) + if config['architecture'] is None or config['os'] is None or config['build_type'] is None or config['cmake_args'] is None: + raise Exception('Invalid configuration file.') + + return Config(**config) if __name__ == '__main__': parser = argparse.ArgumentParser() parser.add_argument('-a', '--all', help='Set to generate all configurations (generally used when merging a PR) or leave unset to generate a subset of configurations (generally used when committing to a PR).', action="store_true") - parser.add_argument('-c', '--config', help='Path to the JSON file containing the strategy matrix configurations.', required=True, type=str) + parser.add_argument('-c', '--config', help='Path to the JSON file containing the strategy matrix configurations.', required=False, type=Path) args = parser.parse_args() - # Load the JSON configuration file. - config = None - with open(args.config, 'r') as f: - config = json.load(f) - if config['architecture'] is None or config['os'] is None or config['build_type'] is None or config['cmake_args'] is None: - raise Exception('Invalid configuration file.') + matrix = [] + if args.config is None or args.config == '': + matrix += generate_strategy_matrix(args.all, read_config(THIS_DIR / "linux.json")) + matrix += generate_strategy_matrix(args.all, read_config(THIS_DIR / "macos.json")) + matrix += generate_strategy_matrix(args.all, read_config(THIS_DIR / "windows.json")) + else: + matrix += generate_strategy_matrix(args.all, read_config(args.config)) # Generate the strategy matrix. - print(f'matrix={json.dumps(generate_strategy_matrix(args.all, config['architecture'], config['os'], config['build_type'], config['cmake_args']))}') + print(f'matrix={json.dumps({"include": matrix})}') diff --git a/.github/workflows/reusable-strategy-matrix.yml b/.github/workflows/reusable-strategy-matrix.yml index 5c84b51de1..20a90fc2e3 100644 --- a/.github/workflows/reusable-strategy-matrix.yml +++ b/.github/workflows/reusable-strategy-matrix.yml @@ -5,7 +5,7 @@ on: inputs: os: description: 'The operating system to use for the build ("linux", "macos", "windows").' - required: true + required: false type: string strategy_matrix: # TODO: Support additional strategies, e.g. "ubuntu" for generating all Ubuntu configurations. @@ -35,4 +35,4 @@ jobs: - name: Generate strategy matrix working-directory: .github/scripts/strategy-matrix id: generate - run: ./generate.py ${{ inputs.strategy_matrix == 'all' && '--all' || '' }} --config=${{ inputs.os }}.json >> "${GITHUB_OUTPUT}" + run: ./generate.py ${{ inputs.strategy_matrix == 'all' && '--all' || '' }} ${{ inputs.os != '' && format('--config={0}.json', inputs.os) || '' }} >> "${GITHUB_OUTPUT}" diff --git a/.github/workflows/reusable-upload-conan-deps-os.yml b/.github/workflows/reusable-upload-conan-deps-os.yml deleted file mode 100644 index 787cf6a39e..0000000000 --- a/.github/workflows/reusable-upload-conan-deps-os.yml +++ /dev/null @@ -1,78 +0,0 @@ -name: Upload Conan Dependencies - -on: - workflow_call: - inputs: - build_dir: - description: "The directory where to build." - required: false - type: string - default: ".build" - conan_remote_name: - description: "The name of the Conan remote to use." - required: false - type: string - default: xrplf - - os: - description: 'The operating system to use for the build ("linux", "macos", "windows").' - required: true - type: string - force_source_build: - description: "Force source build of all dependencies" - required: true - type: boolean - force_upload: - description: "Force upload of all dependencies" - required: true - type: boolean - secrets: - CONAN_USERNAME: - required: true - CONAN_PASSWORD: - required: true - -jobs: - generate-matrix: - uses: ./.github/workflows/reusable-strategy-matrix.yml - with: - os: ${{ inputs.os }} - strategy_matrix: all - - upload-conan-deps: - needs: - - generate-matrix - strategy: - fail-fast: false - matrix: ${{ fromJson(needs.generate-matrix.outputs.matrix) }} - runs-on: ${{ matrix.architecture.runner }} - container: ${{ inputs.os == 'linux' && format('ghcr.io/xrplf/ci/{0}-{1}:{2}-{3}', matrix.os.distro_name, matrix.os.distro_version, matrix.os.compiler_name, matrix.os.compiler_version) || null }} - - steps: - - name: Cleanup workspace - if: ${{ runner.os == 'macOS' }} - uses: XRPLF/actions/.github/actions/cleanup-workspace@3f044c7478548e3c32ff68980eeb36ece02b364e - - - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 - - name: Prepare runner - uses: XRPLF/actions/.github/actions/prepare-runner@638e0dc11ea230f91bd26622fb542116bb5254d5 - with: - disable_ccache: false - - - name: Setup Conan - uses: ./.github/actions/setup-conan - - - name: Build dependencies - uses: ./.github/actions/build-deps - with: - build_dir: ${{ inputs.build_dir }} - build_type: ${{ matrix.build_type }} - force_build: ${{ inputs.force_source_build }} - - - name: Login to Conan - if: github.repository_owner == 'XRPLF' && github.event_name != 'pull_request' - run: conan remote login -p ${{ secrets.CONAN_PASSWORD }} ${{ inputs.conan_remote_name }} ${{ secrets.CONAN_USERNAME }} - - - name: Upload Conan packages - if: github.repository_owner == 'XRPLF' && github.event_name != 'pull_request' && github.event_name != 'schedule' - run: conan upload "*" -r=${{ inputs.conan_remote_name }} --confirm ${{ inputs.force_upload == 'true' && '--force' || '' }} diff --git a/.github/workflows/upload-conan-deps.yml b/.github/workflows/upload-conan-deps.yml index 5a6adc99be..f63482761d 100644 --- a/.github/workflows/upload-conan-deps.yml +++ b/.github/workflows/upload-conan-deps.yml @@ -21,7 +21,6 @@ on: - .github/workflows/upload-conan-deps.yml - .github/workflows/reusable-strategy-matrix.yml - - .github/workflows/reusable-upload-conan-deps-os.yml - .github/actions/build-deps/action.yml - ".github/scripts/strategy-matrix/**" @@ -34,7 +33,6 @@ on: - .github/workflows/upload-conan-deps.yml - .github/workflows/reusable-strategy-matrix.yml - - .github/workflows/reusable-upload-conan-deps-os.yml - .github/actions/build-deps/action.yml - ".github/scripts/strategy-matrix/**" @@ -47,16 +45,46 @@ concurrency: cancel-in-progress: true jobs: - run-upload-conan-deps: - strategy: - fail-fast: true - matrix: - os: ["linux", "macos", "windows"] - uses: ./.github/workflows/reusable-upload-conan-deps-os.yml + generate-matrix: + uses: ./.github/workflows/reusable-strategy-matrix.yml with: - force_source_build: ${{ github.event_name == 'schedule' || github.event.inputs.force_source_build == 'true' }} - force_upload: ${{ github.event.inputs.force_upload == 'true' }} - os: ${{ matrix.os }} - secrets: - CONAN_USERNAME: ${{ secrets.CONAN_REMOTE_USERNAME }} - CONAN_PASSWORD: ${{ secrets.CONAN_REMOTE_PASSWORD }} + strategy_matrix: ${{ github.event_name == 'pull_request' && 'minimal' || 'all' }} + + run-upload-conan-deps: + needs: + - generate-matrix + strategy: + fail-fast: false + matrix: ${{ fromJson(needs.generate-matrix.outputs.matrix) }} + max-parallel: 10 + runs-on: ${{ matrix.architecture.runner }} + container: ${{ contains(matrix.architecture.platform, 'linux') && format('ghcr.io/xrplf/ci/{0}-{1}:{2}-{3}', matrix.os.distro_name, matrix.os.distro_version, matrix.os.compiler_name, matrix.os.compiler_version) || null }} + + steps: + - name: Cleanup workspace + if: ${{ runner.os == 'macOS' }} + uses: XRPLF/actions/.github/actions/cleanup-workspace@3f044c7478548e3c32ff68980eeb36ece02b364e + + - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + - name: Prepare runner + uses: XRPLF/actions/.github/actions/prepare-runner@638e0dc11ea230f91bd26622fb542116bb5254d5 + with: + disable_ccache: false + + - name: Setup Conan + uses: ./.github/actions/setup-conan + + - name: Build dependencies + uses: ./.github/actions/build-deps + with: + build_dir: .build + build_type: ${{ matrix.build_type }} + force_build: ${{ github.event_name == 'schedule' || github.event.inputs.force_source_build == 'true' }} + + - name: Login to Conan + if: github.repository_owner == 'XRPLF' && github.event_name != 'pull_request' + run: conan remote login -p ${{ secrets.CONAN_PASSWORD }} ${{ inputs.conan_remote_name }} ${{ secrets.CONAN_USERNAME }} + + - name: Upload Conan packages + if: github.repository_owner == 'XRPLF' && github.event_name != 'pull_request' && github.event_name != 'schedule' + run: conan upload "*" -r=${{ inputs.conan_remote_name }} --confirm ${{ github.event.inputs.force_upload == 'true' && '--force' || '' }} From e5f7a8442d763870db531070c587fc0aec70a5d8 Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Tue, 9 Sep 2025 21:21:12 +0100 Subject: [PATCH 10/18] ci: Change `upload-conan-deps` workflow is run (#5782) - Don't run upload-conan-deps in PRs, unless the PR changes the workflow file. - Change cron schedule for uploading Conan dependencies to run after work hours for most dev. --- .github/workflows/upload-conan-deps.yml | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/.github/workflows/upload-conan-deps.yml b/.github/workflows/upload-conan-deps.yml index f63482761d..07e9a60dbd 100644 --- a/.github/workflows/upload-conan-deps.yml +++ b/.github/workflows/upload-conan-deps.yml @@ -2,7 +2,7 @@ name: Upload Conan Dependencies on: schedule: - - cron: "0 9 * * 1-5" + - cron: "0 3 * * 2-6" workflow_dispatch: inputs: force_source_build: @@ -18,15 +18,8 @@ on: pull_request: branches: [develop] paths: + # This allows testing changes to the upload workflow in a PR - .github/workflows/upload-conan-deps.yml - - - .github/workflows/reusable-strategy-matrix.yml - - - .github/actions/build-deps/action.yml - - ".github/scripts/strategy-matrix/**" - - - conanfile.py - - conan.lock push: branches: [develop] paths: From f6426ca1832d61e3b752124673eddf61553b7b56 Mon Sep 17 00:00:00 2001 From: Bronek Kozicki Date: Tue, 9 Sep 2025 22:23:07 +0100 Subject: [PATCH 11/18] Switch CI pipeline bookworm:gcc-13 from arm64 to amd64 (#5779) --- .github/scripts/strategy-matrix/generate.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/scripts/strategy-matrix/generate.py b/.github/scripts/strategy-matrix/generate.py index b6f6601291..ac39803fff 100755 --- a/.github/scripts/strategy-matrix/generate.py +++ b/.github/scripts/strategy-matrix/generate.py @@ -45,7 +45,7 @@ def generate_strategy_matrix(all: bool, config: Config) -> list: # Only generate a subset of configurations in PRs. if not all: # Debian: - # - Bookworm using GCC 13: Release and Unity on linux/arm64, set + # - Bookworm using GCC 13: Release and Unity on linux/amd64, set # the reference fee to 500. # - Bookworm using GCC 15: Debug and no Unity on linux/amd64, enable # code coverage (which will be done below). @@ -57,7 +57,7 @@ def generate_strategy_matrix(all: bool, config: Config) -> list: if os['distro_name'] == 'debian': skip = True if os['distro_version'] == 'bookworm': - if f'{os['compiler_name']}-{os['compiler_version']}' == 'gcc-13' and build_type == 'Release' and '-Dunity=ON' in cmake_args and architecture['platform'] == 'linux/arm64': + if f'{os['compiler_name']}-{os['compiler_version']}' == 'gcc-13' and build_type == 'Release' and '-Dunity=ON' in cmake_args and architecture['platform'] == 'linux/amd64': cmake_args = f'-DUNIT_TEST_REFERENCE_FEE=500 {cmake_args}' skip = False if f'{os['compiler_name']}-{os['compiler_version']}' == 'gcc-15' and build_type == 'Debug' and '-Dunity=OFF' in cmake_args and architecture['platform'] == 'linux/amd64': From cdbe70b2a7d54e85cf2e5eddd55e93bc206a5ef6 Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Wed, 10 Sep 2025 08:35:58 +0100 Subject: [PATCH 12/18] ci: Use default conan install format (#5784) --- .github/actions/build-deps/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/build-deps/action.yml b/.github/actions/build-deps/action.yml index c3b405e70f..351d8a6361 100644 --- a/.github/actions/build-deps/action.yml +++ b/.github/actions/build-deps/action.yml @@ -30,4 +30,4 @@ runs: --options:host '&:tests=True' \ --options:host '&:xrpld=True' \ --settings:all build_type=${{ inputs.build_type }} \ - --format=json .. + .. From 3d92375d127acb61ca7b931d561e5cca7762cabf Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Wed, 10 Sep 2025 09:20:45 +0100 Subject: [PATCH 13/18] ci: Add missing dependencies to workflows (#5783) --- .github/workflows/on-pr.yml | 2 ++ .github/workflows/on-trigger.yml | 2 ++ .github/workflows/upload-conan-deps.yml | 1 + 3 files changed, 5 insertions(+) diff --git a/.github/workflows/on-pr.yml b/.github/workflows/on-pr.yml index f194bd1e37..c480cc5476 100644 --- a/.github/workflows/on-pr.yml +++ b/.github/workflows/on-pr.yml @@ -60,8 +60,10 @@ jobs: # Keep the paths below in sync with those in `on-trigger.yml`. .github/actions/build-deps/** .github/actions/build-test/** + .github/actions/setup-conan/** .github/scripts/strategy-matrix/** .github/workflows/build-test.yml + .github/workflows/reusable-strategy-matrix.yml .codecov.yml cmake/** conan/** diff --git a/.github/workflows/on-trigger.yml b/.github/workflows/on-trigger.yml index 14884391ef..7c17621d67 100644 --- a/.github/workflows/on-trigger.yml +++ b/.github/workflows/on-trigger.yml @@ -21,8 +21,10 @@ on: # Keep the paths below in sync with those in `on-pr.yml`. - ".github/actions/build-deps/**" - ".github/actions/build-test/**" + - ".github/actions/setup-conan/**" - ".github/scripts/strategy-matrix/**" - ".github/workflows/build-test.yml" + - ".github/workflows/reusable-strategy-matrix.yml" - ".codecov.yml" - "cmake/**" - "conan/**" diff --git a/.github/workflows/upload-conan-deps.yml b/.github/workflows/upload-conan-deps.yml index 07e9a60dbd..5af72a9e41 100644 --- a/.github/workflows/upload-conan-deps.yml +++ b/.github/workflows/upload-conan-deps.yml @@ -28,6 +28,7 @@ on: - .github/workflows/reusable-strategy-matrix.yml - .github/actions/build-deps/action.yml + - .github/actions/setup-conan/action.yml - ".github/scripts/strategy-matrix/**" - conanfile.py From 61d628d654fd03135a47bf0dea28f56b03a8bd22 Mon Sep 17 00:00:00 2001 From: yinyiqian1 Date: Wed, 10 Sep 2025 13:47:33 -0400 Subject: [PATCH 14/18] fix: Add restrictions to Permission Delegation: fixDelegateV1_1 (#5650) - Amendment: fixDelegateV1_1 - In DelegateSet, disallow invalid PermissionValues like 0, and transaction values when the transaction's amendment is not enabled. Acts as if the transaction doesn't exist, which is the same thing older versions without the amendment will do. - Payment burn/mint should disallow DEX currency exchange. - Support MPT for Payment burn/mint. --- include/xrpl/protocol/Permissions.h | 7 +- include/xrpl/protocol/TxFormats.h | 2 +- include/xrpl/protocol/detail/features.macro | 1 + .../xrpl/protocol/detail/transactions.macro | 332 ++++++++++++--- include/xrpl/protocol/jss.h | 2 +- src/libxrpl/protocol/Permissions.cpp | 42 +- src/libxrpl/protocol/TxFormats.cpp | 2 +- src/test/app/AMMClawback_test.cpp | 3 +- src/test/app/Delegate_test.cpp | 390 +++++++++++++++++- src/xrpld/app/tx/detail/DelegateSet.cpp | 22 +- src/xrpld/app/tx/detail/Payment.cpp | 27 +- src/xrpld/app/tx/detail/applySteps.cpp | 4 +- 12 files changed, 732 insertions(+), 102 deletions(-) diff --git a/include/xrpl/protocol/Permissions.h b/include/xrpl/protocol/Permissions.h index 67f3eea8d7..cf49ff7382 100644 --- a/include/xrpl/protocol/Permissions.h +++ b/include/xrpl/protocol/Permissions.h @@ -20,6 +20,8 @@ #ifndef RIPPLE_PROTOCOL_PERMISSION_H_INCLUDED #define RIPPLE_PROTOCOL_PERMISSION_H_INCLUDED +#include +#include #include #include @@ -53,6 +55,8 @@ class Permission private: Permission(); + std::unordered_map txFeatureMap_; + std::unordered_map delegatableTx_; std::unordered_map @@ -80,7 +84,8 @@ public: getGranularTxType(GranularPermissionType const& gpType) const; bool - isDelegatable(std::uint32_t const& permissionValue) const; + isDelegatable(std::uint32_t const& permissionValue, Rules const& rules) + const; // for tx level permission, permission value is equal to tx type plus one uint32_t diff --git a/include/xrpl/protocol/TxFormats.h b/include/xrpl/protocol/TxFormats.h index 70b721a3d7..d17eea7644 100644 --- a/include/xrpl/protocol/TxFormats.h +++ b/include/xrpl/protocol/TxFormats.h @@ -59,7 +59,7 @@ enum TxType : std::uint16_t #pragma push_macro("TRANSACTION") #undef TRANSACTION -#define TRANSACTION(tag, value, name, delegatable, fields) tag = value, +#define TRANSACTION(tag, value, ...) tag = value, #include diff --git a/include/xrpl/protocol/detail/features.macro b/include/xrpl/protocol/detail/features.macro index 264fad7fc2..9aacbbe3d9 100644 --- a/include/xrpl/protocol/detail/features.macro +++ b/include/xrpl/protocol/detail/features.macro @@ -32,6 +32,7 @@ // If you add an amendment here, then do not forget to increment `numFeatures` // in include/xrpl/protocol/Feature.h. +XRPL_FIX (DelegateV1_1, Supported::no, VoteBehavior::DefaultNo) XRPL_FIX (PriceOracleOrder, Supported::no, VoteBehavior::DefaultNo) XRPL_FIX (MPTDeliveredAmount, Supported::no, VoteBehavior::DefaultNo) XRPL_FIX (AMMClawbackRounding, Supported::yes, VoteBehavior::DefaultNo) diff --git a/include/xrpl/protocol/detail/transactions.macro b/include/xrpl/protocol/detail/transactions.macro index 1131e24f61..bfbc18aa1b 100644 --- a/include/xrpl/protocol/detail/transactions.macro +++ b/include/xrpl/protocol/detail/transactions.macro @@ -22,14 +22,17 @@ #endif /** - * TRANSACTION(tag, value, name, delegatable, fields) + * TRANSACTION(tag, value, name, delegatable, amendments, fields) * * You must define a transactor class in the `ripple` namespace named `name`, * and include its header in `src/xrpld/app/tx/detail/applySteps.cpp`. */ /** This transaction type executes a payment. */ -TRANSACTION(ttPAYMENT, 0, Payment, Delegation::delegatable, ({ +TRANSACTION(ttPAYMENT, 0, Payment, + Delegation::delegatable, + uint256{}, + ({ {sfDestination, soeREQUIRED}, {sfAmount, soeREQUIRED, soeMPTSupported}, {sfSendMax, soeOPTIONAL, soeMPTSupported}, @@ -42,7 +45,10 @@ TRANSACTION(ttPAYMENT, 0, Payment, Delegation::delegatable, ({ })) /** This transaction type creates an escrow object. */ -TRANSACTION(ttESCROW_CREATE, 1, EscrowCreate, Delegation::delegatable, ({ +TRANSACTION(ttESCROW_CREATE, 1, EscrowCreate, + Delegation::delegatable, + uint256{}, + ({ {sfDestination, soeREQUIRED}, {sfAmount, soeREQUIRED, soeMPTSupported}, {sfCondition, soeOPTIONAL}, @@ -52,7 +58,10 @@ TRANSACTION(ttESCROW_CREATE, 1, EscrowCreate, Delegation::delegatable, ({ })) /** This transaction type completes an existing escrow. */ -TRANSACTION(ttESCROW_FINISH, 2, EscrowFinish, Delegation::delegatable, ({ +TRANSACTION(ttESCROW_FINISH, 2, EscrowFinish, + Delegation::delegatable, + uint256{}, + ({ {sfOwner, soeREQUIRED}, {sfOfferSequence, soeREQUIRED}, {sfFulfillment, soeOPTIONAL}, @@ -62,7 +71,10 @@ TRANSACTION(ttESCROW_FINISH, 2, EscrowFinish, Delegation::delegatable, ({ /** This transaction type adjusts various account settings. */ -TRANSACTION(ttACCOUNT_SET, 3, AccountSet, Delegation::notDelegatable, ({ +TRANSACTION(ttACCOUNT_SET, 3, AccountSet, + Delegation::notDelegatable, + uint256{}, + ({ {sfEmailHash, soeOPTIONAL}, {sfWalletLocator, soeOPTIONAL}, {sfWalletSize, soeOPTIONAL}, @@ -76,20 +88,29 @@ TRANSACTION(ttACCOUNT_SET, 3, AccountSet, Delegation::notDelegatable, ({ })) /** This transaction type cancels an existing escrow. */ -TRANSACTION(ttESCROW_CANCEL, 4, EscrowCancel, Delegation::delegatable, ({ +TRANSACTION(ttESCROW_CANCEL, 4, EscrowCancel, + Delegation::delegatable, + uint256{}, + ({ {sfOwner, soeREQUIRED}, {sfOfferSequence, soeREQUIRED}, })) /** This transaction type sets or clears an account's "regular key". */ -TRANSACTION(ttREGULAR_KEY_SET, 5, SetRegularKey, Delegation::notDelegatable, ({ +TRANSACTION(ttREGULAR_KEY_SET, 5, SetRegularKey, + Delegation::notDelegatable, + uint256{}, + ({ {sfRegularKey, soeOPTIONAL}, })) // 6 deprecated /** This transaction type creates an offer to trade one asset for another. */ -TRANSACTION(ttOFFER_CREATE, 7, OfferCreate, Delegation::delegatable, ({ +TRANSACTION(ttOFFER_CREATE, 7, OfferCreate, + Delegation::delegatable, + uint256{}, + ({ {sfTakerPays, soeREQUIRED}, {sfTakerGets, soeREQUIRED}, {sfExpiration, soeOPTIONAL}, @@ -98,14 +119,20 @@ TRANSACTION(ttOFFER_CREATE, 7, OfferCreate, Delegation::delegatable, ({ })) /** This transaction type cancels existing offers to trade one asset for another. */ -TRANSACTION(ttOFFER_CANCEL, 8, OfferCancel, Delegation::delegatable, ({ +TRANSACTION(ttOFFER_CANCEL, 8, OfferCancel, + Delegation::delegatable, + uint256{}, + ({ {sfOfferSequence, soeREQUIRED}, })) // 9 deprecated /** This transaction type creates a new set of tickets. */ -TRANSACTION(ttTICKET_CREATE, 10, TicketCreate, Delegation::delegatable, ({ +TRANSACTION(ttTICKET_CREATE, 10, TicketCreate, + Delegation::delegatable, + featureTicketBatch, + ({ {sfTicketCount, soeREQUIRED}, })) @@ -114,13 +141,19 @@ TRANSACTION(ttTICKET_CREATE, 10, TicketCreate, Delegation::delegatable, ({ /** This transaction type modifies the signer list associated with an account. */ // The SignerEntries are optional because a SignerList is deleted by // setting the SignerQuorum to zero and omitting SignerEntries. -TRANSACTION(ttSIGNER_LIST_SET, 12, SignerListSet, Delegation::notDelegatable, ({ +TRANSACTION(ttSIGNER_LIST_SET, 12, SignerListSet, + Delegation::notDelegatable, + uint256{}, + ({ {sfSignerQuorum, soeREQUIRED}, {sfSignerEntries, soeOPTIONAL}, })) /** This transaction type creates a new unidirectional XRP payment channel. */ -TRANSACTION(ttPAYCHAN_CREATE, 13, PaymentChannelCreate, Delegation::delegatable, ({ +TRANSACTION(ttPAYCHAN_CREATE, 13, PaymentChannelCreate, + Delegation::delegatable, + uint256{}, + ({ {sfDestination, soeREQUIRED}, {sfAmount, soeREQUIRED}, {sfSettleDelay, soeREQUIRED}, @@ -130,14 +163,20 @@ TRANSACTION(ttPAYCHAN_CREATE, 13, PaymentChannelCreate, Delegation::delegatable, })) /** This transaction type funds an existing unidirectional XRP payment channel. */ -TRANSACTION(ttPAYCHAN_FUND, 14, PaymentChannelFund, Delegation::delegatable, ({ +TRANSACTION(ttPAYCHAN_FUND, 14, PaymentChannelFund, + Delegation::delegatable, + uint256{}, + ({ {sfChannel, soeREQUIRED}, {sfAmount, soeREQUIRED}, {sfExpiration, soeOPTIONAL}, })) /** This transaction type submits a claim against an existing unidirectional payment channel. */ -TRANSACTION(ttPAYCHAN_CLAIM, 15, PaymentChannelClaim, Delegation::delegatable, ({ +TRANSACTION(ttPAYCHAN_CLAIM, 15, PaymentChannelClaim, + Delegation::delegatable, + uint256{}, + ({ {sfChannel, soeREQUIRED}, {sfAmount, soeOPTIONAL}, {sfBalance, soeOPTIONAL}, @@ -147,7 +186,10 @@ TRANSACTION(ttPAYCHAN_CLAIM, 15, PaymentChannelClaim, Delegation::delegatable, ( })) /** This transaction type creates a new check. */ -TRANSACTION(ttCHECK_CREATE, 16, CheckCreate, Delegation::delegatable, ({ +TRANSACTION(ttCHECK_CREATE, 16, CheckCreate, + Delegation::delegatable, + featureChecks, + ({ {sfDestination, soeREQUIRED}, {sfSendMax, soeREQUIRED}, {sfExpiration, soeOPTIONAL}, @@ -156,19 +198,28 @@ TRANSACTION(ttCHECK_CREATE, 16, CheckCreate, Delegation::delegatable, ({ })) /** This transaction type cashes an existing check. */ -TRANSACTION(ttCHECK_CASH, 17, CheckCash, Delegation::delegatable, ({ +TRANSACTION(ttCHECK_CASH, 17, CheckCash, + Delegation::delegatable, + featureChecks, + ({ {sfCheckID, soeREQUIRED}, {sfAmount, soeOPTIONAL}, {sfDeliverMin, soeOPTIONAL}, })) /** This transaction type cancels an existing check. */ -TRANSACTION(ttCHECK_CANCEL, 18, CheckCancel, Delegation::delegatable, ({ +TRANSACTION(ttCHECK_CANCEL, 18, CheckCancel, + Delegation::delegatable, + featureChecks, + ({ {sfCheckID, soeREQUIRED}, })) /** This transaction type grants or revokes authorization to transfer funds. */ -TRANSACTION(ttDEPOSIT_PREAUTH, 19, DepositPreauth, Delegation::delegatable, ({ +TRANSACTION(ttDEPOSIT_PREAUTH, 19, DepositPreauth, + Delegation::delegatable, + featureDepositPreauth, + ({ {sfAuthorize, soeOPTIONAL}, {sfUnauthorize, soeOPTIONAL}, {sfAuthorizeCredentials, soeOPTIONAL}, @@ -176,14 +227,20 @@ TRANSACTION(ttDEPOSIT_PREAUTH, 19, DepositPreauth, Delegation::delegatable, ({ })) /** This transaction type modifies a trustline between two accounts. */ -TRANSACTION(ttTRUST_SET, 20, TrustSet, Delegation::delegatable, ({ +TRANSACTION(ttTRUST_SET, 20, TrustSet, + Delegation::delegatable, + uint256{}, + ({ {sfLimitAmount, soeOPTIONAL}, {sfQualityIn, soeOPTIONAL}, {sfQualityOut, soeOPTIONAL}, })) /** This transaction type deletes an existing account. */ -TRANSACTION(ttACCOUNT_DELETE, 21, AccountDelete, Delegation::notDelegatable, ({ +TRANSACTION(ttACCOUNT_DELETE, 21, AccountDelete, + Delegation::notDelegatable, + uint256{}, + ({ {sfDestination, soeREQUIRED}, {sfDestinationTag, soeOPTIONAL}, {sfCredentialIDs, soeOPTIONAL}, @@ -192,7 +249,10 @@ TRANSACTION(ttACCOUNT_DELETE, 21, AccountDelete, Delegation::notDelegatable, ({ // 22 reserved /** This transaction mints a new NFT. */ -TRANSACTION(ttNFTOKEN_MINT, 25, NFTokenMint, Delegation::delegatable, ({ +TRANSACTION(ttNFTOKEN_MINT, 25, NFTokenMint, + Delegation::delegatable, + featureNonFungibleTokensV1, + ({ {sfNFTokenTaxon, soeREQUIRED}, {sfTransferFee, soeOPTIONAL}, {sfIssuer, soeOPTIONAL}, @@ -203,13 +263,19 @@ TRANSACTION(ttNFTOKEN_MINT, 25, NFTokenMint, Delegation::delegatable, ({ })) /** This transaction burns (i.e. destroys) an existing NFT. */ -TRANSACTION(ttNFTOKEN_BURN, 26, NFTokenBurn, Delegation::delegatable, ({ +TRANSACTION(ttNFTOKEN_BURN, 26, NFTokenBurn, + Delegation::delegatable, + featureNonFungibleTokensV1, + ({ {sfNFTokenID, soeREQUIRED}, {sfOwner, soeOPTIONAL}, })) /** This transaction creates a new offer to buy or sell an NFT. */ -TRANSACTION(ttNFTOKEN_CREATE_OFFER, 27, NFTokenCreateOffer, Delegation::delegatable, ({ +TRANSACTION(ttNFTOKEN_CREATE_OFFER, 27, NFTokenCreateOffer, + Delegation::delegatable, + featureNonFungibleTokensV1, + ({ {sfNFTokenID, soeREQUIRED}, {sfAmount, soeREQUIRED}, {sfDestination, soeOPTIONAL}, @@ -218,25 +284,37 @@ TRANSACTION(ttNFTOKEN_CREATE_OFFER, 27, NFTokenCreateOffer, Delegation::delegata })) /** This transaction cancels an existing offer to buy or sell an existing NFT. */ -TRANSACTION(ttNFTOKEN_CANCEL_OFFER, 28, NFTokenCancelOffer, Delegation::delegatable, ({ +TRANSACTION(ttNFTOKEN_CANCEL_OFFER, 28, NFTokenCancelOffer, + Delegation::delegatable, + featureNonFungibleTokensV1, + ({ {sfNFTokenOffers, soeREQUIRED}, })) /** This transaction accepts an existing offer to buy or sell an existing NFT. */ -TRANSACTION(ttNFTOKEN_ACCEPT_OFFER, 29, NFTokenAcceptOffer, Delegation::delegatable, ({ +TRANSACTION(ttNFTOKEN_ACCEPT_OFFER, 29, NFTokenAcceptOffer, + Delegation::delegatable, + featureNonFungibleTokensV1, + ({ {sfNFTokenBuyOffer, soeOPTIONAL}, {sfNFTokenSellOffer, soeOPTIONAL}, {sfNFTokenBrokerFee, soeOPTIONAL}, })) /** This transaction claws back issued tokens. */ -TRANSACTION(ttCLAWBACK, 30, Clawback, Delegation::delegatable, ({ +TRANSACTION(ttCLAWBACK, 30, Clawback, + Delegation::delegatable, + featureClawback, + ({ {sfAmount, soeREQUIRED, soeMPTSupported}, {sfHolder, soeOPTIONAL}, })) /** This transaction claws back tokens from an AMM pool. */ -TRANSACTION(ttAMM_CLAWBACK, 31, AMMClawback, Delegation::delegatable, ({ +TRANSACTION(ttAMM_CLAWBACK, 31, AMMClawback, + Delegation::delegatable, + featureAMMClawback, + ({ {sfHolder, soeREQUIRED}, {sfAsset, soeREQUIRED}, {sfAsset2, soeREQUIRED}, @@ -244,14 +322,20 @@ TRANSACTION(ttAMM_CLAWBACK, 31, AMMClawback, Delegation::delegatable, ({ })) /** This transaction type creates an AMM instance */ -TRANSACTION(ttAMM_CREATE, 35, AMMCreate, Delegation::delegatable, ({ +TRANSACTION(ttAMM_CREATE, 35, AMMCreate, + Delegation::delegatable, + featureAMM, + ({ {sfAmount, soeREQUIRED}, {sfAmount2, soeREQUIRED}, {sfTradingFee, soeREQUIRED}, })) /** This transaction type deposits into an AMM instance */ -TRANSACTION(ttAMM_DEPOSIT, 36, AMMDeposit, Delegation::delegatable, ({ +TRANSACTION(ttAMM_DEPOSIT, 36, AMMDeposit, + Delegation::delegatable, + featureAMM, + ({ {sfAsset, soeREQUIRED}, {sfAsset2, soeREQUIRED}, {sfAmount, soeOPTIONAL}, @@ -262,7 +346,10 @@ TRANSACTION(ttAMM_DEPOSIT, 36, AMMDeposit, Delegation::delegatable, ({ })) /** This transaction type withdraws from an AMM instance */ -TRANSACTION(ttAMM_WITHDRAW, 37, AMMWithdraw, Delegation::delegatable, ({ +TRANSACTION(ttAMM_WITHDRAW, 37, AMMWithdraw, + Delegation::delegatable, + featureAMM, + ({ {sfAsset, soeREQUIRED}, {sfAsset2, soeREQUIRED}, {sfAmount, soeOPTIONAL}, @@ -272,14 +359,20 @@ TRANSACTION(ttAMM_WITHDRAW, 37, AMMWithdraw, Delegation::delegatable, ({ })) /** This transaction type votes for the trading fee */ -TRANSACTION(ttAMM_VOTE, 38, AMMVote, Delegation::delegatable, ({ +TRANSACTION(ttAMM_VOTE, 38, AMMVote, + Delegation::delegatable, + featureAMM, + ({ {sfAsset, soeREQUIRED}, {sfAsset2, soeREQUIRED}, {sfTradingFee, soeREQUIRED}, })) /** This transaction type bids for the auction slot */ -TRANSACTION(ttAMM_BID, 39, AMMBid, Delegation::delegatable, ({ +TRANSACTION(ttAMM_BID, 39, AMMBid, + Delegation::delegatable, + featureAMM, + ({ {sfAsset, soeREQUIRED}, {sfAsset2, soeREQUIRED}, {sfBidMin, soeOPTIONAL}, @@ -288,20 +381,29 @@ TRANSACTION(ttAMM_BID, 39, AMMBid, Delegation::delegatable, ({ })) /** This transaction type deletes AMM in the empty state */ -TRANSACTION(ttAMM_DELETE, 40, AMMDelete, Delegation::delegatable, ({ +TRANSACTION(ttAMM_DELETE, 40, AMMDelete, + Delegation::delegatable, + featureAMM, + ({ {sfAsset, soeREQUIRED}, {sfAsset2, soeREQUIRED}, })) /** This transactions creates a crosschain sequence number */ -TRANSACTION(ttXCHAIN_CREATE_CLAIM_ID, 41, XChainCreateClaimID, Delegation::delegatable, ({ +TRANSACTION(ttXCHAIN_CREATE_CLAIM_ID, 41, XChainCreateClaimID, + Delegation::delegatable, + featureXChainBridge, + ({ {sfXChainBridge, soeREQUIRED}, {sfSignatureReward, soeREQUIRED}, {sfOtherChainSource, soeREQUIRED}, })) /** This transactions initiates a crosschain transaction */ -TRANSACTION(ttXCHAIN_COMMIT, 42, XChainCommit, Delegation::delegatable, ({ +TRANSACTION(ttXCHAIN_COMMIT, 42, XChainCommit, + Delegation::delegatable, + featureXChainBridge, + ({ {sfXChainBridge, soeREQUIRED}, {sfXChainClaimID, soeREQUIRED}, {sfAmount, soeREQUIRED}, @@ -309,7 +411,10 @@ TRANSACTION(ttXCHAIN_COMMIT, 42, XChainCommit, Delegation::delegatable, ({ })) /** This transaction completes a crosschain transaction */ -TRANSACTION(ttXCHAIN_CLAIM, 43, XChainClaim, Delegation::delegatable, ({ +TRANSACTION(ttXCHAIN_CLAIM, 43, XChainClaim, + Delegation::delegatable, + featureXChainBridge, + ({ {sfXChainBridge, soeREQUIRED}, {sfXChainClaimID, soeREQUIRED}, {sfDestination, soeREQUIRED}, @@ -318,7 +423,10 @@ TRANSACTION(ttXCHAIN_CLAIM, 43, XChainClaim, Delegation::delegatable, ({ })) /** This transaction initiates a crosschain account create transaction */ -TRANSACTION(ttXCHAIN_ACCOUNT_CREATE_COMMIT, 44, XChainAccountCreateCommit, Delegation::delegatable, ({ +TRANSACTION(ttXCHAIN_ACCOUNT_CREATE_COMMIT, 44, XChainAccountCreateCommit, + Delegation::delegatable, + featureXChainBridge, + ({ {sfXChainBridge, soeREQUIRED}, {sfDestination, soeREQUIRED}, {sfAmount, soeREQUIRED}, @@ -326,7 +434,10 @@ TRANSACTION(ttXCHAIN_ACCOUNT_CREATE_COMMIT, 44, XChainAccountCreateCommit, Deleg })) /** This transaction adds an attestation to a claim */ -TRANSACTION(ttXCHAIN_ADD_CLAIM_ATTESTATION, 45, XChainAddClaimAttestation, Delegation::delegatable, ({ +TRANSACTION(ttXCHAIN_ADD_CLAIM_ATTESTATION, 45, XChainAddClaimAttestation, + Delegation::delegatable, + featureXChainBridge, + ({ {sfXChainBridge, soeREQUIRED}, {sfAttestationSignerAccount, soeREQUIRED}, @@ -342,7 +453,10 @@ TRANSACTION(ttXCHAIN_ADD_CLAIM_ATTESTATION, 45, XChainAddClaimAttestation, Deleg })) /** This transaction adds an attestation to an account */ -TRANSACTION(ttXCHAIN_ADD_ACCOUNT_CREATE_ATTESTATION, 46, XChainAddAccountCreateAttestation, Delegation::delegatable, ({ +TRANSACTION(ttXCHAIN_ADD_ACCOUNT_CREATE_ATTESTATION, 46, XChainAddAccountCreateAttestation, + Delegation::delegatable, + featureXChainBridge, + ({ {sfXChainBridge, soeREQUIRED}, {sfAttestationSignerAccount, soeREQUIRED}, @@ -359,31 +473,46 @@ TRANSACTION(ttXCHAIN_ADD_ACCOUNT_CREATE_ATTESTATION, 46, XChainAddAccountCreateA })) /** This transaction modifies a sidechain */ -TRANSACTION(ttXCHAIN_MODIFY_BRIDGE, 47, XChainModifyBridge, Delegation::delegatable, ({ +TRANSACTION(ttXCHAIN_MODIFY_BRIDGE, 47, XChainModifyBridge, + Delegation::delegatable, + featureXChainBridge, + ({ {sfXChainBridge, soeREQUIRED}, {sfSignatureReward, soeOPTIONAL}, {sfMinAccountCreateAmount, soeOPTIONAL}, })) /** This transactions creates a sidechain */ -TRANSACTION(ttXCHAIN_CREATE_BRIDGE, 48, XChainCreateBridge, Delegation::delegatable, ({ +TRANSACTION(ttXCHAIN_CREATE_BRIDGE, 48, XChainCreateBridge, + Delegation::delegatable, + featureXChainBridge, + ({ {sfXChainBridge, soeREQUIRED}, {sfSignatureReward, soeREQUIRED}, {sfMinAccountCreateAmount, soeOPTIONAL}, })) /** This transaction type creates or updates a DID */ -TRANSACTION(ttDID_SET, 49, DIDSet, Delegation::delegatable, ({ +TRANSACTION(ttDID_SET, 49, DIDSet, + Delegation::delegatable, + featureDID, + ({ {sfDIDDocument, soeOPTIONAL}, {sfURI, soeOPTIONAL}, {sfData, soeOPTIONAL}, })) /** This transaction type deletes a DID */ -TRANSACTION(ttDID_DELETE, 50, DIDDelete, Delegation::delegatable, ({})) +TRANSACTION(ttDID_DELETE, 50, DIDDelete, + Delegation::delegatable, + featureDID, + ({})) /** This transaction type creates an Oracle instance */ -TRANSACTION(ttORACLE_SET, 51, OracleSet, Delegation::delegatable, ({ +TRANSACTION(ttORACLE_SET, 51, OracleSet, + Delegation::delegatable, + featurePriceOracle, + ({ {sfOracleDocumentID, soeREQUIRED}, {sfProvider, soeOPTIONAL}, {sfURI, soeOPTIONAL}, @@ -393,18 +522,27 @@ TRANSACTION(ttORACLE_SET, 51, OracleSet, Delegation::delegatable, ({ })) /** This transaction type deletes an Oracle instance */ -TRANSACTION(ttORACLE_DELETE, 52, OracleDelete, Delegation::delegatable, ({ +TRANSACTION(ttORACLE_DELETE, 52, OracleDelete, + Delegation::delegatable, + featurePriceOracle, + ({ {sfOracleDocumentID, soeREQUIRED}, })) /** This transaction type fixes a problem in the ledger state */ -TRANSACTION(ttLEDGER_STATE_FIX, 53, LedgerStateFix, Delegation::delegatable, ({ +TRANSACTION(ttLEDGER_STATE_FIX, 53, LedgerStateFix, + Delegation::delegatable, + fixNFTokenPageLinks, + ({ {sfLedgerFixType, soeREQUIRED}, {sfOwner, soeOPTIONAL}, })) /** This transaction type creates a MPTokensIssuance instance */ -TRANSACTION(ttMPTOKEN_ISSUANCE_CREATE, 54, MPTokenIssuanceCreate, Delegation::delegatable, ({ +TRANSACTION(ttMPTOKEN_ISSUANCE_CREATE, 54, MPTokenIssuanceCreate, + Delegation::delegatable, + featureMPTokensV1, + ({ {sfAssetScale, soeOPTIONAL}, {sfTransferFee, soeOPTIONAL}, {sfMaximumAmount, soeOPTIONAL}, @@ -413,25 +551,37 @@ TRANSACTION(ttMPTOKEN_ISSUANCE_CREATE, 54, MPTokenIssuanceCreate, Delegation::de })) /** This transaction type destroys a MPTokensIssuance instance */ -TRANSACTION(ttMPTOKEN_ISSUANCE_DESTROY, 55, MPTokenIssuanceDestroy, Delegation::delegatable, ({ +TRANSACTION(ttMPTOKEN_ISSUANCE_DESTROY, 55, MPTokenIssuanceDestroy, + Delegation::delegatable, + featureMPTokensV1, + ({ {sfMPTokenIssuanceID, soeREQUIRED}, })) /** This transaction type sets flags on a MPTokensIssuance or MPToken instance */ -TRANSACTION(ttMPTOKEN_ISSUANCE_SET, 56, MPTokenIssuanceSet, Delegation::delegatable, ({ +TRANSACTION(ttMPTOKEN_ISSUANCE_SET, 56, MPTokenIssuanceSet, + Delegation::delegatable, + featureMPTokensV1, + ({ {sfMPTokenIssuanceID, soeREQUIRED}, {sfHolder, soeOPTIONAL}, {sfDomainID, soeOPTIONAL}, })) /** This transaction type authorizes a MPToken instance */ -TRANSACTION(ttMPTOKEN_AUTHORIZE, 57, MPTokenAuthorize, Delegation::delegatable, ({ +TRANSACTION(ttMPTOKEN_AUTHORIZE, 57, MPTokenAuthorize, + Delegation::delegatable, + featureMPTokensV1, + ({ {sfMPTokenIssuanceID, soeREQUIRED}, {sfHolder, soeOPTIONAL}, })) /** This transaction type create an Credential instance */ -TRANSACTION(ttCREDENTIAL_CREATE, 58, CredentialCreate, Delegation::delegatable, ({ +TRANSACTION(ttCREDENTIAL_CREATE, 58, CredentialCreate, + Delegation::delegatable, + featureCredentials, + ({ {sfSubject, soeREQUIRED}, {sfCredentialType, soeREQUIRED}, {sfExpiration, soeOPTIONAL}, @@ -439,44 +589,65 @@ TRANSACTION(ttCREDENTIAL_CREATE, 58, CredentialCreate, Delegation::delegatable, })) /** This transaction type accept an Credential object */ -TRANSACTION(ttCREDENTIAL_ACCEPT, 59, CredentialAccept, Delegation::delegatable, ({ +TRANSACTION(ttCREDENTIAL_ACCEPT, 59, CredentialAccept, + Delegation::delegatable, + featureCredentials, + ({ {sfIssuer, soeREQUIRED}, {sfCredentialType, soeREQUIRED}, })) /** This transaction type delete an Credential object */ -TRANSACTION(ttCREDENTIAL_DELETE, 60, CredentialDelete, Delegation::delegatable, ({ +TRANSACTION(ttCREDENTIAL_DELETE, 60, CredentialDelete, + Delegation::delegatable, + featureCredentials, + ({ {sfSubject, soeOPTIONAL}, {sfIssuer, soeOPTIONAL}, {sfCredentialType, soeREQUIRED}, })) /** This transaction type modify a NFToken */ -TRANSACTION(ttNFTOKEN_MODIFY, 61, NFTokenModify, Delegation::delegatable, ({ +TRANSACTION(ttNFTOKEN_MODIFY, 61, NFTokenModify, + Delegation::delegatable, + featureDynamicNFT, + ({ {sfNFTokenID, soeREQUIRED}, {sfOwner, soeOPTIONAL}, {sfURI, soeOPTIONAL}, })) /** This transaction type creates or modifies a Permissioned Domain */ -TRANSACTION(ttPERMISSIONED_DOMAIN_SET, 62, PermissionedDomainSet, Delegation::delegatable, ({ +TRANSACTION(ttPERMISSIONED_DOMAIN_SET, 62, PermissionedDomainSet, + Delegation::delegatable, + featurePermissionedDomains, + ({ {sfDomainID, soeOPTIONAL}, {sfAcceptedCredentials, soeREQUIRED}, })) /** This transaction type deletes a Permissioned Domain */ -TRANSACTION(ttPERMISSIONED_DOMAIN_DELETE, 63, PermissionedDomainDelete, Delegation::delegatable, ({ +TRANSACTION(ttPERMISSIONED_DOMAIN_DELETE, 63, PermissionedDomainDelete, + Delegation::delegatable, + featurePermissionedDomains, + ({ {sfDomainID, soeREQUIRED}, })) /** This transaction type delegates authorized account specified permissions */ -TRANSACTION(ttDELEGATE_SET, 64, DelegateSet, Delegation::notDelegatable, ({ +TRANSACTION(ttDELEGATE_SET, 64, DelegateSet, + Delegation::notDelegatable, + featurePermissionDelegation, + ({ {sfAuthorize, soeREQUIRED}, {sfPermissions, soeREQUIRED}, })) /** This transaction creates a single asset vault. */ -TRANSACTION(ttVAULT_CREATE, 65, VaultCreate, Delegation::delegatable, ({ +TRANSACTION(ttVAULT_CREATE, 65, VaultCreate, + Delegation::delegatable, + featureSingleAssetVault, + ({ {sfAsset, soeREQUIRED, soeMPTSupported}, {sfAssetsMaximum, soeOPTIONAL}, {sfMPTokenMetadata, soeOPTIONAL}, @@ -487,7 +658,10 @@ TRANSACTION(ttVAULT_CREATE, 65, VaultCreate, Delegation::delegatable, ({ })) /** This transaction updates a single asset vault. */ -TRANSACTION(ttVAULT_SET, 66, VaultSet, Delegation::delegatable, ({ +TRANSACTION(ttVAULT_SET, 66, VaultSet, + Delegation::delegatable, + featureSingleAssetVault, + ({ {sfVaultID, soeREQUIRED}, {sfAssetsMaximum, soeOPTIONAL}, {sfDomainID, soeOPTIONAL}, @@ -495,18 +669,27 @@ TRANSACTION(ttVAULT_SET, 66, VaultSet, Delegation::delegatable, ({ })) /** This transaction deletes a single asset vault. */ -TRANSACTION(ttVAULT_DELETE, 67, VaultDelete, Delegation::delegatable, ({ +TRANSACTION(ttVAULT_DELETE, 67, VaultDelete, + Delegation::delegatable, + featureSingleAssetVault, + ({ {sfVaultID, soeREQUIRED}, })) /** This transaction trades assets for shares with a vault. */ -TRANSACTION(ttVAULT_DEPOSIT, 68, VaultDeposit, Delegation::delegatable, ({ +TRANSACTION(ttVAULT_DEPOSIT, 68, VaultDeposit, + Delegation::delegatable, + featureSingleAssetVault, + ({ {sfVaultID, soeREQUIRED}, {sfAmount, soeREQUIRED, soeMPTSupported}, })) /** This transaction trades shares for assets with a vault. */ -TRANSACTION(ttVAULT_WITHDRAW, 69, VaultWithdraw, Delegation::delegatable, ({ +TRANSACTION(ttVAULT_WITHDRAW, 69, VaultWithdraw, + Delegation::delegatable, + featureSingleAssetVault, + ({ {sfVaultID, soeREQUIRED}, {sfAmount, soeREQUIRED, soeMPTSupported}, {sfDestination, soeOPTIONAL}, @@ -514,14 +697,20 @@ TRANSACTION(ttVAULT_WITHDRAW, 69, VaultWithdraw, Delegation::delegatable, ({ })) /** This transaction claws back tokens from a vault. */ -TRANSACTION(ttVAULT_CLAWBACK, 70, VaultClawback, Delegation::delegatable, ({ +TRANSACTION(ttVAULT_CLAWBACK, 70, VaultClawback, + Delegation::delegatable, + featureSingleAssetVault, + ({ {sfVaultID, soeREQUIRED}, {sfHolder, soeREQUIRED}, {sfAmount, soeOPTIONAL, soeMPTSupported}, })) /** This transaction type batches together transactions. */ -TRANSACTION(ttBATCH, 71, Batch, Delegation::notDelegatable, ({ +TRANSACTION(ttBATCH, 71, Batch, + Delegation::notDelegatable, + featureBatch, + ({ {sfRawTransactions, soeREQUIRED}, {sfBatchSigners, soeOPTIONAL}, })) @@ -530,7 +719,10 @@ TRANSACTION(ttBATCH, 71, Batch, Delegation::notDelegatable, ({ For details, see: https://xrpl.org/amendments.html */ -TRANSACTION(ttAMENDMENT, 100, EnableAmendment, Delegation::notDelegatable, ({ +TRANSACTION(ttAMENDMENT, 100, EnableAmendment, + Delegation::notDelegatable, + uint256{}, + ({ {sfLedgerSequence, soeREQUIRED}, {sfAmendment, soeREQUIRED}, })) @@ -538,7 +730,10 @@ TRANSACTION(ttAMENDMENT, 100, EnableAmendment, Delegation::notDelegatable, ({ /** This system-generated transaction type is used to update the network's fee settings. For details, see: https://xrpl.org/fee-voting.html */ -TRANSACTION(ttFEE, 101, SetFee, Delegation::notDelegatable, ({ +TRANSACTION(ttFEE, 101, SetFee, + Delegation::notDelegatable, + uint256{}, + ({ {sfLedgerSequence, soeOPTIONAL}, // Old version uses raw numbers {sfBaseFee, soeOPTIONAL}, @@ -555,7 +750,10 @@ TRANSACTION(ttFEE, 101, SetFee, Delegation::notDelegatable, ({ For details, see: https://xrpl.org/negative-unl.html */ -TRANSACTION(ttUNL_MODIFY, 102, UNLModify, Delegation::notDelegatable, ({ +TRANSACTION(ttUNL_MODIFY, 102, UNLModify, + Delegation::notDelegatable, + uint256{}, + ({ {sfUNLModifyDisabling, soeREQUIRED}, {sfLedgerSequence, soeREQUIRED}, {sfUNLModifyValidator, soeREQUIRED}, diff --git a/include/xrpl/protocol/jss.h b/include/xrpl/protocol/jss.h index 68d2497aca..d847cf6012 100644 --- a/include/xrpl/protocol/jss.h +++ b/include/xrpl/protocol/jss.h @@ -710,7 +710,7 @@ JSS(write_load); // out: GetCounts #pragma push_macro("TRANSACTION") #undef TRANSACTION -#define TRANSACTION(tag, value, name, delegatable, fields) JSS(name); +#define TRANSACTION(tag, value, name, ...) JSS(name); #include diff --git a/src/libxrpl/protocol/Permissions.cpp b/src/libxrpl/protocol/Permissions.cpp index ca8cb26f36..781799f128 100644 --- a/src/libxrpl/protocol/Permissions.cpp +++ b/src/libxrpl/protocol/Permissions.cpp @@ -18,6 +18,7 @@ //============================================================================== #include +#include #include #include @@ -25,11 +26,24 @@ namespace ripple { Permission::Permission() { + txFeatureMap_ = { +#pragma push_macro("TRANSACTION") +#undef TRANSACTION + +#define TRANSACTION(tag, value, name, delegatable, amendment, ...) \ + {value, amendment}, + +#include + +#undef TRANSACTION +#pragma pop_macro("TRANSACTION") + }; + delegatableTx_ = { #pragma push_macro("TRANSACTION") #undef TRANSACTION -#define TRANSACTION(tag, value, name, delegatable, fields) {value, delegatable}, +#define TRANSACTION(tag, value, name, delegatable, ...) {value, delegatable}, #include @@ -118,7 +132,9 @@ Permission::getGranularTxType(GranularPermissionType const& gpType) const } bool -Permission::isDelegatable(std::uint32_t const& permissionValue) const +Permission::isDelegatable( + std::uint32_t const& permissionValue, + Rules const& rules) const { auto const granularPermission = getGranularName(static_cast(permissionValue)); @@ -126,7 +142,27 @@ Permission::isDelegatable(std::uint32_t const& permissionValue) const // granular permissions are always allowed to be delegated return true; - auto const it = delegatableTx_.find(permissionValue - 1); + auto const txType = permissionToTxType(permissionValue); + auto const it = delegatableTx_.find(txType); + + if (rules.enabled(fixDelegateV1_1)) + { + if (it == delegatableTx_.end()) + return false; + + auto const txFeaturesIt = txFeatureMap_.find(txType); + XRPL_ASSERT( + txFeaturesIt != txFeatureMap_.end(), + "ripple::Permissions::isDelegatable : tx exists in txFeatureMap_"); + + // fixDelegateV1_1: Delegation is only allowed if the required amendment + // for the transaction is enabled. For transactions that do not require + // an amendment, delegation is always allowed. + if (txFeaturesIt->second != uint256{} && + !rules.enabled(txFeaturesIt->second)) + return false; + } + if (it != delegatableTx_.end() && it->second == Delegation::notDelegatable) return false; diff --git a/src/libxrpl/protocol/TxFormats.cpp b/src/libxrpl/protocol/TxFormats.cpp index 5edffeb666..c10c023ee9 100644 --- a/src/libxrpl/protocol/TxFormats.cpp +++ b/src/libxrpl/protocol/TxFormats.cpp @@ -55,7 +55,7 @@ TxFormats::TxFormats() #undef TRANSACTION #define UNWRAP(...) __VA_ARGS__ -#define TRANSACTION(tag, value, name, delegatable, fields) \ +#define TRANSACTION(tag, value, name, delegatable, amendment, fields) \ add(jss::name, tag, UNWRAP fields, commonFields); #include diff --git a/src/test/app/AMMClawback_test.cpp b/src/test/app/AMMClawback_test.cpp index 9564911664..707113fe32 100644 --- a/src/test/app/AMMClawback_test.cpp +++ b/src/test/app/AMMClawback_test.cpp @@ -2442,8 +2442,7 @@ class AMMClawback_test : public beast::unit_test::suite void run() override { - FeatureBitset const all{ - jtx::testable_amendments() | fixAMMClawbackRounding}; + FeatureBitset const all = jtx::testable_amendments(); testInvalidRequest(); testFeatureDisabled(all - featureAMMClawback); diff --git a/src/test/app/Delegate_test.cpp b/src/test/app/Delegate_test.cpp index 44cb6a54b6..ea5e073a55 100644 --- a/src/test/app/Delegate_test.cpp +++ b/src/test/app/Delegate_test.cpp @@ -16,6 +16,7 @@ //============================================================================== #include +#include #include #include @@ -139,12 +140,12 @@ class Delegate_test : public beast::unit_test::suite } void - testInvalidRequest() + testInvalidRequest(FeatureBitset features) { testcase("test invalid DelegateSet"); using namespace jtx; - Env env(*this); + Env env(*this, features); Account gw{"gateway"}; Account alice{"alice"}; Account bob{"bob"}; @@ -216,22 +217,17 @@ class Delegate_test : public beast::unit_test::suite } // non-delegatable transaction + auto const res = features[fixDelegateV1_1] ? ter(temMALFORMED) + : ter(tecNO_PERMISSION); { - env(delegate::set(gw, alice, {"SetRegularKey"}), - ter(tecNO_PERMISSION)); - env(delegate::set(gw, alice, {"AccountSet"}), - ter(tecNO_PERMISSION)); - env(delegate::set(gw, alice, {"SignerListSet"}), - ter(tecNO_PERMISSION)); - env(delegate::set(gw, alice, {"DelegateSet"}), - ter(tecNO_PERMISSION)); - env(delegate::set(gw, alice, {"SetRegularKey"}), - ter(tecNO_PERMISSION)); - env(delegate::set(gw, alice, {"EnableAmendment"}), - ter(tecNO_PERMISSION)); - env(delegate::set(gw, alice, {"UNLModify"}), ter(tecNO_PERMISSION)); - env(delegate::set(gw, alice, {"SetFee"}), ter(tecNO_PERMISSION)); - env(delegate::set(gw, alice, {"Batch"}), ter(tecNO_PERMISSION)); + env(delegate::set(gw, alice, {"SetRegularKey"}), res); + env(delegate::set(gw, alice, {"AccountSet"}), res); + env(delegate::set(gw, alice, {"SignerListSet"}), res); + env(delegate::set(gw, alice, {"DelegateSet"}), res); + env(delegate::set(gw, alice, {"EnableAmendment"}), res); + env(delegate::set(gw, alice, {"UNLModify"}), res); + env(delegate::set(gw, alice, {"SetFee"}), res); + env(delegate::set(gw, alice, {"Batch"}), res); } } @@ -536,7 +532,7 @@ class Delegate_test : public beast::unit_test::suite } void - testPaymentGranular() + testPaymentGranular(FeatureBitset features) { testcase("test payment granular"); using namespace jtx; @@ -706,6 +702,158 @@ class Delegate_test : public beast::unit_test::suite env.require(balance(alice, USD(50))); BEAST_EXPECT(env.balance(bob, USD) == USD(0)); } + + // disallow cross currency payment with only PaymentBurn/PaymentMint + // permission + { + Env env(*this, features); + Account const alice{"alice"}; + Account const bob{"bob"}; + Account const gw{"gateway"}; + Account const carol{"carol"}; + auto const USD = gw["USD"]; + + env.fund(XRP(10000), alice, bob, carol, gw); + env.close(); + env.trust(USD(50000), alice); + env.trust(USD(50000), bob); + env.trust(USD(50000), carol); + env(pay(gw, alice, USD(10000))); + env(pay(gw, bob, USD(10000))); + env(pay(gw, carol, USD(10000))); + env.close(); + + auto const result = features[fixDelegateV1_1] + ? static_cast(tecNO_DELEGATE_PERMISSION) + : static_cast(tesSUCCESS); + auto const offerCount = features[fixDelegateV1_1] ? 1 : 0; + + // PaymentMint + { + env(offer(carol, XRP(100), USD(501))); + BEAST_EXPECT(expectOffers(env, carol, 1)); + env(delegate::set(gw, bob, {"PaymentMint"})); + env.close(); + + // post-amendment: fixDelegateV1_1 + // bob can not send cross currency payment on behalf of the gw, + // even with PaymentMint permission and gw being the issuer. + env(pay(gw, alice, USD(5000)), + path(~USD), + sendmax(XRP(1001)), + txflags(tfPartialPayment), + delegate::as(bob), + ter(result)); + BEAST_EXPECT(expectOffers(env, carol, offerCount)); + + // succeed with direct payment + env(pay(gw, alice, USD(100)), delegate::as(bob)); + env.close(); + } + + // PaymentBurn + { + env(offer(bob, XRP(100), USD(501))); + BEAST_EXPECT(expectOffers(env, bob, 1)); + env(delegate::set(alice, bob, {"PaymentBurn"})); + env.close(); + + // post-amendment: fixDelegateV1_1 + // bob can not send cross currency payment on behalf of alice, + // even with PaymentBurn permission and gw being the issuer. + env(pay(alice, gw, USD(5000)), + path(~USD), + sendmax(XRP(1001)), + txflags(tfPartialPayment), + delegate::as(bob), + ter(result)); + BEAST_EXPECT(expectOffers(env, bob, offerCount)); + + // succeed with direct payment + env(pay(alice, gw, USD(100)), delegate::as(bob)); + env.close(); + } + } + + // PaymentMint and PaymentBurn for MPT + { + std::string logs; + Env env(*this, features, std::make_unique(&logs)); + Account const alice{"alice"}; + Account const bob{"bob"}; + Account const gw{"gateway"}; + + MPTTester mpt(env, gw, {.holders = {alice, bob}}); + mpt.create( + {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanTransfer}); + + mpt.authorize({.account = alice}); + mpt.authorize({.account = bob}); + + auto const MPT = mpt["MPT"]; + env(pay(gw, alice, MPT(500))); + env(pay(gw, bob, MPT(500))); + env.close(); + auto aliceMPT = env.balance(alice, MPT); + auto bobMPT = env.balance(bob, MPT); + + // PaymentMint + { + env(delegate::set(gw, bob, {"PaymentMint"})); + env.close(); + + if (!features[fixDelegateV1_1]) + { + // pre-amendment: PaymentMint is not supported for MPT + env(pay(gw, alice, MPT(50)), + delegate::as(bob), + ter(tefEXCEPTION)); + } + else + { + env(pay(gw, alice, MPT(50)), delegate::as(bob)); + BEAST_EXPECT(env.balance(alice, MPT) == aliceMPT + MPT(50)); + BEAST_EXPECT(env.balance(bob, MPT) == bobMPT); + aliceMPT = env.balance(alice, MPT); + } + } + + // PaymentBurn + { + env(delegate::set(alice, bob, {"PaymentBurn"})); + env.close(); + + if (!features[fixDelegateV1_1]) + { + // pre-amendment: PaymentBurn is not supported for MPT + env(pay(alice, gw, MPT(50)), + delegate::as(bob), + ter(tefEXCEPTION)); + } + else + { + env(pay(alice, gw, MPT(50)), delegate::as(bob)); + BEAST_EXPECT(env.balance(alice, MPT) == aliceMPT - MPT(50)); + BEAST_EXPECT(env.balance(bob, MPT) == bobMPT); + aliceMPT = env.balance(alice, MPT); + } + } + + // Payment transaction for MPT is allowed for both pre and post + // amendment + { + env(delegate::set( + alice, bob, {"PaymentBurn", "PaymentMint", "Payment"})); + env.close(); + env(pay(alice, gw, MPT(50)), delegate::as(bob)); + BEAST_EXPECT(env.balance(alice, MPT) == aliceMPT - MPT(50)); + BEAST_EXPECT(env.balance(bob, MPT) == bobMPT); + aliceMPT = env.balance(alice, MPT); + env(pay(alice, bob, MPT(100)), delegate::as(bob)); + BEAST_EXPECT(env.balance(alice, MPT) == aliceMPT - MPT(100)); + BEAST_EXPECT(env.balance(bob, MPT) == bobMPT + MPT(100)); + } + } } void @@ -1476,18 +1624,216 @@ class Delegate_test : public beast::unit_test::suite BEAST_EXPECT(env.balance(edward) == edwardBalance); } + void + testPermissionValue(FeatureBitset features) + { + testcase("test permission value"); + using namespace jtx; + + Env env(*this, features); + + Account alice{"alice"}; + Account bob{"bob"}; + env.fund(XRP(100000), alice, bob); + env.close(); + + auto buildRequest = [&](auto value) -> Json::Value { + Json::Value jv; + jv[jss::TransactionType] = jss::DelegateSet; + jv[jss::Account] = alice.human(); + jv[sfAuthorize.jsonName] = bob.human(); + + Json::Value permissionsJson(Json::arrayValue); + Json::Value permissionValue; + permissionValue[sfPermissionValue.jsonName] = value; + Json::Value permissionObj; + permissionObj[sfPermission.jsonName] = permissionValue; + permissionsJson.append(permissionObj); + jv[sfPermissions.jsonName] = permissionsJson; + + return jv; + }; + + // invalid permission value. + // neither granular permission nor transaction level permission + for (auto value : {0, 100000, 54321}) + { + auto jv = buildRequest(value); + if (!features[fixDelegateV1_1]) + env(jv); + else + env(jv, ter(temMALFORMED)); + } + } + + void + testTxReqireFeatures(FeatureBitset features) + { + testcase("test delegate disabled tx"); + using namespace jtx; + + // map of tx and required feature. + // non-delegatable tx are not included. + // NFTokenMint, NFTokenBurn, NFTokenCreateOffer, NFTokenCancelOffer, + // NFTokenAcceptOffer are not included, they are tested separately. + std::unordered_map txRequiredFeatures{ + {"TicketCreate", featureTicketBatch}, + {"CheckCreate", featureChecks}, + {"CheckCash", featureChecks}, + {"CheckCancel", featureChecks}, + {"DepositPreauth", featureDepositPreauth}, + {"Clawback", featureClawback}, + {"AMMClawback", featureAMMClawback}, + {"AMMCreate", featureAMM}, + {"AMMDeposit", featureAMM}, + {"AMMWithdraw", featureAMM}, + {"AMMVote", featureAMM}, + {"AMMBid", featureAMM}, + {"AMMDelete", featureAMM}, + {"XChainCreateClaimID", featureXChainBridge}, + {"XChainCommit", featureXChainBridge}, + {"XChainClaim", featureXChainBridge}, + {"XChainAccountCreateCommit", featureXChainBridge}, + {"XChainAddClaimAttestation", featureXChainBridge}, + {"XChainAddAccountCreateAttestation", featureXChainBridge}, + {"XChainModifyBridge", featureXChainBridge}, + {"XChainCreateBridge", featureXChainBridge}, + {"DIDSet", featureDID}, + {"DIDDelete", featureDID}, + {"OracleSet", featurePriceOracle}, + {"OracleDelete", featurePriceOracle}, + {"LedgerStateFix", fixNFTokenPageLinks}, + {"MPTokenIssuanceCreate", featureMPTokensV1}, + {"MPTokenIssuanceDestroy", featureMPTokensV1}, + {"MPTokenIssuanceSet", featureMPTokensV1}, + {"MPTokenAuthorize", featureMPTokensV1}, + {"CredentialCreate", featureCredentials}, + {"CredentialAccept", featureCredentials}, + {"CredentialDelete", featureCredentials}, + {"NFTokenModify", featureDynamicNFT}, + {"PermissionedDomainSet", featurePermissionedDomains}, + {"PermissionedDomainDelete", featurePermissionedDomains}, + {"VaultCreate", featureSingleAssetVault}, + {"VaultSet", featureSingleAssetVault}, + {"VaultDelete", featureSingleAssetVault}, + {"VaultDeposit", featureSingleAssetVault}, + {"VaultWithdraw", featureSingleAssetVault}, + {"VaultClawback", featureSingleAssetVault}}; + + // fixDelegateV1_1 post-amendment: can not delegate tx if any + // required feature disabled. + { + auto txAmendmentDisabled = [&](FeatureBitset features, + std::string const& tx) { + BEAST_EXPECT(txRequiredFeatures.contains(tx)); + + Env env(*this, features - txRequiredFeatures[tx]); + + Account const alice{"alice"}; + Account const bob{"bob"}; + env.fund(XRP(100000), alice, bob); + env.close(); + + if (!features[fixDelegateV1_1]) + env(delegate::set(alice, bob, {tx})); + else + env(delegate::set(alice, bob, {tx}), ter(temMALFORMED)); + }; + + for (auto const& tx : txRequiredFeatures) + txAmendmentDisabled(features, tx.first); + } + + // if all the required features in txRequiredFeatures are enabled, will + // succeed + { + auto txAmendmentEnabled = [&](std::string const& tx) { + Env env(*this, features); + + Account const alice{"alice"}; + Account const bob{"bob"}; + env.fund(XRP(100000), alice, bob); + env.close(); + + env(delegate::set(alice, bob, {tx})); + }; + + for (auto const& tx : txRequiredFeatures) + txAmendmentEnabled(tx.first); + } + + // NFTokenMint, NFTokenBurn, NFTokenCreateOffer, NFTokenCancelOffer, and + // NFTokenAcceptOffer are tested separately. Since + // featureNonFungibleTokensV1_1 includes the functionality of + // featureNonFungibleTokensV1, fixNFTokenNegOffer, and fixNFTokenDirV1, + // both featureNonFungibleTokensV1_1 and featureNonFungibleTokensV1 need + // to be disabled to block these transactions from being delegated. + { + Env env( + *this, + features - featureNonFungibleTokensV1 - + featureNonFungibleTokensV1_1); + + Account const alice{"alice"}; + Account const bob{"bob"}; + env.fund(XRP(100000), alice, bob); + env.close(); + + for (auto const tx : + {"NFTokenMint", + "NFTokenBurn", + "NFTokenCreateOffer", + "NFTokenCancelOffer", + "NFTokenAcceptOffer"}) + { + if (!features[fixDelegateV1_1]) + env(delegate::set(alice, bob, {tx})); + else + env(delegate::set(alice, bob, {tx}), ter(temMALFORMED)); + } + } + + // NFTokenMint, NFTokenBurn, NFTokenCreateOffer, NFTokenCancelOffer, and + // NFTokenAcceptOffer are allowed to be delegated if either + // featureNonFungibleTokensV1 or featureNonFungibleTokensV1_1 is + // enabled. + { + for (auto const feature : + {featureNonFungibleTokensV1, featureNonFungibleTokensV1_1}) + { + Env env(*this, features - feature); + Account const alice{"alice"}; + Account const bob{"bob"}; + env.fund(XRP(100000), alice, bob); + env.close(); + + for (auto const tx : + {"NFTokenMint", + "NFTokenBurn", + "NFTokenCreateOffer", + "NFTokenCancelOffer", + "NFTokenAcceptOffer"}) + env(delegate::set(alice, bob, {tx})); + } + } + } + void run() override { + FeatureBitset const all = jtx::testable_amendments(); + testFeatureDisabled(); testDelegateSet(); - testInvalidRequest(); + testInvalidRequest(all); + testInvalidRequest(all - fixDelegateV1_1); testReserve(); testFee(); testSequence(); testAccountDelete(); testDelegateTransaction(); - testPaymentGranular(); + testPaymentGranular(all); + testPaymentGranular(all - fixDelegateV1_1); testTrustSetGranular(); testAccountSetGranular(); testMPTokenIssuanceSetGranular(); @@ -1495,6 +1841,10 @@ class Delegate_test : public beast::unit_test::suite testSingleSignBadSecret(); testMultiSign(); testMultiSignQuorumNotMet(); + testPermissionValue(all); + testPermissionValue(all - fixDelegateV1_1); + testTxReqireFeatures(all); + testTxReqireFeatures(all - fixDelegateV1_1); } }; BEAST_DEFINE_TESTSUITE(Delegate, app, ripple); diff --git a/src/xrpld/app/tx/detail/DelegateSet.cpp b/src/xrpld/app/tx/detail/DelegateSet.cpp index 708cdf0dc2..ddeb01b399 100644 --- a/src/xrpld/app/tx/detail/DelegateSet.cpp +++ b/src/xrpld/app/tx/detail/DelegateSet.cpp @@ -23,7 +23,6 @@ #include #include #include -#include #include namespace ripple { @@ -51,6 +50,11 @@ DelegateSet::preflight(PreflightContext const& ctx) { if (!permissionSet.insert(permission[sfPermissionValue]).second) return temMALFORMED; + + if (ctx.rules.enabled(fixDelegateV1_1) && + !Permission::getInstance().isDelegatable( + permission[sfPermissionValue], ctx.rules)) + return temMALFORMED; } return preflight2(ctx); @@ -68,9 +72,21 @@ DelegateSet::preclaim(PreclaimContext const& ctx) auto const& permissions = ctx.tx.getFieldArray(sfPermissions); for (auto const& permission : permissions) { - auto const permissionValue = permission[sfPermissionValue]; - if (!Permission::getInstance().isDelegatable(permissionValue)) + if (!ctx.view.rules().enabled(fixDelegateV1_1) && + !Permission::getInstance().isDelegatable( + permission[sfPermissionValue], ctx.view.rules())) + { + // Before fixDelegateV1_1: + // - The check was performed during preclaim. + // - Transactions from amendments not yet enabled could still be + // delegated. + // + // After fixDelegateV1_1: + // - The check is performed during preflight. + // - Transactions from amendments not yet enabled can no longer be + // delegated. return tecNO_PERMISSION; + } } return tesSUCCESS; diff --git a/src/xrpld/app/tx/detail/Payment.cpp b/src/xrpld/app/tx/detail/Payment.cpp index 386b170ed1..784330b203 100644 --- a/src/xrpld/app/tx/detail/Payment.cpp +++ b/src/xrpld/app/tx/detail/Payment.cpp @@ -265,8 +265,33 @@ Payment::checkPermission(ReadView const& view, STTx const& tx) loadGranularPermission(sle, ttPAYMENT, granularPermissions); auto const& dstAmount = tx.getFieldAmount(sfAmount); - auto const& amountIssue = dstAmount.issue(); + // post-amendment: disallow cross currency payments for PaymentMint and + // PaymentBurn + if (view.rules().enabled(fixDelegateV1_1)) + { + auto const& amountAsset = dstAmount.asset(); + if (tx.isFieldPresent(sfSendMax) && + tx[sfSendMax].asset() != amountAsset) + return tecNO_DELEGATE_PERMISSION; + if (granularPermissions.contains(PaymentMint) && !isXRP(amountAsset) && + amountAsset.getIssuer() == tx[sfAccount]) + return tesSUCCESS; + + if (granularPermissions.contains(PaymentBurn) && !isXRP(amountAsset) && + amountAsset.getIssuer() == tx[sfDestination]) + return tesSUCCESS; + + return tecNO_DELEGATE_PERMISSION; + } + + // Calling dstAmount.issue() in the next line would throw if it holds MPT. + // That exception would be caught in preclaim and returned as tefEXCEPTION. + // This check is just a cleaner, more explicit way to get the same result. + if (dstAmount.holds()) + return tefEXCEPTION; + + auto const& amountIssue = dstAmount.issue(); if (granularPermissions.contains(PaymentMint) && !isXRP(amountIssue) && amountIssue.account == tx[sfAccount]) return tesSUCCESS; diff --git a/src/xrpld/app/tx/detail/applySteps.cpp b/src/xrpld/app/tx/detail/applySteps.cpp index 34259ebef0..03ef7244f8 100644 --- a/src/xrpld/app/tx/detail/applySteps.cpp +++ b/src/xrpld/app/tx/detail/applySteps.cpp @@ -97,8 +97,8 @@ with_txn_type(TxType txnType, F&& f) #pragma push_macro("TRANSACTION") #undef TRANSACTION -#define TRANSACTION(tag, value, name, delegatable, fields) \ - case tag: \ +#define TRANSACTION(tag, value, name, ...) \ + case tag: \ return f.template operator()(); #include From fbd60fc0007d95caa3fbbfcfcbbfc097c25ed502 Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Thu, 11 Sep 2025 13:58:11 +0100 Subject: [PATCH 15/18] ci: Use pre-commit reusable workflow (#5772) --- .github/workflows/check-format.yml | 44 ------------------------------ .github/workflows/on-pr.yml | 9 ------ .github/workflows/pre-commit.yml | 14 ++++++++++ 3 files changed, 14 insertions(+), 53 deletions(-) delete mode 100644 .github/workflows/check-format.yml create mode 100644 .github/workflows/pre-commit.yml diff --git a/.github/workflows/check-format.yml b/.github/workflows/check-format.yml deleted file mode 100644 index c63589017d..0000000000 --- a/.github/workflows/check-format.yml +++ /dev/null @@ -1,44 +0,0 @@ -# This workflow checks if the code is properly formatted. -name: Check format - -# This workflow can only be triggered by other workflows. -on: workflow_call - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }}-format - cancel-in-progress: true - -defaults: - run: - shell: bash - -jobs: - pre-commit: - runs-on: ubuntu-latest - container: ghcr.io/xrplf/ci/tools-rippled-pre-commit - steps: - - name: Checkout repository - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 - - name: Prepare runner - uses: XRPLF/actions/.github/actions/prepare-runner@638e0dc11ea230f91bd26622fb542116bb5254d5 - - name: Format code - run: pre-commit run --show-diff-on-failure --color=always --all-files - - name: Check for differences - env: - MESSAGE: | - One or more files did not conform to the formatting. Maybe you did - not run 'pre-commit' before committing, or your version of - 'clang-format' or 'prettier' has an incompatibility with the ones - used here (see the "Check configuration" step above). - - Run 'pre-commit run --all-files' in your repo, and then commit and - push the changes. - run: | - DIFF=$(git status --porcelain) - if [ -n "${DIFF}" ]; then - # Print the files that changed to give the contributor a hint about - # what to expect when running pre-commit on their own machine. - git status - echo "${MESSAGE}" - exit 1 - fi diff --git a/.github/workflows/on-pr.yml b/.github/workflows/on-pr.yml index c480cc5476..24f27d5162 100644 --- a/.github/workflows/on-pr.yml +++ b/.github/workflows/on-pr.yml @@ -50,12 +50,9 @@ jobs: files: | # These paths are unique to `on-pr.yml`. .github/scripts/levelization/** - .github/workflows/check-format.yml .github/workflows/check-levelization.yml .github/workflows/notify-clio.yml .github/workflows/on-pr.yml - .clang-format - .pre-commit-config.yaml # Keep the paths below in sync with those in `on-trigger.yml`. .github/actions/build-deps/** @@ -93,11 +90,6 @@ jobs: outputs: go: ${{ steps.go.outputs.go == 'true' }} - check-format: - needs: should-run - if: needs.should-run.outputs.go == 'true' - uses: ./.github/workflows/check-format.yml - check-levelization: needs: should-run if: needs.should-run.outputs.go == 'true' @@ -130,7 +122,6 @@ jobs: if: failure() || cancelled() needs: - build-test - - check-format - check-levelization runs-on: ubuntu-latest steps: diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml new file mode 100644 index 0000000000..ead137308d --- /dev/null +++ b/.github/workflows/pre-commit.yml @@ -0,0 +1,14 @@ +name: Run pre-commit hooks + +on: + pull_request: + push: + branches: [develop, release, master] + workflow_dispatch: + +jobs: + run-hooks: + uses: XRPLF/actions/.github/workflows/pre-commit.yml@af1b0f0d764cda2e5435f5ac97b240d4bd4d95d3 + with: + runs_on: ubuntu-latest + container: '{ "image": "ghcr.io/xrplf/ci/tools-rippled-pre-commit" }' From e6f8bc720fdbbe63ee907f5ac429aa9cfc7e56c9 Mon Sep 17 00:00:00 2001 From: tequ Date: Thu, 11 Sep 2025 23:17:06 +0900 Subject: [PATCH 16/18] Add additional metadata to simulate response (#5754) --- src/test/rpc/Simulate_test.cpp | 104 ++++++++++++++++++++++++++++ src/xrpld/rpc/handlers/Simulate.cpp | 14 ++++ 2 files changed, 118 insertions(+) diff --git a/src/test/rpc/Simulate_test.cpp b/src/test/rpc/Simulate_test.cpp index 5b3c0d2372..0a36a8a841 100644 --- a/src/test/rpc/Simulate_test.cpp +++ b/src/test/rpc/Simulate_test.cpp @@ -131,6 +131,32 @@ class Simulate_test : public beast::unit_test::suite std::to_string(env.current()->txCount())); } + void + testTxJsonMetadataField( + jtx::Env& env, + Json::Value const& tx, + std::function const& validate, + Json::Value const& expectedMetadataKey, + bool testSerialized = true) + { + env.close(); + + Json::Value params; + params[jss::tx_json] = tx; + validate( + env.rpc("json", "simulate", to_string(params)), + tx, + expectedMetadataKey); + validate(env.rpc("simulate", to_string(tx)), tx, expectedMetadataKey); + + BEAST_EXPECTS( + env.current()->txCount() == 0, + std::to_string(env.current()->txCount())); + } + Json::Value getJsonMetadata(Json::Value txResult) const { @@ -1186,6 +1212,83 @@ class Simulate_test : public beast::unit_test::suite } } + void + testSuccessfulTransactionAdditionalMetadata() + { + testcase("Successful transaction with additional metadata"); + + using namespace jtx; + Env env{*this, envconfig([&](std::unique_ptr cfg) { + cfg->NETWORK_ID = 1025; + return cfg; + })}; + + Account const alice("alice"); + + env.fund(XRP(10000), alice); + env.close(); + + { + auto validateOutput = [&](Json::Value const& resp, + Json::Value const& tx, + Json::Value const& expectedMetadataKey) { + auto result = resp[jss::result]; + + BEAST_EXPECT(result[jss::engine_result] == "tesSUCCESS"); + BEAST_EXPECT(result[jss::engine_result_code] == 0); + BEAST_EXPECT( + result[jss::engine_result_message] == + "The simulated transaction would have been applied."); + + if (BEAST_EXPECT( + result.isMember(jss::meta) || + result.isMember(jss::meta_blob))) + { + Json::Value const metadata = getJsonMetadata(result); + + BEAST_EXPECT(metadata[sfTransactionIndex.jsonName] == 0); + BEAST_EXPECT( + metadata[sfTransactionResult.jsonName] == "tesSUCCESS"); + BEAST_EXPECT( + metadata.isMember(expectedMetadataKey.asString())); + } + }; + + { + Json::Value tx; + tx[jss::Account] = env.master.human(); + tx[jss::TransactionType] = jss::Payment; + tx[sfDestination] = alice.human(); + tx[sfAmount] = "100"; + + // test delivered amount + testTxJsonMetadataField( + env, tx, validateOutput, jss::delivered_amount); + } + + { + Json::Value tx; + tx[jss::Account] = env.master.human(); + tx[jss::TransactionType] = jss::NFTokenMint; + tx[sfNFTokenTaxon] = 1; + + // test nft synthetic + testTxJsonMetadataField( + env, tx, validateOutput, jss::nftoken_id); + } + + { + Json::Value tx; + tx[jss::Account] = env.master.human(); + tx[jss::TransactionType] = jss::MPTokenIssuanceCreate; + + // test mpt issuance id + testTxJsonMetadataField( + env, tx, validateOutput, jss::mpt_issuance_id); + } + } + } + public: void run() override @@ -1202,6 +1305,7 @@ public: testMultisignedBadPubKey(); testDeleteExpiredCredentials(); testSuccessfulTransactionNetworkID(); + testSuccessfulTransactionAdditionalMetadata(); } }; diff --git a/src/xrpld/rpc/handlers/Simulate.cpp b/src/xrpld/rpc/handlers/Simulate.cpp index 3c175883c5..092b0b4562 100644 --- a/src/xrpld/rpc/handlers/Simulate.cpp +++ b/src/xrpld/rpc/handlers/Simulate.cpp @@ -24,10 +24,13 @@ #include #include #include +#include #include +#include #include #include +#include #include #include #include @@ -272,6 +275,17 @@ simulateTxn(RPC::JsonContext& context, std::shared_ptr transaction) else { jvResult[jss::meta] = result.metadata->getJson(JsonOptions::none); + RPC::insertDeliveredAmount( + jvResult[jss::meta], + view, + transaction->getSTransaction(), + *result.metadata); + RPC::insertNFTSyntheticInJson( + jvResult, transaction->getSTransaction(), *result.metadata); + RPC::insertMPTokenIssuanceID( + jvResult[jss::meta], + transaction->getSTransaction(), + *result.metadata); } } From 6fe0599cc26db01e4912b133a70b9d252281a568 Mon Sep 17 00:00:00 2001 From: Mayukha Vadari Date: Thu, 11 Sep 2025 10:49:26 -0400 Subject: [PATCH 17/18] refactor: clean up `CTID.h` (#5681) --- src/xrpld/rpc/CTID.h | 85 +++++++++++++++++++++++++++++++++----------- 1 file changed, 64 insertions(+), 21 deletions(-) diff --git a/src/xrpld/rpc/CTID.h b/src/xrpld/rpc/CTID.h index be531c536a..0e2b7e0d65 100644 --- a/src/xrpld/rpc/CTID.h +++ b/src/xrpld/rpc/CTID.h @@ -39,53 +39,96 @@ namespace RPC { // The Concise Transaction ID provides a way to identify a transaction // that includes which network the transaction was submitted to. +/** + * @brief Encodes ledger sequence, transaction index, and network ID into a CTID + * string. + * + * @param ledgerSeq Ledger sequence number (max 0x0FFF'FFFF). + * @param txnIndex Transaction index within the ledger (max 0xFFFF). + * @param networkID Network identifier (max 0xFFFF). + * @return Optional CTID string in uppercase hexadecimal, or std::nullopt if + * inputs are out of range. + */ inline std::optional encodeCTID(uint32_t ledgerSeq, uint32_t txnIndex, uint32_t networkID) noexcept { - if (ledgerSeq > 0x0FFF'FFFF || txnIndex > 0xFFFF || networkID > 0xFFFF) - return {}; + constexpr uint32_t maxLedgerSeq = 0x0FFF'FFFF; + constexpr uint32_t maxTxnIndex = 0xFFFF; + constexpr uint32_t maxNetworkID = 0xFFFF; + + if (ledgerSeq > maxLedgerSeq || txnIndex > maxTxnIndex || + networkID > maxNetworkID) + return std::nullopt; uint64_t ctidValue = - ((0xC000'0000ULL + static_cast(ledgerSeq)) << 32) + - (static_cast(txnIndex) << 16) + networkID; + ((0xC000'0000ULL + static_cast(ledgerSeq)) << 32) | + ((static_cast(txnIndex) << 16) | networkID); std::stringstream buffer; buffer << std::hex << std::uppercase << std::setfill('0') << std::setw(16) << ctidValue; - return {buffer.str()}; + return buffer.str(); } +/** + * @brief Decodes a CTID string or integer into its component parts. + * + * @tparam T Type of the CTID input (string, string_view, char*, integral). + * @param ctid CTID value to decode. + * @return Optional tuple of (ledgerSeq, txnIndex, networkID), or std::nullopt + * if invalid. + */ template inline std::optional> decodeCTID(T const ctid) noexcept { - uint64_t ctidValue{0}; + uint64_t ctidValue = 0; + if constexpr ( - std::is_same_v || std::is_same_v || - std::is_same_v || std::is_same_v) + std::is_same_v || std::is_same_v || + std::is_same_v || std::is_same_v) { std::string const ctidString(ctid); - if (ctidString.length() != 16) - return {}; + if (ctidString.size() != 16) + return std::nullopt; - if (!boost::regex_match(ctidString, boost::regex("^[0-9A-Fa-f]+$"))) - return {}; + static boost::regex const hexRegex("^[0-9A-Fa-f]{16}$"); + if (!boost::regex_match(ctidString, hexRegex)) + return std::nullopt; - ctidValue = std::stoull(ctidString, nullptr, 16); + try + { + ctidValue = std::stoull(ctidString, nullptr, 16); + } + // LCOV_EXCL_START + catch (...) + { + // should be impossible to hit given the length/regex check + return std::nullopt; + } + // LCOV_EXCL_STOP } else if constexpr (std::is_integral_v) - ctidValue = ctid; + { + ctidValue = static_cast(ctid); + } else - return {}; + { + return std::nullopt; + } - if ((ctidValue & 0xF000'0000'0000'0000ULL) != 0xC000'0000'0000'0000ULL) - return {}; + // Validate CTID prefix. + constexpr uint64_t ctidPrefixMask = 0xF000'0000'0000'0000ULL; + constexpr uint64_t ctidPrefix = 0xC000'0000'0000'0000ULL; + if ((ctidValue & ctidPrefixMask) != ctidPrefix) + return std::nullopt; - uint32_t ledger_seq = (ctidValue >> 32) & 0xFFFF'FFFUL; - uint16_t txn_index = (ctidValue >> 16) & 0xFFFFU; - uint16_t network_id = ctidValue & 0xFFFFU; - return {{ledger_seq, txn_index, network_id}}; + uint32_t ledgerSeq = static_cast((ctidValue >> 32) & 0x0FFF'FFFF); + uint16_t txnIndex = static_cast((ctidValue >> 16) & 0xFFFF); + uint16_t networkID = static_cast(ctidValue & 0xFFFF); + + return std::make_tuple(ledgerSeq, txnIndex, networkID); } } // namespace RPC From f69ad4eff66d26b8c57e6e1649850652abfbb3b9 Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Thu, 11 Sep 2025 16:42:27 +0100 Subject: [PATCH 18/18] docs: Add remote to `conan lock create` command (#5770) * docs: Add remote to `conan lock create` command * Document error resolution for conan package issues * Update BUILD.md * Add more info about lockfiles --- BUILD.md | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/BUILD.md b/BUILD.md index 6b1594bb5e..fd7a0b855d 100644 --- a/BUILD.md +++ b/BUILD.md @@ -132,7 +132,7 @@ higher index than the default Conan Center remote, so it is consulted first. You can do this by running: ```bash -conan remote add --index 0 xrplf "https://conan.ripplex.io" +conan remote add --index 0 xrplf https://conan.ripplex.io ``` Alternatively, you can pull the patched recipes into the repository and use them @@ -479,12 +479,24 @@ It is implicitly used when running `conan` commands, you don't need to specify i You have to update this file every time you add a new dependency or change a revision or version of an existing dependency. -To do that, run the following command in the repository root: +> [!NOTE] +> Conan uses local cache by default when creating a lockfile. +> +> To ensure, that lockfile creation works the same way on all developer machines, you should clear the local cache before creating a new lockfile. + +To create a new lockfile, run the following commands in the repository root: ```bash +conan remove '*' --confirm +rm conan.lock +# This ensure that xrplf remote is the first to be consulted +conan remote add --force --index 0 xrplf https://conan.ripplex.io conan lock create . -o '&:jemalloc=True' -o '&:rocksdb=True' ``` +> [!NOTE] +> If some dependencies are exclusive for some OS, you may need to run the last command for them adding `--profile:all `. + ## Coverage report The coverage report is intended for developers using compilers GCC @@ -586,6 +598,11 @@ After any updates or changes to dependencies, you may need to do the following: 4. [Regenerate lockfile](#conan-lockfile). 5. Re-run [conan install](#build-and-test). +#### ERROR: Package not resolved + +If you're seeing an error like `ERROR: Package 'snappy/1.1.10' not resolved: Unable to find 'snappy/1.1.10#968fef506ff261592ec30c574d4a7809%1756234314.246' in remotes.`, +please add `xrplf` remote or re-run `conan export` for [patched recipes](#patched-recipes). + ### `protobuf/port_def.inc` file not found If `cmake --build .` results in an error due to a missing a protobuf file, then