diff --git a/.buildkite/hooks/pre-command b/.buildkite/hooks/pre-command index 025b228f857..4c798a83d36 100644 --- a/.buildkite/hooks/pre-command +++ b/.buildkite/hooks/pre-command @@ -28,5 +28,11 @@ fi export SBF_TOOLS_VERSION -SCCACHE_S3_KEY_PREFIX="${rust_stable}_${rust_nightly}_${SBF_TOOLS_VERSION}" +SCCACHE_KEY_PREFIX="${rust_stable}_${rust_nightly}_${SBF_TOOLS_VERSION}" +export SCCACHE_KEY_PREFIX + +SCCACHE_S3_KEY_PREFIX="$SCCACHE_KEY_PREFIX" export SCCACHE_S3_KEY_PREFIX + +SCCACHE_GCS_KEY_PREFIX="$SCCACHE_KEY_PREFIX" +export SCCACHE_GCS_KEY_PREFIX diff --git a/.github/scripts/cargo-clippy-before-script.sh b/.github/scripts/cargo-clippy-before-script.sh new file mode 100755 index 00000000000..b9426203aa6 --- /dev/null +++ b/.github/scripts/cargo-clippy-before-script.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash + +set -e + +os_name="$1" + +case "$os_name" in +"Windows") + ;; +"macOS") + brew install protobuf + ;; +"Linux") ;; +*) + echo "Unknown Operating System" + ;; +esac diff --git a/.github/scripts/downstream-project-spl-common.sh b/.github/scripts/downstream-project-spl-common.sh index c6dcfaca007..47237e71c9c 100644 --- a/.github/scripts/downstream-project-spl-common.sh +++ b/.github/scripts/downstream-project-spl-common.sh @@ -8,7 +8,7 @@ source "$here"/../../ci/downstream-projects/common.sh set -x rm -rf spl -git clone https://github.com/solana-labs/solana-program-library.git spl +git clone https://github.com/solana-labs/solana-program-library.git spl -b v1.17 # copy toolchain file to use solana's rust version cp "$SOLANA_DIR"/rust-toolchain.toml spl/ @@ -22,3 +22,5 @@ if semverGT "$project_used_solana_version" "$SOLANA_VER"; then fi ./patch.crates-io.sh "$SOLANA_DIR" +# anza migration stopgap. can be removed when agave is fully recommended for public usage. +sed -i 's/solana-geyser-plugin-interface/agave-geyser-plugin-interface/g' ./Cargo.toml diff --git a/.github/workflows/cargo.yml b/.github/workflows/cargo.yml new file mode 100644 index 00000000000..d0bad722e0d --- /dev/null +++ b/.github/workflows/cargo.yml @@ -0,0 +1,71 @@ +name: Cargo + +on: + push: + branches: + - master + - v[0-9]+.[0-9]+ + pull_request: + branches: + - master + - v[0-9]+.[0-9]+ + paths: + - "**.rs" + - "**/Cargo.toml" + - "**/Cargo.lock" + - ".github/scripts/cargo-clippy-before-script.sh" + - ".github/workflows/cargo.yml" + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +env: + SHELL: /bin/bash + SCCACHE_GHA_ENABLED: "true" + RUSTC_WRAPPER: "sccache" + +jobs: + clippy-stable: + strategy: + matrix: + os: + - macos-latest + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + + - uses: mozilla-actions/sccache-action@v0.0.3 + with: + version: "v0.5.4" + + - shell: bash + run: .github/scripts/cargo-clippy-before-script.sh ${{ runner.os }} + + - shell: bash + run: | + source ci/rust-version.sh stable + rustup component add clippy --toolchain "$rust_stable" + scripts/cargo-clippy-stable.sh + + clippy-nightly: + strategy: + matrix: + os: + - macos-latest + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + + - uses: mozilla-actions/sccache-action@v0.0.3 + with: + version: "v0.5.4" + + - shell: bash + run: .github/scripts/cargo-clippy-before-script.sh ${{ runner.os }} + + - shell: bash + run: | + source ci/rust-version.sh nightly + rustup component add clippy --toolchain "$rust_nightly" + scripts/cargo-clippy-nightly.sh diff --git a/.github/workflows/downstream-project-spl.yml b/.github/workflows/downstream-project-spl.yml index f0ecfb20acc..6afd398f43a 100644 --- a/.github/workflows/downstream-project-spl.yml +++ b/.github/workflows/downstream-project-spl.yml @@ -43,6 +43,8 @@ jobs: .github/scripts/purge-ubuntu-runner.sh - uses: mozilla-actions/sccache-action@v0.0.3 + with: + version: "v0.5.4" - shell: bash run: | @@ -90,6 +92,8 @@ jobs: .github/scripts/purge-ubuntu-runner.sh - uses: mozilla-actions/sccache-action@v0.0.3 + with: + version: "v0.5.4" - shell: bash run: | @@ -139,6 +143,8 @@ jobs: .github/scripts/purge-ubuntu-runner.sh - uses: mozilla-actions/sccache-action@v0.0.3 + with: + version: "v0.5.4" - shell: bash run: | diff --git a/.github/workflows/release-artifacts-auto.yml b/.github/workflows/release-artifacts-auto.yml index a8309cdffc8..0cdd176e043 100644 --- a/.github/workflows/release-artifacts-auto.yml +++ b/.github/workflows/release-artifacts-auto.yml @@ -14,14 +14,12 @@ concurrency: jobs: release-artifacts: - if: github.repository == 'solana-labs/solana' + if: github.repository == 'anza-xyz/agave' uses: ./.github/workflows/release-artifacts.yml with: commit: ${{ github.sha }} secrets: - AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - AWS_S3_BUCKET: ${{ secrets.AWS_S3_BUCKET }} + GCS_RELEASE_BUCKET_WRITER_CREDIENTIAL: ${{ secrets.GCS_RELEASE_BUCKET_WRITER_CREDIENTIAL }} error_reporting: needs: diff --git a/.github/workflows/release-artifacts-manually.yml b/.github/workflows/release-artifacts-manually.yml index 35de72922c3..fe5c1b03b63 100644 --- a/.github/workflows/release-artifacts-manually.yml +++ b/.github/workflows/release-artifacts-manually.yml @@ -14,6 +14,4 @@ jobs: with: commit: ${{ github.event.inputs.commit }} secrets: - AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - AWS_S3_BUCKET: ${{ secrets.AWS_S3_BUCKET }} + GCS_RELEASE_BUCKET_WRITER_CREDIENTIAL: ${{ secrets.GCS_RELEASE_BUCKET_WRITER_CREDIENTIAL }} diff --git a/.github/workflows/release-artifacts.yml b/.github/workflows/release-artifacts.yml index 3e5ab89fe33..08d7e56fdab 100644 --- a/.github/workflows/release-artifacts.yml +++ b/.github/workflows/release-artifacts.yml @@ -7,11 +7,7 @@ on: required: false type: string secrets: - AWS_ACCESS_KEY_ID: - required: true - AWS_SECRET_ACCESS_KEY: - required: true - AWS_S3_BUCKET: + GCS_RELEASE_BUCKET_WRITER_CREDIENTIAL: required: true jobs: @@ -47,7 +43,7 @@ jobs: id: build shell: bash run: | - choco install openssl + choco install openssl --version=3.1.1 if [[ -d "C:\Program Files\OpenSSL" ]]; then echo "OPENSSL_DIR: C:\Program Files\OpenSSL" export OPENSSL_DIR="C:\Program Files\OpenSSL" @@ -71,19 +67,19 @@ jobs: shell: bash run: | FOLDER_NAME=${{ steps.build.outputs.tag || steps.build.outputs.channel }} - mkdir -p "github-action-s3-upload/$FOLDER_NAME" - cp -v "solana-release-x86_64-pc-windows-msvc.tar.bz2" "github-action-s3-upload/$FOLDER_NAME/" - cp -v "solana-release-x86_64-pc-windows-msvc.yml" "github-action-s3-upload/$FOLDER_NAME/" - cp -v "solana-install-init-x86_64-pc-windows-msvc"* "github-action-s3-upload/$FOLDER_NAME" + mkdir -p "windows-release/$FOLDER_NAME" + cp -v "solana-release-x86_64-pc-windows-msvc.tar.bz2" "windows-release/$FOLDER_NAME/" + cp -v "solana-release-x86_64-pc-windows-msvc.yml" "windows-release/$FOLDER_NAME/" + cp -v "agave-install-init-x86_64-pc-windows-msvc"* "windows-release/$FOLDER_NAME" - name: Upload Artifacts if: ${{ steps.build.outputs.channel != '' || steps.build.outputs.tag != '' }} uses: actions/upload-artifact@v3 with: name: windows-artifact - path: github-action-s3-upload/ + path: windows-release/ - windows-s3-upload: + windows-gcs-upload: if: ${{ needs.windows-build.outputs.channel != '' || needs.windows-build.outputs.tag != '' }} needs: [windows-build] runs-on: ubuntu-20.04 @@ -92,18 +88,16 @@ jobs: uses: actions/download-artifact@v3 with: name: windows-artifact - path: ./github-action-s3-upload + path: ./windows-release - - name: Upload - uses: jakejarvis/s3-sync-action@master + - name: Setup crediential + uses: "google-github-actions/auth@v2" with: - args: --acl public-read --follow-symlinks - env: - AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - AWS_S3_BUCKET: ${{ secrets.AWS_S3_BUCKET }} - AWS_REGION: "us-west-1" - SOURCE_DIR: "github-action-s3-upload" + credentials_json: "${{ secrets.GCS_RELEASE_BUCKET_WRITER_CREDIENTIAL }}" + + - name: Upload files to GCS + run: | + gcloud storage cp --recursive windows-release/* gs://anza-release/ windows-gh-release: if: ${{ needs.windows-build.outputs.tag != '' }} @@ -114,7 +108,7 @@ jobs: uses: actions/download-artifact@v3 with: name: windows-artifact - path: ./github-action-s3-upload + path: ./windows-release/ - name: Release uses: softprops/action-gh-release@v1 @@ -122,4 +116,4 @@ jobs: tag_name: ${{ needs.windows-build.outputs.tag }} draft: true files: | - github-action-s3-upload/${{ needs.windows-build.outputs.tag }}/* + windows-release/${{ needs.windows-build.outputs.tag }}/* diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000000..78a6d029d4c --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,89 @@ +name: Release + +on: + push: + tags: + - "*" + +jobs: + trigger-buildkite-pipeline: + runs-on: ubuntu-latest + steps: + - name: Trigger a Buildkite Build + uses: "buildkite/trigger-pipeline-action@v2.0.0" + with: + buildkite_api_access_token: ${{ secrets.TRIGGER_BK_BUILD_TOKEN }} + pipeline: "anza/agave-secondary" + branch: "${{ github.ref_name }}" + build_env_vars: '{"TRIGGERED_BUILDKITE_TAG": "${{ github.ref_name }}"}' + commit: "HEAD" + message: ":github: Triggered from a GitHub Action" + + draft-release: + runs-on: ubuntu-latest + steps: + - name: Create Release + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + github.rest.repos.createRelease({ + owner: context.repo.owner, + repo: context.repo.repo, + tag_name: '${{ github.ref_name }}', + name: 'Release ${{ github.ref_name }}', + body: '🚧', + draft: false, + prerelease: true + }) + + version-bump: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Parse Info + id: parse_info + run: | + # get the next version + version=${{ github.ref_name }} + major=$(echo $version | cut -d'.' -f1) + minor=$(echo $version | cut -d'.' -f2) + patch=$(echo $version | cut -d'.' -f3) + next_version=$major.$minor.$((patch+1)) + : "${next_version:?}" + + # get the traget branch + target_branch=$major.$minor + : "${target_branch:?}" + + echo "next_version=$next_version" | tee -a $GITHUB_OUTPUT + echo "target_branch=$target_branch" | tee -a $GITHUB_OUTPUT + + - name: Create branch and make changes + run: | + next_version=${{ steps.parse_info.outputs.next_version }} + + git checkout -b version-bump-$next_version + ./scripts/increment-cargo-version.sh patch + + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git config user.name "github-actions[bot]" + git commit -am "Bump version to $next_version" + git push origin version-bump-$next_version + + - name: Create PR + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + github.rest.pulls.create({ + owner: context.repo.owner, + repo: context.repo.repo, + title: 'Bump version to ${{ steps.parse_info.outputs.next_version }}', + head: 'version-bump-${{ steps.parse_info.outputs.next_version }}', + base: '${{ steps.parse_info.outputs.target_branch }}' + }) diff --git a/.mergify.yml b/.mergify.yml index 1fca661dddd..fe910363019 100644 --- a/.mergify.yml +++ b/.mergify.yml @@ -33,7 +33,7 @@ pull_request_rules: actions: request_reviews: teams: - - "@solana-labs/community-pr-subscribers" + - "@anza-xyz/community-pr-subscribers" - name: label changes from monorepo-triage conditions: - author≠@core-contributors @@ -50,7 +50,7 @@ pull_request_rules: - name: automatic merge (squash) on CI success conditions: - and: - - status-success=buildkite/solana + - status-success=buildkite/agave - status-success=ci-gate - label=automerge - label!=no-automerge @@ -80,7 +80,7 @@ pull_request_rules: actions: backport: assignees: &BackportAssignee - - "{{ merged_by|replace('mergify[bot]', label|select('equalto', 'community')|first|default(author)|replace('community', '@solana-labs/community-pr-subscribers')) }}" + - "{{ merged_by|replace('mergify[bot]', label|select('equalto', 'community')|first|default(author)|replace('community', '@anza-xyz/community-pr-subscribers')) }}" title: "{{ destination_branch }}: {{ title }} (backport of #{{ number }})" ignore_conflicts: true labels: diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index c2dd13e3255..00000000000 --- a/.travis.yml +++ /dev/null @@ -1,94 +0,0 @@ -branches: - only: - - master - - /^v\d+\.\d+/ - -notifications: - email: false - slack: - on_success: change - if: NOT type = pull_request - secure: F4IjOE05MyaMOdPRL+r8qhs7jBvv4yDM3RmFKE1zNXnfUOqV4X38oQM1EI+YVsgpMQLj/pxnEB7wcTE4Bf86N6moLssEULCpvAuMVoXj4QbWdomLX+01WbFa6fLVeNQIg45NHrz2XzVBhoKOrMNnl+QI5mbR2AlS5oqsudHsXDnyLzZtd4Y5SDMdYG1zVWM01+oNNjgNfjcCGmOE/K0CnOMl6GPi3X9C34tJ19P2XT7MTDsz1/IfEF7fro2Q8DHEYL9dchJMoisXSkem5z7IDQkGzXsWdWT4NnndUvmd1MlTCE9qgoXDqRf95Qh8sB1Dz08HtvgfaosP2XjtNTfDI9BBYS15Ibw9y7PchAJE1luteNjF35EOy6OgmCLw/YpnweqfuNViBZz+yOPWXVC0kxnPIXKZ1wyH9ibeH6E4hr7a8o9SV/6SiWIlbYF+IR9jPXyTCLP/cc3sYljPWxDnhWFwFdRVIi3PbVAhVu7uWtVUO17Oc9gtGPgs/GrhOMkJfwQPXaudRJDpVZowxTX4x9kefNotlMAMRgq+Drbmgt4eEBiCNp0ITWgh17BiE1U09WS3myuduhoct85+FoVeaUkp1sxzHVtGsNQH0hcz7WcpZyOM+AwistJA/qzeEDQao5zi1eKWPbO2xAhi2rV1bDH6bPf/4lDBwLRqSiwvlWU= - -os: linux -dist: bionic -language: minimal - -jobs: - include: - - &release-artifacts - if: type IN (api, cron) OR tag IS present - name: "macOS release artifacts" - os: osx - osx_image: xcode12 - language: rust - rust: - - stable - install: - - source ci/rust-version.sh - - PATH="/usr/local/opt/coreutils/libexec/gnubin:$PATH" - - readlink -f . - - brew install gnu-tar - - PATH="/usr/local/opt/gnu-tar/libexec/gnubin:$PATH" - - tar --version - script: - - source ci/env.sh - - rustup set profile default - - ci/publish-tarball.sh - deploy: - - provider: s3 - access_key_id: $AWS_ACCESS_KEY_ID - secret_access_key: $AWS_SECRET_ACCESS_KEY - bucket: release.solana.com - region: us-west-1 - skip_cleanup: true - acl: public_read - local_dir: travis-s3-upload - on: - all_branches: true - - provider: releases - token: $GITHUB_TOKEN - skip_cleanup: true - file_glob: true - file: travis-release-upload/* - on: - tags: true - - <<: *release-artifacts - name: "Windows release artifacts" - os: windows - install: - - choco install openssl - - export OPENSSL_DIR="C:\Program Files\OpenSSL-Win64" - - source ci/rust-version.sh - - PATH="/usr/local/opt/coreutils/libexec/gnubin:$PATH" - - readlink -f . - # Linux release artifacts are still built by ci/buildkite-secondary.yml - #- <<: *release-artifacts - # name: "Linux release artifacts" - # os: linux - # before_install: - # - sudo apt-get install libssl-dev libudev-dev - - # docs pull request - - name: "docs" - if: type IN (push, pull_request) OR tag IS present - language: node_js - node_js: - - "lts/*" - - services: - - docker - - cache: - directories: - - ~/.npm - - before_install: - - source ci/env.sh - - .travis/channel_restriction.sh edge beta || travis_terminate 0 - - .travis/affects.sh docs/ .travis || travis_terminate 0 - - cd docs/ - - source .travis/before_install.sh - - script: - - source .travis/script.sh diff --git a/CHANGELOG.md b/CHANGELOG.md index df54c187651..a5c7410ba55 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,17 +4,22 @@ All notable changes to this project will be documented in this file. Please follow the [guidance](#adding-to-this-changelog) at the bottom of this file when making changes The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html) -and follows a [Backwards Compatability Policy](https://docs.solana.com/developing/backwards-compatibility) +and follows a [Backwards Compatibility Policy](https://docs.solana.com/developing/backwards-compatibility) -Release channels have their own copy of this changelog: -* [edge - v1.17](#edge-channel) -* [beta - v1.16](https://github.com/solana-labs/solana/blob/v1.16/CHANGELOG.md) -* [stable - v1.14](https://github.com/solana-labs/solana/blob/v1.14/CHANGELOG.md) +## [1.17.3] - Unreleased +* Changes +* Upgrade Notes + +## [1.17.2] +* minor bug fixes and improvements + +## [1.17.1] +* minor bug fixes and improvements - -## [1.17.0] - Unreleased +## [1.17.0] * Changes * Added a changelog. + * Added `--use-snapshot-archives-at-startup` for faster validator restarts * Upgrade Notes ## Adding to this Changelog diff --git a/Cargo.lock b/Cargo.lock index c892fd686a6..468693b0a7a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -63,6 +63,183 @@ dependencies = [ "zeroize", ] +[[package]] +name = "agave-geyser-plugin-interface" +version = "1.17.30" +dependencies = [ + "log", + "solana-sdk", + "solana-transaction-status", + "thiserror", +] + +[[package]] +name = "agave-install" +version = "1.17.30" +dependencies = [ + "atty", + "bincode", + "bzip2", + "chrono", + "clap 2.33.3", + "console", + "crossbeam-channel", + "ctrlc", + "dirs-next", + "indicatif", + "lazy_static", + "nix 0.26.4", + "reqwest", + "scopeguard", + "semver 1.0.20", + "serde", + "serde_yaml 0.8.26", + "serde_yaml 0.9.25", + "solana-clap-utils", + "solana-config-program", + "solana-logger", + "solana-rpc-client", + "solana-sdk", + "solana-version", + "tar", + "tempfile", + "url 2.4.1", + "winapi 0.3.9", + "winreg", +] + +[[package]] +name = "agave-ledger-tool" +version = "1.17.30" +dependencies = [ + "assert_cmd", + "bs58", + "bytecount", + "chrono", + "clap 2.33.3", + "crossbeam-channel", + "csv", + "dashmap 4.0.2", + "futures 0.3.28", + "histogram", + "itertools", + "log", + "num_cpus", + "regex", + "serde", + "serde_json", + "signal-hook", + "solana-account-decoder", + "solana-accounts-db", + "solana-bpf-loader-program", + "solana-clap-utils", + "solana-cli-output", + "solana-core", + "solana-cost-model", + "solana-entry", + "solana-geyser-plugin-manager", + "solana-gossip", + "solana-ledger", + "solana-logger", + "solana-measure", + "solana-program-runtime", + "solana-rpc", + "solana-runtime", + "solana-sdk", + "solana-stake-program", + "solana-storage-bigtable", + "solana-streamer", + "solana-transaction-status", + "solana-version", + "solana-vote-program", + "solana_rbpf", + "tikv-jemallocator", + "tokio", +] + +[[package]] +name = "agave-validator" +version = "1.17.30" +dependencies = [ + "agave-geyser-plugin-interface", + "chrono", + "clap 2.33.3", + "console", + "core_affinity", + "crossbeam-channel", + "fd-lock", + "indicatif", + "itertools", + "jsonrpc-core", + "jsonrpc-core-client", + "jsonrpc-derive", + "jsonrpc-ipc-server", + "jsonrpc-server-utils", + "lazy_static", + "libc", + "libloading", + "log", + "num_cpus", + "rand 0.8.5", + "rayon", + "serde", + "serde_json", + "serde_yaml 0.9.25", + "signal-hook", + "solana-account-decoder", + "solana-accounts-db", + "solana-clap-utils", + "solana-cli-config", + "solana-core", + "solana-download-utils", + "solana-entry", + "solana-faucet", + "solana-genesis-utils", + "solana-geyser-plugin-manager", + "solana-gossip", + "solana-ledger", + "solana-logger", + "solana-metrics", + "solana-net-utils", + "solana-perf", + "solana-poh", + "solana-rpc", + "solana-rpc-client", + "solana-rpc-client-api", + "solana-runtime", + "solana-sdk", + "solana-send-transaction-service", + "solana-storage-bigtable", + "solana-streamer", + "solana-test-validator", + "solana-tpu-client", + "solana-version", + "solana-vote-program", + "spl-token-2022", + "symlink", + "thiserror", + "tikv-jemallocator", +] + +[[package]] +name = "agave-watchtower" +version = "1.17.30" +dependencies = [ + "clap 2.33.3", + "humantime", + "log", + "solana-clap-utils", + "solana-cli-config", + "solana-cli-output", + "solana-logger", + "solana-metrics", + "solana-notifier", + "solana-rpc-client", + "solana-rpc-client-api", + "solana-sdk", + "solana-version", +] + [[package]] name = "ahash" version = "0.7.6" @@ -76,14 +253,15 @@ dependencies = [ [[package]] name = "ahash" -version = "0.8.3" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" +checksum = "cd7d5a2cecb58716e47d67d5703a249964b14c7be1ec3cad3affc295b2d1c35d" dependencies = [ "cfg-if 1.0.0", "getrandom 0.2.10", "once_cell", "version_check", + "zerocopy", ] [[package]] @@ -430,13 +608,13 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.73" +version = "0.1.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0" +checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -590,7 +768,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -709,7 +887,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4114279215a005bc675e386011e594e1d9b800918cea18fcadadcce864a2046b" dependencies = [ "borsh-derive 0.10.3", - "hashbrown 0.13.2", + "hashbrown 0.12.3", ] [[package]] @@ -864,9 +1042,9 @@ dependencies = [ [[package]] name = "bytecount" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c676a478f63e9fa2dd5368a42f28bba0d6c560b775f38583c8bbaa7fcd67c9c" +checksum = "ad152d03a2c813c80bb94fedbf3a3f02b28f793e39e7c214c8a0bcc196343de7" [[package]] name = "bytemuck" @@ -890,9 +1068,9 @@ dependencies = [ [[package]] name = "byteorder" -version = "1.4.3" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" @@ -963,7 +1141,7 @@ checksum = "eee4243f1f26fc7a42710e7439c149e2b10b05472f88090acce52632f231a73a" dependencies = [ "camino", "cargo-platform", - "semver 1.0.19", + "semver 1.0.20", "serde", "serde_json", "thiserror", @@ -1228,18 +1406,18 @@ checksum = "e4c78c047431fee22c1a7bb92e00ad095a02a983affe4d8a72e2a2c62c1b94f3" [[package]] name = "const_format" -version = "0.2.31" +version = "0.2.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c990efc7a285731f9a4378d81aff2f0e85a2c8781a05ef0f8baa8dac54d0ff48" +checksum = "e3a214c7af3d04997541b18d432afaff4c455e79e2029079647e72fc2bd27673" dependencies = [ "const_format_proc_macros", ] [[package]] name = "const_format_proc_macros" -version = "0.2.31" +version = "0.2.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e026b6ce194a874cb9cf32cd5772d1ef9767cc8fcb5765948d74f37a9d8b2bf6" +checksum = "c7f6ff08fd20f4f299298a28e2dfa8a8ba1036e6cd2460ac1de7b425d76f2500" dependencies = [ "proc-macro2", "quote", @@ -1423,9 +1601,9 @@ dependencies = [ [[package]] name = "csv" -version = "1.2.2" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "626ae34994d3d8d668f4269922248239db4ae42d538b14c398b74a52208e8086" +checksum = "ac574ff4d437a7b5ad237ef331c17ccca63c46479e5b5453eb8e10bb99a759fe" dependencies = [ "csv-core", "itoa", @@ -1435,9 +1613,9 @@ dependencies = [ [[package]] name = "csv-core" -version = "0.1.10" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90" +checksum = "5efa2b3d7902f4b634a20cae3c9c4e6209dc4779feb6863329607560143efa70" dependencies = [ "memchr", ] @@ -1496,7 +1674,7 @@ dependencies = [ "proc-macro2", "quote", "strsim 0.10.0", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -1507,7 +1685,7 @@ checksum = "29a358ff9f12ec09c3e61fef9b5a9902623a695a46a917b07f269bff1445611a" dependencies = [ "darling_core", "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -1523,13 +1701,15 @@ dependencies = [ [[package]] name = "dashmap" -version = "5.2.0" +version = "5.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c8858831f7781322e539ea39e72449c46b059638250c14344fec8d0aa6e539c" +checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" dependencies = [ "cfg-if 1.0.0", - "num_cpus", - "parking_lot 0.12.1", + "hashbrown 0.14.1", + "lock_api", + "once_cell", + "parking_lot_core 0.9.8", ] [[package]] @@ -1699,7 +1879,7 @@ checksum = "a6cbae11b3de8fce2a456e8ea3dada226b35fe791f0dc1d360c0941f0bb681f3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -1746,7 +1926,7 @@ dependencies = [ "derivation-path", "ed25519-dalek", "hmac 0.12.1", - "sha2 0.10.7", + "sha2 0.10.8", ] [[package]] @@ -1799,7 +1979,7 @@ checksum = "eecf8589574ce9b895052fa12d69af7a233f99e6107f5cb8dd1044f2a17bfdcb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -1946,9 +2126,9 @@ checksum = "398ea4fabe40b9b0d885340a2a991a44c8a645624075ad966d21f88688e2b69e" [[package]] name = "flate2" -version = "1.0.27" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6c98ee8095e9d1dcbf2fcc6d95acccb90d1c81db1e44725c6a984b1dbdfb010" +checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" dependencies = [ "crc32fast", "miniz_oxide", @@ -2074,7 +2254,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -2134,7 +2314,7 @@ dependencies = [ [[package]] name = "gen-headers" -version = "1.17.0" +version = "1.17.30" dependencies = [ "log", "regex", @@ -2142,7 +2322,7 @@ dependencies = [ [[package]] name = "gen-syscall-list" -version = "1.17.0" +version = "1.17.30" dependencies = [ "regex", ] @@ -2260,9 +2440,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.18" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17f8a914c2987b688368b5138aa05321db91f4090cf26118185672ad588bce21" +checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" dependencies = [ "bytes", "fnv", @@ -2270,7 +2450,7 @@ dependencies = [ "futures-sink", "futures-util", "http", - "indexmap 1.9.3", + "indexmap 2.0.2", "slab", "tokio", "tokio-util 0.7.1", @@ -2316,14 +2496,14 @@ version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" dependencies = [ - "ahash 0.8.3", + "ahash 0.8.5", ] [[package]] name = "hashbrown" -version = "0.14.0" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" +checksum = "7dfda62a12f55daeae5015f81b0baea145391cb4520f86c248fc615d72640d12" [[package]] name = "headers" @@ -2626,12 +2806,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.0.1" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad227c3af19d4914570ad36d30409928b75967c298feb9ea1969db3a610bb14e" +checksum = "8adf3ddd720272c6ea8bf59463c04e0f93d0bbf7c5439b691bca2987e0270897" dependencies = [ "equivalent", - "hashbrown 0.14.0", + "hashbrown 0.14.1", "rayon", ] @@ -2868,9 +3048,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.148" +version = "0.2.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b" +checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" [[package]] name = "libloading" @@ -2964,12 +3144,13 @@ dependencies = [ [[package]] name = "light-poseidon" -version = "0.1.1" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "949bdd22e4ed93481d45e9a6badb34b99132bcad0c8a8d4f05c42f7dcc7b90bc" +checksum = "3c9a85a9752c549ceb7578064b4ed891179d20acd85f27318573b64d2d7ee7ee" dependencies = [ "ark-bn254", "ark-ff", + "num-bigint 0.4.4", "thiserror", ] @@ -2987,10 +3168,11 @@ checksum = "09fc20d2ca12cb9f044c93e3bd6d32d523e6e2ec3db4f7b2939cd99026ecd3f0" [[package]] name = "lock_api" -version = "0.4.6" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88943dd7ef4a2e5a4bfa2753aaab3013e34ce2533d1996fb18ef591e315e2b3b" +checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" dependencies = [ + "autocfg", "scopeguard", ] @@ -3301,13 +3483,13 @@ dependencies = [ [[package]] name = "num-derive" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e6a0fd4f737c707bd9086cc16c925f294943eb62eb71499e9fd4cf71f8b9f4e" +checksum = "cfb77679af88f8b125209d354a202862602672222e7f2313fdd6dc349bad4712" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -3345,9 +3527,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" dependencies = [ "autocfg", "libm", @@ -3383,11 +3565,11 @@ dependencies = [ [[package]] name = "num_enum" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70bf6736f74634d299d00086f02986875b3c2d924781a6a2cb6c201e73da0ceb" +checksum = "683751d591e6d81200c39fb0d1032608b77724f34114db54f571ff1317b337c0" dependencies = [ - "num_enum_derive 0.7.0", + "num_enum_derive 0.7.1", ] [[package]] @@ -3411,19 +3593,19 @@ dependencies = [ "proc-macro-crate 1.1.0", "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] name = "num_enum_derive" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56ea360eafe1022f7cc56cd7b869ed57330fb2453d0c7831d99b74c65d2f5597" +checksum = "6c11e44798ad209ccdd91fc192f0526a369a01234f7373e1b141c96d7cee4f0e" dependencies = [ "proc-macro-crate 1.1.0", "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -3617,7 +3799,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ "lock_api", - "parking_lot_core 0.9.1", + "parking_lot_core 0.9.8", ] [[package]] @@ -3636,15 +3818,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.1" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28141e0cc4143da2443301914478dc976a61ffdb3f043058310c70df2fed8954" +checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" dependencies = [ "cfg-if 1.0.0", "libc", - "redox_syscall 0.2.10", + "redox_syscall 0.3.5", "smallvec", - "windows-sys 0.32.0", + "windows-targets 0.48.0", ] [[package]] @@ -3792,9 +3974,9 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.2.7" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d31d11c69a6b52a174b42bdc0c30e5e11670f90788b2c471c31c1d17d449443" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" [[package]] name = "pin-utils" @@ -3930,7 +4112,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ceca8aaf45b5c46ec7ed39fff75f57290368c1846d33d24a122ca81416ab058" dependencies = [ "proc-macro2", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -3978,28 +4160,28 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.67" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328" +checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" dependencies = [ "unicode-ident", ] [[package]] name = "proptest" -version = "1.2.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e35c06b98bf36aba164cc17cb25f7e232f5c4aeea73baa14b8a9f0d92dbfa65" +checksum = "7c003ac8c77cb07bb74f5f198bce836a689bcd5a42574612bf14d17bfd08c20e" dependencies = [ "bit-set", - "bitflags 1.3.2", - "byteorder", + "bit-vec", + "bitflags 2.3.3", "lazy_static", "num-traits", "rand 0.8.5", "rand_chacha 0.3.1", "rand_xorshift", - "regex-syntax 0.6.29", + "regex-syntax 0.7.5", "rusty-fork", "tempfile", "unarray", @@ -4061,7 +4243,7 @@ dependencies = [ [[package]] name = "proto" -version = "1.17.0" +version = "1.17.30" dependencies = [ "protobuf-src", "tonic-build", @@ -4093,7 +4275,7 @@ checksum = "9e2e25ee72f5b24d773cae88422baddefff7714f97aab68d96fe2b6fc4a28fb2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -4145,7 +4327,7 @@ checksum = "6df19e284d93757a9fb91d63672f7741b129246a669db09d1c0063071debc0c0" dependencies = [ "bytes", "libc", - "socket2 0.5.4", + "socket2 0.5.5", "tracing", "windows-sys 0.48.0", ] @@ -4284,9 +4466,9 @@ checksum = "655b020bbf5c89791160a30f0d4706d8ec7aa5718d6a198f6df19c400e4f4470" [[package]] name = "rayon" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b" +checksum = "9c27db03db7734835b3f53954b534c91069375ce6ccaa2e065441e07d9b6cdb1" dependencies = [ "either", "rayon-core", @@ -4294,19 +4476,17 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d" +checksum = "5ce3fb6ad83f861aac485e76e1985cd109d9a3713802152be56c3b1f0e0658ed" dependencies = [ - "crossbeam-channel", "crossbeam-deque", "crossbeam-utils", - "num_cpus", ] [[package]] name = "rbpf-cli" -version = "1.17.0" +version = "1.17.30" [[package]] name = "rcgen" @@ -4374,14 +4554,14 @@ dependencies = [ [[package]] name = "regex" -version = "1.9.6" +version = "1.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebee201405406dbf528b8b672104ae6d6d63e6d118cb10e4d51abbc7b58044ff" +checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" dependencies = [ "aho-corasick 1.0.1", "memchr", - "regex-automata 0.3.9", - "regex-syntax 0.7.5", + "regex-automata 0.4.3", + "regex-syntax 0.8.2", ] [[package]] @@ -4392,32 +4572,32 @@ checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" [[package]] name = "regex-automata" -version = "0.3.9" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59b23e92ee4318893fa3fe3e6fb365258efbfe6ac6ab30f090cdcbb7aa37efa9" +checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" dependencies = [ "aho-corasick 1.0.1", "memchr", - "regex-syntax 0.7.5", + "regex-syntax 0.8.2", ] [[package]] name = "regex-syntax" -version = "0.6.29" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" +checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" [[package]] name = "regex-syntax" -version = "0.7.5" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" [[package]] name = "reqwest" -version = "0.11.20" +version = "0.11.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e9ad3fe7488d7e34558a2033d45a0c90b72d97b4f80705666fea71472e2e6a1" +checksum = "046cd98826c46c2ac8ddecae268eb5c2e58628688a5fc7a2643704a73faba95b" dependencies = [ "async-compression", "base64 0.21.4", @@ -4444,6 +4624,7 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", + "system-configuration", "tokio", "tokio-native-tls", "tokio-rustls", @@ -4539,7 +4720,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" dependencies = [ - "semver 1.0.19", + "semver 1.0.20", ] [[package]] @@ -4729,9 +4910,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.19" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad977052201c6de01a8ef2aa3378c4bd23217a056337d1d6da40468d267a4fb0" +checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" dependencies = [ "serde", ] @@ -4747,9 +4928,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.188" +version = "1.0.189" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" +checksum = "8e422a44e74ad4001bdc8eede9a4570ab52f71190e9c076d14369f38b9200537" dependencies = [ "serde_derive", ] @@ -4765,13 +4946,13 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.188" +version = "1.0.189" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" +checksum = "1e48d1f918009ce3145511378cf68d613e3b3d9137d67272562080d68a2b32d5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -4816,7 +4997,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -4837,7 +5018,7 @@ version = "0.9.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a49e178e4452f45cb61d0cd8cebc1b0fafd3e41929e996cef79aa3aca91f574" dependencies = [ - "indexmap 2.0.1", + "indexmap 2.0.2", "itoa", "ryu", "serde", @@ -4850,7 +5031,7 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e56dd856803e253c8f298af3f4d7eb0ae5e23a737252cd90bb4f3b435033b2d" dependencies = [ - "dashmap 5.2.0", + "dashmap 5.5.3", "futures 0.3.28", "lazy_static", "log", @@ -4866,7 +5047,7 @@ checksum = "91d129178576168c589c9ec973feedf7d3126c01ac2bf08795109aa35b69fb8f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -4931,9 +5112,9 @@ dependencies = [ [[package]] name = "sha2" -version = "0.10.7" +version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" dependencies = [ "cfg-if 1.0.0", "cpufeatures", @@ -4979,9 +5160,9 @@ checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" [[package]] name = "shlex" -version = "1.1.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook" @@ -5038,9 +5219,9 @@ checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5" [[package]] name = "smallvec" -version = "1.7.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ecab6c735a6bb4139c0caafd0cc3635748bbb3acf4550e8138122099251f309" +checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a" [[package]] name = "smpl_jwt" @@ -5070,9 +5251,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.5.4" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4031e820eb552adee9295814c0ced9e5cf38ddf1e8b7d566d6de8e2538ea989e" +checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" dependencies = [ "libc", "windows-sys 0.48.0", @@ -5095,7 +5276,7 @@ dependencies = [ [[package]] name = "solana-account-decoder" -version = "1.17.0" +version = "1.17.30" dependencies = [ "Inflector", "assert_matches", @@ -5112,6 +5293,7 @@ dependencies = [ "spl-pod", "spl-token", "spl-token-2022", + "spl-token-group-interface", "spl-token-metadata-interface", "thiserror", "zstd", @@ -5119,7 +5301,7 @@ dependencies = [ [[package]] name = "solana-accounts-bench" -version = "1.17.0" +version = "1.17.30" dependencies = [ "clap 2.33.3", "log", @@ -5133,7 +5315,7 @@ dependencies = [ [[package]] name = "solana-accounts-cluster-bench" -version = "1.17.0" +version = "1.17.30" dependencies = [ "clap 2.33.3", "log", @@ -5163,7 +5345,7 @@ dependencies = [ [[package]] name = "solana-accounts-db" -version = "1.17.0" +version = "1.17.30" dependencies = [ "arrayref", "assert_matches", @@ -5227,7 +5409,7 @@ dependencies = [ [[package]] name = "solana-address-lookup-table-program" -version = "1.17.0" +version = "1.17.30" dependencies = [ "bincode", "bytemuck", @@ -5246,7 +5428,7 @@ dependencies = [ [[package]] name = "solana-address-lookup-table-program-tests" -version = "1.17.0" +version = "1.17.30" dependencies = [ "assert_matches", "bincode", @@ -5257,7 +5439,7 @@ dependencies = [ [[package]] name = "solana-banking-bench" -version = "1.17.0" +version = "1.17.30" dependencies = [ "clap 3.2.23", "crossbeam-channel", @@ -5281,7 +5463,7 @@ dependencies = [ [[package]] name = "solana-banks-client" -version = "1.17.0" +version = "1.17.30" dependencies = [ "borsh 0.10.3", "futures 0.3.28", @@ -5298,7 +5480,7 @@ dependencies = [ [[package]] name = "solana-banks-interface" -version = "1.17.0" +version = "1.17.30" dependencies = [ "serde", "solana-sdk", @@ -5307,7 +5489,7 @@ dependencies = [ [[package]] name = "solana-banks-server" -version = "1.17.0" +version = "1.17.30" dependencies = [ "bincode", "crossbeam-channel", @@ -5325,7 +5507,7 @@ dependencies = [ [[package]] name = "solana-bench-streamer" -version = "1.17.0" +version = "1.17.30" dependencies = [ "clap 3.2.23", "crossbeam-channel", @@ -5336,7 +5518,7 @@ dependencies = [ [[package]] name = "solana-bench-tps" -version = "1.17.0" +version = "1.17.30" dependencies = [ "clap 2.33.3", "crossbeam-channel", @@ -5377,7 +5559,7 @@ dependencies = [ [[package]] name = "solana-bloom" -version = "1.17.0" +version = "1.17.30" dependencies = [ "bv", "fnv", @@ -5394,7 +5576,7 @@ dependencies = [ [[package]] name = "solana-bpf-loader-program" -version = "1.17.0" +version = "1.17.30" dependencies = [ "assert_matches", "bincode", @@ -5409,12 +5591,13 @@ dependencies = [ "solana-sdk", "solana-zk-token-sdk", "solana_rbpf", + "test-case", "thiserror", ] [[package]] name = "solana-bpf-loader-program-tests" -version = "1.17.0" +version = "1.17.30" dependencies = [ "assert_matches", "bincode", @@ -5425,7 +5608,7 @@ dependencies = [ [[package]] name = "solana-bucket-map" -version = "1.17.0" +version = "1.17.30" dependencies = [ "bv", "bytemuck", @@ -5444,7 +5627,7 @@ dependencies = [ [[package]] name = "solana-cargo-build-bpf" -version = "1.17.0" +version = "1.17.30" dependencies = [ "log", "solana-logger", @@ -5452,7 +5635,7 @@ dependencies = [ [[package]] name = "solana-cargo-build-sbf" -version = "1.17.0" +version = "1.17.30" dependencies = [ "assert_cmd", "bzip2", @@ -5463,7 +5646,7 @@ dependencies = [ "predicates", "regex", "reqwest", - "semver 1.0.19", + "semver 1.0.20", "serial_test", "solana-download-utils", "solana-logger", @@ -5473,11 +5656,11 @@ dependencies = [ [[package]] name = "solana-cargo-test-bpf" -version = "1.17.0" +version = "1.17.30" [[package]] name = "solana-cargo-test-sbf" -version = "1.17.0" +version = "1.17.30" dependencies = [ "cargo_metadata", "clap 3.2.23", @@ -5488,7 +5671,7 @@ dependencies = [ [[package]] name = "solana-clap-utils" -version = "1.17.0" +version = "1.17.30" dependencies = [ "assert_matches", "chrono", @@ -5505,7 +5688,7 @@ dependencies = [ [[package]] name = "solana-clap-v3-utils" -version = "1.17.0" +version = "1.17.30" dependencies = [ "assert_matches", "chrono", @@ -5523,7 +5706,7 @@ dependencies = [ [[package]] name = "solana-cli" -version = "1.17.0" +version = "1.17.30" dependencies = [ "assert_matches", "bincode", @@ -5540,7 +5723,7 @@ dependencies = [ "num-traits", "pretty-hex", "reqwest", - "semver 1.0.19", + "semver 1.0.20", "serde", "serde_derive", "serde_json", @@ -5576,7 +5759,7 @@ dependencies = [ [[package]] name = "solana-cli-config" -version = "1.17.0" +version = "1.17.30" dependencies = [ "anyhow", "dirs-next", @@ -5591,7 +5774,7 @@ dependencies = [ [[package]] name = "solana-cli-output" -version = "1.17.0" +version = "1.17.30" dependencies = [ "Inflector", "base64 0.21.4", @@ -5602,7 +5785,7 @@ dependencies = [ "humantime", "indicatif", "pretty-hex", - "semver 1.0.19", + "semver 1.0.20", "serde", "serde_json", "solana-account-decoder", @@ -5617,7 +5800,7 @@ dependencies = [ [[package]] name = "solana-client" -version = "1.17.0" +version = "1.17.30" dependencies = [ "async-trait", "bincode", @@ -5625,7 +5808,7 @@ dependencies = [ "dashmap 4.0.2", "futures 0.3.28", "futures-util", - "indexmap 2.0.1", + "indexmap 2.0.2", "indicatif", "log", "quinn", @@ -5649,7 +5832,7 @@ dependencies = [ [[package]] name = "solana-client-test" -version = "1.17.0" +version = "1.17.30" dependencies = [ "futures-util", "rand 0.8.5", @@ -5679,7 +5862,7 @@ dependencies = [ [[package]] name = "solana-compute-budget-program" -version = "1.17.0" +version = "1.17.30" dependencies = [ "solana-program-runtime", "solana-sdk", @@ -5687,7 +5870,7 @@ dependencies = [ [[package]] name = "solana-config-program" -version = "1.17.0" +version = "1.17.30" dependencies = [ "bincode", "chrono", @@ -5700,13 +5883,13 @@ dependencies = [ [[package]] name = "solana-connection-cache" -version = "1.17.0" +version = "1.17.30" dependencies = [ "async-trait", "bincode", "crossbeam-channel", "futures-util", - "indexmap 2.0.1", + "indexmap 2.0.2", "indicatif", "log", "rand 0.8.5", @@ -5724,7 +5907,7 @@ dependencies = [ [[package]] name = "solana-core" -version = "1.17.0" +version = "1.17.30" dependencies = [ "assert_matches", "base64 0.21.4", @@ -5807,7 +5990,7 @@ dependencies = [ [[package]] name = "solana-cost-model" -version = "1.17.0" +version = "1.17.30" dependencies = [ "lazy_static", "log", @@ -5832,7 +6015,7 @@ dependencies = [ [[package]] name = "solana-dos" -version = "1.17.0" +version = "1.17.30" dependencies = [ "bincode", "clap 3.2.23", @@ -5862,7 +6045,7 @@ dependencies = [ [[package]] name = "solana-download-utils" -version = "1.17.0" +version = "1.17.30" dependencies = [ "console", "indicatif", @@ -5874,7 +6057,7 @@ dependencies = [ [[package]] name = "solana-ed25519-program-tests" -version = "1.17.0" +version = "1.17.30" dependencies = [ "assert_matches", "ed25519-dalek", @@ -5885,7 +6068,7 @@ dependencies = [ [[package]] name = "solana-entry" -version = "1.17.0" +version = "1.17.30" dependencies = [ "assert_matches", "bincode", @@ -5907,7 +6090,7 @@ dependencies = [ [[package]] name = "solana-faucet" -version = "1.17.0" +version = "1.17.30" dependencies = [ "bincode", "byteorder", @@ -5929,9 +6112,10 @@ dependencies = [ [[package]] name = "solana-frozen-abi" -version = "1.17.0" +version = "1.17.30" dependencies = [ - "ahash 0.8.3", + "ahash 0.8.5", + "bitflags 2.3.3", "blake3", "block-buffer 0.10.4", "bs58", @@ -5949,7 +6133,7 @@ dependencies = [ "serde_bytes", "serde_derive", "serde_json", - "sha2 0.10.7", + "sha2 0.10.8", "solana-frozen-abi-macro", "solana-logger", "subtle", @@ -5958,17 +6142,17 @@ dependencies = [ [[package]] name = "solana-frozen-abi-macro" -version = "1.17.0" +version = "1.17.30" dependencies = [ "proc-macro2", "quote", "rustc_version 0.4.0", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] name = "solana-genesis" -version = "1.17.0" +version = "1.17.30" dependencies = [ "base64 0.21.4", "bincode", @@ -5993,7 +6177,7 @@ dependencies = [ [[package]] name = "solana-genesis-utils" -version = "1.17.0" +version = "1.17.30" dependencies = [ "log", "solana-accounts-db", @@ -6002,20 +6186,11 @@ dependencies = [ "solana-sdk", ] -[[package]] -name = "solana-geyser-plugin-interface" -version = "1.17.0" -dependencies = [ - "log", - "solana-sdk", - "solana-transaction-status", - "thiserror", -] - [[package]] name = "solana-geyser-plugin-manager" -version = "1.17.0" +version = "1.17.30" dependencies = [ + "agave-geyser-plugin-interface", "bs58", "crossbeam-channel", "json5", @@ -6026,7 +6201,6 @@ dependencies = [ "serde_json", "solana-accounts-db", "solana-entry", - "solana-geyser-plugin-interface", "solana-ledger", "solana-measure", "solana-metrics", @@ -6039,7 +6213,7 @@ dependencies = [ [[package]] name = "solana-gossip" -version = "1.17.0" +version = "1.17.30" dependencies = [ "assert_matches", "bincode", @@ -6047,7 +6221,7 @@ dependencies = [ "clap 2.33.3", "crossbeam-channel", "flate2", - "indexmap 2.0.1", + "indexmap 2.0.2", "itertools", "log", "lru", @@ -6088,44 +6262,9 @@ dependencies = [ "thiserror", ] -[[package]] -name = "solana-install" -version = "1.17.0" -dependencies = [ - "atty", - "bincode", - "bzip2", - "chrono", - "clap 2.33.3", - "console", - "crossbeam-channel", - "ctrlc", - "dirs-next", - "indicatif", - "lazy_static", - "nix 0.26.4", - "reqwest", - "scopeguard", - "semver 1.0.19", - "serde", - "serde_yaml 0.8.26", - "serde_yaml 0.9.25", - "solana-clap-utils", - "solana-config-program", - "solana-logger", - "solana-rpc-client", - "solana-sdk", - "solana-version", - "tar", - "tempfile", - "url 2.4.1", - "winapi 0.3.9", - "winreg", -] - [[package]] name = "solana-keygen" -version = "1.17.0" +version = "1.17.30" dependencies = [ "bs58", "clap 3.2.23", @@ -6142,7 +6281,7 @@ dependencies = [ [[package]] name = "solana-ledger" -version = "1.17.0" +version = "1.17.30" dependencies = [ "assert_matches", "bincode", @@ -6172,7 +6311,7 @@ dependencies = [ "scopeguard", "serde", "serde_bytes", - "sha2 0.10.7", + "sha2 0.10.8", "solana-account-decoder", "solana-accounts-db", "solana-bpf-loader-program", @@ -6208,58 +6347,9 @@ dependencies = [ "trees", ] -[[package]] -name = "solana-ledger-tool" -version = "1.17.0" -dependencies = [ - "assert_cmd", - "bs58", - "bytecount", - "chrono", - "clap 2.33.3", - "crossbeam-channel", - "csv", - "dashmap 4.0.2", - "futures 0.3.28", - "histogram", - "itertools", - "log", - "num_cpus", - "regex", - "serde", - "serde_json", - "signal-hook", - "solana-account-decoder", - "solana-accounts-db", - "solana-bpf-loader-program", - "solana-clap-utils", - "solana-cli-output", - "solana-core", - "solana-cost-model", - "solana-entry", - "solana-geyser-plugin-manager", - "solana-gossip", - "solana-ledger", - "solana-logger", - "solana-measure", - "solana-program-runtime", - "solana-rpc", - "solana-runtime", - "solana-sdk", - "solana-stake-program", - "solana-storage-bigtable", - "solana-streamer", - "solana-transaction-status", - "solana-version", - "solana-vote-program", - "solana_rbpf", - "tikv-jemallocator", - "tokio", -] - [[package]] name = "solana-loader-v4-program" -version = "1.17.0" +version = "1.17.30" dependencies = [ "bincode", "log", @@ -6271,7 +6361,7 @@ dependencies = [ [[package]] name = "solana-local-cluster" -version = "1.17.0" +version = "1.17.30" dependencies = [ "assert_matches", "crossbeam-channel", @@ -6310,7 +6400,7 @@ dependencies = [ [[package]] name = "solana-log-analyzer" -version = "1.17.0" +version = "1.17.30" dependencies = [ "byte-unit", "clap 3.2.23", @@ -6322,7 +6412,7 @@ dependencies = [ [[package]] name = "solana-logger" -version = "1.17.0" +version = "1.17.30" dependencies = [ "env_logger", "lazy_static", @@ -6331,7 +6421,7 @@ dependencies = [ [[package]] name = "solana-measure" -version = "1.17.0" +version = "1.17.30" dependencies = [ "log", "solana-sdk", @@ -6339,11 +6429,11 @@ dependencies = [ [[package]] name = "solana-memory-management" -version = "1.17.0" +version = "1.17.30" [[package]] name = "solana-merkle-root-bench" -version = "1.17.0" +version = "1.17.30" dependencies = [ "clap 2.33.3", "log", @@ -6356,7 +6446,7 @@ dependencies = [ [[package]] name = "solana-merkle-tree" -version = "1.17.0" +version = "1.17.30" dependencies = [ "fast-math", "hex", @@ -6365,7 +6455,7 @@ dependencies = [ [[package]] name = "solana-metrics" -version = "1.17.0" +version = "1.17.30" dependencies = [ "crossbeam-channel", "env_logger", @@ -6381,7 +6471,7 @@ dependencies = [ [[package]] name = "solana-net-shaper" -version = "1.17.0" +version = "1.17.30" dependencies = [ "clap 3.2.23", "rand 0.8.5", @@ -6392,7 +6482,7 @@ dependencies = [ [[package]] name = "solana-net-utils" -version = "1.17.0" +version = "1.17.30" dependencies = [ "bincode", "clap 3.2.23", @@ -6402,7 +6492,7 @@ dependencies = [ "rand 0.8.5", "serde", "serde_derive", - "socket2 0.5.4", + "socket2 0.5.5", "solana-logger", "solana-sdk", "solana-version", @@ -6412,7 +6502,7 @@ dependencies = [ [[package]] name = "solana-notifier" -version = "1.17.0" +version = "1.17.30" dependencies = [ "log", "reqwest", @@ -6422,9 +6512,9 @@ dependencies = [ [[package]] name = "solana-perf" -version = "1.17.0" +version = "1.17.30" dependencies = [ - "ahash 0.8.3", + "ahash 0.8.5", "assert_matches", "bincode", "bv", @@ -6439,7 +6529,10 @@ dependencies = [ "rand 0.8.5", "rand_chacha 0.3.1", "rayon", + "rustc_version 0.4.0", "serde", + "solana-frozen-abi", + "solana-frozen-abi-macro", "solana-logger", "solana-metrics", "solana-rayon-threadlimit", @@ -6450,7 +6543,7 @@ dependencies = [ [[package]] name = "solana-poh" -version = "1.17.0" +version = "1.17.30" dependencies = [ "assert_matches", "bincode", @@ -6471,7 +6564,7 @@ dependencies = [ [[package]] name = "solana-poh-bench" -version = "1.17.0" +version = "1.17.30" dependencies = [ "clap 3.2.23", "log", @@ -6486,7 +6579,7 @@ dependencies = [ [[package]] name = "solana-program" -version = "1.17.0" +version = "1.17.30" dependencies = [ "anyhow", "ark-bn254", @@ -6528,7 +6621,7 @@ dependencies = [ "serde_bytes", "serde_derive", "serde_json", - "sha2 0.10.7", + "sha2 0.10.8", "sha3 0.10.4", "solana-frozen-abi", "solana-frozen-abi-macro", @@ -6543,7 +6636,7 @@ dependencies = [ [[package]] name = "solana-program-runtime" -version = "1.17.0" +version = "1.17.30" dependencies = [ "assert_matches", "base64 0.21.4", @@ -6572,7 +6665,7 @@ dependencies = [ [[package]] name = "solana-program-test" -version = "1.17.0" +version = "1.17.30" dependencies = [ "assert_matches", "async-trait", @@ -6593,6 +6686,7 @@ dependencies = [ "solana-sdk", "solana-stake-program", "solana-vote-program", + "solana_rbpf", "test-case", "thiserror", "tokio", @@ -6600,14 +6694,14 @@ dependencies = [ [[package]] name = "solana-pubsub-client" -version = "1.17.0" +version = "1.17.30" dependencies = [ "anyhow", "crossbeam-channel", "futures-util", "log", "reqwest", - "semver 1.0.19", + "semver 1.0.20", "serde", "serde_derive", "serde_json", @@ -6624,7 +6718,7 @@ dependencies = [ [[package]] name = "solana-quic-client" -version = "1.17.0" +version = "1.17.30" dependencies = [ "async-mutex", "async-trait", @@ -6652,7 +6746,7 @@ dependencies = [ [[package]] name = "solana-rayon-threadlimit" -version = "1.17.0" +version = "1.17.30" dependencies = [ "lazy_static", "num_cpus", @@ -6660,7 +6754,7 @@ dependencies = [ [[package]] name = "solana-remote-wallet" -version = "1.17.0" +version = "1.17.30" dependencies = [ "assert_matches", "console", @@ -6671,7 +6765,7 @@ dependencies = [ "num-traits", "parking_lot 0.12.1", "qstring", - "semver 1.0.19", + "semver 1.0.20", "solana-sdk", "thiserror", "uriparse", @@ -6679,7 +6773,7 @@ dependencies = [ [[package]] name = "solana-rpc" -version = "1.17.0" +version = "1.17.30" dependencies = [ "base64 0.21.4", "bincode", @@ -6738,7 +6832,7 @@ dependencies = [ [[package]] name = "solana-rpc-client" -version = "1.17.0" +version = "1.17.30" dependencies = [ "assert_matches", "async-trait", @@ -6752,7 +6846,7 @@ dependencies = [ "jsonrpc-http-server", "log", "reqwest", - "semver 1.0.19", + "semver 1.0.20", "serde", "serde_derive", "serde_json", @@ -6767,13 +6861,13 @@ dependencies = [ [[package]] name = "solana-rpc-client-api" -version = "1.17.0" +version = "1.17.30" dependencies = [ "base64 0.21.4", "bs58", "jsonrpc-core", "reqwest", - "semver 1.0.19", + "semver 1.0.20", "serde", "serde_derive", "serde_json", @@ -6787,7 +6881,7 @@ dependencies = [ [[package]] name = "solana-rpc-client-nonce-utils" -version = "1.17.0" +version = "1.17.30" dependencies = [ "anyhow", "clap 2.33.3", @@ -6804,7 +6898,7 @@ dependencies = [ [[package]] name = "solana-rpc-test" -version = "1.17.0" +version = "1.17.30" dependencies = [ "bincode", "bs58", @@ -6831,7 +6925,7 @@ dependencies = [ [[package]] name = "solana-runtime" -version = "1.17.0" +version = "1.17.30" dependencies = [ "arrayref", "assert_matches", @@ -6914,7 +7008,7 @@ dependencies = [ [[package]] name = "solana-sdk" -version = "1.17.0" +version = "1.17.30" dependencies = [ "anyhow", "assert_matches", @@ -6955,7 +7049,7 @@ dependencies = [ "serde_derive", "serde_json", "serde_with", - "sha2 0.10.7", + "sha2 0.10.8", "sha3 0.10.4", "solana-frozen-abi", "solana-frozen-abi-macro", @@ -6972,18 +7066,24 @@ dependencies = [ [[package]] name = "solana-sdk-macro" -version = "1.17.0" +version = "1.17.30" dependencies = [ "bs58", "proc-macro2", "quote", "rustversion", - "syn 2.0.37", + "syn 2.0.38", ] +[[package]] +name = "solana-security-txt" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "468aa43b7edb1f9b7b7b686d5c3aeb6630dc1708e86e31343499dd5c4d775183" + [[package]] name = "solana-send-transaction-service" -version = "1.17.0" +version = "1.17.30" dependencies = [ "crossbeam-channel", "log", @@ -6998,7 +7098,7 @@ dependencies = [ [[package]] name = "solana-stake-accounts" -version = "1.17.0" +version = "1.17.30" dependencies = [ "clap 2.33.3", "solana-clap-utils", @@ -7014,7 +7114,7 @@ dependencies = [ [[package]] name = "solana-stake-program" -version = "1.17.0" +version = "1.17.30" dependencies = [ "assert_matches", "bincode", @@ -7031,7 +7131,7 @@ dependencies = [ [[package]] name = "solana-storage-bigtable" -version = "1.17.0" +version = "1.17.30" dependencies = [ "backoff", "bincode", @@ -7063,7 +7163,7 @@ dependencies = [ [[package]] name = "solana-storage-proto" -version = "1.17.0" +version = "1.17.30" dependencies = [ "bincode", "bs58", @@ -7079,7 +7179,7 @@ dependencies = [ [[package]] name = "solana-store-tool" -version = "1.17.0" +version = "1.17.30" dependencies = [ "clap 2.33.3", "log", @@ -7091,15 +7191,16 @@ dependencies = [ [[package]] name = "solana-streamer" -version = "1.17.0" +version = "1.17.30" dependencies = [ "assert_matches", "async-channel", "bytes", "crossbeam-channel", + "futures 0.3.28", "futures-util", "histogram", - "indexmap 2.0.1", + "indexmap 2.0.2", "itertools", "libc", "log", @@ -7112,6 +7213,7 @@ dependencies = [ "rand 0.8.5", "rcgen", "rustls", + "socket2 0.5.5", "solana-logger", "solana-metrics", "solana-perf", @@ -7123,7 +7225,7 @@ dependencies = [ [[package]] name = "solana-system-program" -version = "1.17.0" +version = "1.17.30" dependencies = [ "assert_matches", "bincode", @@ -7137,7 +7239,7 @@ dependencies = [ [[package]] name = "solana-test-validator" -version = "1.17.0" +version = "1.17.30" dependencies = [ "base64 0.21.4", "bincode", @@ -7167,7 +7269,7 @@ dependencies = [ [[package]] name = "solana-thin-client" -version = "1.17.0" +version = "1.17.30" dependencies = [ "bincode", "log", @@ -7181,7 +7283,7 @@ dependencies = [ [[package]] name = "solana-tokens" -version = "1.17.0" +version = "1.17.30" dependencies = [ "assert_matches", "bincode", @@ -7190,7 +7292,7 @@ dependencies = [ "console", "csv", "ctrlc", - "indexmap 2.0.1", + "indexmap 2.0.2", "indicatif", "pickledb", "serde", @@ -7214,12 +7316,12 @@ dependencies = [ [[package]] name = "solana-tpu-client" -version = "1.17.0" +version = "1.17.30" dependencies = [ "async-trait", "bincode", "futures-util", - "indexmap 2.0.1", + "indexmap 2.0.2", "indicatif", "log", "rayon", @@ -7236,7 +7338,7 @@ dependencies = [ [[package]] name = "solana-transaction-dos" -version = "1.17.0" +version = "1.17.30" dependencies = [ "bincode", "clap 2.33.3", @@ -7263,7 +7365,7 @@ dependencies = [ [[package]] name = "solana-transaction-status" -version = "1.17.0" +version = "1.17.30" dependencies = [ "Inflector", "base64 0.21.4", @@ -7286,7 +7388,7 @@ dependencies = [ [[package]] name = "solana-turbine" -version = "1.17.0" +version = "1.17.30" dependencies = [ "assert_matches", "bincode", @@ -7323,7 +7425,7 @@ dependencies = [ [[package]] name = "solana-udp-client" -version = "1.17.0" +version = "1.17.30" dependencies = [ "async-trait", "solana-connection-cache", @@ -7336,83 +7438,19 @@ dependencies = [ [[package]] name = "solana-upload-perf" -version = "1.17.0" +version = "1.17.30" dependencies = [ "serde_json", "solana-metrics", ] -[[package]] -name = "solana-validator" -version = "1.17.0" -dependencies = [ - "chrono", - "clap 2.33.3", - "console", - "core_affinity", - "crossbeam-channel", - "fd-lock", - "indicatif", - "itertools", - "jsonrpc-core", - "jsonrpc-core-client", - "jsonrpc-derive", - "jsonrpc-ipc-server", - "jsonrpc-server-utils", - "lazy_static", - "libc", - "libloading", - "log", - "num_cpus", - "rand 0.8.5", - "rayon", - "serde", - "serde_json", - "serde_yaml 0.9.25", - "signal-hook", - "solana-account-decoder", - "solana-accounts-db", - "solana-clap-utils", - "solana-cli-config", - "solana-core", - "solana-download-utils", - "solana-entry", - "solana-faucet", - "solana-genesis-utils", - "solana-geyser-plugin-interface", - "solana-geyser-plugin-manager", - "solana-gossip", - "solana-ledger", - "solana-logger", - "solana-metrics", - "solana-net-utils", - "solana-perf", - "solana-poh", - "solana-rpc", - "solana-rpc-client", - "solana-rpc-client-api", - "solana-runtime", - "solana-sdk", - "solana-send-transaction-service", - "solana-storage-bigtable", - "solana-streamer", - "solana-test-validator", - "solana-tpu-client", - "solana-version", - "solana-vote-program", - "spl-token-2022", - "symlink", - "thiserror", - "tikv-jemallocator", -] - [[package]] name = "solana-version" -version = "1.17.0" +version = "1.17.30" dependencies = [ "log", "rustc_version 0.4.0", - "semver 1.0.19", + "semver 1.0.20", "serde", "serde_derive", "solana-frozen-abi", @@ -7422,7 +7460,7 @@ dependencies = [ [[package]] name = "solana-vote" -version = "1.17.0" +version = "1.17.30" dependencies = [ "bincode", "crossbeam-channel", @@ -7441,7 +7479,7 @@ dependencies = [ [[package]] name = "solana-vote-program" -version = "1.17.0" +version = "1.17.30" dependencies = [ "assert_matches", "bincode", @@ -7462,28 +7500,9 @@ dependencies = [ "thiserror", ] -[[package]] -name = "solana-watchtower" -version = "1.17.0" -dependencies = [ - "clap 2.33.3", - "humantime", - "log", - "solana-clap-utils", - "solana-cli-config", - "solana-cli-output", - "solana-logger", - "solana-metrics", - "solana-notifier", - "solana-rpc-client", - "solana-rpc-client-api", - "solana-sdk", - "solana-version", -] - [[package]] name = "solana-zk-keygen" -version = "1.17.0" +version = "1.17.30" dependencies = [ "bs58", "clap 3.2.23", @@ -7502,7 +7521,7 @@ dependencies = [ [[package]] name = "solana-zk-token-proof-program" -version = "1.17.0" +version = "1.17.30" dependencies = [ "bytemuck", "criterion", @@ -7516,7 +7535,7 @@ dependencies = [ [[package]] name = "solana-zk-token-proof-program-tests" -version = "1.17.0" +version = "1.17.30" dependencies = [ "bytemuck", "curve25519-dalek", @@ -7528,7 +7547,7 @@ dependencies = [ [[package]] name = "solana-zk-token-sdk" -version = "1.17.0" +version = "1.17.30" dependencies = [ "aes-gcm-siv", "base64 0.21.4", @@ -7556,9 +7575,9 @@ dependencies = [ [[package]] name = "solana_rbpf" -version = "0.7.2" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "103318aa365ff7caa8cf534f2246b5eb7e5b34668736d52b1266b143f7a21196" +checksum = "3d457cc2ba742c120492a64b7fa60e22c575e891f6b55039f4d736568fb112a3" dependencies = [ "byteorder", "combine", @@ -7598,13 +7617,13 @@ dependencies = [ [[package]] name = "spl-associated-token-account" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "385e31c29981488f2820b2022d8e731aae3b02e6e18e2fd854e4c9a94dc44fc3" +checksum = "992d9c64c2564cc8f63a4b508bf3ebcdf2254b0429b13cd1d31adb6162432a5f" dependencies = [ "assert_matches", "borsh 0.10.3", - "num-derive 0.4.0", + "num-derive 0.4.1", "num-traits", "solana-program", "spl-token", @@ -7631,7 +7650,7 @@ checksum = "fadbefec4f3c678215ca72bd71862697bb06b41fd77c0088902dd3203354387b" dependencies = [ "quote", "spl-discriminator-syn", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -7642,8 +7661,8 @@ checksum = "0e5f2044ca42c8938d54d1255ce599c79a1ffd86b677dfab695caa20f9ffc3f2" dependencies = [ "proc-macro2", "quote", - "sha2 0.10.7", - "syn 2.0.37", + "sha2 0.10.8", + "syn 2.0.38", "thiserror", ] @@ -7685,7 +7704,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "249e0318493b6bcf27ae9902600566c689b7dfba9f1bdff5893e92253374e78c" dependencies = [ - "num-derive 0.4.0", + "num-derive 0.4.1", "num-traits", "solana-program", "spl-program-error-derive", @@ -7700,15 +7719,15 @@ checksum = "ab5269c8e868da17b6552ef35a51355a017bd8e0eae269c201fef830d35fa52c" dependencies = [ "proc-macro2", "quote", - "sha2 0.10.7", - "syn 2.0.37", + "sha2 0.10.8", + "syn 2.0.38", ] [[package]] name = "spl-tlv-account-resolution" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "062e148d3eab7b165582757453632ffeef490c02c86a48bfdb4988f63eefb3b9" +checksum = "3f7020347c07892c08560d230fbb8a980316c9e198e22b198b7b9d951ff96047" dependencies = [ "bytemuck", "solana-program", @@ -7735,26 +7754,41 @@ dependencies = [ [[package]] name = "spl-token-2022" -version = "0.9.0" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4abf34a65ba420584a0c35f3903f8d727d1f13ababbdc3f714c6b065a686e86" +checksum = "d697fac19fd74ff472dfcc13f0b442dd71403178ce1de7b5d16f83a33561c059" dependencies = [ "arrayref", "bytemuck", - "num-derive 0.4.0", + "num-derive 0.4.1", "num-traits", - "num_enum 0.7.0", + "num_enum 0.7.1", "solana-program", + "solana-security-txt", "solana-zk-token-sdk", "spl-memo", "spl-pod", "spl-token", + "spl-token-group-interface", "spl-token-metadata-interface", "spl-transfer-hook-interface", "spl-type-length-value", "thiserror", ] +[[package]] +name = "spl-token-group-interface" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b889509d49fa74a4a033ca5dae6c2307e9e918122d97e58562f5c4ffa795c75d" +dependencies = [ + "bytemuck", + "solana-program", + "spl-discriminator", + "spl-pod", + "spl-program-error", +] + [[package]] name = "spl-token-metadata-interface" version = "0.2.0" @@ -7771,9 +7805,9 @@ dependencies = [ [[package]] name = "spl-transfer-hook-interface" -version = "0.3.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "051d31803f873cabe71aec3c1b849f35248beae5d19a347d93a5c9cccc5d5a9b" +checksum = "7aabdb7c471566f6ddcee724beb8618449ea24b399e58d464d6b5bc7db550259" dependencies = [ "arrayref", "bytemuck", @@ -7874,9 +7908,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.37" +version = "2.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7303ef2c05cd654186cb250d29049a24840ca25d2747c25c0381c8d9e2f582e8" +checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b" dependencies = [ "proc-macro2", "quote", @@ -7924,6 +7958,27 @@ dependencies = [ "walkdir", ] +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "systemstat" version = "0.2.3" @@ -8031,7 +8086,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -8043,7 +8098,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", "test-case-core", ] @@ -8064,22 +8119,22 @@ checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" [[package]] name = "thiserror" -version = "1.0.49" +version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1177e8c6d7ede7afde3585fd2513e611227efd6481bd78d2e82ba1ce16557ed4" +checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.49" +version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10712f02019e9288794769fba95cd6847df9874d49d871d062172f9dd41bc4cc" +checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -8183,8 +8238,7 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" version = "1.29.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "532826ff75199d5833b9d2c5fe410f29235e25704ee5f0ef599fb51c21f4a4da" +source = "git+https://github.com/solana-labs/solana-tokio.git?rev=7cf47705faacf7bf0e43e4131a5377b3291fce21#7cf47705faacf7bf0e43e4131a5377b3291fce21" dependencies = [ "autocfg", "backtrace", @@ -8213,12 +8267,11 @@ dependencies = [ [[package]] name = "tokio-macros" version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" +source = "git+https://github.com/solana-labs/solana-tokio.git?rev=7cf47705faacf7bf0e43e4131a5377b3291fce21#7cf47705faacf7bf0e43e4131a5377b3291fce21" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -8400,11 +8453,10 @@ checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" [[package]] name = "tracing" -version = "0.1.29" +version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "375a639232caf30edfc78e8d89b2d4c375515393e7af7e16f01cd96917fb2105" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ - "cfg-if 1.0.0", "log", "pin-project-lite", "tracing-attributes", @@ -8413,22 +8465,23 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.18" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4f480b8f81512e825f337ad51e94c1eb5d3bbdf2b363dcd01e2b19a9ffe3f8e" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.38", ] [[package]] name = "tracing-core" -version = "0.1.21" +version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f4ed65637b8390770814083d20756f87bfa2c21bf2f110babdc5438351746e4" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" dependencies = [ - "lazy_static", + "once_cell", + "valuable", ] [[package]] @@ -8622,6 +8675,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7cf7d77f457ef8dfa11e4cd5933c5ddb5dc52a94664071951219a97710f0a32b" +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + [[package]] name = "vcpkg" version = "0.2.15" @@ -8709,7 +8768,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", "wasm-bindgen-shared", ] @@ -8743,7 +8802,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -8833,19 +8892,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "windows-sys" -version = "0.32.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3df6e476185f92a12c072be4a189a0210dcdcf512a1891d6dff9edb874deadc6" -dependencies = [ - "windows_aarch64_msvc 0.32.0", - "windows_i686_gnu 0.32.0", - "windows_i686_msvc 0.32.0", - "windows_x86_64_gnu 0.32.0", - "windows_x86_64_msvc 0.32.0", -] - [[package]] name = "windows-sys" version = "0.45.0" @@ -8906,12 +8952,6 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" -[[package]] -name = "windows_aarch64_msvc" -version = "0.32.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8e92753b1c443191654ec532f14c199742964a061be25d77d7a96f09db20bf5" - [[package]] name = "windows_aarch64_msvc" version = "0.42.2" @@ -8924,12 +8964,6 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" -[[package]] -name = "windows_i686_gnu" -version = "0.32.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a711c68811799e017b6038e0922cb27a5e2f43a2ddb609fe0b6f3eeda9de615" - [[package]] name = "windows_i686_gnu" version = "0.42.2" @@ -8942,12 +8976,6 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" -[[package]] -name = "windows_i686_msvc" -version = "0.32.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "146c11bb1a02615db74680b32a68e2d61f553cc24c4eb5b4ca10311740e44172" - [[package]] name = "windows_i686_msvc" version = "0.42.2" @@ -8960,12 +8988,6 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" -[[package]] -name = "windows_x86_64_gnu" -version = "0.32.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c912b12f7454c6620635bbff3450962753834be2a594819bd5e945af18ec64bc" - [[package]] name = "windows_x86_64_gnu" version = "0.42.2" @@ -8990,12 +9012,6 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" -[[package]] -name = "windows_x86_64_msvc" -version = "0.32.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "504a2476202769977a040c6364301a3f65d0cc9e3fb08600b2bda150a0488316" - [[package]] name = "windows_x86_64_msvc" version = "0.42.2" @@ -9063,6 +9079,26 @@ dependencies = [ "time", ] +[[package]] +name = "zerocopy" +version = "0.7.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c4061bedbb353041c12f413700357bec76df2c7e2ca8e4df8bac24c6bf68e3d" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3c129550b3e6de3fd0ba67ba5c81818f9805e58b8d7fee80a3a59d2c9fc601a" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.38", +] + [[package]] name = "zeroize" version = "1.3.0" @@ -9080,7 +9116,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index a93092f2e89..cb571d72d2f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -123,7 +123,7 @@ exclude = [ resolver = "2" [workspace.package] -version = "1.17.0" +version = "1.17.30" authors = ["Solana Labs Maintainers "] repository = "https://github.com/solana-labs/solana" homepage = "https://solanalabs.com/" @@ -132,7 +132,7 @@ edition = "2021" [workspace.dependencies] aes-gcm-siv = "0.10.3" -ahash = "0.8.3" +ahash = "=0.8.5" anyhow = "1.0.75" ark-bn254 = "0.4.0" ark-ec = "0.4.0" @@ -233,7 +233,7 @@ lazy_static = "1.4.0" libc = "0.2.148" libloading = "0.7.4" libsecp256k1 = "0.6.0" -light-poseidon = "0.1.1" +light-poseidon = "0.2.0" log = "0.4.20" lru = "0.7.7" lz4 = "1.24.0" @@ -296,89 +296,90 @@ sha3 = "0.10.4" signal-hook = "0.3.17" siphasher = "0.3.11" smpl_jwt = "0.7.1" -socket2 = "0.5.4" +socket2 = { version= "0.5.4", features=["all"]} soketto = "0.7" -solana_rbpf = "=0.7.2" -solana-account-decoder = { path = "account-decoder", version = "=1.17.0" } -solana-accounts-db = { path = "accounts-db", version = "=1.17.0" } -solana-address-lookup-table-program = { path = "programs/address-lookup-table", version = "=1.17.0" } -solana-banks-client = { path = "banks-client", version = "=1.17.0" } -solana-banks-interface = { path = "banks-interface", version = "=1.17.0" } -solana-banks-server = { path = "banks-server", version = "=1.17.0" } -solana-bench-tps = { path = "bench-tps", version = "=1.17.0" } -solana-bloom = { path = "bloom", version = "=1.17.0" } -solana-bpf-loader-program = { path = "programs/bpf_loader", version = "=1.17.0" } -solana-bucket-map = { path = "bucket_map", version = "=1.17.0" } -solana-connection-cache = { path = "connection-cache", version = "=1.17.0", default-features = false } -solana-clap-utils = { path = "clap-utils", version = "=1.17.0" } -solana-clap-v3-utils = { path = "clap-v3-utils", version = "=1.17.0" } -solana-cli = { path = "cli", version = "=1.17.0" } -solana-cli-config = { path = "cli-config", version = "=1.17.0" } -solana-cli-output = { path = "cli-output", version = "=1.17.0" } -solana-client = { path = "client", version = "=1.17.0" } -solana-compute-budget-program = { path = "programs/compute-budget", version = "=1.17.0" } -solana-config-program = { path = "programs/config", version = "=1.17.0" } -solana-core = { path = "core", version = "=1.17.0" } -solana-cost-model = { path = "cost-model", version = "=1.17.0" } -solana-download-utils = { path = "download-utils", version = "=1.17.0" } -solana-entry = { path = "entry", version = "=1.17.0" } -solana-faucet = { path = "faucet", version = "=1.17.0" } -solana-frozen-abi = { path = "frozen-abi", version = "=1.17.0" } -solana-frozen-abi-macro = { path = "frozen-abi/macro", version = "=1.17.0" } -solana-genesis = { path = "genesis", version = "=1.17.0" } -solana-genesis-utils = { path = "genesis-utils", version = "=1.17.0" } -solana-geyser-plugin-interface = { path = "geyser-plugin-interface", version = "=1.17.0" } -solana-geyser-plugin-manager = { path = "geyser-plugin-manager", version = "=1.17.0" } -solana-gossip = { path = "gossip", version = "=1.17.0" } -solana-loader-v4-program = { path = "programs/loader-v4", version = "=1.17.0" } -solana-ledger = { path = "ledger", version = "=1.17.0" } -solana-local-cluster = { path = "local-cluster", version = "=1.17.0" } -solana-logger = { path = "logger", version = "=1.17.0" } -solana-measure = { path = "measure", version = "=1.17.0" } -solana-merkle-tree = { path = "merkle-tree", version = "=1.17.0" } -solana-metrics = { path = "metrics", version = "=1.17.0" } -solana-net-utils = { path = "net-utils", version = "=1.17.0" } -solana-notifier = { path = "notifier", version = "=1.17.0" } -solana-perf = { path = "perf", version = "=1.17.0" } -solana-poh = { path = "poh", version = "=1.17.0" } -solana-program = { path = "sdk/program", version = "=1.17.0" } -solana-program-runtime = { path = "program-runtime", version = "=1.17.0" } -solana-program-test = { path = "program-test", version = "=1.17.0" } -solana-pubsub-client = { path = "pubsub-client", version = "=1.17.0" } -solana-quic-client = { path = "quic-client", version = "=1.17.0" } -solana-rayon-threadlimit = { path = "rayon-threadlimit", version = "=1.17.0" } -solana-remote-wallet = { path = "remote-wallet", version = "=1.17.0", default-features = false } -solana-rpc = { path = "rpc", version = "=1.17.0" } -solana-rpc-client = { path = "rpc-client", version = "=1.17.0", default-features = false } -solana-rpc-client-api = { path = "rpc-client-api", version = "=1.17.0" } -solana-rpc-client-nonce-utils = { path = "rpc-client-nonce-utils", version = "=1.17.0" } -solana-runtime = { path = "runtime", version = "=1.17.0" } -solana-sdk = { path = "sdk", version = "=1.17.0" } -solana-sdk-macro = { path = "sdk/macro", version = "=1.17.0" } -solana-send-transaction-service = { path = "send-transaction-service", version = "=1.17.0" } -solana-stake-program = { path = "programs/stake", version = "=1.17.0" } -solana-storage-bigtable = { path = "storage-bigtable", version = "=1.17.0" } -solana-storage-proto = { path = "storage-proto", version = "=1.17.0" } -solana-streamer = { path = "streamer", version = "=1.17.0" } -solana-system-program = { path = "programs/system", version = "=1.17.0" } -solana-test-validator = { path = "test-validator", version = "=1.17.0" } -solana-thin-client = { path = "thin-client", version = "=1.17.0" } -solana-tpu-client = { path = "tpu-client", version = "=1.17.0", default-features = false } -solana-transaction-status = { path = "transaction-status", version = "=1.17.0" } -solana-turbine = { path = "turbine", version = "=1.17.0" } -solana-udp-client = { path = "udp-client", version = "=1.17.0" } -solana-version = { path = "version", version = "=1.17.0" } -solana-vote = { path = "vote", version = "=1.17.0" } -solana-vote-program = { path = "programs/vote", version = "=1.17.0" } -solana-zk-keygen = { path = "zk-keygen", version = "=1.17.0" } -solana-zk-token-proof-program = { path = "programs/zk-token-proof", version = "=1.17.0" } -solana-zk-token-sdk = { path = "zk-token-sdk", version = "=1.17.0" } -spl-associated-token-account = "=2.2.0" +solana_rbpf = "=0.8.0" +solana-account-decoder = { path = "account-decoder", version = "=1.17.30" } +solana-accounts-db = { path = "accounts-db", version = "=1.17.30" } +solana-address-lookup-table-program = { path = "programs/address-lookup-table", version = "=1.17.30" } +solana-banks-client = { path = "banks-client", version = "=1.17.30" } +solana-banks-interface = { path = "banks-interface", version = "=1.17.30" } +solana-banks-server = { path = "banks-server", version = "=1.17.30" } +solana-bench-tps = { path = "bench-tps", version = "=1.17.30" } +solana-bloom = { path = "bloom", version = "=1.17.30" } +solana-bpf-loader-program = { path = "programs/bpf_loader", version = "=1.17.30" } +solana-bucket-map = { path = "bucket_map", version = "=1.17.30" } +solana-connection-cache = { path = "connection-cache", version = "=1.17.30", default-features = false } +solana-clap-utils = { path = "clap-utils", version = "=1.17.30" } +solana-clap-v3-utils = { path = "clap-v3-utils", version = "=1.17.30" } +solana-cli = { path = "cli", version = "=1.17.30" } +solana-cli-config = { path = "cli-config", version = "=1.17.30" } +solana-cli-output = { path = "cli-output", version = "=1.17.30" } +solana-client = { path = "client", version = "=1.17.30" } +solana-compute-budget-program = { path = "programs/compute-budget", version = "=1.17.30" } +solana-config-program = { path = "programs/config", version = "=1.17.30" } +solana-core = { path = "core", version = "=1.17.30" } +solana-cost-model = { path = "cost-model", version = "=1.17.30" } +solana-download-utils = { path = "download-utils", version = "=1.17.30" } +solana-entry = { path = "entry", version = "=1.17.30" } +solana-faucet = { path = "faucet", version = "=1.17.30" } +solana-frozen-abi = { path = "frozen-abi", version = "=1.17.30" } +solana-frozen-abi-macro = { path = "frozen-abi/macro", version = "=1.17.30" } +solana-genesis = { path = "genesis", version = "=1.17.30" } +solana-genesis-utils = { path = "genesis-utils", version = "=1.17.30" } +agave-geyser-plugin-interface = { path = "geyser-plugin-interface", version = "=1.17.30" } +solana-geyser-plugin-manager = { path = "geyser-plugin-manager", version = "=1.17.30" } +solana-gossip = { path = "gossip", version = "=1.17.30" } +solana-loader-v4-program = { path = "programs/loader-v4", version = "=1.17.30" } +solana-ledger = { path = "ledger", version = "=1.17.30" } +solana-local-cluster = { path = "local-cluster", version = "=1.17.30" } +solana-logger = { path = "logger", version = "=1.17.30" } +solana-measure = { path = "measure", version = "=1.17.30" } +solana-merkle-tree = { path = "merkle-tree", version = "=1.17.30" } +solana-metrics = { path = "metrics", version = "=1.17.30" } +solana-net-utils = { path = "net-utils", version = "=1.17.30" } +solana-notifier = { path = "notifier", version = "=1.17.30" } +solana-perf = { path = "perf", version = "=1.17.30" } +solana-poh = { path = "poh", version = "=1.17.30" } +solana-program = { path = "sdk/program", version = "=1.17.30" } +solana-program-runtime = { path = "program-runtime", version = "=1.17.30" } +solana-program-test = { path = "program-test", version = "=1.17.30" } +solana-pubsub-client = { path = "pubsub-client", version = "=1.17.30" } +solana-quic-client = { path = "quic-client", version = "=1.17.30" } +solana-rayon-threadlimit = { path = "rayon-threadlimit", version = "=1.17.30" } +solana-remote-wallet = { path = "remote-wallet", version = "=1.17.30", default-features = false } +solana-rpc = { path = "rpc", version = "=1.17.30" } +solana-rpc-client = { path = "rpc-client", version = "=1.17.30", default-features = false } +solana-rpc-client-api = { path = "rpc-client-api", version = "=1.17.30" } +solana-rpc-client-nonce-utils = { path = "rpc-client-nonce-utils", version = "=1.17.30" } +solana-runtime = { path = "runtime", version = "=1.17.30" } +solana-sdk = { path = "sdk", version = "=1.17.30" } +solana-sdk-macro = { path = "sdk/macro", version = "=1.17.30" } +solana-send-transaction-service = { path = "send-transaction-service", version = "=1.17.30" } +solana-stake-program = { path = "programs/stake", version = "=1.17.30" } +solana-storage-bigtable = { path = "storage-bigtable", version = "=1.17.30" } +solana-storage-proto = { path = "storage-proto", version = "=1.17.30" } +solana-streamer = { path = "streamer", version = "=1.17.30" } +solana-system-program = { path = "programs/system", version = "=1.17.30" } +solana-test-validator = { path = "test-validator", version = "=1.17.30" } +solana-thin-client = { path = "thin-client", version = "=1.17.30" } +solana-tpu-client = { path = "tpu-client", version = "=1.17.30", default-features = false } +solana-transaction-status = { path = "transaction-status", version = "=1.17.30" } +solana-turbine = { path = "turbine", version = "=1.17.30" } +solana-udp-client = { path = "udp-client", version = "=1.17.30" } +solana-version = { path = "version", version = "=1.17.30" } +solana-vote = { path = "vote", version = "=1.17.30" } +solana-vote-program = { path = "programs/vote", version = "=1.17.30" } +solana-zk-keygen = { path = "zk-keygen", version = "=1.17.30" } +solana-zk-token-proof-program = { path = "programs/zk-token-proof", version = "=1.17.30" } +solana-zk-token-sdk = { path = "zk-token-sdk", version = "=1.17.30" } +spl-associated-token-account = "=2.3.0" spl-instruction-padding = "0.1" spl-memo = "=4.0.0" spl-pod = "=0.1.0" spl-token = "=4.0.0" -spl-token-2022 = "=0.9.0" +spl-token-2022 = "=1.0.0" +spl-token-group-interface = "=0.1.0" spl-token-metadata-interface = "=0.2.0" static_assertions = "1.1.0" stream-cancel = "0.8.1" @@ -396,6 +397,7 @@ tempfile = "3.8.0" test-case = "3.2.1" thiserror = "1.0.49" tiny-bip39 = "0.8.2" +# Update solana-tokio patch below when updating this version tokio = "1.29.1" tokio-serde = "0.8" tokio-stream = "0.1.14" @@ -445,3 +447,21 @@ crossbeam-epoch = { git = "https://github.com/solana-labs/crossbeam", rev = "fd2 # overrides in sync. solana-program = { path = "sdk/program" } solana-zk-token-sdk = { path = "zk-token-sdk" } +# +# Solana RPC nodes experience stalls when running with `tokio` containing this +# commit: +# https://github.com/tokio-rs/tokio/commit/4eed411519783ef6f58cbf74f886f91142b5cfa6 +# +# Tokio maintainers believe performance degradation is due to application bugs: +# https://github.com/tokio-rs/tokio/issues/4873#issuecomment-1198277677 +# +# This may indeed be true of the code in this monorepo, but we haven't yet +# identified the bug or a way to fix. As a stopgap, this patches `tokio` to the +# tagged version specified above with commit `4eed411` reverted. +# +# Comparison: +# https://github.com/tokio-rs/tokio/compare/tokio-1.29.1...solana-labs:solana-tokio:tokio-1.29.1-revert-4eed411 +# +[patch.crates-io.tokio] +git = "https://github.com/solana-labs/solana-tokio.git" +rev = "7cf47705faacf7bf0e43e4131a5377b3291fce21" diff --git a/README.md b/README.md index 4fccacf2ba0..e5e50d5c482 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

- Solana + Solana

@@ -111,35 +111,3 @@ problem is solved by this code?" On the other hand, if a test does fail and you better way to solve the same problem, a Pull Request with your solution would most certainly be welcome! Likewise, if rewriting a test can better communicate what code it's protecting, please send us that patch! - -# Disclaimer - -All claims, content, designs, algorithms, estimates, roadmaps, -specifications, and performance measurements described in this project -are done with the Solana Labs, Inc. (“SL”) good faith efforts. It is up to -the reader to check and validate their accuracy and truthfulness. -Furthermore, nothing in this project constitutes a solicitation for -investment. - -Any content produced by SL or developer resources that SL provides are -for educational and inspirational purposes only. SL does not encourage, -induce or sanction the deployment, integration or use of any such -applications (including the code comprising the Solana blockchain -protocol) in violation of applicable laws or regulations and hereby -prohibits any such deployment, integration or use. This includes the use of -any such applications by the reader (a) in violation of export control -or sanctions laws of the United States or any other applicable -jurisdiction, (b) if the reader is located in or ordinarily resident in -a country or territory subject to comprehensive sanctions administered -by the U.S. Office of Foreign Assets Control (OFAC), or (c) if the -reader is or is working on behalf of a Specially Designated National -(SDN) or a person subject to similar blocking or denied party -prohibitions. - -The reader should be aware that U.S. export control and sanctions laws prohibit -U.S. persons (and other persons that are subject to such laws) from transacting -with persons in certain countries and territories or that are on the SDN list. -Accordingly, there is a risk to individuals that other persons using any of the -code contained in this repo, or a derivation thereof, may be sanctioned persons -and that transactions with such persons would be a violation of U.S. export -controls and sanctions law. diff --git a/RELEASE.md b/RELEASE.md index abb79a32a8c..c5aa5d540b1 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -98,6 +98,7 @@ Alternatively use the Github UI. ### Miscellaneous Clean up +1. Pin the spl-token-cli version in the newly promoted stable branch by setting `splTokenCliVersion` in scripts/spl-token-cli-version.sh to the latest release that depends on the stable branch (usually this will be the latest spl-token-cli release). 1. Update [mergify.yml](https://github.com/solana-labs/solana/blob/master/.mergify.yml) to add backport actions for the new branch and remove actions for the obsolete branch. 1. Adjust the [Github backport labels](https://github.com/solana-labs/solana/labels) to add the new branch label and remove the label for the obsolete branch. 1. Announce on Discord #development that the release branch exists so people know to use the new backport labels. diff --git a/account-decoder/Cargo.toml b/account-decoder/Cargo.toml index 3f883ddc23f..7aee8478b4f 100644 --- a/account-decoder/Cargo.toml +++ b/account-decoder/Cargo.toml @@ -23,6 +23,7 @@ solana-config-program = { workspace = true } solana-sdk = { workspace = true } spl-token = { workspace = true, features = ["no-entrypoint"] } spl-token-2022 = { workspace = true, features = ["no-entrypoint"] } +spl-token-group-interface = { workspace = true } spl-token-metadata-interface = { workspace = true } thiserror = { workspace = true } zstd = { workspace = true } diff --git a/account-decoder/src/parse_token_extension.rs b/account-decoder/src/parse_token_extension.rs index 39d26d83a20..a2fdef41b47 100644 --- a/account-decoder/src/parse_token_extension.rs +++ b/account-decoder/src/parse_token_extension.rs @@ -6,6 +6,7 @@ use { solana_program::pubkey::Pubkey, solana_zk_token_sdk::zk_token_elgamal::pod::ElGamalPubkey, }, + spl_token_group_interface::state::{TokenGroup, TokenGroupMember}, spl_token_metadata_interface::state::TokenMetadata, }; @@ -32,6 +33,10 @@ pub enum UiExtension { TransferHookAccount(UiTransferHookAccount), MetadataPointer(UiMetadataPointer), TokenMetadata(UiTokenMetadata), + GroupPointer(UiGroupPointer), + GroupMemberPointer(UiGroupMemberPointer), + TokenGroup(UiTokenGroup), + TokenGroupMember(UiTokenGroupMember), UnparseableExtension, } @@ -108,6 +113,22 @@ pub fn parse_extension( .get_extension::() .map(|&extension| UiExtension::TransferHookAccount(extension.into())) .unwrap_or(UiExtension::UnparseableExtension), + ExtensionType::GroupPointer => account + .get_extension::() + .map(|&extension| UiExtension::GroupPointer(extension.into())) + .unwrap_or(UiExtension::UnparseableExtension), + ExtensionType::GroupMemberPointer => account + .get_extension::() + .map(|&extension| UiExtension::GroupMemberPointer(extension.into())) + .unwrap_or(UiExtension::UnparseableExtension), + ExtensionType::TokenGroup => account + .get_extension::() + .map(|&extension| UiExtension::TokenGroup(extension.into())) + .unwrap_or(UiExtension::UnparseableExtension), + ExtensionType::TokenGroupMember => account + .get_extension::() + .map(|&extension| UiExtension::TokenGroupMember(extension.into())) + .unwrap_or(UiExtension::UnparseableExtension), } } @@ -481,3 +502,78 @@ impl From for UiTransferHookAccou } } } + +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct UiGroupPointer { + pub authority: Option, + pub group_address: Option, +} + +impl From for UiGroupPointer { + fn from(group_pointer: extension::group_pointer::GroupPointer) -> Self { + let authority: Option = group_pointer.authority.into(); + let group_address: Option = group_pointer.group_address.into(); + Self { + authority: authority.map(|pubkey| pubkey.to_string()), + group_address: group_address.map(|pubkey| pubkey.to_string()), + } + } +} + +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct UiGroupMemberPointer { + pub authority: Option, + pub member_address: Option, +} + +impl From for UiGroupMemberPointer { + fn from(member_pointer: extension::group_member_pointer::GroupMemberPointer) -> Self { + let authority: Option = member_pointer.authority.into(); + let member_address: Option = member_pointer.member_address.into(); + Self { + authority: authority.map(|pubkey| pubkey.to_string()), + member_address: member_address.map(|pubkey| pubkey.to_string()), + } + } +} + +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct UiTokenGroup { + pub update_authority: Option, + pub mint: String, + pub size: u32, + pub max_size: u32, +} + +impl From for UiTokenGroup { + fn from(token_group: TokenGroup) -> Self { + let update_authority: Option = token_group.update_authority.into(); + Self { + update_authority: update_authority.map(|pubkey| pubkey.to_string()), + mint: token_group.mint.to_string(), + size: token_group.size.into(), + max_size: token_group.max_size.into(), + } + } +} + +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct UiTokenGroupMember { + pub mint: String, + pub group: String, + pub member_number: u32, +} + +impl From for UiTokenGroupMember { + fn from(member: TokenGroupMember) -> Self { + Self { + mint: member.mint.to_string(), + group: member.group.to_string(), + member_number: member.member_number.into(), + } + } +} diff --git a/accounts-cluster-bench/src/main.rs b/accounts-cluster-bench/src/main.rs index 9e592131a16..2135e9ee19c 100644 --- a/accounts-cluster-bench/src/main.rs +++ b/accounts-cluster-bench/src/main.rs @@ -486,7 +486,7 @@ fn run_accounts_bench( } fn main() { - solana_logger::setup_with_default("solana=info"); + solana_logger::setup_with_default_filter(); let matches = App::new(crate_name!()) .about(crate_description!()) .version(solana_version::version!()) diff --git a/accounts-db/src/accounts_db.rs b/accounts-db/src/accounts_db.rs index dad1f152f36..9dece549903 100644 --- a/accounts-db/src/accounts_db.rs +++ b/accounts-db/src/accounts_db.rs @@ -4395,11 +4395,12 @@ impl AccountsDb { /// get a sorted list of slots older than an epoch /// squash those slots into ancient append vecs - fn shrink_ancient_slots(&self, oldest_non_ancient_slot: Slot) { + pub fn shrink_ancient_slots(&self, epoch_schedule: &EpochSchedule) { if self.ancient_append_vec_offset.is_none() { return; } + let oldest_non_ancient_slot = self.get_oldest_non_ancient_slot(epoch_schedule); let can_randomly_shrink = true; let sorted_slots = self.get_sorted_potential_ancient_slots(oldest_non_ancient_slot); if self.create_ancient_storage == CreateAncientStorage::Append { @@ -4744,10 +4745,6 @@ impl AccountsDb { pub fn shrink_candidate_slots(&self, epoch_schedule: &EpochSchedule) -> usize { let oldest_non_ancient_slot = self.get_oldest_non_ancient_slot(epoch_schedule); - if !self.shrink_candidate_slots.lock().unwrap().is_empty() { - // this can affect 'shrink_candidate_slots', so don't 'take' it until after this completes - self.shrink_ancient_slots(oldest_non_ancient_slot); - } let shrink_candidates_slots = std::mem::take(&mut *self.shrink_candidate_slots.lock().unwrap()); @@ -7514,6 +7511,11 @@ impl AccountsDb { self.accounts_hashes.lock().unwrap().get(&slot).cloned() } + /// Get all accounts hashes + pub fn get_accounts_hashes(&self) -> HashMap { + self.accounts_hashes.lock().unwrap().clone() + } + /// Set the incremental accounts hash for `slot` /// /// returns the previous incremental accounts hash for `slot` @@ -7550,6 +7552,13 @@ impl AccountsDb { .cloned() } + /// Get all incremental accounts hashes + pub fn get_incremental_accounts_hashes( + &self, + ) -> HashMap { + self.incremental_accounts_hashes.lock().unwrap().clone() + } + /// Purge accounts hashes that are older than `last_full_snapshot_slot` /// /// Should only be called by AccountsHashVerifier, since it consumes the accounts hashes and @@ -7640,6 +7649,7 @@ impl AccountsDb { config: &CalcAccountsHashConfig<'_>, kind: CalcAccountsHashKind, slot: Slot, + storages_start_slot: Slot, ) -> CacheHashData { let accounts_hash_cache_path = if !config.store_detailed_debug_info_on_failure { accounts_hash_cache_path @@ -7651,7 +7661,10 @@ impl AccountsDb { _ = std::fs::remove_dir_all(&failed_dir); failed_dir }; - CacheHashData::new(accounts_hash_cache_path, kind == CalcAccountsHashKind::Full) + CacheHashData::new( + accounts_hash_cache_path, + (kind == CalcAccountsHashKind::Incremental).then_some(storages_start_slot), + ) } // modeled after calculate_accounts_delta_hash @@ -7710,7 +7723,8 @@ impl AccountsDb { ) -> Result<(AccountsHashKind, u64), AccountsHashVerificationError> { let total_time = Measure::start(""); let _guard = self.active_stats.activate(ActiveStatItem::Hash); - stats.oldest_root = storages.range().start; + let storages_start_slot = storages.range().start; + stats.oldest_root = storages_start_slot; self.mark_old_slots_as_dirty(storages, config.epoch_schedule.slots_per_epoch, &mut stats); @@ -7726,7 +7740,8 @@ impl AccountsDb { accounts_hash_cache_path, config, kind, - slot + slot, + storages_start_slot, )); stats.cache_hash_data_us += cache_hash_data_us; @@ -9966,7 +9981,7 @@ pub mod tests { let temp_dir = TempDir::new().unwrap(); let accounts_hash_cache_path = temp_dir.path().to_path_buf(); self.scan_snapshot_stores_with_cache( - &CacheHashData::new(accounts_hash_cache_path, true), + &CacheHashData::new(accounts_hash_cache_path, None), storage, stats, bins, @@ -11086,7 +11101,7 @@ pub mod tests { }; let result = accounts_db.scan_account_storage_no_bank( - &CacheHashData::new(accounts_hash_cache_path, true), + &CacheHashData::new(accounts_hash_cache_path, None), &CalcAccountsHashConfig::default(), &get_storage_refs(&[storage]), test_scan, diff --git a/accounts-db/src/accounts_hash.rs b/accounts-db/src/accounts_hash.rs index 66a77c81883..ff832cb5612 100644 --- a/accounts-db/src/accounts_hash.rs +++ b/accounts-db/src/accounts_hash.rs @@ -26,6 +26,7 @@ use { atomic::{AtomicU64, AtomicUsize, Ordering}, Arc, }, + thread, time, }, tempfile::tempfile_in, }; @@ -87,21 +88,59 @@ impl AccountHashesFile { if self.writer.is_none() { // we have hashes to write but no file yet, so create a file that will auto-delete on drop - let mut data = tempfile_in(&self.dir_for_temp_cache_files).unwrap_or_else(|err| { - panic!( - "Unable to create file within {}: {err}", - self.dir_for_temp_cache_files.display() - ) - }); + let get_file = || -> Result<_, std::io::Error> { + let mut data = tempfile_in(&self.dir_for_temp_cache_files).unwrap_or_else(|err| { + panic!( + "Unable to create file within {}: {err}", + self.dir_for_temp_cache_files.display() + ) + }); + + // Theoretical performance optimization: write a zero to the end of + // the file so that we won't have to resize it later, which may be + // expensive. + assert!(self.capacity > 0); + data.seek(SeekFrom::Start((self.capacity - 1) as u64))?; + data.write_all(&[0])?; + data.rewind()?; + data.flush()?; + Ok(data) + }; + + // Retry 5 times to allocate the AccountHashesFile. The memory might be fragmented and + // causes memory allocation failure. Therefore, let's retry after failure. Hoping that the + // kernel has the chance to defrag the memory between the retries, and retries succeed. + let mut num_retries = 0; + let data = loop { + num_retries += 1; + + match get_file() { + Ok(data) => { + break data; + } + Err(err) => { + info!( + "Unable to create account hashes file within {}: {}, retry counter {}", + self.dir_for_temp_cache_files.display(), + err, + num_retries + ); - // Theoretical performance optimization: write a zero to the end of - // the file so that we won't have to resize it later, which may be - // expensive. - data.seek(SeekFrom::Start((self.capacity - 1) as u64)) - .unwrap(); - data.write_all(&[0]).unwrap(); - data.rewind().unwrap(); - data.flush().unwrap(); + if num_retries > 5 { + panic!( + "Unable to create account hashes file within {}: after {} retries", + self.dir_for_temp_cache_files.display(), + num_retries + ); + } + datapoint_info!( + "retry_account_hashes_file_allocation", + ("retry", num_retries, i64) + ); + thread::sleep(time::Duration::from_millis(num_retries * 100)); + } + } + }; //UNSAFE: Required to create a Mmap let map = unsafe { MmapMut::map_mut(&data) }; diff --git a/accounts-db/src/cache_hash_data.rs b/accounts-db/src/cache_hash_data.rs index 630d650b36f..ce5ff5f3aa6 100644 --- a/accounts-db/src/cache_hash_data.rs +++ b/accounts-db/src/cache_hash_data.rs @@ -6,6 +6,7 @@ use { bytemuck::{Pod, Zeroable}, memmap2::MmapMut, solana_measure::measure::Measure, + solana_sdk::clock::Slot, std::{ collections::HashSet, fs::{self, remove_file, File, OpenOptions}, @@ -192,24 +193,20 @@ impl CacheHashDataFile { pub(crate) struct CacheHashData { cache_dir: PathBuf, pre_existing_cache_files: Arc>>, - should_delete_old_cache_files_on_drop: bool, + /// Decides which old cache files to delete. See `delete_old_cache_files()` for more info. + storages_start_slot: Option, pub stats: Arc, } impl Drop for CacheHashData { fn drop(&mut self) { - if self.should_delete_old_cache_files_on_drop { - self.delete_old_cache_files(); - } + self.delete_old_cache_files(); self.stats.report(); } } impl CacheHashData { - pub(crate) fn new( - cache_dir: PathBuf, - should_delete_old_cache_files_on_drop: bool, - ) -> CacheHashData { + pub(crate) fn new(cache_dir: PathBuf, storages_start_slot: Option) -> CacheHashData { std::fs::create_dir_all(&cache_dir).unwrap_or_else(|err| { panic!("error creating cache dir {}: {err}", cache_dir.display()) }); @@ -217,15 +214,38 @@ impl CacheHashData { let result = CacheHashData { cache_dir, pre_existing_cache_files: Arc::new(Mutex::new(HashSet::default())), - should_delete_old_cache_files_on_drop, + storages_start_slot, stats: Arc::default(), }; result.get_cache_files(); result } + + /// delete all pre-existing files that will not be used fn delete_old_cache_files(&self) { - let old_cache_files = std::mem::take(&mut *self.pre_existing_cache_files.lock().unwrap()); + // all the renaming files in `pre_existing_cache_files` were *not* used for this + // accounts hash calculation + let mut old_cache_files = + std::mem::take(&mut *self.pre_existing_cache_files.lock().unwrap()); + + // If `storages_start_slot` is None, we're doing a full accounts hash calculation, and thus + // all unused cache files can be deleted. + // If `storages_start_slot` is Some, we're doing an incremental accounts hash calculation, + // and we only want to delete the unused cache files *that IAH considered*. + if let Some(storages_start_slot) = self.storages_start_slot { + old_cache_files.retain(|old_cache_file| { + let Some(parsed_filename) = parse_filename(old_cache_file) else { + // if parsing the cache filename fails, we *do* want to delete it + return true; + }; + + // if the old cache file is in the incremental accounts hash calculation range, + // then delete it + parsed_filename.slot_range_start >= storages_start_slot + }); + } + if !old_cache_files.is_empty() { self.stats .unused_cache_files @@ -356,6 +376,39 @@ impl CacheHashData { } } +/// The values of each part of a cache hash data filename +#[derive(Debug)] +pub struct ParsedFilename { + pub slot_range_start: Slot, + pub slot_range_end: Slot, + pub bin_range_start: u64, + pub bin_range_end: u64, + pub hash: u64, +} + +/// Parses a cache hash data filename into its parts +/// +/// Returns None if the filename is invalid +fn parse_filename(cache_filename: impl AsRef) -> Option { + let filename = cache_filename.as_ref().to_string_lossy().to_string(); + let parts: Vec<_> = filename.split('.').collect(); // The parts are separated by a `.` + if parts.len() != 5 { + return None; + } + let slot_range_start = parts.first()?.parse().ok()?; + let slot_range_end = parts.get(1)?.parse().ok()?; + let bin_range_start = parts.get(2)?.parse().ok()?; + let bin_range_end = parts.get(3)?.parse().ok()?; + let hash = u64::from_str_radix(parts.get(4)?, 16).ok()?; // the hash is in hex + Some(ParsedFilename { + slot_range_start, + slot_range_end, + bin_range_start, + bin_range_end, + hash, + }) +} + #[cfg(test)] mod tests { use {super::*, rand::Rng}; @@ -423,7 +476,7 @@ mod tests { data_this_pass.push(this_bin_data); } } - let cache = CacheHashData::new(cache_dir.clone(), true); + let cache = CacheHashData::new(cache_dir.clone(), None); let file_name = PathBuf::from("test"); cache.save(&file_name, &data_this_pass).unwrap(); cache.get_cache_files(); @@ -513,4 +566,39 @@ mod tests { ct, ) } + + #[test] + fn test_parse_filename() { + let good_filename = "123.456.0.65536.537d65697d9b2baa"; + let parsed_filename = parse_filename(good_filename).unwrap(); + assert_eq!(parsed_filename.slot_range_start, 123); + assert_eq!(parsed_filename.slot_range_end, 456); + assert_eq!(parsed_filename.bin_range_start, 0); + assert_eq!(parsed_filename.bin_range_end, 65536); + assert_eq!(parsed_filename.hash, 0x537d65697d9b2baa); + + let bad_filenames = [ + // bad separator + "123-456-0-65536.537d65697d9b2baa", + // bad values + "abc.456.0.65536.537d65697d9b2baa", + "123.xyz.0.65536.537d65697d9b2baa", + "123.456.?.65536.537d65697d9b2baa", + "123.456.0.@#$%^.537d65697d9b2baa", + "123.456.0.65536.base19shouldfail", + "123.456.0.65536.123456789012345678901234567890", + // missing values + "123.456.0.65536.", + "123.456.0.65536", + // extra junk + "123.456.0.65536.537d65697d9b2baa.42", + "123.456.0.65536.537d65697d9b2baa.", + "123.456.0.65536.537d65697d9b2baa/", + ".123.456.0.65536.537d65697d9b2baa", + "/123.456.0.65536.537d65697d9b2baa", + ]; + for bad_filename in bad_filenames { + assert!(parse_filename(bad_filename).is_none()); + } + } } diff --git a/accounts-db/src/verify_accounts_hash_in_background.rs b/accounts-db/src/verify_accounts_hash_in_background.rs index d4676cfe128..f03e4e0482c 100644 --- a/accounts-db/src/verify_accounts_hash_in_background.rs +++ b/accounts-db/src/verify_accounts_hash_in_background.rs @@ -67,7 +67,7 @@ impl VerifyAccountsHashInBackground { } let result = lock.take().unwrap().join().unwrap(); if !result { - panic!("initial hash verification failed: {result:?}"); + panic!("initial background accounts hash verification failed: {result}"); } // we never have to check again self.verification_complete(); @@ -139,7 +139,7 @@ pub mod tests { } #[test] - #[should_panic(expected = "initial hash verification failed")] + #[should_panic(expected = "initial background accounts hash verification failed")] fn test_panic() { let verify = Arc::new(VerifyAccountsHashInBackground::default()); start_thread_and_return(&verify, false, || {}); diff --git a/banking-bench/src/main.rs b/banking-bench/src/main.rs index bb5149f47c8..524928aee7c 100644 --- a/banking-bench/src/main.rs +++ b/banking-bench/src/main.rs @@ -334,7 +334,7 @@ fn main() { let (replay_vote_sender, _replay_vote_receiver) = unbounded(); let bank0 = Bank::new_for_benches(&genesis_config); - let bank_forks = Arc::new(RwLock::new(BankForks::new(bank0))); + let bank_forks = BankForks::new_rw_arc(bank0); let mut bank = bank_forks.read().unwrap().working_bank(); // set cost tracker limits to MAX so it will not filter out TXs diff --git a/banks-client/src/lib.rs b/banks-client/src/lib.rs index f0c2f17d8c0..61105575e2c 100644 --- a/banks-client/src/lib.rs +++ b/banks-client/src/lib.rs @@ -587,7 +587,7 @@ mod tests { let block_commitment_cache = Arc::new(RwLock::new( BlockCommitmentCache::new_for_tests_with_slots(slot, slot), )); - let bank_forks = Arc::new(RwLock::new(BankForks::new(bank))); + let bank_forks = BankForks::new_rw_arc(bank); let bob_pubkey = solana_sdk::pubkey::new_rand(); let mint_pubkey = genesis.mint_keypair.pubkey(); @@ -626,7 +626,7 @@ mod tests { let block_commitment_cache = Arc::new(RwLock::new( BlockCommitmentCache::new_for_tests_with_slots(slot, slot), )); - let bank_forks = Arc::new(RwLock::new(BankForks::new(bank))); + let bank_forks = BankForks::new_rw_arc(bank); let mint_pubkey = &genesis.mint_keypair.pubkey(); let bob_pubkey = solana_sdk::pubkey::new_rand(); diff --git a/bench-tps/src/main.rs b/bench-tps/src/main.rs index 519612bd423..e9cc2100635 100644 --- a/bench-tps/src/main.rs +++ b/bench-tps/src/main.rs @@ -221,7 +221,7 @@ fn create_client( } fn main() { - solana_logger::setup_with_default("solana=info"); + solana_logger::setup_with_default_filter(); solana_metrics::set_panic_hook("bench-tps", /*version:*/ None); let matches = cli::build_args(solana_version::version!()).get_matches(); diff --git a/bench-tps/src/send_batch.rs b/bench-tps/src/send_batch.rs index b6f1fe776ff..5ea916530ca 100644 --- a/bench-tps/src/send_batch.rs +++ b/bench-tps/src/send_batch.rs @@ -248,9 +248,13 @@ where fn send(&self, client: &Arc) { let mut send_txs = Measure::start("send_and_clone_txs"); let batch: Vec<_> = self.iter().map(|(_keypair, tx)| tx.clone()).collect(); - client.send_batch(batch).expect("transfer"); + let result = client.send_batch(batch); send_txs.stop(); - debug!("send {} {}", self.len(), send_txs); + if result.is_err() { + debug!("Failed to send batch {result:?}"); + } else { + debug!("send {} {}", self.len(), send_txs); + } } fn verify( diff --git a/ci/buildkite-pipeline-in-disk.sh b/ci/buildkite-pipeline-in-disk.sh index c9816e30a6d..7ca00d7f72e 100755 --- a/ci/buildkite-pipeline-in-disk.sh +++ b/ci/buildkite-pipeline-in-disk.sh @@ -196,33 +196,6 @@ EOF "Stable-SBF skipped as no relevant files were modified" fi - # Perf test suite - if affects \ - .rs$ \ - Cargo.lock$ \ - Cargo.toml$ \ - ^ci/rust-version.sh \ - ^ci/test-stable-perf.sh \ - ^ci/test-stable.sh \ - ^ci/test-local-cluster.sh \ - ^core/build.rs \ - ^fetch-perf-libs.sh \ - ^programs/ \ - ^sdk/ \ - ; then - cat >> "$output_file" <<"EOF" - - command: "ci/test-stable-perf.sh" - name: "stable-perf" - timeout_in_minutes: 35 - artifact_paths: "log-*.txt" - agents: - queue: "cuda" -EOF - else - annotate --style info \ - "Stable-perf skipped as no relevant files were modified" - fi - # Downstream backwards compatibility if affects \ .rs$ \ @@ -316,7 +289,7 @@ if [[ -n $BUILDKITE_TAG ]]; then start_pipeline "Tag pipeline for $BUILDKITE_TAG" annotate --style info --context release-tag \ - "https://github.com/solana-labs/solana/releases/$BUILDKITE_TAG" + "https://github.com/anza-xyz/agave/releases/$BUILDKITE_TAG" # Jump directly to the secondary build to publish release artifacts quickly trigger_secondary_step @@ -334,7 +307,7 @@ if [[ $BUILDKITE_BRANCH =~ ^pull ]]; then # Add helpful link back to the corresponding Github Pull Request annotate --style info --context pr-backlink \ - "Github Pull Request: https://github.com/solana-labs/solana/$BUILDKITE_BRANCH" + "Github Pull Request: https://github.com/anza-xyz/agave/$BUILDKITE_BRANCH" if [[ $GITHUB_USER = "dependabot[bot]" ]]; then command_step dependabot "ci/dependabot-pr.sh" 5 diff --git a/ci/buildkite-pipeline.sh b/ci/buildkite-pipeline.sh index e130c585ad6..68c0c6252f7 100755 --- a/ci/buildkite-pipeline.sh +++ b/ci/buildkite-pipeline.sh @@ -121,8 +121,8 @@ EOF trigger_secondary_step() { cat >> "$output_file" <<"EOF" - - name: "Trigger Build on solana-secondary" - trigger: "solana-secondary" + - name: "Trigger Build on agave-secondary" + trigger: "agave-secondary" branches: "!pull/*" async: true soft_fail: true @@ -194,33 +194,6 @@ EOF "Stable-SBF skipped as no relevant files were modified" fi - # Perf test suite - if affects \ - .rs$ \ - Cargo.lock$ \ - Cargo.toml$ \ - ^ci/rust-version.sh \ - ^ci/test-stable-perf.sh \ - ^ci/test-stable.sh \ - ^ci/test-local-cluster.sh \ - ^core/build.rs \ - ^fetch-perf-libs.sh \ - ^programs/ \ - ^sdk/ \ - ; then - cat >> "$output_file" <<"EOF" - - command: "ci/test-stable-perf.sh" - name: "stable-perf" - timeout_in_minutes: 35 - artifact_paths: "log-*.txt" - agents: - queue: "cuda" -EOF - else - annotate --style info \ - "Stable-perf skipped as no relevant files were modified" - fi - # Downstream backwards compatibility if affects \ .rs$ \ @@ -340,7 +313,7 @@ if [[ -n $BUILDKITE_TAG ]]; then start_pipeline "Tag pipeline for $BUILDKITE_TAG" annotate --style info --context release-tag \ - "https://github.com/solana-labs/solana/releases/$BUILDKITE_TAG" + "https://github.com/anza-xyz/agave/releases/$BUILDKITE_TAG" # Jump directly to the secondary build to publish release artifacts quickly trigger_secondary_step @@ -358,7 +331,7 @@ if [[ $BUILDKITE_BRANCH =~ ^pull ]]; then # Add helpful link back to the corresponding Github Pull Request annotate --style info --context pr-backlink \ - "Github Pull Request: https://github.com/solana-labs/solana/$BUILDKITE_BRANCH" + "Github Pull Request: https://github.com/anza-xyz/agave/$BUILDKITE_BRANCH" if [[ $GITHUB_USER = "dependabot[bot]" ]]; then command_step dependabot "ci/dependabot-pr.sh" 5 diff --git a/ci/buildkite-solana-private.sh b/ci/buildkite-solana-private.sh index 57a3d3de3b2..a2366d5701c 100755 --- a/ci/buildkite-solana-private.sh +++ b/ci/buildkite-solana-private.sh @@ -180,33 +180,6 @@ EOF "Stable-SBF skipped as no relevant files were modified" fi - # Perf test suite - if affects \ - .rs$ \ - Cargo.lock$ \ - Cargo.toml$ \ - ^ci/rust-version.sh \ - ^ci/test-stable-perf.sh \ - ^ci/test-stable.sh \ - ^ci/test-local-cluster.sh \ - ^core/build.rs \ - ^fetch-perf-libs.sh \ - ^programs/ \ - ^sdk/ \ - ; then - cat >> "$output_file" <<"EOF" - - command: "ci/test-stable-perf.sh" - name: "stable-perf" - timeout_in_minutes: 35 - artifact_paths: "log-*.txt" - agents: - queue: "sol-private" -EOF - else - annotate --style info \ - "Stable-perf skipped as no relevant files were modified" - fi - # Downstream backwards compatibility if affects \ .rs$ \ @@ -314,7 +287,7 @@ if [[ $BUILDKITE_BRANCH =~ ^pull ]]; then # Add helpful link back to the corresponding Github Pull Request annotate --style info --context pr-backlink \ - "Github Pull Request: https://github.com/solana-labs/solana/$BUILDKITE_BRANCH" + "Github Pull Request: https://github.com/anza-xyz/agave/$BUILDKITE_BRANCH" if [[ $GITHUB_USER = "dependabot[bot]" ]]; then command_step dependabot "ci/dependabot-pr.sh" 5 diff --git a/ci/channel-info.sh b/ci/channel-info.sh index c82806454d0..2bb80836565 100755 --- a/ci/channel-info.sh +++ b/ci/channel-info.sh @@ -11,7 +11,7 @@ here="$(dirname "$0")" # shellcheck source=ci/semver_bash/semver.sh source "$here"/semver_bash/semver.sh -remote=https://github.com/solana-labs/solana.git +remote=https://github.com/anza-xyz/agave.git # Fetch all vX.Y.Z tags # diff --git a/ci/check-install-all.sh b/ci/check-install-all.sh new file mode 100755 index 00000000000..0ffb52a3708 --- /dev/null +++ b/ci/check-install-all.sh @@ -0,0 +1,5 @@ +source scripts/spl-token-cli-version.sh +if [[ -z $splTokenCliVersion ]]; then + echo "On the stable channel, splTokenCliVersion must be set in scripts/spl-token-cli-version.sh" + exit 1 +fi diff --git a/ci/dependabot-pr.sh b/ci/dependabot-pr.sh index 9ef6816cec5..8638a7d5257 100755 --- a/ci/dependabot-pr.sh +++ b/ci/dependabot-pr.sh @@ -21,7 +21,7 @@ fi echo --- "(FAILING) Backpropagating dependabot-triggered Cargo.lock updates" name="dependabot-buildkite" -api_base="https://api.github.com/repos/solana-labs/solana/pulls" +api_base="https://api.github.com/repos/anza-xyz/agave/pulls" pr_num=$(echo "$BUILDKITE_BRANCH" | grep -Eo '[0-9]+') branch=$(curl -s "$api_base/$pr_num" | python3 -c 'import json,sys;print(json.load(sys.stdin)["head"]["ref"])') diff --git a/ci/do-audit.sh b/ci/do-audit.sh index 039df6b63cb..0118c84c5f0 100755 --- a/ci/do-audit.sh +++ b/ci/do-audit.sh @@ -30,6 +30,9 @@ cargo_audit_ignores=( --ignore RUSTSEC-2023-0001 --ignore RUSTSEC-2022-0093 + + # mio + --ignore RUSTSEC-2024-0019 ) scripts/cargo-for-all-lock-files.sh audit "${cargo_audit_ignores[@]}" | $dep_tree_filter # we want the `cargo audit` exit code, not `$dep_tree_filter`'s diff --git a/ci/docker-run.sh b/ci/docker-run.sh index eb9d06836f6..aa39a3c844e 100755 --- a/ci/docker-run.sh +++ b/ci/docker-run.sh @@ -54,16 +54,34 @@ if [[ -n $CI ]]; then # sccache-related bugs echo "--- $0 ... (with sccache being DISABLED due to many (${BUILDKITE_RETRY_COUNT}) retries)" else - echo "--- $0 ... (with sccache enabled with prefix: $SCCACHE_S3_KEY_PREFIX)" + echo "--- $0 ... (with sccache enabled with prefix: $SCCACHE_KEY_PREFIX)" + # sccache ARGS+=( --env "RUSTC_WRAPPER=/usr/local/cargo/bin/sccache" - --env AWS_ACCESS_KEY_ID - --env AWS_SECRET_ACCESS_KEY - --env SCCACHE_BUCKET - --env SCCACHE_REGION - --env SCCACHE_S3_KEY_PREFIX ) + + # s3 + if [ -n "$AWS_ACCESS_KEY_ID" ]; then + ARGS+=( + --env AWS_ACCESS_KEY_ID + --env AWS_SECRET_ACCESS_KEY + --env SCCACHE_BUCKET + --env SCCACHE_REGION + --env SCCACHE_S3_KEY_PREFIX + ) + fi + + # gcs + if [ -n "$SCCACHE_GCS_KEY_PATH" ]; then + ARGS+=( + --env SCCACHE_GCS_KEY_PATH + --volume "$SCCACHE_GCS_KEY_PATH:$SCCACHE_GCS_KEY_PATH" + --env SCCACHE_GCS_BUCKET + --env SCCACHE_GCS_RW_MODE + --env SCCACHE_GCS_KEY_PREFIX + ) + fi fi fi else diff --git a/ci/docker-rust-nightly/Dockerfile b/ci/docker-rust-nightly/Dockerfile index 23262061e4f..a5d933b2a2d 100644 --- a/ci/docker-rust-nightly/Dockerfile +++ b/ci/docker-rust-nightly/Dockerfile @@ -1,4 +1,4 @@ -FROM solanalabs/rust:1.72.1 +FROM solanalabs/rust:1.73.0 ARG date RUN set -x \ diff --git a/ci/docker-rust/Dockerfile b/ci/docker-rust/Dockerfile index e5d80f9e04b..8dfc347d54d 100644 --- a/ci/docker-rust/Dockerfile +++ b/ci/docker-rust/Dockerfile @@ -1,6 +1,6 @@ # Note: when the rust version is changed also modify # ci/rust-version.sh to pick up the new image tag -FROM rust:1.72.1 +FROM rust:1.73.0 ARG NODE_MAJOR=18 diff --git a/ci/localnet-sanity.sh b/ci/localnet-sanity.sh index e6734e180aa..b01eca31d50 100755 --- a/ci/localnet-sanity.sh +++ b/ci/localnet-sanity.sh @@ -202,8 +202,8 @@ killNodes() { # Try to use the RPC exit API to cleanly exit the first two nodes # (dynamic nodes, -x, are just killed) echo "--- RPC exit" - $solana_validator --ledger "$SOLANA_CONFIG_DIR"/bootstrap-validator exit --force || true - $solana_validator --ledger "$SOLANA_CONFIG_DIR"/validator exit --force || true + $agave_validator --ledger "$SOLANA_CONFIG_DIR"/bootstrap-validator exit --force || true + $agave_validator --ledger "$SOLANA_CONFIG_DIR"/validator exit --force || true # Give the nodes a splash of time to cleanly exit before killing them sleep 2 diff --git a/ci/publish-crate.sh b/ci/publish-crate.sh index fb13ec1d53d..099d02129e3 100755 --- a/ci/publish-crate.sh +++ b/ci/publish-crate.sh @@ -63,37 +63,27 @@ for Cargo_toml in $Cargo_tomls; do ( set -x + crate=$(dirname "$Cargo_toml") - # The rocksdb package does not build with the stock rust docker image so use - # the solana rust docker image cargoCommand="cargo publish --token $CRATES_IO_TOKEN" - ci/docker-run.sh "$rust_stable_docker_image" bash -exc "cd $crate; $cargoCommand" - ) || true # <-- Don't fail. We want to be able to retry the job in cases when a publish fails halfway due to network/cloud issues - - numRetries=30 - for ((i = 1 ; i <= numRetries ; i++)); do - echo "Attempt ${i} of ${numRetries}" - if [[ $(is_crate_version_uploaded "$crate_name" "$expectedCrateVersion") = True ]] ; then - echo "Found ${crate_name} version ${expectedCrateVersion} on crates.io REST API" - - really_uploaded=0 - ( - set -x - rm -rf crate-test - cargo init crate-test - cd crate-test/ - echo "${crate_name} = \"=${expectedCrateVersion}\"" >> Cargo.toml - echo "[workspace]" >> Cargo.toml - cargo check - ) && really_uploaded=1 - if ((really_uploaded)); then - break; + + numRetries=10 + for ((i = 1; i <= numRetries; i++)); do + echo "Attempt ${i} of ${numRetries}" + # The rocksdb package does not build with the stock rust docker image so use + # the solana rust docker image + if ci/docker-run.sh "$rust_stable_docker_image" bash -exc "cd $crate; $cargoCommand"; then + break + fi + + if [ "$i" -lt "$numRetries" ]; then + sleep 3 + else + echo "couldn't publish '$crate_name'" + exit 1 fi - echo "${crate_name} not yet available for download from crates.io" - fi - echo "Did not find ${crate_name} version ${expectedCrateVersion} on crates.io. Sleeping for 2 seconds." - sleep 2 - done + done + ) done exit 0 diff --git a/ci/publish-installer.sh b/ci/publish-installer.sh index 4b5345ae0d2..f7d98ffd5dd 100755 --- a/ci/publish-installer.sh +++ b/ci/publish-installer.sh @@ -26,14 +26,14 @@ fi # upload install script source ci/upload-ci-artifact.sh -cat >release.solana.com-install <release.anza.xyz-install <>release.solana.com-install +cat install/agave-install-init.sh >>release.anza.xyz-install -echo --- AWS S3 Store: "install" -upload-s3-artifact "/solana/release.solana.com-install" "s3://release.solana.com/$CHANNEL_OR_TAG/install" +echo --- GCS: "install" +upload-gcs-artifact "/solana/release.anza.xyz-install" "gs://anza-release/$CHANNEL_OR_TAG/install" echo Published to: -ci/format-url.sh https://release.solana.com/"$CHANNEL_OR_TAG"/install +ci/format-url.sh https://release.anza.xyz/"$CHANNEL_OR_TAG"/install diff --git a/ci/publish-tarball.sh b/ci/publish-tarball.sh index ff72bb7da2d..da5862fb3de 100755 --- a/ci/publish-tarball.sh +++ b/ci/publish-tarball.sh @@ -93,7 +93,7 @@ echo --- Creating release tarball tar cvf "${TARBALL_BASENAME}"-$TARGET.tar "${RELEASE_BASENAME}" bzip2 "${TARBALL_BASENAME}"-$TARGET.tar - cp "${RELEASE_BASENAME}"/bin/solana-install-init solana-install-init-$TARGET + cp "${RELEASE_BASENAME}"/bin/agave-install-init agave-install-init-$TARGET cp "${RELEASE_BASENAME}"/version.yml "${TARBALL_BASENAME}"-$TARGET.yml ) @@ -110,7 +110,7 @@ fi source ci/upload-ci-artifact.sh -for file in "${TARBALL_BASENAME}"-$TARGET.tar.bz2 "${TARBALL_BASENAME}"-$TARGET.yml solana-install-init-"$TARGET"* $MAYBE_TARBALLS; do +for file in "${TARBALL_BASENAME}"-$TARGET.tar.bz2 "${TARBALL_BASENAME}"-$TARGET.yml agave-install-init-"$TARGET"* $MAYBE_TARBALLS; do if [[ -n $DO_NOT_PUBLISH_TAR ]]; then upload-ci-artifact "$file" echo "Skipped $file due to DO_NOT_PUBLISH_TAR" @@ -118,11 +118,11 @@ for file in "${TARBALL_BASENAME}"-$TARGET.tar.bz2 "${TARBALL_BASENAME}"-$TARGET. fi if [[ -n $BUILDKITE ]]; then - echo --- AWS S3 Store: "$file" - upload-s3-artifact "/solana/$file" s3://release.solana.com/"$CHANNEL_OR_TAG"/"$file" + echo --- GCS Store: "$file" + upload-gcs-artifact "/solana/$file" gs://anza-release/"$CHANNEL_OR_TAG"/"$file" echo Published to: - $DRYRUN ci/format-url.sh https://release.solana.com/"$CHANNEL_OR_TAG"/"$file" + $DRYRUN ci/format-url.sh https://release.anza.xyz/"$CHANNEL_OR_TAG"/"$file" if [[ -n $TAG ]]; then ci/upload-github-release-asset.sh "$file" diff --git a/ci/run-sanity.sh b/ci/run-sanity.sh index 3e674d92f4e..d9527def4e2 100755 --- a/ci/run-sanity.sh +++ b/ci/run-sanity.sh @@ -31,7 +31,7 @@ while [[ $latest_slot -le $((snapshot_slot + 1)) ]]; do latest_slot=$($solana_cli --url http://localhost:8899 slot --commitment processed) done -$solana_validator --ledger config/ledger exit --force || true +$agave_validator --ledger config/ledger exit --force || true wait $pid diff --git a/ci/rust-version.sh b/ci/rust-version.sh index 76f929277ba..a38910accda 100644 --- a/ci/rust-version.sh +++ b/ci/rust-version.sh @@ -29,7 +29,7 @@ fi if [[ -n $RUST_NIGHTLY_VERSION ]]; then nightly_version="$RUST_NIGHTLY_VERSION" else - nightly_version=2023-09-20 + nightly_version=2023-10-05 fi diff --git a/ci/test-checks.sh b/ci/test-checks.sh index 0f037cb3478..533d05e1720 100755 --- a/ci/test-checks.sh +++ b/ci/test-checks.sh @@ -61,35 +61,7 @@ fi _ ci/order-crates-for-publishing.py -nightly_clippy_allows=(--allow=clippy::redundant_clone) - -# Use nightly clippy, as frozen-abi proc-macro generates a lot of code across -# various crates in this whole monorepo (frozen-abi is enabled only under nightly -# due to the use of unstable rust feature). Likewise, frozen-abi(-macro) crates' -# unit tests are only compiled under nightly. -# Similarly, nightly is desired to run clippy over all of bench files because -# the bench itself isn't stabilized yet... -# ref: https://github.com/rust-lang/rust/issues/66287 -_ scripts/cargo-for-all-lock-files.sh -- "+${rust_nightly}" clippy --workspace --all-targets --features dummy-for-ci-check -- \ - --deny=warnings \ - --deny=clippy::default_trait_access \ - --deny=clippy::arithmetic_side_effects \ - --deny=clippy::manual_let_else \ - --deny=clippy::used_underscore_binding \ - "${nightly_clippy_allows[@]}" - -# temporarily run stable clippy as well to scan the codebase for -# `redundant_clone`s, which is disabled as nightly clippy is buggy: -# https://github.com/solana-labs/solana/issues/31834 -# -# can't use --all-targets: -# error[E0554]: `#![feature]` may not be used on the stable release channel -_ scripts/cargo-for-all-lock-files.sh -- clippy --workspace --tests --bins --examples --features dummy-for-ci-check -- \ - --deny=warnings \ - --deny=clippy::default_trait_access \ - --deny=clippy::arithmetic_side_effects \ - --deny=clippy::manual_let_else \ - --deny=clippy::used_underscore_binding +_ scripts/cargo-clippy.sh if [[ -n $CI ]]; then # exclude from printing "Checking xxx ..." @@ -104,4 +76,8 @@ _ scripts/cargo-for-all-lock-files.sh -- "+${rust_nightly}" fmt --all -- --check _ ci/do-audit.sh +if [[ -n $CI ]] && [[ $CHANNEL = "stable" ]]; then + _ ci/check-install-all.sh +fi + echo --- ok diff --git a/ci/test-coverage.sh b/ci/test-coverage.sh index 44231cd338a..ffd362acd28 100755 --- a/ci/test-coverage.sh +++ b/ci/test-coverage.sh @@ -32,5 +32,5 @@ else codecov -t "${CODECOV_TOKEN}" annotate --style success --context codecov.io \ - "CodeCov report: https://codecov.io/github/solana-labs/solana/commit/${CI_COMMIT:0:9}" + "CodeCov report: https://codecov.io/github/anza-xyz/agave/commit/${CI_COMMIT:0:9}" fi diff --git a/ci/upload-ci-artifact.sh b/ci/upload-ci-artifact.sh index 1236da9f271..e7cc34ab2b2 100644 --- a/ci/upload-ci-artifact.sh +++ b/ci/upload-ci-artifact.sh @@ -40,3 +40,13 @@ upload-s3-artifact() { docker run "${args[@]}" ) } + +upload-gcs-artifact() { + echo "--- artifact: $1 to $2" + docker run --rm \ + -v "$GCS_RELEASE_BUCKET_WRITER_CREDIENTIAL:/application_default_credentials.json" \ + -v "$PWD:/solana" \ + -e CLOUDSDK_AUTH_CREDENTIAL_FILE_OVERRIDE=/application_default_credentials.json \ + gcr.io/google.com/cloudsdktool/google-cloud-cli:latest \ + gcloud storage cp "$1" "$2" +} diff --git a/ci/upload-github-release-asset.sh b/ci/upload-github-release-asset.sh index ca2ae2a8f60..229fb8993ed 100755 --- a/ci/upload-github-release-asset.sh +++ b/ci/upload-github-release-asset.sh @@ -26,7 +26,7 @@ fi # Force CI_REPO_SLUG since sometimes # BUILDKITE_TRIGGERED_FROM_BUILD_PIPELINE_SLUG is not set correctly, causing the # artifact upload to fail -CI_REPO_SLUG=solana-labs/solana +CI_REPO_SLUG=anza-xyz/agave #if [[ -z $CI_REPO_SLUG ]]; then # echo Error: CI_REPO_SLUG not defined # exit 1 diff --git a/cli/src/cluster_query.rs b/cli/src/cluster_query.rs index 0470cf761ad..ee683081ed4 100644 --- a/cli/src/cluster_query.rs +++ b/cli/src/cluster_query.rs @@ -1886,23 +1886,17 @@ pub fn process_show_validators( progress_bar.set_message("Fetching block production..."); let skip_rate: HashMap<_, _> = rpc_client - .get_block_production() - .ok() - .map(|result| { - result - .value - .by_identity - .into_iter() - .map(|(identity, (leader_slots, blocks_produced))| { - ( - identity, - 100. * (leader_slots.saturating_sub(blocks_produced)) as f64 - / leader_slots as f64, - ) - }) - .collect() + .get_block_production()? + .value + .by_identity + .into_iter() + .map(|(identity, (leader_slots, blocks_produced))| { + ( + identity, + 100. * (leader_slots.saturating_sub(blocks_produced)) as f64 / leader_slots as f64, + ) }) - .unwrap_or_default(); + .collect(); progress_bar.set_message("Fetching version information..."); let mut node_version = HashMap::new(); diff --git a/cli/src/feature.rs b/cli/src/feature.rs index 8c065d78fee..f4fc225aa0f 100644 --- a/cli/src/feature.rs +++ b/cli/src/feature.rs @@ -850,8 +850,7 @@ fn process_status( let mut features = vec![]; for feature_ids in feature_ids.chunks(MAX_MULTIPLE_ACCOUNTS) { let mut feature_chunk = rpc_client - .get_multiple_accounts(feature_ids) - .unwrap_or_default() + .get_multiple_accounts(feature_ids)? .into_iter() .zip(feature_ids) .map(|(account, feature_id)| { diff --git a/cli/src/stake.rs b/cli/src/stake.rs index 96c1b50b357..337b2843ff2 100644 --- a/cli/src/stake.rs +++ b/cli/src/stake.rs @@ -710,7 +710,7 @@ impl StakeSubCommands for App<'_, '_> { Arg::with_name("csv") .long("csv") .takes_value(false) - .help("Format stake account data in csv") + .help("Format stake rewards data in csv") ) .arg( Arg::with_name("num_rewards_epochs") diff --git a/client-test/tests/client.rs b/client-test/tests/client.rs index 65acd1adaae..71db9e0f5e6 100644 --- a/client-test/tests/client.rs +++ b/client-test/tests/client.rs @@ -132,7 +132,7 @@ fn test_account_subscription() { } = create_genesis_config(10_000); let bank = Bank::new_for_tests(&genesis_config); let blockhash = bank.last_blockhash(); - let bank_forks = Arc::new(RwLock::new(BankForks::new(bank))); + let bank_forks = BankForks::new_rw_arc(bank); let bank0 = bank_forks.read().unwrap().get(0).unwrap(); let bank1 = Bank::new_from_parent(bank0, &Pubkey::default(), 1); bank_forks.write().unwrap().insert(bank1); @@ -168,7 +168,7 @@ fn test_account_subscription() { // Transfer 100 lamports from alice to bob let tx = system_transaction::transfer(&alice, &bob.pubkey(), 100, blockhash); bank_forks - .write() + .read() .unwrap() .get(1) .unwrap() @@ -230,7 +230,7 @@ fn test_block_subscription() { } = create_genesis_config(10_000); let bank = Bank::new_for_tests(&genesis_config); let rent_exempt_amount = bank.get_minimum_balance_for_rent_exemption(0); - let bank_forks = Arc::new(RwLock::new(BankForks::new(bank))); + let bank_forks = BankForks::new_rw_arc(bank); // setup Blockstore let ledger_path = get_tmp_ledger_path!(); @@ -338,7 +338,7 @@ fn test_program_subscription() { } = create_genesis_config(10_000); let bank = Bank::new_for_tests(&genesis_config); let blockhash = bank.last_blockhash(); - let bank_forks = Arc::new(RwLock::new(BankForks::new(bank))); + let bank_forks = BankForks::new_rw_arc(bank); let bank0 = bank_forks.read().unwrap().get(0).unwrap(); let bank1 = Bank::new_from_parent(bank0, &Pubkey::default(), 1); bank_forks.write().unwrap().insert(bank1); @@ -373,7 +373,7 @@ fn test_program_subscription() { // Create new program account at bob's address let tx = system_transaction::create_account(&alice, &bob, blockhash, 100, 0, &program_id); bank_forks - .write() + .read() .unwrap() .get(1) .unwrap() @@ -425,7 +425,7 @@ fn test_root_subscription() { let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(10_000); let bank = Bank::new_for_tests(&genesis_config); - let bank_forks = Arc::new(RwLock::new(BankForks::new(bank))); + let bank_forks = BankForks::new_rw_arc(bank); let bank0 = bank_forks.read().unwrap().get(0).unwrap(); let bank1 = Bank::new_from_parent(bank0, &Pubkey::default(), 1); bank_forks.write().unwrap().insert(bank1); @@ -477,7 +477,7 @@ fn test_slot_subscription() { let exit = Arc::new(AtomicBool::new(false)); let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(10_000); let bank = Bank::new_for_tests(&genesis_config); - let bank_forks = Arc::new(RwLock::new(BankForks::new(bank))); + let bank_forks = BankForks::new_rw_arc(bank); let optimistically_confirmed_bank = OptimisticallyConfirmedBank::locked_from_bank_forks_root(&bank_forks); let max_complete_transaction_status_slot = Arc::new(AtomicU64::default()); @@ -553,7 +553,7 @@ async fn test_slot_subscription_async() { let exit = Arc::new(AtomicBool::new(false)); let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(10_000); let bank = Bank::new_for_tests(&genesis_config); - let bank_forks = Arc::new(RwLock::new(BankForks::new(bank))); + let bank_forks = BankForks::new_rw_arc(bank); let optimistically_confirmed_bank = OptimisticallyConfirmedBank::locked_from_bank_forks_root(&bank_forks); let max_complete_transaction_status_slot = Arc::new(AtomicU64::default()); diff --git a/core/benches/banking_stage.rs b/core/benches/banking_stage.rs index 2526c2a6369..b399abba0a9 100644 --- a/core/benches/banking_stage.rs +++ b/core/benches/banking_stage.rs @@ -55,7 +55,7 @@ use { }, std::{ iter::repeat_with, - sync::{atomic::Ordering, Arc, RwLock}, + sync::{atomic::Ordering, Arc}, time::{Duration, Instant}, }, test::Bencher, @@ -219,7 +219,7 @@ fn bench_banking(bencher: &mut Bencher, tx_type: TransactionType) { let mut bank = Bank::new_for_benches(&genesis_config); // Allow arbitrary transaction processing time for the purposes of this bench bank.ns_per_slot = u128::MAX; - let bank_forks = Arc::new(RwLock::new(BankForks::new(bank))); + let bank_forks = BankForks::new_rw_arc(bank); let bank = bank_forks.read().unwrap().get(0).unwrap(); // set cost tracker limits to MAX so it will not filter out TXs diff --git a/core/benches/consensus.rs b/core/benches/consensus.rs index a3dd97755e9..cff71502df9 100644 --- a/core/benches/consensus.rs +++ b/core/benches/consensus.rs @@ -28,7 +28,10 @@ fn bench_save_tower(bench: &mut Bencher) { let vote_account_pubkey = &Pubkey::default(); let node_keypair = Arc::new(Keypair::new()); - let heaviest_bank = BankForks::new(Bank::default_for_tests()).working_bank(); + let heaviest_bank = BankForks::new_rw_arc(Bank::default_for_tests()) + .read() + .unwrap() + .working_bank(); let tower_storage = FileTowerStorage::new(dir.path().to_path_buf()); let tower = Tower::new( &node_keypair.pubkey(), @@ -47,7 +50,10 @@ fn bench_save_tower(bench: &mut Bencher) { fn bench_generate_ancestors_descendants(bench: &mut Bencher) { let vote_account_pubkey = &Pubkey::default(); let node_keypair = Arc::new(Keypair::new()); - let heaviest_bank = BankForks::new(Bank::default_for_tests()).working_bank(); + let heaviest_bank = BankForks::new_rw_arc(Bank::default_for_tests()) + .read() + .unwrap() + .working_bank(); let mut tower = Tower::new( &node_keypair.pubkey(), vote_account_pubkey, diff --git a/core/src/accounts_hash_verifier.rs b/core/src/accounts_hash_verifier.rs index cb87cdc513a..1d7e5ae7fb5 100644 --- a/core/src/accounts_hash_verifier.rs +++ b/core/src/accounts_hash_verifier.rs @@ -104,12 +104,21 @@ impl AccountsHashVerifier { )); if let Some(snapshot_storages_for_fastboot) = snapshot_storages_for_fastboot { - let num_storages = snapshot_storages_for_fastboot.len(); + // Get the number of storages that are being kept alive for fastboot. + // Looking at the storage Arc's strong reference count, we know that one + // ref is for fastboot, and one ref is for snapshot packaging. If there + // are no others, then the storage will be kept alive because of fastboot. + let num_storages_kept_alive = snapshot_storages_for_fastboot + .iter() + .filter(|storage| Arc::strong_count(storage) == 2) + .count(); + let num_storages_total = snapshot_storages_for_fastboot.len(); fastboot_storages = Some(snapshot_storages_for_fastboot); datapoint_info!( "fastboot", ("slot", slot, i64), - ("num_storages", num_storages, i64), + ("num_storages_total", num_storages_total, i64), + ("num_storages_kept_alive", num_storages_kept_alive, i64), ); } @@ -312,11 +321,26 @@ impl AccountsHashVerifier { else { panic!("Calculating incremental accounts hash requires a base slot"); }; - let (base_accounts_hash, base_capitalization) = accounts_package - .accounts - .accounts_db - .get_accounts_hash(base_slot) - .expect("incremental snapshot requires accounts hash and capitalization from the full snapshot it is based on"); + let accounts_db = &accounts_package.accounts.accounts_db; + let Some((base_accounts_hash, base_capitalization)) = + accounts_db.get_accounts_hash(base_slot) + else { + panic!( + "incremental snapshot requires accounts hash and capitalization \ + from the full snapshot it is based on \n\ + package: {accounts_package:?} \n\ + accounts hashes: {:?} \n\ + incremental accounts hashes: {:?} \n\ + full snapshot archives: {:?} \n\ + bank snapshots: {:?}", + accounts_db.get_accounts_hashes(), + accounts_db.get_incremental_accounts_hashes(), + snapshot_utils::get_full_snapshot_archives( + &snapshot_config.full_snapshot_archives_dir, + ), + snapshot_utils::get_bank_snapshots(&snapshot_config.bank_snapshots_dir), + ); + }; let (incremental_accounts_hash, incremental_capitalization) = Self::_calculate_incremental_accounts_hash(accounts_package, base_slot); let bank_incremental_snapshot_persistence = BankIncrementalSnapshotPersistence { diff --git a/core/src/banking_stage.rs b/core/src/banking_stage.rs index e8b61de94dc..ad52db88ac4 100644 --- a/core/src/banking_stage.rs +++ b/core/src/banking_stage.rs @@ -670,7 +670,7 @@ mod tests { fn test_banking_stage_shutdown1() { let genesis_config = create_genesis_config(2).genesis_config; let bank = Bank::new_no_wallclock_throttle_for_tests(&genesis_config); - let bank_forks = Arc::new(RwLock::new(BankForks::new(bank))); + let bank_forks = BankForks::new_rw_arc(bank); let bank = bank_forks.read().unwrap().get(0).unwrap(); let banking_tracer = BankingTracer::new_disabled(); let (non_vote_sender, non_vote_receiver) = banking_tracer.create_channel_non_vote(); @@ -722,7 +722,7 @@ mod tests { genesis_config.ticks_per_slot = 4; let num_extra_ticks = 2; let bank = Bank::new_no_wallclock_throttle_for_tests(&genesis_config); - let bank_forks = Arc::new(RwLock::new(BankForks::new(bank))); + let bank_forks = BankForks::new_rw_arc(bank); let bank = bank_forks.read().unwrap().get(0).unwrap(); let start_hash = bank.last_blockhash(); let banking_tracer = BankingTracer::new_disabled(); @@ -802,7 +802,7 @@ mod tests { .. } = create_slow_genesis_config(10); let bank = Bank::new_no_wallclock_throttle_for_tests(&genesis_config); - let bank_forks = Arc::new(RwLock::new(BankForks::new(bank))); + let bank_forks = BankForks::new_rw_arc(bank); let bank = bank_forks.read().unwrap().get(0).unwrap(); let start_hash = bank.last_blockhash(); let banking_tracer = BankingTracer::new_disabled(); @@ -974,7 +974,7 @@ mod tests { let entry_receiver = { // start a banking_stage to eat verified receiver let bank = Bank::new_no_wallclock_throttle_for_tests(&genesis_config); - let bank_forks = Arc::new(RwLock::new(BankForks::new(bank))); + let bank_forks = BankForks::new_rw_arc(bank); let bank = bank_forks.read().unwrap().get(0).unwrap(); let blockstore = Arc::new( Blockstore::open(ledger_path.path()) @@ -1155,7 +1155,7 @@ mod tests { .. } = create_slow_genesis_config(10000); let bank = Bank::new_no_wallclock_throttle_for_tests(&genesis_config); - let bank_forks = Arc::new(RwLock::new(BankForks::new(bank))); + let bank_forks = BankForks::new_rw_arc(bank); let bank = bank_forks.read().unwrap().get(0).unwrap(); let start_hash = bank.last_blockhash(); let banking_tracer = BankingTracer::new_disabled(); diff --git a/core/src/banking_stage/committer.rs b/core/src/banking_stage/committer.rs index a5e42cbc75f..88b129aae3d 100644 --- a/core/src/banking_stage/committer.rs +++ b/core/src/banking_stage/committer.rs @@ -15,7 +15,7 @@ use { prioritization_fee_cache::PrioritizationFeeCache, transaction_batch::TransactionBatch, }, - solana_sdk::{pubkey::Pubkey, saturating_add_assign}, + solana_sdk::{hash::Hash, pubkey::Pubkey, saturating_add_assign}, solana_transaction_status::{ token_balances::TransactionTokenBalancesSet, TransactionTokenBalance, }, @@ -65,6 +65,8 @@ impl Committer { batch: &TransactionBatch, loaded_transactions: &mut [TransactionLoadResult], execution_results: Vec, + last_blockhash: Hash, + lamports_per_signature: u64, starting_transaction_index: Option, bank: &Arc, pre_balance_info: &mut PreBalanceInfo, @@ -74,9 +76,6 @@ impl Committer { executed_non_vote_transactions_count: usize, executed_with_successful_result_count: usize, ) -> (u64, Vec) { - let (last_blockhash, lamports_per_signature) = - bank.last_blockhash_and_lamports_per_signature(); - let executed_transactions = execution_results .iter() .zip(batch.sanitized_transactions()) diff --git a/core/src/banking_stage/consume_worker.rs b/core/src/banking_stage/consume_worker.rs index 1795db97439..d2451efa1c4 100644 --- a/core/src/banking_stage/consume_worker.rs +++ b/core/src/banking_stage/consume_worker.rs @@ -174,7 +174,7 @@ mod tests { .. } = create_slow_genesis_config(10_000); let bank = Bank::new_no_wallclock_throttle_for_tests(&genesis_config); - let bank_forks = Arc::new(RwLock::new(BankForks::new(bank))); + let bank_forks = BankForks::new_rw_arc(bank); let bank = bank_forks.read().unwrap().working_bank(); let ledger_path = get_tmp_ledger_path_auto_delete!(); diff --git a/core/src/banking_stage/consumer.rs b/core/src/banking_stage/consumer.rs index ba915bc767e..c8624a96aad 100644 --- a/core/src/banking_stage/consumer.rs +++ b/core/src/banking_stage/consumer.rs @@ -554,7 +554,8 @@ impl Consumer { transaction_status_sender_enabled, &mut execute_and_commit_timings.execute_timings, None, // account_overrides - self.log_messages_bytes_limit + self.log_messages_bytes_limit, + true, )); execute_and_commit_timings.load_execute_us = load_execute_us; @@ -587,6 +588,10 @@ impl Consumer { let (freeze_lock, freeze_lock_us) = measure_us!(bank.freeze_lock()); execute_and_commit_timings.freeze_lock_us = freeze_lock_us; + let ((last_blockhash, lamports_per_signature), last_blockhash_us) = + measure_us!(bank.last_blockhash_and_lamports_per_signature()); + execute_and_commit_timings.last_blockhash_us = last_blockhash_us; + let (record_transactions_summary, record_us) = measure_us!(self .transaction_recorder .record_transactions(bank.slot(), executed_transactions)); @@ -623,6 +628,8 @@ impl Consumer { batch, &mut loaded_transactions, execution_results, + last_blockhash, + lamports_per_signature, starting_transaction_index, bank, &mut pre_balance_info, diff --git a/core/src/banking_stage/forward_worker.rs b/core/src/banking_stage/forward_worker.rs index cabd891e761..c13b8c42637 100644 --- a/core/src/banking_stage/forward_worker.rs +++ b/core/src/banking_stage/forward_worker.rs @@ -129,7 +129,7 @@ mod tests { .. } = create_slow_genesis_config(10_000); let bank = Bank::new_no_wallclock_throttle_for_tests(&genesis_config); - let bank_forks = Arc::new(RwLock::new(BankForks::new(bank))); + let bank_forks = BankForks::new_rw_arc(bank); let bank = bank_forks.read().unwrap().working_bank(); let ledger_path = get_tmp_ledger_path_auto_delete!(); diff --git a/core/src/banking_stage/forwarder.rs b/core/src/banking_stage/forwarder.rs index 777ea12d95b..1cb656f0ddc 100644 --- a/core/src/banking_stage/forwarder.rs +++ b/core/src/banking_stage/forwarder.rs @@ -307,7 +307,7 @@ mod tests { let GenesisConfigInfo { genesis_config, .. } = &genesis_config_info; let bank: Bank = Bank::new_no_wallclock_throttle_for_tests(genesis_config); - let bank_forks = Arc::new(RwLock::new(BankForks::new(bank))); + let bank_forks = BankForks::new_rw_arc(bank); let bank = bank_forks.read().unwrap().working_bank(); let ledger_path = TempDir::new().unwrap(); diff --git a/core/src/banking_stage/immutable_deserialized_packet.rs b/core/src/banking_stage/immutable_deserialized_packet.rs index 4617702059b..0fd6a3f16e1 100644 --- a/core/src/banking_stage/immutable_deserialized_packet.rs +++ b/core/src/banking_stage/immutable_deserialized_packet.rs @@ -1,4 +1,5 @@ use { + solana_cost_model::block_cost_limits::BUILT_IN_INSTRUCTION_COSTS, solana_perf::packet::Packet, solana_runtime::transaction_priority_details::{ GetTransactionPriorityDetails, TransactionPriorityDetails, @@ -8,6 +9,7 @@ use { hash::Hash, message::Message, sanitize::SanitizeError, + saturating_add_assign, short_vec::decode_shortu16_len, signature::Signature, transaction::{ @@ -96,6 +98,22 @@ impl ImmutableDeserializedPacket { self.priority_details.compute_unit_limit } + /// Returns true if the transaction's compute unit limit is at least as + /// large as the sum of the static builtins' costs. + /// This is a simple sanity check so the leader can discard transactions + /// which are statically known to exceed the compute budget, and will + /// result in no useful state-change. + pub fn compute_unit_limit_above_static_builtins(&self) -> bool { + let mut static_builtin_cost_sum: u64 = 0; + for (program_id, _) in self.transaction.get_message().program_instructions_iter() { + if let Some(ix_cost) = BUILT_IN_INSTRUCTION_COSTS.get(program_id) { + saturating_add_assign!(static_builtin_cost_sum, *ix_cost); + } + } + + self.compute_unit_limit() >= static_builtin_cost_sum + } + // This function deserializes packets into transactions, computes the blake3 hash of transaction // messages, and verifies secp256k1 instructions. pub fn build_sanitized_transaction( @@ -148,7 +166,10 @@ fn packet_message(packet: &Packet) -> Result<&[u8], DeserializedPacketError> { mod tests { use { super::*, - solana_sdk::{signature::Keypair, system_transaction}, + solana_sdk::{ + compute_budget, instruction::Instruction, pubkey::Pubkey, signature::Keypair, + signer::Signer, system_instruction, system_transaction, transaction::Transaction, + }, }; #[test] @@ -164,4 +185,33 @@ mod tests { assert!(deserialized_packet.is_ok()); } + + #[test] + fn compute_unit_limit_above_static_builtins() { + // Cases: + // 1. compute_unit_limit under static builtins + // 2. compute_unit_limit equal to static builtins + // 3. compute_unit_limit above static builtins + for (cu_limit, expectation) in [(250, false), (300, true), (350, true)] { + let keypair = Keypair::new(); + let bpf_program_id = Pubkey::new_unique(); + let ixs = vec![ + system_instruction::transfer(&keypair.pubkey(), &Pubkey::new_unique(), 1), + compute_budget::ComputeBudgetInstruction::set_compute_unit_limit(cu_limit), + Instruction::new_with_bytes(bpf_program_id, &[], vec![]), // non-builtin - not counted in filter + ]; + let tx = Transaction::new_signed_with_payer( + &ixs, + Some(&keypair.pubkey()), + &[&keypair], + Hash::new_unique(), + ); + let packet = Packet::from_data(None, tx).unwrap(); + let deserialized_packet = ImmutableDeserializedPacket::new(packet).unwrap(); + assert_eq!( + deserialized_packet.compute_unit_limit_above_static_builtins(), + expectation + ); + } + } } diff --git a/core/src/banking_stage/leader_slot_timing_metrics.rs b/core/src/banking_stage/leader_slot_timing_metrics.rs index 543b80b4a48..7727b6cf6c6 100644 --- a/core/src/banking_stage/leader_slot_timing_metrics.rs +++ b/core/src/banking_stage/leader_slot_timing_metrics.rs @@ -10,6 +10,7 @@ pub struct LeaderExecuteAndCommitTimings { pub collect_balances_us: u64, pub load_execute_us: u64, pub freeze_lock_us: u64, + pub last_blockhash_us: u64, pub record_us: u64, pub commit_us: u64, pub find_and_send_votes_us: u64, @@ -22,6 +23,7 @@ impl LeaderExecuteAndCommitTimings { saturating_add_assign!(self.collect_balances_us, other.collect_balances_us); saturating_add_assign!(self.load_execute_us, other.load_execute_us); saturating_add_assign!(self.freeze_lock_us, other.freeze_lock_us); + saturating_add_assign!(self.last_blockhash_us, other.last_blockhash_us); saturating_add_assign!(self.record_us, other.record_us); saturating_add_assign!(self.commit_us, other.commit_us); saturating_add_assign!(self.find_and_send_votes_us, other.find_and_send_votes_us); @@ -38,6 +40,7 @@ impl LeaderExecuteAndCommitTimings { ("collect_balances_us", self.collect_balances_us as i64, i64), ("load_execute_us", self.load_execute_us as i64, i64), ("freeze_lock_us", self.freeze_lock_us as i64, i64), + ("last_blockhash_us", self.last_blockhash_us as i64, i64), ("record_us", self.record_us as i64, i64), ("commit_us", self.commit_us as i64, i64), ( diff --git a/core/src/banking_stage/packet_deserializer.rs b/core/src/banking_stage/packet_deserializer.rs index a405b626568..1d1079eaf97 100644 --- a/core/src/banking_stage/packet_deserializer.rs +++ b/core/src/banking_stage/packet_deserializer.rs @@ -50,6 +50,7 @@ impl PacketDeserializer { &self, recv_timeout: Duration, capacity: usize, + packet_filter: impl Fn(&ImmutableDeserializedPacket) -> bool, ) -> Result { let (packet_count, packet_batches) = self.receive_until(recv_timeout, capacity)?; @@ -62,6 +63,7 @@ impl PacketDeserializer { packet_count, &packet_batches, round_compute_unit_price_enabled, + &packet_filter, )) } @@ -71,6 +73,7 @@ impl PacketDeserializer { packet_count: usize, banking_batches: &[BankingPacketBatch], round_compute_unit_price_enabled: bool, + packet_filter: &impl Fn(&ImmutableDeserializedPacket) -> bool, ) -> ReceivePacketResults { let mut passed_sigverify_count: usize = 0; let mut failed_sigverify_count: usize = 0; @@ -88,6 +91,7 @@ impl PacketDeserializer { packet_batch, &packet_indexes, round_compute_unit_price_enabled, + packet_filter, )); } @@ -158,13 +162,16 @@ impl PacketDeserializer { packet_batch: &'a PacketBatch, packet_indexes: &'a [usize], round_compute_unit_price_enabled: bool, + packet_filter: &'a (impl Fn(&ImmutableDeserializedPacket) -> bool + 'a), ) -> impl Iterator + 'a { packet_indexes.iter().filter_map(move |packet_index| { let mut packet_clone = packet_batch[*packet_index].clone(); packet_clone .meta_mut() .set_round_compute_unit_price(round_compute_unit_price_enabled); - ImmutableDeserializedPacket::new(packet_clone).ok() + ImmutableDeserializedPacket::new(packet_clone) + .ok() + .filter(packet_filter) }) } } @@ -186,7 +193,7 @@ mod tests { #[test] fn test_deserialize_and_collect_packets_empty() { - let results = PacketDeserializer::deserialize_and_collect_packets(0, &[], false); + let results = PacketDeserializer::deserialize_and_collect_packets(0, &[], false, &|_| true); assert_eq!(results.deserialized_packets.len(), 0); assert!(results.new_tracer_stats_option.is_none()); assert_eq!(results.passed_sigverify_count, 0); @@ -204,6 +211,7 @@ mod tests { packet_count, &[BankingPacketBatch::new((packet_batches, None))], false, + &|_| true, ); assert_eq!(results.deserialized_packets.len(), 2); assert!(results.new_tracer_stats_option.is_none()); @@ -223,6 +231,7 @@ mod tests { packet_count, &[BankingPacketBatch::new((packet_batches, None))], false, + &|_| true, ); assert_eq!(results.deserialized_packets.len(), 1); assert!(results.new_tracer_stats_option.is_none()); diff --git a/core/src/banking_stage/packet_receiver.rs b/core/src/banking_stage/packet_receiver.rs index a566ef7cf3e..bbb753967f2 100644 --- a/core/src/banking_stage/packet_receiver.rs +++ b/core/src/banking_stage/packet_receiver.rs @@ -49,6 +49,7 @@ impl PacketReceiver { .receive_packets( recv_timeout, unprocessed_transaction_storage.max_receive_size(), + |packet| packet.compute_unit_limit_above_static_builtins(), ) // Consumes results if Ok, otherwise we keep the Err .map(|receive_packet_results| { diff --git a/core/src/banking_trace.rs b/core/src/banking_trace.rs index 760121dc7c5..ba76b794ba2 100644 --- a/core/src/banking_trace.rs +++ b/core/src/banking_trace.rs @@ -62,16 +62,17 @@ pub struct BankingTracer { active_tracer: Option, } -#[derive(Serialize, Deserialize, Debug)] +#[frozen_abi(digest = "Eq6YrAFtTbtPrCEvh6Et1mZZDCARUg1gcK2qiZdqyjUz")] +#[derive(Serialize, Deserialize, Debug, AbiExample)] pub struct TimedTracedEvent(pub std::time::SystemTime, pub TracedEvent); -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, AbiExample, AbiEnumVisitor)] pub enum TracedEvent { PacketBatch(ChannelLabel, BankingPacketBatch), BlockAndBankHash(Slot, Hash, Hash), } -#[derive(Serialize, Deserialize, Debug, Clone, Copy)] +#[derive(Serialize, Deserialize, Debug, Clone, Copy, AbiExample, AbiEnumVisitor)] pub enum ChannelLabel { NonVote, TpuVote, diff --git a/core/src/cluster_info_vote_listener.rs b/core/src/cluster_info_vote_listener.rs index 183cabcf04d..782f10d976b 100644 --- a/core/src/cluster_info_vote_listener.rs +++ b/core/src/cluster_info_vote_listener.rs @@ -1439,7 +1439,7 @@ mod tests { ); let bank = Bank::new_for_tests(&genesis_config); let exit = Arc::new(AtomicBool::new(false)); - let bank_forks = Arc::new(RwLock::new(BankForks::new(bank))); + let bank_forks = BankForks::new_rw_arc(bank); let bank = bank_forks.read().unwrap().get(0).unwrap(); let vote_tracker = VoteTracker::default(); let optimistically_confirmed_bank = @@ -1556,7 +1556,7 @@ mod tests { let bank = Bank::new_for_tests(&genesis_config); let vote_tracker = VoteTracker::default(); let exit = Arc::new(AtomicBool::new(false)); - let bank_forks = Arc::new(RwLock::new(BankForks::new(bank))); + let bank_forks = BankForks::new_rw_arc(bank); let bank = bank_forks.read().unwrap().get(0).unwrap(); let optimistically_confirmed_bank = OptimisticallyConfirmedBank::locked_from_bank_forks_root(&bank_forks); @@ -1584,7 +1584,7 @@ mod tests { solana_logger::setup(); let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(10_000); let bank = Bank::new_for_tests(&genesis_config); - let bank_forks = RwLock::new(BankForks::new(bank)); + let bank_forks = BankForks::new_rw_arc(bank); let votes = vec![]; let (vote_txs, packets) = ClusterInfoVoteListener::verify_votes(votes, &bank_forks); assert!(vote_txs.is_empty()); @@ -1629,7 +1629,7 @@ mod tests { vec![100; voting_keypairs.len()], // stakes ); let bank = Bank::new_for_tests(&genesis_config); - let bank_forks = RwLock::new(BankForks::new(bank)); + let bank_forks = BankForks::new_rw_arc(bank); let vote_tx = test_vote_tx(voting_keypairs.first(), hash); let votes = vec![vote_tx]; let (vote_txs, packets) = ClusterInfoVoteListener::verify_votes(votes, &bank_forks); @@ -1654,7 +1654,7 @@ mod tests { vec![100; voting_keypairs.len()], // stakes ); let bank = Bank::new_for_tests(&genesis_config); - let bank_forks = RwLock::new(BankForks::new(bank)); + let bank_forks = BankForks::new_rw_arc(bank); let vote_tx = test_vote_tx(voting_keypairs.first(), hash); let mut bad_vote = vote_tx.clone(); bad_vote.signatures[0] = Signature::default(); diff --git a/core/src/commitment_service.rs b/core/src/commitment_service.rs index bdadb1ff72f..84242b44c64 100644 --- a/core/src/commitment_service.rs +++ b/core/src/commitment_service.rs @@ -509,12 +509,12 @@ mod tests { ); let bank0 = Bank::new_for_tests(&genesis_config); - let mut bank_forks = BankForks::new(bank0); + let bank_forks = BankForks::new_rw_arc(bank0); // Fill bank_forks with banks with votes landing in the next slot // Create enough banks such that vote account will root slots 0 and 1 for x in 0..33 { - let previous_bank = bank_forks.get(x).unwrap(); + let previous_bank = bank_forks.read().unwrap().get(x).unwrap(); let bank = Bank::new_from_parent(previous_bank.clone(), &Pubkey::default(), x + 1); let vote = vote_transaction::new_vote_transaction( vec![x], @@ -526,20 +526,23 @@ mod tests { None, ); bank.process_transaction(&vote).unwrap(); - bank_forks.insert(bank); + bank_forks.write().unwrap().insert(bank); } - let working_bank = bank_forks.working_bank(); + let working_bank = bank_forks.read().unwrap().working_bank(); let root = get_vote_account_root_slot( validator_vote_keypairs.vote_keypair.pubkey(), &working_bank, ); for x in 0..root { - bank_forks.set_root(x, &AbsRequestSender::default(), None); + bank_forks + .write() + .unwrap() + .set_root(x, &AbsRequestSender::default(), None); } // Add an additional bank/vote that will root slot 2 - let bank33 = bank_forks.get(33).unwrap(); + let bank33 = bank_forks.read().unwrap().get(33).unwrap(); let bank34 = Bank::new_from_parent(bank33.clone(), &Pubkey::default(), 34); let vote33 = vote_transaction::new_vote_transaction( vec![33], @@ -551,9 +554,9 @@ mod tests { None, ); bank34.process_transaction(&vote33).unwrap(); - bank_forks.insert(bank34); + bank_forks.write().unwrap().insert(bank34); - let working_bank = bank_forks.working_bank(); + let working_bank = bank_forks.read().unwrap().working_bank(); let root = get_vote_account_root_slot( validator_vote_keypairs.vote_keypair.pubkey(), &working_bank, @@ -572,21 +575,22 @@ mod tests { .read() .unwrap() .highest_super_majority_root(); - bank_forks.set_root( + bank_forks.write().unwrap().set_root( root, &AbsRequestSender::default(), Some(highest_super_majority_root), ); - let highest_super_majority_root_bank = bank_forks.get(highest_super_majority_root); + let highest_super_majority_root_bank = + bank_forks.read().unwrap().get(highest_super_majority_root); assert!(highest_super_majority_root_bank.is_some()); // Add a forked bank. Because the vote for bank 33 landed in the non-ancestor, the vote // account's root (and thus the highest_super_majority_root) rolls back to slot 1 - let bank33 = bank_forks.get(33).unwrap(); + let bank33 = bank_forks.read().unwrap().get(33).unwrap(); let bank35 = Bank::new_from_parent(bank33, &Pubkey::default(), 35); - bank_forks.insert(bank35); + bank_forks.write().unwrap().insert(bank35); - let working_bank = bank_forks.working_bank(); + let working_bank = bank_forks.read().unwrap().working_bank(); let ancestors = working_bank.status_cache_ancestors(); let _ = AggregateCommitmentService::update_commitment_cache( &block_commitment_cache, @@ -601,13 +605,14 @@ mod tests { .read() .unwrap() .highest_super_majority_root(); - let highest_super_majority_root_bank = bank_forks.get(highest_super_majority_root); + let highest_super_majority_root_bank = + bank_forks.read().unwrap().get(highest_super_majority_root); assert!(highest_super_majority_root_bank.is_some()); // Add additional banks beyond lockout built on the new fork to ensure that behavior // continues normally for x in 35..=37 { - let previous_bank = bank_forks.get(x).unwrap(); + let previous_bank = bank_forks.read().unwrap().get(x).unwrap(); let bank = Bank::new_from_parent(previous_bank.clone(), &Pubkey::default(), x + 1); let vote = vote_transaction::new_vote_transaction( vec![x], @@ -619,10 +624,10 @@ mod tests { None, ); bank.process_transaction(&vote).unwrap(); - bank_forks.insert(bank); + bank_forks.write().unwrap().insert(bank); } - let working_bank = bank_forks.working_bank(); + let working_bank = bank_forks.read().unwrap().working_bank(); let root = get_vote_account_root_slot( validator_vote_keypairs.vote_keypair.pubkey(), &working_bank, @@ -641,12 +646,13 @@ mod tests { .read() .unwrap() .highest_super_majority_root(); - bank_forks.set_root( + bank_forks.write().unwrap().set_root( root, &AbsRequestSender::default(), Some(highest_super_majority_root), ); - let highest_super_majority_root_bank = bank_forks.get(highest_super_majority_root); + let highest_super_majority_root_bank = + bank_forks.read().unwrap().get(highest_super_majority_root); assert!(highest_super_majority_root_bank.is_some()); } } diff --git a/core/src/consensus.rs b/core/src/consensus.rs index 675dfc691e6..59c0adf7243 100644 --- a/core/src/consensus.rs +++ b/core/src/consensus.rs @@ -279,6 +279,28 @@ impl Tower { } } + #[cfg(test)] + pub fn new_random(node_pubkey: Pubkey) -> Self { + use rand::Rng; + + let mut rng = rand::thread_rng(); + let root_slot = rng.gen(); + let vote_state = VoteState::new_rand_for_tests(node_pubkey, root_slot); + let last_vote = VoteStateUpdate::from( + vote_state + .votes + .iter() + .map(|lv| (lv.slot(), lv.confirmation_count())) + .collect::>(), + ); + Self { + node_pubkey, + vote_state, + last_vote: VoteTransaction::CompactVoteStateUpdate(last_vote), + ..Tower::default() + } + } + pub fn new_from_bankforks( bank_forks: &BankForks, node_pubkey: &Pubkey, @@ -1427,6 +1449,9 @@ impl TowerError { false } } + pub fn is_too_old(&self) -> bool { + matches!(self, TowerError::TooOldTower(_, _)) + } } #[derive(Debug)] diff --git a/core/src/consensus/heaviest_subtree_fork_choice.rs b/core/src/consensus/heaviest_subtree_fork_choice.rs index 639272e1048..4b58ee78b99 100644 --- a/core/src/consensus/heaviest_subtree_fork_choice.rs +++ b/core/src/consensus/heaviest_subtree_fork_choice.rs @@ -244,7 +244,8 @@ impl HeaviestSubtreeForkChoice { heaviest_subtree_fork_choice } - pub fn new_from_bank_forks(bank_forks: &BankForks) -> Self { + pub fn new_from_bank_forks(bank_forks: Arc>) -> Self { + let bank_forks = bank_forks.read().unwrap(); let mut frozen_banks: Vec<_> = bank_forks.frozen_banks().values().cloned().collect(); frozen_banks.sort_by_key(|bank| bank.slot()); diff --git a/core/src/repair/ancestor_hashes_service.rs b/core/src/repair/ancestor_hashes_service.rs index 3214c89e14e..fc70dbab16c 100644 --- a/core/src/repair/ancestor_hashes_service.rs +++ b/core/src/repair/ancestor_hashes_service.rs @@ -1928,7 +1928,7 @@ mod test { #[test] fn test_verify_and_process_ancestor_responses_invalid_packet() { let bank0 = Bank::default_for_tests(); - let bank_forks = Arc::new(RwLock::new(BankForks::new(bank0))); + let bank_forks = BankForks::new_rw_arc(bank0); let ManageAncestorHashesState { ancestor_hashes_request_statuses, diff --git a/core/src/repair/quic_endpoint.rs b/core/src/repair/quic_endpoint.rs index f7b445011c9..89f9de78491 100644 --- a/core/src/repair/quic_endpoint.rs +++ b/core/src/repair/quic_endpoint.rs @@ -6,31 +6,35 @@ use { log::error, quinn::{ ClientConfig, ConnectError, Connecting, Connection, ConnectionError, Endpoint, - EndpointConfig, ReadToEndError, RecvStream, SendStream, ServerConfig, TokioRuntime, - TransportConfig, VarInt, WriteError, + EndpointConfig, IdleTimeout, ReadError, ReadToEndError, RecvStream, SendStream, + ServerConfig, TokioRuntime, TransportConfig, VarInt, WriteError, }, rcgen::RcgenError, rustls::{Certificate, PrivateKey}, serde_bytes::ByteBuf, solana_quic_client::nonblocking::quic_client::SkipServerVerification, + solana_runtime::bank_forks::BankForks, solana_sdk::{packet::PACKET_DATA_SIZE, pubkey::Pubkey, signature::Keypair}, solana_streamer::{ quic::SkipClientVerification, tls_certificates::new_self_signed_tls_certificate, }, std::{ + cmp::Reverse, collections::{hash_map::Entry, HashMap}, io::{Cursor, Error as IoError}, net::{IpAddr, SocketAddr, UdpSocket}, - ops::Deref, - sync::Arc, + sync::{ + atomic::{AtomicBool, AtomicU64, Ordering}, + Arc, RwLock, + }, time::Duration, }, thiserror::Error, tokio::{ sync::{ - mpsc::{Receiver as AsyncReceiver, Sender as AsyncSender}, + mpsc::{error::TrySendError, Receiver as AsyncReceiver, Sender as AsyncSender}, oneshot::Sender as OneShotSender, - RwLock, + Mutex, RwLock as AsyncRwLock, }, task::JoinHandle, }, @@ -39,22 +43,30 @@ use { const ALPN_REPAIR_PROTOCOL_ID: &[u8] = b"solana-repair"; const CONNECT_SERVER_NAME: &str = "solana-repair"; -const CLIENT_CHANNEL_CAPACITY: usize = 1 << 14; -const CONNECTION_CACHE_CAPACITY: usize = 4096; +const CLIENT_CHANNEL_BUFFER: usize = 1 << 14; +const ROUTER_CHANNEL_BUFFER: usize = 64; +const CONNECTION_CACHE_CAPACITY: usize = 3072; + +// Transport config. +// Repair randomly samples peers, uses bi-directional streams and generally has +// low to moderate load and so is configured separately from other protocols. +const KEEP_ALIVE_INTERVAL: Duration = Duration::from_secs(4); const MAX_CONCURRENT_BIDI_STREAMS: VarInt = VarInt::from_u32(512); +const MAX_IDLE_TIMEOUT: Duration = Duration::from_secs(10); const CONNECTION_CLOSE_ERROR_CODE_SHUTDOWN: VarInt = VarInt::from_u32(1); const CONNECTION_CLOSE_ERROR_CODE_DROPPED: VarInt = VarInt::from_u32(2); const CONNECTION_CLOSE_ERROR_CODE_INVALID_IDENTITY: VarInt = VarInt::from_u32(3); const CONNECTION_CLOSE_ERROR_CODE_REPLACED: VarInt = VarInt::from_u32(4); +const CONNECTION_CLOSE_ERROR_CODE_PRUNED: VarInt = VarInt::from_u32(5); const CONNECTION_CLOSE_REASON_SHUTDOWN: &[u8] = b"SHUTDOWN"; const CONNECTION_CLOSE_REASON_DROPPED: &[u8] = b"DROPPED"; const CONNECTION_CLOSE_REASON_INVALID_IDENTITY: &[u8] = b"INVALID_IDENTITY"; const CONNECTION_CLOSE_REASON_REPLACED: &[u8] = b"REPLACED"; +const CONNECTION_CLOSE_REASON_PRUNED: &[u8] = b"PRUNED"; pub(crate) type AsyncTryJoinHandle = TryJoin, JoinHandle<()>>; -type ConnectionCache = HashMap<(SocketAddr, Option), Arc>>>; // Outgoing local requests. pub struct LocalRequest { @@ -76,16 +88,14 @@ pub struct RemoteRequest { #[derive(Error, Debug)] #[allow(clippy::enum_variant_names)] pub(crate) enum Error { - #[error(transparent)] - BincodeError(#[from] bincode::Error), #[error(transparent)] CertificateError(#[from] RcgenError), + #[error("Channel Send Error")] + ChannelSendError, #[error(transparent)] ConnectError(#[from] ConnectError), #[error(transparent)] ConnectionError(#[from] ConnectionError), - #[error("Channel Send Error")] - ChannelSendError, #[error("Invalid Identity: {0:?}")] InvalidIdentity(SocketAddr), #[error(transparent)] @@ -97,9 +107,15 @@ pub(crate) enum Error { #[error("read_to_end Timeout")] ReadToEndTimeout, #[error(transparent)] - WriteError(#[from] WriteError), - #[error(transparent)] TlsError(#[from] rustls::Error), + #[error(transparent)] + WriteError(#[from] WriteError), +} + +macro_rules! add_metric { + ($metric: expr) => {{ + $metric.fetch_add(1, Ordering::Relaxed); + }}; } #[allow(clippy::type_complexity)] @@ -109,6 +125,7 @@ pub(crate) fn new_quic_endpoint( socket: UdpSocket, address: IpAddr, remote_request_sender: Sender, + bank_forks: Arc>, ) -> Result<(Endpoint, AsyncSender, AsyncTryJoinHandle), Error> { let (cert, key) = new_self_signed_tls_certificate(keypair, address)?; let server_config = new_server_config(cert.clone(), key.clone())?; @@ -125,17 +142,25 @@ pub(crate) fn new_quic_endpoint( )? }; endpoint.set_default_client_config(client_config); - let cache = Arc::>::default(); - let (client_sender, client_receiver) = tokio::sync::mpsc::channel(CLIENT_CHANNEL_CAPACITY); + let prune_cache_pending = Arc::::default(); + let cache = Arc::>>::default(); + let (client_sender, client_receiver) = tokio::sync::mpsc::channel(CLIENT_CHANNEL_BUFFER); + let router = Arc::>>>::default(); let server_task = runtime.spawn(run_server( endpoint.clone(), remote_request_sender.clone(), + bank_forks.clone(), + prune_cache_pending.clone(), + router.clone(), cache.clone(), )); let client_task = runtime.spawn(run_client( endpoint.clone(), client_receiver, remote_request_sender, + bank_forks, + prune_cache_pending, + router, cache, )); let task = futures::future::try_join(server_task, client_task); @@ -176,54 +201,135 @@ fn new_client_config(cert: Certificate, key: PrivateKey) -> Result TransportConfig { + let max_idle_timeout = IdleTimeout::try_from(MAX_IDLE_TIMEOUT).unwrap(); let mut config = TransportConfig::default(); + // Disable datagrams and uni streams. config + .datagram_receive_buffer_size(None) + .keep_alive_interval(Some(KEEP_ALIVE_INTERVAL)) .max_concurrent_bidi_streams(MAX_CONCURRENT_BIDI_STREAMS) .max_concurrent_uni_streams(VarInt::from(0u8)) - .datagram_receive_buffer_size(None); + .max_idle_timeout(Some(max_idle_timeout)); config } async fn run_server( endpoint: Endpoint, remote_request_sender: Sender, - cache: Arc>, + bank_forks: Arc>, + prune_cache_pending: Arc, + router: Arc>>>, + cache: Arc>>, ) { + let stats = Arc::::default(); + let report_metrics_task = + tokio::task::spawn(report_metrics_task("repair_quic_server", stats.clone())); while let Some(connecting) = endpoint.accept().await { - tokio::task::spawn(handle_connecting_error( + tokio::task::spawn(handle_connecting_task( endpoint.clone(), connecting, remote_request_sender.clone(), + bank_forks.clone(), + prune_cache_pending.clone(), + router.clone(), cache.clone(), + stats.clone(), )); } + report_metrics_task.abort(); } async fn run_client( endpoint: Endpoint, mut receiver: AsyncReceiver, remote_request_sender: Sender, - cache: Arc>, + bank_forks: Arc>, + prune_cache_pending: Arc, + router: Arc>>>, + cache: Arc>>, ) { + let stats = Arc::::default(); + let report_metrics_task = + tokio::task::spawn(report_metrics_task("repair_quic_client", stats.clone())); while let Some(request) = receiver.recv().await { - tokio::task::spawn(send_request_task( + let Some(request) = try_route_request(request, &*router.read().await, &stats) else { + continue; + }; + let remote_address = request.remote_address; + let receiver = { + let mut router = router.write().await; + let Some(request) = try_route_request(request, &router, &stats) else { + continue; + }; + let (sender, receiver) = tokio::sync::mpsc::channel(ROUTER_CHANNEL_BUFFER); + sender.try_send(request).unwrap(); + router.insert(remote_address, sender); + receiver + }; + tokio::task::spawn(make_connection_task( endpoint.clone(), - request, + remote_address, remote_request_sender.clone(), + receiver, + bank_forks.clone(), + prune_cache_pending.clone(), + router.clone(), cache.clone(), + stats.clone(), )); } close_quic_endpoint(&endpoint); + // Drop sender channels to unblock threads waiting on the receiving end. + router.write().await.clear(); + report_metrics_task.abort(); } -async fn handle_connecting_error( +// Routes the local request to respective channel. Drops the request if the +// channel is full. Bounces the request back if the channel is closed or does +// not exist. +fn try_route_request( + request: LocalRequest, + router: &HashMap>, + stats: &RepairQuicStats, +) -> Option { + match router.get(&request.remote_address) { + None => Some(request), + Some(sender) => match sender.try_send(request) { + Ok(()) => None, + Err(TrySendError::Full(request)) => { + debug!("TrySendError::Full {}", request.remote_address); + add_metric!(stats.router_try_send_error_full); + None + } + Err(TrySendError::Closed(request)) => Some(request), + }, + } +} + +async fn handle_connecting_task( endpoint: Endpoint, connecting: Connecting, remote_request_sender: Sender, - cache: Arc>, + bank_forks: Arc>, + prune_cache_pending: Arc, + router: Arc>>>, + cache: Arc>>, + stats: Arc, ) { - if let Err(err) = handle_connecting(endpoint, connecting, remote_request_sender, cache).await { - error!("handle_connecting: {err:?}"); + if let Err(err) = handle_connecting( + endpoint, + connecting, + remote_request_sender, + bank_forks, + prune_cache_pending, + router, + cache, + stats.clone(), + ) + .await + { + debug!("handle_connecting: {err:?}"); + record_error(&err, &stats); } } @@ -231,52 +337,103 @@ async fn handle_connecting( endpoint: Endpoint, connecting: Connecting, remote_request_sender: Sender, - cache: Arc>, + bank_forks: Arc>, + prune_cache_pending: Arc, + router: Arc>>>, + cache: Arc>>, + stats: Arc, ) -> Result<(), Error> { let connection = connecting.await?; let remote_address = connection.remote_address(); let remote_pubkey = get_remote_pubkey(&connection)?; - handle_connection_error( + let receiver = { + let (sender, receiver) = tokio::sync::mpsc::channel(ROUTER_CHANNEL_BUFFER); + router.write().await.insert(remote_address, sender); + receiver + }; + handle_connection( endpoint, remote_address, remote_pubkey, connection, remote_request_sender, + receiver, + bank_forks, + prune_cache_pending, + router, cache, + stats, ) .await; Ok(()) } -async fn handle_connection_error( +#[allow(clippy::too_many_arguments)] +async fn handle_connection( endpoint: Endpoint, remote_address: SocketAddr, remote_pubkey: Pubkey, connection: Connection, remote_request_sender: Sender, - cache: Arc>, + receiver: AsyncReceiver, + bank_forks: Arc>, + prune_cache_pending: Arc, + router: Arc>>>, + cache: Arc>>, + stats: Arc, ) { - cache_connection(remote_address, remote_pubkey, connection.clone(), &cache).await; - if let Err(err) = handle_connection( - &endpoint, - remote_address, + cache_connection( remote_pubkey, - &connection, - &remote_request_sender, + connection.clone(), + bank_forks, + prune_cache_pending, + router.clone(), + cache.clone(), ) - .await - { - drop_connection(remote_address, remote_pubkey, &connection, &cache).await; - error!("handle_connection: {remote_pubkey}, {remote_address}, {err:?}"); + .await; + let send_requests_task = tokio::task::spawn(send_requests_task( + endpoint.clone(), + remote_address, + connection.clone(), + receiver, + stats.clone(), + )); + let recv_requests_task = tokio::task::spawn(recv_requests_task( + endpoint, + remote_address, + remote_pubkey, + connection.clone(), + remote_request_sender, + stats.clone(), + )); + match futures::future::try_join(send_requests_task, recv_requests_task).await { + Err(err) => error!("handle_connection: {remote_pubkey}, {remote_address}, {err:?}"), + Ok(out) => { + if let (Err(ref err), _) = out { + debug!("send_requests_task: {remote_pubkey}, {remote_address}, {err:?}"); + record_error(err, &stats); + } + if let (_, Err(ref err)) = out { + debug!("recv_requests_task: {remote_pubkey}, {remote_address}, {err:?}"); + record_error(err, &stats); + } + } + } + drop_connection(remote_pubkey, &connection, &cache).await; + if let Entry::Occupied(entry) = router.write().await.entry(remote_address) { + if entry.get().is_closed() { + entry.remove(); + } } } -async fn handle_connection( - endpoint: &Endpoint, +async fn recv_requests_task( + endpoint: Endpoint, remote_address: SocketAddr, remote_pubkey: Pubkey, - connection: &Connection, - remote_request_sender: &Sender, + connection: Connection, + remote_request_sender: Sender, + stats: Arc, ) -> Result<(), Error> { loop { let (send_stream, recv_stream) = connection.accept_bi().await?; @@ -287,6 +444,7 @@ async fn handle_connection( send_stream, recv_stream, remote_request_sender.clone(), + stats.clone(), )); } } @@ -298,6 +456,7 @@ async fn handle_streams_task( send_stream: SendStream, recv_stream: RecvStream, remote_request_sender: Sender, + stats: Arc, ) { if let Err(err) = handle_streams( &endpoint, @@ -309,7 +468,8 @@ async fn handle_streams_task( ) .await { - error!("handle_stream: {remote_address}, {remote_pubkey}, {err:?}"); + debug!("handle_stream: {remote_address}, {remote_pubkey}, {err:?}"); + record_error(&err, &stats); } } @@ -352,32 +512,62 @@ async fn handle_streams( send_stream.finish().await.map_err(Error::from) } +async fn send_requests_task( + endpoint: Endpoint, + remote_address: SocketAddr, + connection: Connection, + mut receiver: AsyncReceiver, + stats: Arc, +) -> Result<(), Error> { + tokio::pin! { + let connection_closed = connection.closed(); + } + loop { + tokio::select! { + biased; + request = receiver.recv() => { + match request { + None => return Ok(()), + Some(request) => tokio::task::spawn(send_request_task( + endpoint.clone(), + remote_address, + connection.clone(), + request, + stats.clone(), + )), + }; + } + err = &mut connection_closed => return Err(Error::from(err)), + } + } +} + async fn send_request_task( endpoint: Endpoint, + remote_address: SocketAddr, + connection: Connection, request: LocalRequest, - remote_request_sender: Sender, - cache: Arc>, + stats: Arc, ) { - if let Err(err) = send_request(&endpoint, request, remote_request_sender, cache).await { - error!("send_request_task: {err:?}"); + if let Err(err) = send_request(endpoint, connection, request).await { + debug!("send_request: {remote_address}, {err:?}"); + record_error(&err, &stats); } } async fn send_request( - endpoint: &Endpoint, + endpoint: Endpoint, + connection: Connection, LocalRequest { - remote_address, + remote_address: _, bytes, num_expected_responses, response_sender, }: LocalRequest, - remote_request_sender: Sender, - cache: Arc>, ) -> Result<(), Error> { // Assert that send won't block. debug_assert_eq!(response_sender.capacity(), None); const READ_TIMEOUT_DURATION: Duration = Duration::from_secs(10); - let connection = get_connection(endpoint, remote_address, remote_request_sender, cache).await?; let (mut send_stream, mut recv_stream) = connection.open_bi().await?; send_stream.write_all(&bytes).await?; send_stream.finish().await?; @@ -405,50 +595,70 @@ async fn send_request( response_sender .send((remote_address, chunk)) .map_err(|err| { - close_quic_endpoint(endpoint); + close_quic_endpoint(&endpoint); Error::from(err) }) }) } -async fn get_connection( - endpoint: &Endpoint, +async fn make_connection_task( + endpoint: Endpoint, remote_address: SocketAddr, remote_request_sender: Sender, - cache: Arc>, -) -> Result { - let entry = get_cache_entry(remote_address, &cache).await; + receiver: AsyncReceiver, + bank_forks: Arc>, + prune_cache_pending: Arc, + router: Arc>>>, + cache: Arc>>, + stats: Arc, +) { + if let Err(err) = make_connection( + endpoint, + remote_address, + remote_request_sender, + receiver, + bank_forks, + prune_cache_pending, + router, + cache, + stats.clone(), + ) + .await { - let connection: Option = entry.read().await.clone(); - if let Some(connection) = connection { - if connection.close_reason().is_none() { - return Ok(connection); - } - } + debug!("make_connection: {remote_address}, {err:?}"); + record_error(&err, &stats); } - let connection = { - // Need to write lock here so that only one task initiates - // a new connection to the same remote_address. - let mut entry = entry.write().await; - if let Some(connection) = entry.deref() { - if connection.close_reason().is_none() { - return Ok(connection.clone()); - } - } - let connection = endpoint - .connect(remote_address, CONNECT_SERVER_NAME)? - .await?; - entry.insert(connection).clone() - }; - tokio::task::spawn(handle_connection_error( - endpoint.clone(), +} + +async fn make_connection( + endpoint: Endpoint, + remote_address: SocketAddr, + remote_request_sender: Sender, + receiver: AsyncReceiver, + bank_forks: Arc>, + prune_cache_pending: Arc, + router: Arc>>>, + cache: Arc>>, + stats: Arc, +) -> Result<(), Error> { + let connection = endpoint + .connect(remote_address, CONNECT_SERVER_NAME)? + .await?; + handle_connection( + endpoint, connection.remote_address(), get_remote_pubkey(&connection)?, - connection.clone(), + connection, remote_request_sender, + receiver, + bank_forks, + prune_cache_pending, + router, cache, - )); - Ok(connection) + stats, + ) + .await; + Ok(()) } fn get_remote_pubkey(connection: &Connection) -> Result { @@ -464,71 +674,95 @@ fn get_remote_pubkey(connection: &Connection) -> Result { } } -async fn get_cache_entry( - remote_address: SocketAddr, - cache: &RwLock, -) -> Arc>> { - let key = (remote_address, /*remote_pubkey:*/ None); - if let Some(entry) = cache.read().await.get(&key) { - return entry.clone(); - } - cache.write().await.entry(key).or_default().clone() -} - async fn cache_connection( - remote_address: SocketAddr, remote_pubkey: Pubkey, connection: Connection, - cache: &RwLock, + bank_forks: Arc>, + prune_cache_pending: Arc, + router: Arc>>>, + cache: Arc>>, ) { - // The 2nd cache entry with remote_pubkey == None allows to lookup an entry - // only by SocketAddr when establishing outgoing connections. - let entries: [Arc>>; 2] = { - let mut cache = cache.write().await; - if cache.len() >= CONNECTION_CACHE_CAPACITY { - connection.close( - CONNECTION_CLOSE_ERROR_CODE_DROPPED, - CONNECTION_CLOSE_REASON_DROPPED, - ); - return; - } - [Some(remote_pubkey), None].map(|remote_pubkey| { - let key = (remote_address, remote_pubkey); - cache.entry(key).or_default().clone() - }) + let (old, should_prune_cache) = { + let mut cache = cache.lock().await; + ( + cache.insert(remote_pubkey, connection), + cache.len() >= CONNECTION_CACHE_CAPACITY.saturating_mul(2), + ) }; - let mut entry = entries[0].write().await; - *entries[1].write().await = Some(connection.clone()); - if let Some(old) = entry.replace(connection) { - drop(entry); + if let Some(old) = old { old.close( CONNECTION_CLOSE_ERROR_CODE_REPLACED, CONNECTION_CLOSE_REASON_REPLACED, ); } + if should_prune_cache && !prune_cache_pending.swap(true, Ordering::Relaxed) { + tokio::task::spawn(prune_connection_cache( + bank_forks, + prune_cache_pending, + router, + cache, + )); + } } async fn drop_connection( - remote_address: SocketAddr, remote_pubkey: Pubkey, connection: &Connection, - cache: &RwLock, + cache: &Mutex>, ) { - if connection.close_reason().is_none() { - connection.close( - CONNECTION_CLOSE_ERROR_CODE_DROPPED, - CONNECTION_CLOSE_REASON_DROPPED, - ); - } - let key = (remote_address, Some(remote_pubkey)); - if let Entry::Occupied(entry) = cache.write().await.entry(key) { - if matches!(entry.get().read().await.deref(), - Some(entry) if entry.stable_id() == connection.stable_id()) - { + connection.close( + CONNECTION_CLOSE_ERROR_CODE_DROPPED, + CONNECTION_CLOSE_REASON_DROPPED, + ); + if let Entry::Occupied(entry) = cache.lock().await.entry(remote_pubkey) { + if entry.get().stable_id() == connection.stable_id() { entry.remove(); } } - // Cache entry for (remote_address, None) will be lazily evicted. +} + +async fn prune_connection_cache( + bank_forks: Arc>, + prune_cache_pending: Arc, + router: Arc>>>, + cache: Arc>>, +) { + debug_assert!(prune_cache_pending.load(Ordering::Relaxed)); + let staked_nodes = { + let root_bank = bank_forks.read().unwrap().root_bank(); + root_bank.staked_nodes() + }; + { + let mut cache = cache.lock().await; + if cache.len() < CONNECTION_CACHE_CAPACITY.saturating_mul(2) { + prune_cache_pending.store(false, Ordering::Relaxed); + return; + } + let mut connections: Vec<_> = cache + .drain() + .filter(|(_, connection)| connection.close_reason().is_none()) + .map(|entry @ (pubkey, _)| { + let stake = staked_nodes.get(&pubkey).copied().unwrap_or_default(); + (stake, entry) + }) + .collect(); + connections + .select_nth_unstable_by_key(CONNECTION_CACHE_CAPACITY, |&(stake, _)| Reverse(stake)); + for (_, (_, connection)) in &connections[CONNECTION_CACHE_CAPACITY..] { + connection.close( + CONNECTION_CLOSE_ERROR_CODE_PRUNED, + CONNECTION_CLOSE_REASON_PRUNED, + ); + } + cache.extend( + connections + .into_iter() + .take(CONNECTION_CACHE_CAPACITY) + .map(|(_, entry)| entry), + ); + prune_cache_pending.store(false, Ordering::Relaxed); + } + router.write().await.retain(|_, sender| !sender.is_closed()); } impl From> for Error { @@ -537,11 +771,257 @@ impl From> for Error { } } +#[derive(Default)] +struct RepairQuicStats { + connect_error_invalid_remote_address: AtomicU64, + connect_error_other: AtomicU64, + connect_error_too_many_connections: AtomicU64, + connection_error_application_closed: AtomicU64, + connection_error_connection_closed: AtomicU64, + connection_error_locally_closed: AtomicU64, + connection_error_reset: AtomicU64, + connection_error_timed_out: AtomicU64, + connection_error_transport_error: AtomicU64, + connection_error_version_mismatch: AtomicU64, + invalid_identity: AtomicU64, + no_response_received: AtomicU64, + read_to_end_error_connection_lost: AtomicU64, + read_to_end_error_illegal_ordered_read: AtomicU64, + read_to_end_error_reset: AtomicU64, + read_to_end_error_too_long: AtomicU64, + read_to_end_error_unknown_stream: AtomicU64, + read_to_end_error_zero_rtt_rejected: AtomicU64, + read_to_end_timeout: AtomicU64, + router_try_send_error_full: AtomicU64, + write_error_connection_lost: AtomicU64, + write_error_stopped: AtomicU64, + write_error_unknown_stream: AtomicU64, + write_error_zero_rtt_rejected: AtomicU64, +} + +async fn report_metrics_task(name: &'static str, stats: Arc) { + const METRICS_SUBMIT_CADENCE: Duration = Duration::from_secs(2); + loop { + tokio::time::sleep(METRICS_SUBMIT_CADENCE).await; + report_metrics(name, &stats); + } +} + +fn record_error(err: &Error, stats: &RepairQuicStats) { + match err { + Error::CertificateError(_) => (), + Error::ChannelSendError => (), + Error::ConnectError(ConnectError::EndpointStopping) => { + add_metric!(stats.connect_error_other) + } + Error::ConnectError(ConnectError::TooManyConnections) => { + add_metric!(stats.connect_error_too_many_connections) + } + Error::ConnectError(ConnectError::InvalidDnsName(_)) => { + add_metric!(stats.connect_error_other) + } + Error::ConnectError(ConnectError::InvalidRemoteAddress(_)) => { + add_metric!(stats.connect_error_invalid_remote_address) + } + Error::ConnectError(ConnectError::NoDefaultClientConfig) => { + add_metric!(stats.connect_error_other) + } + Error::ConnectError(ConnectError::UnsupportedVersion) => { + add_metric!(stats.connect_error_other) + } + Error::ConnectionError(ConnectionError::VersionMismatch) => { + add_metric!(stats.connection_error_version_mismatch) + } + Error::ConnectionError(ConnectionError::TransportError(_)) => { + add_metric!(stats.connection_error_transport_error) + } + Error::ConnectionError(ConnectionError::ConnectionClosed(_)) => { + add_metric!(stats.connection_error_connection_closed) + } + Error::ConnectionError(ConnectionError::ApplicationClosed(_)) => { + add_metric!(stats.connection_error_application_closed) + } + Error::ConnectionError(ConnectionError::Reset) => add_metric!(stats.connection_error_reset), + Error::ConnectionError(ConnectionError::TimedOut) => { + add_metric!(stats.connection_error_timed_out) + } + Error::ConnectionError(ConnectionError::LocallyClosed) => { + add_metric!(stats.connection_error_locally_closed) + } + Error::InvalidIdentity(_) => add_metric!(stats.invalid_identity), + Error::IoError(_) => (), + Error::NoResponseReceived => add_metric!(stats.no_response_received), + Error::ReadToEndError(ReadToEndError::Read(ReadError::Reset(_))) => { + add_metric!(stats.read_to_end_error_reset) + } + Error::ReadToEndError(ReadToEndError::Read(ReadError::ConnectionLost(_))) => { + add_metric!(stats.read_to_end_error_connection_lost) + } + Error::ReadToEndError(ReadToEndError::Read(ReadError::UnknownStream)) => { + add_metric!(stats.read_to_end_error_unknown_stream) + } + Error::ReadToEndError(ReadToEndError::Read(ReadError::IllegalOrderedRead)) => { + add_metric!(stats.read_to_end_error_illegal_ordered_read) + } + Error::ReadToEndError(ReadToEndError::Read(ReadError::ZeroRttRejected)) => { + add_metric!(stats.read_to_end_error_zero_rtt_rejected) + } + Error::ReadToEndError(ReadToEndError::TooLong) => { + add_metric!(stats.read_to_end_error_too_long) + } + Error::ReadToEndTimeout => add_metric!(stats.read_to_end_timeout), + Error::TlsError(_) => (), + Error::WriteError(WriteError::Stopped(_)) => add_metric!(stats.write_error_stopped), + Error::WriteError(WriteError::ConnectionLost(_)) => { + add_metric!(stats.write_error_connection_lost) + } + Error::WriteError(WriteError::UnknownStream) => { + add_metric!(stats.write_error_unknown_stream) + } + Error::WriteError(WriteError::ZeroRttRejected) => { + add_metric!(stats.write_error_zero_rtt_rejected) + } + } +} + +fn report_metrics(name: &'static str, stats: &RepairQuicStats) { + macro_rules! reset_metric { + ($metric: expr) => { + $metric.swap(0, Ordering::Relaxed) + }; + } + datapoint_info!( + name, + ( + "connect_error_invalid_remote_address", + reset_metric!(stats.connect_error_invalid_remote_address), + i64 + ), + ( + "connect_error_other", + reset_metric!(stats.connect_error_other), + i64 + ), + ( + "connect_error_too_many_connections", + reset_metric!(stats.connect_error_too_many_connections), + i64 + ), + ( + "connection_error_application_closed", + reset_metric!(stats.connection_error_application_closed), + i64 + ), + ( + "connection_error_connection_closed", + reset_metric!(stats.connection_error_connection_closed), + i64 + ), + ( + "connection_error_locally_closed", + reset_metric!(stats.connection_error_locally_closed), + i64 + ), + ( + "connection_error_reset", + reset_metric!(stats.connection_error_reset), + i64 + ), + ( + "connection_error_timed_out", + reset_metric!(stats.connection_error_timed_out), + i64 + ), + ( + "connection_error_transport_error", + reset_metric!(stats.connection_error_transport_error), + i64 + ), + ( + "connection_error_version_mismatch", + reset_metric!(stats.connection_error_version_mismatch), + i64 + ), + ( + "invalid_identity", + reset_metric!(stats.invalid_identity), + i64 + ), + ( + "no_response_received", + reset_metric!(stats.no_response_received), + i64 + ), + ( + "read_to_end_error_connection_lost", + reset_metric!(stats.read_to_end_error_connection_lost), + i64 + ), + ( + "read_to_end_error_illegal_ordered_read", + reset_metric!(stats.read_to_end_error_illegal_ordered_read), + i64 + ), + ( + "read_to_end_error_reset", + reset_metric!(stats.read_to_end_error_reset), + i64 + ), + ( + "read_to_end_error_too_long", + reset_metric!(stats.read_to_end_error_too_long), + i64 + ), + ( + "read_to_end_error_unknown_stream", + reset_metric!(stats.read_to_end_error_unknown_stream), + i64 + ), + ( + "read_to_end_error_zero_rtt_rejected", + reset_metric!(stats.read_to_end_error_zero_rtt_rejected), + i64 + ), + ( + "read_to_end_timeout", + reset_metric!(stats.read_to_end_timeout), + i64 + ), + ( + "router_try_send_error_full", + reset_metric!(stats.router_try_send_error_full), + i64 + ), + ( + "write_error_connection_lost", + reset_metric!(stats.write_error_connection_lost), + i64 + ), + ( + "write_error_stopped", + reset_metric!(stats.write_error_stopped), + i64 + ), + ( + "write_error_unknown_stream", + reset_metric!(stats.write_error_unknown_stream), + i64 + ), + ( + "write_error_zero_rtt_rejected", + reset_metric!(stats.write_error_zero_rtt_rejected), + i64 + ), + ); +} + #[cfg(test)] mod tests { use { super::*, itertools::{izip, multiunzip}, + solana_ledger::genesis_utils::{create_genesis_config, GenesisConfigInfo}, + solana_runtime::bank::Bank, solana_sdk::signature::Signer, std::{iter::repeat_with, net::Ipv4Addr, time::Duration}, }; @@ -569,6 +1049,12 @@ mod tests { repeat_with(crossbeam_channel::unbounded::) .take(NUM_ENDPOINTS) .unzip(); + let bank_forks = { + let GenesisConfigInfo { genesis_config, .. } = + create_genesis_config(/*mint_lamports:*/ 100_000); + let bank = Bank::new_for_tests(&genesis_config); + BankForks::new_rw_arc(bank) + }; let (endpoints, senders, tasks): (Vec<_>, Vec<_>, Vec<_>) = multiunzip( keypairs .iter() @@ -581,6 +1067,7 @@ mod tests { socket, IpAddr::V4(Ipv4Addr::LOCALHOST), remote_request_sender, + bank_forks.clone(), ) .unwrap() }), diff --git a/core/src/repair/repair_service.rs b/core/src/repair/repair_service.rs index c7cfab03f8f..ad8bc413b68 100644 --- a/core/src/repair/repair_service.rs +++ b/core/src/repair/repair_service.rs @@ -1202,7 +1202,7 @@ mod test { pub fn test_generate_and_send_duplicate_repairs() { let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(10_000); let bank = Bank::new_for_tests(&genesis_config); - let bank_forks = Arc::new(RwLock::new(BankForks::new(bank))); + let bank_forks = BankForks::new_rw_arc(bank); let blockstore_path = get_tmp_ledger_path!(); let blockstore = Blockstore::open(&blockstore_path).unwrap(); let cluster_slots = ClusterSlots::default(); @@ -1301,7 +1301,7 @@ mod test { pub fn test_update_duplicate_slot_repair_addr() { let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(10_000); let bank = Bank::new_for_tests(&genesis_config); - let bank_forks = Arc::new(RwLock::new(BankForks::new(bank))); + let bank_forks = BankForks::new_rw_arc(bank); let dummy_addr = Some(( Pubkey::default(), UdpSocket::bind("0.0.0.0:0").unwrap().local_addr().unwrap(), diff --git a/core/src/repair/serve_repair.rs b/core/src/repair/serve_repair.rs index 8ab42c28829..fb9256be7eb 100644 --- a/core/src/repair/serve_repair.rs +++ b/core/src/repair/serve_repair.rs @@ -1561,7 +1561,7 @@ mod tests { fn test_serialize_deserialize_signed_request() { let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(10_000); let bank = Bank::new_for_tests(&genesis_config); - let bank_forks = Arc::new(RwLock::new(BankForks::new(bank))); + let bank_forks = BankForks::new_rw_arc(bank); let cluster_info = Arc::new(new_test_cluster_info()); let serve_repair = ServeRepair::new( cluster_info.clone(), @@ -1611,7 +1611,7 @@ mod tests { let mut bank = Bank::new_for_tests(&genesis_config); bank.feature_set = Arc::new(FeatureSet::all_enabled()); - let bank_forks = Arc::new(RwLock::new(BankForks::new(bank))); + let bank_forks = BankForks::new_rw_arc(bank); let serve_repair = ServeRepair::new( cluster_info, bank_forks, @@ -1647,7 +1647,7 @@ mod tests { fn test_map_requests_signed() { let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(10_000); let bank = Bank::new_for_tests(&genesis_config); - let bank_forks = Arc::new(RwLock::new(BankForks::new(bank))); + let bank_forks = BankForks::new_rw_arc(bank); let cluster_info = Arc::new(new_test_cluster_info()); let serve_repair = ServeRepair::new( cluster_info.clone(), @@ -1976,7 +1976,7 @@ mod tests { fn window_index_request() { let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(10_000); let bank = Bank::new_for_tests(&genesis_config); - let bank_forks = Arc::new(RwLock::new(BankForks::new(bank))); + let bank_forks = BankForks::new_rw_arc(bank); let cluster_slots = ClusterSlots::default(); let cluster_info = Arc::new(new_test_cluster_info()); let serve_repair = ServeRepair::new( @@ -2318,7 +2318,7 @@ mod tests { fn test_repair_with_repair_validators() { let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(10_000); let bank = Bank::new_for_tests(&genesis_config); - let bank_forks = Arc::new(RwLock::new(BankForks::new(bank))); + let bank_forks = BankForks::new_rw_arc(bank); let cluster_slots = ClusterSlots::default(); let cluster_info = Arc::new(new_test_cluster_info()); let me = cluster_info.my_contact_info(); diff --git a/core/src/replay_stage.rs b/core/src/replay_stage.rs index 37067ce38f5..aaf4a09d266 100644 --- a/core/src/replay_stage.rs +++ b/core/src/replay_stage.rs @@ -66,6 +66,7 @@ use { }, solana_sdk::{ clock::{BankId, Slot, MAX_PROCESSING_AGE, NUM_CONSECUTIVE_LEADER_SLOTS}, + feature_set, genesis_config::ClusterType, hash::Hash, pubkey::Pubkey, @@ -544,6 +545,21 @@ impl ReplayStage { let _exit = Finalizer::new(exit.clone()); let mut identity_keypair = cluster_info.keypair().clone(); let mut my_pubkey = identity_keypair.pubkey(); + if my_pubkey != tower.node_pubkey { + // set-identity was called during the startup procedure, ensure the tower is consistent + // before starting the loop. further calls to set-identity will reload the tower in the loop + let my_old_pubkey = tower.node_pubkey; + tower = Self::load_tower( + tower_storage.as_ref(), + &my_pubkey, + &vote_account, + &bank_forks, + ); + warn!( + "Identity changed during startup from {} to {}", + my_old_pubkey, my_pubkey + ); + } let (mut progress, mut heaviest_subtree_fork_choice) = Self::initialize_progress_and_fork_choice_with_locked_bank_forks( &bank_forks, @@ -946,28 +962,12 @@ impl ReplayStage { my_pubkey = identity_keypair.pubkey(); // Load the new identity's tower - tower = Tower::restore(tower_storage.as_ref(), &my_pubkey) - .and_then(|restored_tower| { - let root_bank = bank_forks.read().unwrap().root_bank(); - let slot_history = root_bank.get_slot_history(); - restored_tower.adjust_lockouts_after_replay( - root_bank.slot(), - &slot_history, - ) - }) - .unwrap_or_else(|err| { - if err.is_file_missing() { - Tower::new_from_bankforks( - &bank_forks.read().unwrap(), - &my_pubkey, - &vote_account, - ) - } else { - error!("Failed to load tower for {}: {}", my_pubkey, err); - std::process::exit(1); - } - }); - + tower = Self::load_tower( + tower_storage.as_ref(), + &my_pubkey, + &vote_account, + &bank_forks, + ); // Ensure the validator can land votes with the new identity before // becoming leader has_new_vote_been_rooted = !wait_for_vote_to_start_leader; @@ -1117,6 +1117,39 @@ impl ReplayStage { }) } + fn load_tower( + tower_storage: &dyn TowerStorage, + node_pubkey: &Pubkey, + vote_account: &Pubkey, + bank_forks: &Arc>, + ) -> Tower { + Tower::restore(tower_storage, node_pubkey) + .and_then(|restored_tower| { + let root_bank = bank_forks.read().unwrap().root_bank(); + let slot_history = root_bank.get_slot_history(); + restored_tower.adjust_lockouts_after_replay(root_bank.slot(), &slot_history) + }) + .unwrap_or_else(|err| { + if err.is_file_missing() { + Tower::new_from_bankforks( + &bank_forks.read().unwrap(), + node_pubkey, + vote_account, + ) + } else if err.is_too_old() { + warn!("Failed to load tower, too old for {}: {}. Creating a new tower from bankforks.", node_pubkey, err); + Tower::new_from_bankforks( + &bank_forks.read().unwrap(), + node_pubkey, + vote_account, + ) + } else { + error!("Failed to load tower for {}: {}", node_pubkey, err); + std::process::exit(1); + } + }) + } + fn check_for_vote_only_mode( heaviest_bank_slot: Slot, forks_root: Slot, @@ -1238,8 +1271,12 @@ impl ReplayStage { let duplicate_slots = blockstore .duplicate_slots_iterator(bank_forks.root_bank().slot()) .unwrap(); - let duplicate_slot_hashes = duplicate_slots - .filter_map(|slot| bank_forks.bank_hash(slot).map(|hash| (slot, hash))); + let duplicate_slot_hashes = duplicate_slots.filter_map(|slot| { + let bank = bank_forks.get(slot)?; + bank.feature_set + .is_active(&feature_set::consume_blockstore_duplicate_proofs::id()) + .then_some((slot, bank.hash())) + }); ( bank_forks.root_bank(), bank_forks.frozen_banks().values().cloned().collect(), @@ -1360,14 +1397,22 @@ impl ReplayStage { ); } - // Should not dump slots for which we were the leader if Some(*my_pubkey) == leader_schedule_cache.slot_leader_at(*duplicate_slot, None) { - panic!("We are attempting to dump a block that we produced. \ - This indicates that we are producing duplicate blocks, \ - or that there is a bug in our runtime/replay code which \ - causes us to compute different bank hashes than the rest of the cluster. \ - We froze slot {duplicate_slot} with hash {frozen_hash:?} while the cluster hash is {correct_hash}"); + if let Some(bank) = bank_forks.read().unwrap().get(*duplicate_slot) { + bank_hash_details::write_bank_hash_details_file(&bank) + .map_err(|err| { + warn!("Unable to write bank hash details file: {err}"); + }) + .ok(); + } else { + warn!("Unable to get bank for slot {duplicate_slot} from bank forks"); + } + panic!("We are attempting to dump a block that we produced. \ + This indicates that we are producing duplicate blocks, \ + or that there is a bug in our runtime/replay code which \ + causes us to compute different bank hashes than the rest of the cluster. \ + We froze slot {duplicate_slot} with hash {frozen_hash:?} while the cluster hash is {correct_hash}"); } let attempt_no = purge_repair_slot_counter @@ -1518,7 +1563,11 @@ impl ReplayStage { let bank = w_bank_forks .remove(*slot) .expect("BankForks should not have been purged yet"); - let _ = bank_hash_details::write_bank_hash_details_file(&bank); + bank_hash_details::write_bank_hash_details_file(&bank) + .map_err(|err| { + warn!("Unable to write bank hash details file: {err}"); + }) + .ok(); ((*slot, bank.bank_id()), bank) }) .unzip() @@ -2107,7 +2156,11 @@ impl ReplayStage { ); // If we previously marked this slot as duplicate in blockstore, let the state machine know - if !duplicate_slots_tracker.contains(&slot) && blockstore.get_duplicate_slot(slot).is_some() + if bank + .feature_set + .is_active(&feature_set::consume_blockstore_duplicate_proofs::id()) + && !duplicate_slots_tracker.contains(&slot) + && blockstore.get_duplicate_slot(slot).is_some() { let duplicate_state = DuplicateState::new_from_state( slot, @@ -2871,7 +2924,10 @@ impl ReplayStage { SlotStateUpdate::BankFrozen(bank_frozen_state), ); // If we previously marked this slot as duplicate in blockstore, let the state machine know - if !duplicate_slots_tracker.contains(&bank.slot()) + if bank + .feature_set + .is_active(&feature_set::consume_blockstore_duplicate_proofs::id()) + && !duplicate_slots_tracker.contains(&bank.slot()) && blockstore.get_duplicate_slot(bank.slot()).is_some() { let duplicate_state = DuplicateState::new_from_state( @@ -2918,9 +2974,13 @@ impl ReplayStage { Self::record_rewards(bank, rewards_recorder_sender); if let Some(ref block_metadata_notifier) = block_metadata_notifier { let block_metadata_notifier = block_metadata_notifier.read().unwrap(); + let parent_blockhash = bank + .parent() + .map(|bank| bank.last_blockhash()) + .unwrap_or_default(); block_metadata_notifier.notify_block_metadata( bank.parent_slot(), - &bank.parent_hash().to_string(), + &parent_blockhash.to_string(), bank.slot(), &bank.last_blockhash().to_string(), &bank.rewards, @@ -3869,6 +3929,7 @@ impl ReplayStage { epoch_slots_frozen_slots: &mut EpochSlotsFrozenSlots, drop_bank_sender: &Sender>>, ) { + bank_forks.read().unwrap().prune_program_cache(new_root); let removed_banks = bank_forks.write().unwrap().set_root( new_root, accounts_background_request_sender, @@ -4057,9 +4118,9 @@ pub(crate) mod tests { crate::{ consensus::{ progress_map::{ValidatorStakeInfo, RETRANSMIT_BASE_DELAY_MS}, - tower_storage::NullTowerStorage, + tower_storage::{FileTowerStorage, NullTowerStorage}, tree_diff::TreeDiff, - Tower, + Tower, VOTE_THRESHOLD_DEPTH, }, replay_stage::ReplayStage, vote_simulator::{self, VoteSimulator}, @@ -4081,7 +4142,7 @@ pub(crate) mod tests { }, solana_runtime::{ accounts_background_service::AbsRequestSender, - commitment::BlockCommitment, + commitment::{BlockCommitment, VOTE_THRESHOLD_SIZE}, genesis_utils::{GenesisConfigInfo, ValidatorVoteKeypairs}, }, solana_sdk::{ @@ -4105,6 +4166,7 @@ pub(crate) mod tests { iter, sync::{atomic::AtomicU64, Arc, RwLock}, }, + tempfile::tempdir, trees::{tr, Tree}, }; @@ -4359,7 +4421,7 @@ pub(crate) mod tests { fn test_handle_new_root() { let genesis_config = create_genesis_config(10_000).genesis_config; let bank0 = Bank::new_for_tests(&genesis_config); - let bank_forks = Arc::new(RwLock::new(BankForks::new(bank0))); + let bank_forks = BankForks::new_rw_arc(bank0); let root = 3; let root_bank = Bank::new_from_parent( @@ -4445,7 +4507,7 @@ pub(crate) mod tests { fn test_handle_new_root_ahead_of_highest_super_majority_root() { let genesis_config = create_genesis_config(10_000).genesis_config; let bank0 = Bank::new_for_tests(&genesis_config); - let bank_forks = Arc::new(RwLock::new(BankForks::new(bank0))); + let bank_forks = BankForks::new_rw_arc(bank0); let confirmed_root = 1; let fork = 2; let bank1 = Bank::new_from_parent( @@ -4848,7 +4910,7 @@ pub(crate) mod tests { } bank0.freeze(); let arc_bank0 = Arc::new(bank0); - let bank_forks = Arc::new(RwLock::new(BankForks::new_from_banks(&[arc_bank0], 0))); + let bank_forks = BankForks::new_from_banks(&[arc_bank0], 0); let exit = Arc::new(AtomicBool::new(false)); let block_commitment_cache = Arc::new(RwLock::new(BlockCommitmentCache::default())); @@ -5027,7 +5089,7 @@ pub(crate) mod tests { vote_simulator::initialize_state(&keypairs, 10_000); let mut latest_validator_votes_for_frozen_banks = LatestValidatorVotesForFrozenBanks::default(); - let bank0 = bank_forks.get(0).unwrap(); + let bank0 = bank_forks.read().unwrap().get(0).unwrap(); let my_keypairs = keypairs.get(&my_node_pubkey).unwrap(); let vote_tx = vote_transaction::new_vote_transaction( vec![0], @@ -5039,7 +5101,6 @@ pub(crate) mod tests { None, ); - let bank_forks = RwLock::new(bank_forks); let bank1 = Bank::new_from_parent(bank0.clone(), &my_node_pubkey, 1); bank1.process_transaction(&vote_tx).unwrap(); bank1.freeze(); @@ -5372,7 +5433,7 @@ pub(crate) mod tests { ) { let stake = 10_000; let (bank_forks, _, _) = vote_simulator::initialize_state(all_keypairs, stake); - let root_bank = bank_forks.root_bank(); + let root_bank = bank_forks.read().unwrap().root_bank(); let mut propagated_stats = PropagatedStats { total_epoch_stake: stake * all_keypairs.len() as u64, ..PropagatedStats::default() @@ -5486,8 +5547,9 @@ pub(crate) mod tests { let vote_pubkey = vote_keypairs.vote_keypair.pubkey(); let keypairs: HashMap<_, _> = vec![(node_pubkey, vote_keypairs)].into_iter().collect(); let stake = 10_000; - let (mut bank_forks, mut progress_map, _) = + let (bank_forks_arc, mut progress_map, _) = vote_simulator::initialize_state(&keypairs, stake); + let mut bank_forks = bank_forks_arc.write().unwrap(); let bank0 = bank_forks.get(0).unwrap(); bank_forks.insert(Bank::new_from_parent(bank0.clone(), &Pubkey::default(), 9)); @@ -5529,12 +5591,14 @@ pub(crate) mod tests { // runs in `update_propagation_status` assert!(!progress_map.get_leader_propagation_slot_must_exist(10).0); + drop(bank_forks); + let vote_tracker = VoteTracker::default(); vote_tracker.insert_vote(10, vote_pubkey); ReplayStage::update_propagation_status( &mut progress_map, 10, - &RwLock::new(bank_forks), + &bank_forks_arc, &vote_tracker, &ClusterSlots::default(), ); @@ -5578,8 +5642,9 @@ pub(crate) mod tests { .collect(); let stake_per_validator = 10_000; - let (mut bank_forks, mut progress_map, _) = + let (bank_forks_arc, mut progress_map, _) = vote_simulator::initialize_state(&keypairs, stake_per_validator); + let mut bank_forks = bank_forks_arc.write().unwrap(); progress_map .get_propagated_stats_mut(0) .unwrap() @@ -5620,12 +5685,14 @@ pub(crate) mod tests { vote_tracker.insert_vote(10, *vote_pubkey); } + drop(bank_forks); + // The last bank should reach propagation threshold, and propagate it all // the way back through earlier leader banks ReplayStage::update_propagation_status( &mut progress_map, 10, - &RwLock::new(bank_forks), + &bank_forks_arc, &vote_tracker, &ClusterSlots::default(), ); @@ -5658,8 +5725,9 @@ pub(crate) mod tests { .collect(); let stake_per_validator = 10_000; - let (mut bank_forks, mut progress_map, _) = + let (bank_forks_arc, mut progress_map, _) = vote_simulator::initialize_state(&keypairs, stake_per_validator); + let mut bank_forks = bank_forks_arc.write().unwrap(); progress_map .get_propagated_stats_mut(0) .unwrap() @@ -5705,12 +5773,13 @@ pub(crate) mod tests { // Insert a new vote vote_tracker.insert_vote(10, vote_pubkeys[2]); + drop(bank_forks); // The last bank should reach propagation threshold, and propagate it all // the way back through earlier leader banks ReplayStage::update_propagation_status( &mut progress_map, 10, - &RwLock::new(bank_forks), + &bank_forks_arc, &vote_tracker, &ClusterSlots::default(), ); @@ -5816,7 +5885,8 @@ pub(crate) mod tests { let bank0 = Bank::new_for_tests(&genesis_config::create_genesis_config(10000).0); let parent_slot_bank = Bank::new_from_parent(Arc::new(bank0), &Pubkey::default(), parent_slot); - let mut bank_forks = BankForks::new(parent_slot_bank); + let bank_forks = BankForks::new_rw_arc(parent_slot_bank); + let mut bank_forks = bank_forks.write().unwrap(); let bank5 = Bank::new_from_parent(bank_forks.get(parent_slot).unwrap(), &Pubkey::default(), 5); bank_forks.insert(bank5); @@ -6366,7 +6436,7 @@ pub(crate) mod tests { &vote_tracker, &ClusterSlots::default(), &bank_forks, - &mut HeaviestSubtreeForkChoice::new_from_bank_forks(&bank_forks.read().unwrap()), + &mut HeaviestSubtreeForkChoice::new_from_bank_forks(bank_forks.clone()), &mut LatestValidatorVotesForFrozenBanks::default(), ); @@ -8132,7 +8202,7 @@ pub(crate) mod tests { let in_vote_only_mode = AtomicBool::new(false); let genesis_config = create_genesis_config(10_000).genesis_config; let bank0 = Bank::new_for_tests(&genesis_config); - let bank_forks = RwLock::new(BankForks::new(bank0)); + let bank_forks = BankForks::new_rw_arc(bank0); ReplayStage::check_for_vote_only_mode(1000, 0, &in_vote_only_mode, &bank_forks); assert!(in_vote_only_mode.load(Ordering::Relaxed)); ReplayStage::check_for_vote_only_mode(10, 0, &in_vote_only_mode, &bank_forks); @@ -8290,4 +8360,54 @@ pub(crate) mod tests { assert_eq!(reset_fork, Some(4)); assert_eq!(failures, vec![HeaviestForkFailures::LockedOut(4),]); } + + #[test] + fn test_tower_load_missing() { + let tower_file = tempdir().unwrap().into_path(); + let tower_storage = FileTowerStorage::new(tower_file); + let node_pubkey = Pubkey::new_unique(); + let vote_account = Pubkey::new_unique(); + let tree = tr(0) / (tr(1) / (tr(3) / (tr(4))) / (tr(2) / (tr(5) / (tr(6))))); + let generate_votes = |pubkeys: Vec| { + pubkeys + .into_iter() + .zip(iter::once(vec![0, 1, 2, 5, 6]).chain(iter::repeat(vec![0, 1, 3, 4]).take(2))) + .collect() + }; + let (vote_simulator, _blockstore) = + setup_forks_from_tree(tree, 3, Some(Box::new(generate_votes))); + let bank_forks = vote_simulator.bank_forks; + + let tower = + ReplayStage::load_tower(&tower_storage, &node_pubkey, &vote_account, &bank_forks); + let expected_tower = Tower::new_for_tests(VOTE_THRESHOLD_DEPTH, VOTE_THRESHOLD_SIZE); + assert_eq!(tower.vote_state, expected_tower.vote_state); + assert_eq!(tower.node_pubkey, node_pubkey); + } + + #[test] + fn test_tower_load() { + let tower_file = tempdir().unwrap().into_path(); + let tower_storage = FileTowerStorage::new(tower_file); + let node_keypair = Keypair::new(); + let node_pubkey = node_keypair.pubkey(); + let vote_account = Pubkey::new_unique(); + let tree = tr(0) / (tr(1) / (tr(3) / (tr(4))) / (tr(2) / (tr(5) / (tr(6))))); + let generate_votes = |pubkeys: Vec| { + pubkeys + .into_iter() + .zip(iter::once(vec![0, 1, 2, 5, 6]).chain(iter::repeat(vec![0, 1, 3, 4]).take(2))) + .collect() + }; + let (vote_simulator, _blockstore) = + setup_forks_from_tree(tree, 3, Some(Box::new(generate_votes))); + let bank_forks = vote_simulator.bank_forks; + let expected_tower = Tower::new_random(node_pubkey); + expected_tower.save(&tower_storage, &node_keypair).unwrap(); + + let tower = + ReplayStage::load_tower(&tower_storage, &node_pubkey, &vote_account, &bank_forks); + assert_eq!(tower.vote_state, expected_tower.vote_state); + assert_eq!(tower.node_pubkey, expected_tower.node_pubkey); + } } diff --git a/core/src/shred_fetch_stage.rs b/core/src/shred_fetch_stage.rs index 62733953cc7..703167b0b44 100644 --- a/core/src/shred_fetch_stage.rs +++ b/core/src/shred_fetch_stage.rs @@ -10,7 +10,9 @@ use { solana_perf::packet::{PacketBatch, PacketBatchRecycler, PacketFlags, PACKETS_PER_BATCH}, solana_runtime::bank_forks::BankForks, solana_sdk::{ - clock::DEFAULT_MS_PER_SLOT, + clock::{Slot, DEFAULT_MS_PER_SLOT}, + epoch_schedule::EpochSchedule, + feature_set::{self, FeatureSet}, packet::{Meta, PACKET_DATA_SIZE}, pubkey::Pubkey, }, @@ -50,12 +52,20 @@ impl ShredFetchStage { .as_ref() .map(|(_, cluster_info)| cluster_info.keypair().clone()); - let (mut last_root, mut last_slot, mut slots_per_epoch) = { + let ( + mut last_root, + mut slots_per_epoch, + mut feature_set, + mut epoch_schedule, + mut last_slot, + ) = { let bank_forks_r = bank_forks.read().unwrap(); let root_bank = bank_forks_r.root_bank(); ( root_bank.slot(), root_bank.get_slots_in_epoch(root_bank.epoch()), + root_bank.feature_set.clone(), + *root_bank.epoch_schedule(), bank_forks_r.highest_slot(), ) }; @@ -69,6 +79,8 @@ impl ShredFetchStage { last_slot = bank_forks_r.highest_slot(); bank_forks_r.root_bank() }; + feature_set = root_bank.feature_set.clone(); + epoch_schedule = *root_bank.epoch_schedule(); last_root = root_bank.slot(); slots_per_epoch = root_bank.get_slots_in_epoch(root_bank.epoch()); keypair = repair_context @@ -92,10 +104,19 @@ impl ShredFetchStage { // Limit shreds to 2 epochs away. let max_slot = last_slot + 2 * slots_per_epoch; + let should_drop_legacy_shreds = + |shred_slot| should_drop_legacy_shreds(shred_slot, &feature_set, &epoch_schedule); let turbine_disabled = turbine_disabled.load(Ordering::Relaxed); for packet in packet_batch.iter_mut().filter(|p| !p.meta().discard()) { if turbine_disabled - || should_discard_shred(packet, last_root, max_slot, shred_version, &mut stats) + || should_discard_shred( + packet, + last_root, + max_slot, + shred_version, + should_drop_legacy_shreds, + &mut stats, + ) { packet.meta_mut().set_discard(true); } else { @@ -373,6 +394,22 @@ pub(crate) fn receive_repair_quic_packets( } } +#[must_use] +fn should_drop_legacy_shreds( + shred_slot: Slot, + feature_set: &FeatureSet, + epoch_schedule: &EpochSchedule, +) -> bool { + match feature_set.activated_slot(&feature_set::drop_legacy_shreds::id()) { + None => false, + Some(feature_slot) => { + let feature_epoch = epoch_schedule.get_epoch(feature_slot); + let shred_epoch = epoch_schedule.get_epoch(shred_slot); + feature_epoch < shred_epoch + } + } +} + #[cfg(test)] mod tests { use { @@ -413,6 +450,7 @@ mod tests { last_root, max_slot, shred_version, + |_| false, // should_drop_legacy_shreds &mut stats, )); let coding = solana_ledger::shred::Shredder::generate_coding_shreds( @@ -426,6 +464,7 @@ mod tests { last_root, max_slot, shred_version, + |_| false, // should_drop_legacy_shreds &mut stats, )); } @@ -447,6 +486,7 @@ mod tests { last_root, max_slot, shred_version, + |_| false, // should_drop_legacy_shreds &mut stats, )); assert_eq!(stats.index_overrun, 1); @@ -468,12 +508,18 @@ mod tests { 3, max_slot, shred_version, + |_| false, // should_drop_legacy_shreds &mut stats, )); assert_eq!(stats.slot_out_of_range, 1); assert!(should_discard_shred( - &packet, last_root, max_slot, /*shred_version:*/ 345, &mut stats, + &packet, + last_root, + max_slot, + 345, // shred_version + |_| false, // should_drop_legacy_shreds + &mut stats, )); assert_eq!(stats.shred_version_mismatch, 1); @@ -483,6 +529,7 @@ mod tests { last_root, max_slot, shred_version, + |_| false, // should_drop_legacy_shreds &mut stats, )); @@ -504,6 +551,7 @@ mod tests { last_root, max_slot, shred_version, + |_| false, // should_drop_legacy_shreds &mut stats, )); @@ -515,6 +563,7 @@ mod tests { last_root, max_slot, shred_version, + |_| false, // should_drop_legacy_shreds &mut stats, )); } diff --git a/core/src/sigverify.rs b/core/src/sigverify.rs index 8140efac7ec..b496452078d 100644 --- a/core/src/sigverify.rs +++ b/core/src/sigverify.rs @@ -16,7 +16,7 @@ use { solana_sdk::{packet::Packet, saturating_add_assign}, }; -#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize, AbiExample)] pub struct SigverifyTracerPacketStats { pub total_removed_before_sigverify_stage: usize, pub total_tracer_packets_received_in_sigverify_stage: usize, diff --git a/core/src/snapshot_packager_service.rs b/core/src/snapshot_packager_service.rs index 4c90a8bd18d..6d81e2b5402 100644 --- a/core/src/snapshot_packager_service.rs +++ b/core/src/snapshot_packager_service.rs @@ -2,7 +2,7 @@ mod snapshot_gossip_manager; use { crossbeam_channel::{Receiver, Sender}, snapshot_gossip_manager::SnapshotGossipManager, - solana_gossip::cluster_info::{ClusterInfo, MAX_LEGACY_SNAPSHOT_HASHES}, + solana_gossip::cluster_info::ClusterInfo, solana_measure::measure_us, solana_perf::thread::renice_this_thread, solana_runtime::{ @@ -39,25 +39,13 @@ impl SnapshotPackagerService { snapshot_config: SnapshotConfig, enable_gossip_push: bool, ) -> Self { - let max_full_snapshot_hashes = std::cmp::min( - MAX_LEGACY_SNAPSHOT_HASHES, - snapshot_config - .maximum_full_snapshot_archives_to_retain - .get(), - ); - let t_snapshot_packager = Builder::new() .name("solSnapshotPkgr".to_string()) .spawn(move || { info!("SnapshotPackagerService has started"); renice_this_thread(snapshot_config.packager_thread_niceness_adj).unwrap(); - let mut snapshot_gossip_manager = enable_gossip_push.then(|| { - SnapshotGossipManager::new( - cluster_info, - max_full_snapshot_hashes, - starting_snapshot_hashes, - ) - }); + let mut snapshot_gossip_manager = enable_gossip_push + .then(|| SnapshotGossipManager::new(cluster_info, starting_snapshot_hashes)); loop { if exit.load(Ordering::Relaxed) { diff --git a/core/src/snapshot_packager_service/snapshot_gossip_manager.rs b/core/src/snapshot_packager_service/snapshot_gossip_manager.rs index a2d7239b319..d4ab9863642 100644 --- a/core/src/snapshot_packager_service/snapshot_gossip_manager.rs +++ b/core/src/snapshot_packager_service/snapshot_gossip_manager.rs @@ -4,7 +4,7 @@ use { snapshot_hash::{ FullSnapshotHash, IncrementalSnapshotHash, SnapshotHash, StartingSnapshotHashes, }, - snapshot_package::{retain_max_n_elements, SnapshotKind}, + snapshot_package::SnapshotKind, }, solana_sdk::{clock::Slot, hash::Hash}, std::sync::Arc, @@ -14,8 +14,6 @@ use { pub struct SnapshotGossipManager { cluster_info: Arc, latest_snapshot_hashes: Option, - max_legacy_full_snapshot_hashes: usize, - legacy_full_snapshot_hashes: Vec, } impl SnapshotGossipManager { @@ -24,14 +22,11 @@ impl SnapshotGossipManager { #[must_use] pub fn new( cluster_info: Arc, - max_legacy_full_snapshot_hashes: usize, starting_snapshot_hashes: Option, ) -> Self { let mut this = SnapshotGossipManager { cluster_info, latest_snapshot_hashes: None, - max_legacy_full_snapshot_hashes, - legacy_full_snapshot_hashes: Vec::default(), }; if let Some(starting_snapshot_hashes) = starting_snapshot_hashes { this.push_starting_snapshot_hashes(starting_snapshot_hashes); @@ -49,10 +44,6 @@ impl SnapshotGossipManager { ); } self.push_latest_snapshot_hashes_to_cluster(); - - // Handle legacy snapshot hashes here too - // Once LegacySnapshotHashes are removed from CRDS, also remove them here - self.push_legacy_full_snapshot_hash(starting_snapshot_hashes.full); } /// Push new snapshot hash to the cluster via CRDS @@ -78,10 +69,6 @@ impl SnapshotGossipManager { fn push_full_snapshot_hash(&mut self, full_snapshot_hash: FullSnapshotHash) { self.update_latest_full_snapshot_hash(full_snapshot_hash); self.push_latest_snapshot_hashes_to_cluster(); - - // Handle legacy snapshot hashes here too - // Once LegacySnapshotHashes are removed from CRDS, also remove them here - self.push_legacy_full_snapshot_hash(full_snapshot_hash); } /// Push new incremental snapshot hash to the cluster via CRDS @@ -146,22 +133,6 @@ impl SnapshotGossipManager { and a new error case has been added that has not been handled here.", ); } - - /// Add `full_snapshot_hash` to the vector of full snapshot hashes, then push that vector to - /// the cluster via CRDS. - fn push_legacy_full_snapshot_hash(&mut self, full_snapshot_hash: FullSnapshotHash) { - self.legacy_full_snapshot_hashes.push(full_snapshot_hash); - - retain_max_n_elements( - &mut self.legacy_full_snapshot_hashes, - self.max_legacy_full_snapshot_hashes, - ); - - self.cluster_info - .push_legacy_snapshot_hashes(clone_hashes_for_crds( - self.legacy_full_snapshot_hashes.as_slice(), - )); - } } #[derive(Debug, Copy, Clone, Eq, PartialEq)] @@ -191,8 +162,3 @@ impl AsSnapshotHash for IncrementalSnapshotHash { &self.0 } } - -/// Clones and maps snapshot hashes into what CRDS expects -fn clone_hashes_for_crds(hashes: &[impl AsSnapshotHash]) -> Vec<(Slot, Hash)> { - hashes.iter().map(AsSnapshotHash::clone_for_crds).collect() -} diff --git a/core/src/tpu.rs b/core/src/tpu.rs index 028a88f416e..93e76e70b17 100644 --- a/core/src/tpu.rs +++ b/core/src/tpu.rs @@ -34,7 +34,7 @@ use { solana_sdk::{clock::Slot, pubkey::Pubkey, signature::Keypair}, solana_streamer::{ nonblocking::quic::DEFAULT_WAIT_FOR_CHUNK_TIMEOUT, - quic::{spawn_server, MAX_STAKED_CONNECTIONS, MAX_UNSTAKED_CONNECTIONS}, + quic::{spawn_server_multi, MAX_STAKED_CONNECTIONS, MAX_UNSTAKED_CONNECTIONS}, streamer::StakedNodes, }, solana_turbine::broadcast_stage::{BroadcastStage, BroadcastStageType}, @@ -57,8 +57,8 @@ pub struct TpuSockets { pub transaction_forwards: Vec, pub vote: Vec, pub broadcast: Vec, - pub transactions_quic: UdpSocket, - pub transactions_forwards_quic: UdpSocket, + pub transactions_quic: Vec, + pub transactions_forwards_quic: Vec, } pub struct Tpu { @@ -148,7 +148,7 @@ impl Tpu { let (non_vote_sender, non_vote_receiver) = banking_tracer.create_channel_non_vote(); - let (_, tpu_quic_t) = spawn_server( + let (_, tpu_quic_t) = spawn_server_multi( "quic_streamer_tpu", transactions_quic_sockets, keypair, @@ -168,7 +168,7 @@ impl Tpu { ) .unwrap(); - let (_, tpu_forwards_quic_t) = spawn_server( + let (_, tpu_forwards_quic_t) = spawn_server_multi( "quic_streamer_tpu_forwards", transactions_forwards_quic_sockets, keypair, diff --git a/core/src/tvu.rs b/core/src/tvu.rs index 0b8358863fb..0fa619033ae 100644 --- a/core/src/tvu.rs +++ b/core/src/tvu.rs @@ -401,7 +401,7 @@ pub mod tests { let starting_balance = 10_000; let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(starting_balance); - let bank_forks = BankForks::new(Bank::new_for_tests(&genesis_config)); + let bank_forks = BankForks::new_rw_arc(Bank::new_for_tests(&genesis_config)); let keypair = Arc::new(Keypair::new()); let (turbine_quic_endpoint_sender, _turbine_quic_endpoint_receiver) = @@ -423,7 +423,7 @@ pub mod tests { } = Blockstore::open_with_signal(&blockstore_path, BlockstoreOptions::default()) .expect("Expected to successfully open ledger"); let blockstore = Arc::new(blockstore); - let bank = bank_forks.working_bank(); + let bank = bank_forks.read().unwrap().working_bank(); let (exit, poh_recorder, poh_service, _entry_receiver) = create_test_recorder(bank.clone(), blockstore.clone(), None, None); let vote_keypair = Keypair::new(); @@ -435,7 +435,6 @@ pub mod tests { let (replay_vote_sender, _replay_vote_receiver) = unbounded(); let (completed_data_sets_sender, _completed_data_sets_receiver) = unbounded(); let (_, gossip_confirmed_slots_receiver) = unbounded(); - let bank_forks = Arc::new(RwLock::new(bank_forks)); let max_complete_transaction_status_slot = Arc::new(AtomicU64::default()); let max_complete_rewards_slot = Arc::new(AtomicU64::default()); let ignored_prioritization_fee_cache = Arc::new(PrioritizationFeeCache::new(0u64)); diff --git a/core/src/validator.rs b/core/src/validator.rs index a0c39da7642..62ef6fe3762 100644 --- a/core/src/validator.rs +++ b/core/src/validator.rs @@ -108,7 +108,7 @@ use { clock::Slot, epoch_schedule::MAX_LEADER_SCHEDULE_EPOCH_OFFSET, exit::Exit, - genesis_config::GenesisConfig, + genesis_config::{ClusterType, GenesisConfig}, hash::Hash, pubkey::Pubkey, shred_version::compute_shred_version, @@ -245,6 +245,7 @@ pub struct ValidatorConfig { pub warp_slot: Option, pub accounts_db_test_hash_calculation: bool, pub accounts_db_skip_shrink: bool, + pub accounts_db_force_initial_clean: bool, pub tpu_coalesce: Duration, pub staked_nodes_overrides: Arc>>, pub validator_exit: Arc>, @@ -311,6 +312,7 @@ impl Default for ValidatorConfig { warp_slot: None, accounts_db_test_hash_calculation: false, accounts_db_skip_shrink: false, + accounts_db_force_initial_clean: false, tpu_coalesce: DEFAULT_TPU_COALESCE, staked_nodes_overrides: Arc::new(RwLock::new(HashMap::new())), validator_exit: Arc::new(RwLock::new(Exit::default())), @@ -463,12 +465,12 @@ pub struct Validator { ledger_metric_report_service: LedgerMetricReportService, accounts_background_service: AccountsBackgroundService, accounts_hash_verifier: AccountsHashVerifier, - turbine_quic_endpoint: Endpoint, + turbine_quic_endpoint: Option, turbine_quic_endpoint_runtime: Option, - turbine_quic_endpoint_join_handle: solana_turbine::quic_endpoint::AsyncTryJoinHandle, - repair_quic_endpoint: Endpoint, + turbine_quic_endpoint_join_handle: Option, + repair_quic_endpoint: Option, repair_quic_endpoint_runtime: Option, - repair_quic_endpoint_join_handle: repair::quic_endpoint::AsyncTryJoinHandle, + repair_quic_endpoint_join_handle: Option, } impl Validator { @@ -934,7 +936,8 @@ impl Validator { // (by both replay stage and banking stage) let prioritization_fee_cache = Arc::new(PrioritizationFeeCache::default()); - let rpc_override_health_check = Arc::new(AtomicBool::new(false)); + let rpc_override_health_check = + Arc::new(AtomicBool::new(config.rpc_config.disable_health_check)); let ( json_rpc_service, pubsub_service, @@ -971,7 +974,6 @@ impl Validator { ledger_path, config.validator_exit.clone(), exit.clone(), - config.known_validators.clone(), rpc_override_health_check.clone(), startup_verification_complete, optimistically_confirmed_bank.clone(), @@ -1134,11 +1136,11 @@ impl Validator { .map_err(|err| format!("{} [{:?}]", &err, &err))?; if banking_tracer.is_enabled() { info!( - "Enabled banking tracer (dir_byte_limit: {})", + "Enabled banking trace (dir_byte_limit: {})", config.banking_trace_dir_byte_limit ); } else { - info!("Disabled banking tracer"); + info!("Disabled banking trace"); } let entry_notification_sender = entry_notifier_service @@ -1151,56 +1153,74 @@ impl Validator { // Outside test-validator crate, we always need a tokio runtime (and // the respective handle) to initialize the turbine QUIC endpoint. let current_runtime_handle = tokio::runtime::Handle::try_current(); - let turbine_quic_endpoint_runtime = current_runtime_handle.is_err().then(|| { - tokio::runtime::Builder::new_multi_thread() - .enable_all() - .thread_name("solTurbineQuic") - .build() - .unwrap() - }); + let turbine_quic_endpoint_runtime = (current_runtime_handle.is_err() + && genesis_config.cluster_type != ClusterType::MainnetBeta) + .then(|| { + tokio::runtime::Builder::new_multi_thread() + .enable_all() + .thread_name("solTurbineQuic") + .build() + .unwrap() + }); let (turbine_quic_endpoint_sender, turbine_quic_endpoint_receiver) = unbounded(); let ( turbine_quic_endpoint, turbine_quic_endpoint_sender, turbine_quic_endpoint_join_handle, - ) = solana_turbine::quic_endpoint::new_quic_endpoint( - turbine_quic_endpoint_runtime - .as_ref() - .map(TokioRuntime::handle) - .unwrap_or_else(|| current_runtime_handle.as_ref().unwrap()), - &identity_keypair, - node.sockets.tvu_quic, - node.info - .tvu(Protocol::QUIC) - .expect("Operator must spin up node with valid QUIC TVU address") - .ip(), - turbine_quic_endpoint_sender, - ) - .unwrap(); - - // Repair quic endpoint. - let repair_quic_endpoint_runtime = current_runtime_handle.is_err().then(|| { - tokio::runtime::Builder::new_multi_thread() - .enable_all() - .thread_name("solRepairQuic") - .build() - .unwrap() - }); - let (repair_quic_endpoint, repair_quic_endpoint_sender, repair_quic_endpoint_join_handle) = - repair::quic_endpoint::new_quic_endpoint( - repair_quic_endpoint_runtime + ) = if genesis_config.cluster_type == ClusterType::MainnetBeta { + let (sender, _receiver) = tokio::sync::mpsc::channel(1); + (None, sender, None) + } else { + solana_turbine::quic_endpoint::new_quic_endpoint( + turbine_quic_endpoint_runtime .as_ref() .map(TokioRuntime::handle) .unwrap_or_else(|| current_runtime_handle.as_ref().unwrap()), &identity_keypair, - node.sockets.serve_repair_quic, + node.sockets.tvu_quic, node.info - .serve_repair(Protocol::QUIC) - .expect("Operator must spin up node with valid QUIC serve-repair address") + .tvu(Protocol::QUIC) + .expect("Operator must spin up node with valid QUIC TVU address") .ip(), - repair_quic_endpoint_sender, + turbine_quic_endpoint_sender, + bank_forks.clone(), ) - .unwrap(); + .map(|(endpoint, sender, join_handle)| (Some(endpoint), sender, Some(join_handle))) + .unwrap() + }; + + // Repair quic endpoint. + let repair_quic_endpoint_runtime = (current_runtime_handle.is_err() + && genesis_config.cluster_type != ClusterType::MainnetBeta) + .then(|| { + tokio::runtime::Builder::new_multi_thread() + .enable_all() + .thread_name("solRepairQuic") + .build() + .unwrap() + }); + let (repair_quic_endpoint, repair_quic_endpoint_sender, repair_quic_endpoint_join_handle) = + if genesis_config.cluster_type == ClusterType::MainnetBeta { + let (sender, _receiver) = tokio::sync::mpsc::channel(1); + (None, sender, None) + } else { + repair::quic_endpoint::new_quic_endpoint( + repair_quic_endpoint_runtime + .as_ref() + .map(TokioRuntime::handle) + .unwrap_or_else(|| current_runtime_handle.as_ref().unwrap()), + &identity_keypair, + node.sockets.serve_repair_quic, + node.info + .serve_repair(Protocol::QUIC) + .expect("Operator must spin up node with valid QUIC serve-repair address") + .ip(), + repair_quic_endpoint_sender, + bank_forks.clone(), + ) + .map(|(endpoint, sender, join_handle)| (Some(endpoint), sender, Some(join_handle))) + .unwrap() + }; let (replay_vote_sender, replay_vote_receiver) = unbounded(); let tvu = Tvu::new( @@ -1308,6 +1328,8 @@ impl Validator { ("id", id.to_string(), String), ("version", solana_version::version!(), String), ("cluster_type", cluster_type as u32, i64), + ("waited_for_supermajority", waited_for_supermajority, bool), + ("expected_shred_version", config.expected_shred_version, Option), ); *start_progress.write().unwrap() = ValidatorStartProgress::Running; @@ -1456,14 +1478,18 @@ impl Validator { } self.gossip_service.join().expect("gossip_service"); - repair::quic_endpoint::close_quic_endpoint(&self.repair_quic_endpoint); + if let Some(repair_quic_endpoint) = &self.repair_quic_endpoint { + repair::quic_endpoint::close_quic_endpoint(repair_quic_endpoint); + } self.serve_repair_service .join() .expect("serve_repair_service"); - self.repair_quic_endpoint_runtime - .map(|runtime| runtime.block_on(self.repair_quic_endpoint_join_handle)) - .transpose() - .unwrap(); + if let Some(repair_quic_endpoint_join_handle) = self.repair_quic_endpoint_join_handle { + self.repair_quic_endpoint_runtime + .map(|runtime| runtime.block_on(repair_quic_endpoint_join_handle)) + .transpose() + .unwrap(); + }; self.stats_reporter_service .join() .expect("stats_reporter_service"); @@ -1476,13 +1502,17 @@ impl Validator { self.accounts_hash_verifier .join() .expect("accounts_hash_verifier"); - solana_turbine::quic_endpoint::close_quic_endpoint(&self.turbine_quic_endpoint); + if let Some(turbine_quic_endpoint) = &self.turbine_quic_endpoint { + solana_turbine::quic_endpoint::close_quic_endpoint(turbine_quic_endpoint); + } self.tpu.join().expect("tpu"); self.tvu.join().expect("tvu"); - self.turbine_quic_endpoint_runtime - .map(|runtime| runtime.block_on(self.turbine_quic_endpoint_join_handle)) - .transpose() - .unwrap(); + if let Some(turbine_quic_endpoint_join_handle) = self.turbine_quic_endpoint_join_handle { + self.turbine_quic_endpoint_runtime + .map(|runtime| runtime.block_on(turbine_quic_endpoint_join_handle)) + .transpose() + .unwrap(); + } self.completed_data_sets_service .join() .expect("completed_data_sets_service"); @@ -1724,6 +1754,7 @@ fn load_blockstore( shrink_ratio: config.accounts_shrink_ratio, accounts_db_test_hash_calculation: config.accounts_db_test_hash_calculation, accounts_db_skip_shrink: config.accounts_db_skip_shrink, + accounts_db_force_initial_clean: config.accounts_db_force_initial_clean, runtime_config: config.runtime_config.clone(), use_snapshot_archives_at_startup: config.use_snapshot_archives_at_startup, ..blockstore_processor::ProcessOptions::default() @@ -2579,7 +2610,7 @@ mod tests { ); let (genesis_config, _mint_keypair) = create_genesis_config(1); - let bank_forks = RwLock::new(BankForks::new(Bank::new_for_tests(&genesis_config))); + let bank_forks = BankForks::new_rw_arc(Bank::new_for_tests(&genesis_config)); let mut config = ValidatorConfig::default_for_test(); let rpc_override_health_check = Arc::new(AtomicBool::new(false)); let start_progress = Arc::new(RwLock::new(ValidatorStartProgress::default())); @@ -2609,11 +2640,11 @@ mod tests { ); // bank=1, wait=0, should pass, bank is past the wait slot - let bank_forks = RwLock::new(BankForks::new(Bank::new_from_parent( + let bank_forks = BankForks::new_rw_arc(Bank::new_from_parent( bank_forks.read().unwrap().root_bank(), &Pubkey::default(), 1, - ))); + )); config.wait_for_supermajority = Some(0); assert!(!wait_for_supermajority( &config, diff --git a/core/src/vote_simulator.rs b/core/src/vote_simulator.rs index 79c418bcc78..77cf818f620 100644 --- a/core/src/vote_simulator.rs +++ b/core/src/vote_simulator.rs @@ -57,7 +57,7 @@ impl VoteSimulator { validator_keypairs, node_pubkeys, vote_pubkeys, - bank_forks: Arc::new(RwLock::new(bank_forks)), + bank_forks, progress, heaviest_subtree_fork_choice, latest_validator_votes_for_frozen_banks: LatestValidatorVotesForFrozenBanks::default(), @@ -296,13 +296,14 @@ impl VoteSimulator { false } + #[allow(clippy::type_complexity)] fn init_state( num_keypairs: usize, ) -> ( HashMap, Vec, Vec, - BankForks, + Arc>, ProgressMap, HeaviestSubtreeForkChoice, ) { @@ -338,7 +339,11 @@ impl VoteSimulator { pub fn initialize_state( validator_keypairs_map: &HashMap, stake: u64, -) -> (BankForks, ProgressMap, HeaviestSubtreeForkChoice) { +) -> ( + Arc>, + ProgressMap, + HeaviestSubtreeForkChoice, +) { let validator_keypairs: Vec<_> = validator_keypairs_map.values().collect(); let GenesisConfigInfo { mut genesis_config, @@ -366,7 +371,8 @@ pub fn initialize_state( 0, ForkProgress::new_from_bank(&bank0, bank0.collector_id(), &Pubkey::default(), None, 0, 0), ); - let bank_forks = BankForks::new(bank0); - let heaviest_subtree_fork_choice = HeaviestSubtreeForkChoice::new_from_bank_forks(&bank_forks); + let bank_forks = BankForks::new_rw_arc(bank0); + let heaviest_subtree_fork_choice = + HeaviestSubtreeForkChoice::new_from_bank_forks(bank_forks.clone()); (bank_forks, progress, heaviest_subtree_fork_choice) } diff --git a/core/src/window_service.rs b/core/src/window_service.rs index a68a20e2078..a36692cc7ef 100644 --- a/core/src/window_service.rs +++ b/core/src/window_service.rs @@ -1,6 +1,7 @@ //! `window_service` handles the data plane incoming shreds, storing them in //! blockstore and retransmitting where required //! + use { crate::{ cluster_info_vote_listener::VerifiedVoteReceiver, @@ -28,7 +29,12 @@ use { solana_metrics::inc_new_counter_error, solana_perf::packet::{Packet, PacketBatch}, solana_rayon_threadlimit::get_thread_count, - solana_sdk::clock::Slot, + solana_runtime::bank_forks::BankForks, + solana_sdk::{ + clock::{Slot, DEFAULT_MS_PER_SLOT}, + feature_set, + }, + solana_turbine::cluster_nodes, std::{ cmp::Reverse, collections::{HashMap, HashSet}, @@ -142,12 +148,31 @@ fn run_check_duplicate( blockstore: &Blockstore, shred_receiver: &Receiver, duplicate_slots_sender: &DuplicateSlotSender, + bank_forks: &RwLock, ) -> Result<()> { + let mut root_bank = bank_forks.read().unwrap().root_bank(); + let mut last_updated = Instant::now(); let check_duplicate = |shred: PossibleDuplicateShred| -> Result<()> { + if last_updated.elapsed().as_millis() as u64 > DEFAULT_MS_PER_SLOT { + // Grabs bank forks lock once a slot + last_updated = Instant::now(); + root_bank = bank_forks.read().unwrap().root_bank(); + } let shred_slot = shred.slot(); + let send_index_and_erasure_conflicts = cluster_nodes::check_feature_activation( + &feature_set::index_erasure_conflict_duplicate_proofs::id(), + shred_slot, + &root_bank, + ); let (shred1, shred2) = match shred { - PossibleDuplicateShred::LastIndexConflict(shred, conflict) => (shred, conflict), - PossibleDuplicateShred::ErasureConflict(shred, conflict) => (shred, conflict), + PossibleDuplicateShred::LastIndexConflict(shred, conflict) + | PossibleDuplicateShred::ErasureConflict(shred, conflict) => { + if send_index_and_erasure_conflicts { + (shred, conflict) + } else { + return Ok(()); + } + } PossibleDuplicateShred::Exists(shred) => { // Unlike the other cases we have to wait until here to decide to handle the duplicate and store // in blockstore. This is because the duplicate could have been part of the same insert batch, @@ -342,6 +367,7 @@ impl WindowService { let outstanding_requests = Arc::>::default(); let cluster_info = repair_info.cluster_info.clone(); + let bank_forks = repair_info.bank_forks.clone(); let repair_service = RepairService::new( blockstore.clone(), @@ -366,6 +392,7 @@ impl WindowService { blockstore.clone(), duplicate_receiver, duplicate_slots_sender, + bank_forks, ); let t_insert = Self::start_window_insert_thread( @@ -392,6 +419,7 @@ impl WindowService { blockstore: Arc, duplicate_receiver: Receiver, duplicate_slots_sender: DuplicateSlotSender, + bank_forks: Arc>, ) -> JoinHandle<()> { let handle_error = || { inc_new_counter_error!("solana-check-duplicate-error", 1, 1); @@ -405,6 +433,7 @@ impl WindowService { &blockstore, &duplicate_receiver, &duplicate_slots_sender, + &bank_forks, ) { if Self::should_exit_on_error(e, &handle_error) { break; @@ -507,9 +536,11 @@ mod test { solana_gossip::contact_info::ContactInfo, solana_ledger::{ blockstore::{make_many_slot_entries, Blockstore}, + genesis_utils::create_genesis_config, get_tmp_ledger_path_auto_delete, shred::{ProcessShredsStats, Shredder}, }, + solana_runtime::bank::Bank, solana_sdk::{ hash::Hash, signature::{Keypair, Signer}, @@ -556,6 +587,8 @@ mod test { #[test] fn test_run_check_duplicate() { let ledger_path = get_tmp_ledger_path_auto_delete!(); + let genesis_config = create_genesis_config(10_000).genesis_config; + let bank_forks = BankForks::new_rw_arc(Bank::new_for_tests(&genesis_config)); let blockstore = Arc::new(Blockstore::open(ledger_path.path()).unwrap()); let (sender, receiver) = unbounded(); let (duplicate_slot_sender, duplicate_slot_receiver) = unbounded(); @@ -587,6 +620,7 @@ mod test { &blockstore, &receiver, &duplicate_slot_sender, + &bank_forks, ) .unwrap(); @@ -616,6 +650,8 @@ mod test { Arc::new(keypair), SocketAddrSpace::Unspecified, )); + let genesis_config = create_genesis_config(10_000).genesis_config; + let bank_forks = BankForks::new_rw_arc(Bank::new_for_tests(&genesis_config)); // Start duplicate thread receiving and inserting duplicates let t_check_duplicate = WindowService::start_check_duplicate_thread( @@ -624,6 +660,7 @@ mod test { blockstore.clone(), duplicate_shred_receiver, duplicate_slot_sender, + bank_forks, ); let handle_duplicate = |shred| { diff --git a/core/tests/epoch_accounts_hash.rs b/core/tests/epoch_accounts_hash.rs index 1f6eb702769..5cc507b1551 100755 --- a/core/tests/epoch_accounts_hash.rs +++ b/core/tests/epoch_accounts_hash.rs @@ -116,13 +116,18 @@ impl TestEnvironment { ..snapshot_config }; - let mut bank_forks = BankForks::new(Bank::new_for_tests_with_config( + let bank_forks = BankForks::new_rw_arc(Bank::new_for_tests_with_config( &genesis_config_info.genesis_config, BankTestConfig::default(), )); - bank_forks.set_snapshot_config(Some(snapshot_config.clone())); - bank_forks.set_accounts_hash_interval_slots(Self::ACCOUNTS_HASH_INTERVAL); - let bank_forks = Arc::new(RwLock::new(bank_forks)); + bank_forks + .write() + .unwrap() + .set_snapshot_config(Some(snapshot_config.clone())); + bank_forks + .write() + .unwrap() + .set_accounts_hash_interval_slots(Self::ACCOUNTS_HASH_INTERVAL); let exit = Arc::new(AtomicBool::new(false)); let node_id = Arc::new(Keypair::new()); @@ -264,7 +269,7 @@ fn test_epoch_accounts_hash_basic(test_environment: TestEnvironment) { const NUM_EPOCHS_TO_TEST: u64 = 2; const SET_ROOT_INTERVAL: Slot = 3; - let bank_forks = &test_environment.bank_forks; + let bank_forks = test_environment.bank_forks.clone(); let mut expected_epoch_accounts_hash = None; @@ -299,6 +304,7 @@ fn test_epoch_accounts_hash_basic(test_environment: TestEnvironment) { // Set roots so that ABS requests are sent (this is what requests EAH calculations) if bank.slot().checked_rem(SET_ROOT_INTERVAL).unwrap() == 0 { trace!("rooting bank {}", bank.slot()); + bank_forks.read().unwrap().prune_program_cache(bank.slot()); bank_forks.write().unwrap().set_root( bank.slot(), &test_environment @@ -379,7 +385,7 @@ fn test_snapshots_have_expected_epoch_accounts_hash() { let test_environment = TestEnvironment::new_with_snapshots(FULL_SNAPSHOT_INTERVAL, FULL_SNAPSHOT_INTERVAL); - let bank_forks = &test_environment.bank_forks; + let bank_forks = test_environment.bank_forks.clone(); let slots_per_epoch = test_environment .genesis_config_info @@ -411,6 +417,7 @@ fn test_snapshots_have_expected_epoch_accounts_hash() { // Root every bank. This is what a normal validator does as well. // `set_root()` is also what requests snapshots and EAH calculations. + bank_forks.read().unwrap().prune_program_cache(bank.slot()); bank_forks.write().unwrap().set_root( bank.slot(), &test_environment @@ -461,6 +468,7 @@ fn test_snapshots_have_expected_epoch_accounts_hash() { AccountShrinkThreshold::default(), true, true, + false, true, None, None, @@ -495,7 +503,7 @@ fn test_background_services_request_handling_for_epoch_accounts_hash() { let test_environment = TestEnvironment::new_with_snapshots(FULL_SNAPSHOT_INTERVAL, FULL_SNAPSHOT_INTERVAL); - let bank_forks = &test_environment.bank_forks; + let bank_forks = test_environment.bank_forks.clone(); let snapshot_config = &test_environment.snapshot_config; let slots_per_epoch = test_environment @@ -529,10 +537,11 @@ fn test_background_services_request_handling_for_epoch_accounts_hash() { // Based on the EAH start and snapshot interval, pick a slot to mass-root all the banks in // this range such that an EAH request will be sent and also a snapshot request. let eah_start_slot = epoch_accounts_hash_utils::calculation_start(&bank); - let set_root_slot = next_multiple_of(eah_start_slot, FULL_SNAPSHOT_INTERVAL); + let set_root_slot = eah_start_slot.next_multiple_of(FULL_SNAPSHOT_INTERVAL); if bank.block_height() == set_root_slot { info!("Calling set_root() on bank {}...", bank.slot()); + bank_forks.read().unwrap().prune_program_cache(bank.slot()); bank_forks.write().unwrap().set_root( bank.slot(), &test_environment @@ -576,7 +585,7 @@ fn test_epoch_accounts_hash_and_warping() { solana_logger::setup(); let test_environment = TestEnvironment::new(); - let bank_forks = &test_environment.bank_forks; + let bank_forks = test_environment.bank_forks.clone(); let bank = bank_forks.read().unwrap().working_bank(); let epoch_schedule = test_environment .genesis_config_info @@ -589,6 +598,7 @@ fn test_epoch_accounts_hash_and_warping() { let eah_stop_slot_in_next_epoch = epoch_schedule.get_first_slot_in_epoch(bank.epoch() + 1) + eah_stop_offset; // have to set root here so that we can flush the write cache + bank_forks.read().unwrap().prune_program_cache(bank.slot()); bank_forks.write().unwrap().set_root( bank.slot(), &test_environment @@ -661,16 +671,3 @@ fn test_epoch_accounts_hash_and_warping() { .wait_get_epoch_accounts_hash(); info!("Waiting for epoch accounts hash... DONE"); } - -// Copy the impl of `next_multiple_of` since it is nightly-only experimental. -// https://doc.rust-lang.org/std/primitive.u64.html#method.next_multiple_of -// https://github.com/rust-lang/rust/issues/88581 -// https://github.com/rust-lang/rust/pull/88582 -// https://github.com/jhpratt/rust/blob/727a4fc7e3f836938dfeb4a2ab237cfca612222d/library/core/src/num/uint_macros.rs#L1811-L1837 -const fn next_multiple_of(lhs: u64, rhs: u64) -> u64 { - #![allow(clippy::arithmetic_side_effects)] - match lhs % rhs { - 0 => lhs, - r => lhs + (rhs - r), - } -} diff --git a/core/tests/snapshots.rs b/core/tests/snapshots.rs index b61e84a9081..9a862138a22 100644 --- a/core/tests/snapshots.rs +++ b/core/tests/snapshots.rs @@ -66,7 +66,7 @@ use { }; struct SnapshotTestConfig { - bank_forks: BankForks, + bank_forks: Arc>, genesis_config_info: GenesisConfigInfo, snapshot_config: SnapshotConfig, incremental_snapshot_archives_dir: TempDir, @@ -109,7 +109,8 @@ impl SnapshotTestConfig { ); bank0.freeze(); bank0.set_startup_verification_complete(); - let mut bank_forks = BankForks::new(bank0); + let bank_forks_arc = BankForks::new_rw_arc(bank0); + let mut bank_forks = bank_forks_arc.write().unwrap(); bank_forks.accounts_hash_interval_slots = accounts_hash_interval_slots; let snapshot_config = SnapshotConfig { @@ -125,7 +126,7 @@ impl SnapshotTestConfig { }; bank_forks.set_snapshot_config(Some(snapshot_config.clone())); SnapshotTestConfig { - bank_forks, + bank_forks: bank_forks_arc.clone(), genesis_config_info, snapshot_config, incremental_snapshot_archives_dir, @@ -138,11 +139,12 @@ impl SnapshotTestConfig { } fn restore_from_snapshot( - old_bank_forks: &BankForks, + old_bank_forks: Arc>, old_last_slot: Slot, old_genesis_config: &GenesisConfig, account_paths: &[PathBuf], ) { + let old_bank_forks = old_bank_forks.read().unwrap(); let snapshot_config = old_bank_forks.snapshot_config.as_ref().unwrap(); let old_last_bank = old_bank_forks.get(old_last_slot).unwrap(); @@ -171,6 +173,7 @@ fn restore_from_snapshot( check_hash_calculation, false, false, + false, Some(ACCOUNTS_DB_CONFIG_FOR_TESTING), None, Arc::default(), @@ -197,7 +200,7 @@ fn run_bank_forks_snapshot_n( { solana_logger::setup(); // Set up snapshotting config - let mut snapshot_test_config = SnapshotTestConfig::new( + let snapshot_test_config = SnapshotTestConfig::new( snapshot_version, cluster_type, set_root_interval, @@ -205,7 +208,7 @@ fn run_bank_forks_snapshot_n( DISABLED_SNAPSHOT_ARCHIVE_INTERVAL, ); - let bank_forks = &mut snapshot_test_config.bank_forks; + let bank_forks = snapshot_test_config.bank_forks.clone(); let mint_keypair = &snapshot_test_config.genesis_config_info.mint_keypair; let (accounts_package_sender, _accounts_package_receiver) = crossbeam_channel::unbounded(); @@ -218,16 +221,23 @@ fn run_bank_forks_snapshot_n( accounts_package_sender, }; for slot in 1..=last_slot { - let mut bank = - Bank::new_from_parent(bank_forks[slot - 1].clone(), &Pubkey::default(), slot); + let mut bank = Bank::new_from_parent( + bank_forks.read().unwrap().get(slot - 1).unwrap().clone(), + &Pubkey::default(), + slot, + ); f(&mut bank, mint_keypair); - let bank = bank_forks.insert(bank); + let bank = bank_forks.write().unwrap().insert(bank); // Set root to make sure we don't end up with too many account storage entries // and to allow snapshotting of bank and the purging logic on status_cache to // kick in if slot % set_root_interval == 0 || slot == last_slot { // set_root should send a snapshot request - bank_forks.set_root(bank.slot(), &request_sender, None); + bank_forks.read().unwrap().prune_program_cache(bank.slot()); + bank_forks + .write() + .unwrap() + .set_root(bank.slot(), &request_sender, None); snapshot_request_handler.handle_snapshot_requests( false, 0, @@ -238,7 +248,7 @@ fn run_bank_forks_snapshot_n( } // Generate a snapshot package for last bank - let last_bank = bank_forks.get(last_slot).unwrap(); + let last_bank = bank_forks.read().unwrap().get(last_slot).unwrap(); let snapshot_config = &snapshot_test_config.snapshot_config; let bank_snapshots_dir = &snapshot_config.bank_snapshots_dir; let last_bank_snapshot_info = snapshot_utils::get_highest_bank_snapshot_pre(bank_snapshots_dir) @@ -277,7 +287,12 @@ fn run_bank_forks_snapshot_n( let (_tmp_dir, temporary_accounts_dir) = create_tmp_accounts_dir_for_tests(); let account_paths = &[temporary_accounts_dir]; let genesis_config = &snapshot_test_config.genesis_config_info.genesis_config; - restore_from_snapshot(bank_forks, last_slot, genesis_config, account_paths); + restore_from_snapshot( + snapshot_test_config.bank_forks.clone(), + last_slot, + genesis_config, + account_paths, + ); } #[test_case(V1_2_0, Development)] @@ -330,7 +345,7 @@ fn test_concurrent_snapshot_packaging( const MAX_BANK_SNAPSHOTS_TO_RETAIN: usize = 8; // Set up snapshotting config - let mut snapshot_test_config = SnapshotTestConfig::new( + let snapshot_test_config = SnapshotTestConfig::new( snapshot_version, cluster_type, 1, @@ -338,7 +353,7 @@ fn test_concurrent_snapshot_packaging( DISABLED_SNAPSHOT_ARCHIVE_INTERVAL, ); - let bank_forks = &mut snapshot_test_config.bank_forks; + let bank_forks = snapshot_test_config.bank_forks.clone(); let snapshot_config = &snapshot_test_config.snapshot_config; let bank_snapshots_dir = &snapshot_config.bank_snapshots_dir; let full_snapshot_archives_dir = &snapshot_config.full_snapshot_archives_dir; @@ -347,7 +362,7 @@ fn test_concurrent_snapshot_packaging( let genesis_config = &snapshot_test_config.genesis_config_info.genesis_config; // Take snapshot of zeroth bank - let bank0 = bank_forks.get(0).unwrap(); + let bank0 = bank_forks.read().unwrap().get(0).unwrap(); let storages = bank0.get_snapshot_storages(None); let slot_deltas = bank0.status_cache.read().unwrap().root_slot_deltas(); snapshot_bank_utils::add_bank_snapshot( @@ -380,7 +395,7 @@ fn test_concurrent_snapshot_packaging( for i in 0..MAX_BANK_SNAPSHOTS_TO_RETAIN + 2 { let parent_slot = i as Slot; let bank = Bank::new_from_parent( - bank_forks[parent_slot].clone(), + bank_forks.read().unwrap().get(parent_slot).unwrap().clone(), &Pubkey::default(), parent_slot + 1, ); @@ -424,10 +439,14 @@ fn test_concurrent_snapshot_packaging( ); accounts_package_sender.send(accounts_package).unwrap(); - bank_forks.insert(bank); + bank_forks.write().unwrap().insert(bank); if slot == saved_slot { // Find the relevant snapshot storages - let snapshot_storage_files: HashSet<_> = bank_forks[slot] + let snapshot_storage_files: HashSet<_> = bank_forks + .read() + .unwrap() + .get(slot) + .unwrap() .get_snapshot_storages(None) .into_iter() .map(|s| s.get_path()) @@ -585,24 +604,31 @@ fn test_slots_to_snapshot(snapshot_version: SnapshotVersion, cluster_type: Clust for add_root_interval in &[1, 3, 9] { let (snapshot_sender, _snapshot_receiver) = unbounded(); // Make sure this test never clears bank.slots_since_snapshot - let mut snapshot_test_config = SnapshotTestConfig::new( + let snapshot_test_config = SnapshotTestConfig::new( snapshot_version, cluster_type, (*add_root_interval * num_set_roots * 2) as Slot, (*add_root_interval * num_set_roots * 2) as Slot, DISABLED_SNAPSHOT_ARCHIVE_INTERVAL, ); - let mut current_bank = snapshot_test_config.bank_forks[0].clone(); + let bank_forks = snapshot_test_config.bank_forks.clone(); + let bank_forks_r = bank_forks.read().unwrap(); + let mut current_bank = bank_forks_r[0].clone(); + drop(bank_forks_r); let request_sender = AbsRequestSender::new(snapshot_sender); for _ in 0..num_set_roots { for _ in 0..*add_root_interval { let new_slot = current_bank.slot() + 1; let new_bank = Bank::new_from_parent(current_bank, &Pubkey::default(), new_slot); - snapshot_test_config.bank_forks.insert(new_bank); - current_bank = snapshot_test_config.bank_forks[new_slot].clone(); + current_bank = bank_forks.write().unwrap().insert(new_bank).clone(); } - snapshot_test_config - .bank_forks + bank_forks + .read() + .unwrap() + .prune_program_cache(current_bank.slot()); + bank_forks + .write() + .unwrap() .set_root(current_bank.slot(), &request_sender, None); // Since the accounts background services are not runnning, EpochAccountsHash @@ -628,10 +654,10 @@ fn test_slots_to_snapshot(snapshot_version: SnapshotVersion, cluster_type: Clust let expected_slots_to_snapshot = num_old_slots as u64..=num_set_roots as u64 * *add_root_interval as u64; - let slots_to_snapshot = snapshot_test_config - .bank_forks - .get(snapshot_test_config.bank_forks.root()) + let slots_to_snapshot = bank_forks + .read() .unwrap() + .root_bank() .status_cache .read() .unwrap() @@ -703,7 +729,7 @@ fn test_bank_forks_incremental_snapshot( info!("Running bank forks incremental snapshot test, full snapshot interval: {} slots, incremental snapshot interval: {} slots, last slot: {}, set root interval: {} slots", FULL_SNAPSHOT_ARCHIVE_INTERVAL_SLOTS, INCREMENTAL_SNAPSHOT_ARCHIVE_INTERVAL_SLOTS, LAST_SLOT, SET_ROOT_INTERVAL); - let mut snapshot_test_config = SnapshotTestConfig::new( + let snapshot_test_config = SnapshotTestConfig::new( snapshot_version, cluster_type, SET_ROOT_INTERVAL, @@ -713,7 +739,7 @@ fn test_bank_forks_incremental_snapshot( trace!("SnapshotTestConfig:\naccounts_dir: {}\nbank_snapshots_dir: {}\nfull_snapshot_archives_dir: {}\nincremental_snapshot_archives_dir: {}", snapshot_test_config.accounts_dir.display(), snapshot_test_config.bank_snapshots_dir.path().display(), snapshot_test_config.full_snapshot_archives_dir.path().display(), snapshot_test_config.incremental_snapshot_archives_dir.path().display()); - let bank_forks = &mut snapshot_test_config.bank_forks; + let bank_forks = snapshot_test_config.bank_forks.clone(); let mint_keypair = &snapshot_test_config.genesis_config_info.mint_keypair; let (accounts_package_sender, _accounts_package_receiver) = crossbeam_channel::unbounded(); @@ -730,8 +756,8 @@ fn test_bank_forks_incremental_snapshot( for slot in 1..=LAST_SLOT { // Make a new bank and perform some transactions let bank = { - let bank = - Bank::new_from_parent(bank_forks[slot - 1].clone(), &Pubkey::default(), slot); + let parent = bank_forks.read().unwrap().get(slot - 1).unwrap(); + let bank = Bank::new_from_parent(parent, &Pubkey::default(), slot); let key = solana_sdk::pubkey::new_rand(); let tx = system_transaction::transfer(mint_keypair, &key, 1, bank.last_blockhash()); @@ -745,7 +771,7 @@ fn test_bank_forks_incremental_snapshot( bank.register_tick(&Hash::new_unique()); } - bank_forks.insert(bank) + bank_forks.write().unwrap().insert(bank) }; // Set root to make sure we don't end up with too many account storage entries @@ -753,7 +779,11 @@ fn test_bank_forks_incremental_snapshot( // kick in if slot % SET_ROOT_INTERVAL == 0 { // set_root sends a snapshot request - bank_forks.set_root(bank.slot(), &request_sender, None); + bank_forks.read().unwrap().prune_program_cache(bank.slot()); + bank_forks + .write() + .unwrap() + .set_root(bank.slot(), &request_sender, None); snapshot_request_handler.handle_snapshot_requests( false, 0, @@ -893,6 +923,7 @@ fn restore_from_snapshots_and_check_banks_are_equal( false, false, false, + false, Some(ACCOUNTS_DB_CONFIG_FOR_TESTING), None, Arc::default(), @@ -962,7 +993,7 @@ fn test_snapshots_with_background_services( let (accounts_package_sender, accounts_package_receiver) = unbounded(); let (snapshot_package_sender, snapshot_package_receiver) = unbounded(); - let bank_forks = Arc::new(RwLock::new(snapshot_test_config.bank_forks)); + let bank_forks = snapshot_test_config.bank_forks.clone(); bank_forks .read() .unwrap() @@ -1113,6 +1144,7 @@ fn test_snapshots_with_background_services( false, false, false, + false, Some(ACCOUNTS_DB_CONFIG_FOR_TESTING), None, exit.clone(), diff --git a/cost-model/src/block_cost_limits.rs b/cost-model/src/block_cost_limits.rs index 328d89cd041..b04f289e055 100644 --- a/cost-model/src/block_cost_limits.rs +++ b/cost-model/src/block_cost_limits.rs @@ -24,6 +24,10 @@ pub const MAX_CONCURRENCY: u64 = 4; pub const COMPUTE_UNIT_TO_US_RATIO: u64 = 30; /// Number of compute units for one signature verification. pub const SIGNATURE_COST: u64 = COMPUTE_UNIT_TO_US_RATIO * 24; +/// Number of compute units for one secp256k1 signature verification. +pub const SECP256K1_VERIFY_COST: u64 = COMPUTE_UNIT_TO_US_RATIO * 223; +/// Number of compute units for one ed25519 signature verification. +pub const ED25519_VERIFY_COST: u64 = COMPUTE_UNIT_TO_US_RATIO * 76; /// Number of compute units for one write lock pub const WRITE_LOCK_UNITS: u64 = COMPUTE_UNIT_TO_US_RATIO * 10; /// Number of data bytes per compute units @@ -43,8 +47,8 @@ lazy_static! { (bpf_loader::id(), solana_bpf_loader_program::DEFAULT_LOADER_COMPUTE_UNITS), (loader_v4::id(), solana_loader_v4_program::DEFAULT_COMPUTE_UNITS), // Note: These are precompile, run directly in bank during sanitizing; - (secp256k1_program::id(), COMPUTE_UNIT_TO_US_RATIO * 24), - (ed25519_program::id(), COMPUTE_UNIT_TO_US_RATIO * 24), + (secp256k1_program::id(), 0), + (ed25519_program::id(), 0), ] .iter() .cloned() diff --git a/cost-model/src/cost_model.rs b/cost-model/src/cost_model.rs index 0e8d6954202..d44d6550dc6 100644 --- a/cost-model/src/cost_model.rs +++ b/cost-model/src/cost_model.rs @@ -43,7 +43,7 @@ impl CostModel { } else { let mut tx_cost = UsageCostDetails::new_with_default_capacity(); - tx_cost.signature_cost = Self::get_signature_cost(transaction); + Self::get_signature_cost(&mut tx_cost, transaction); Self::get_write_lock_cost(&mut tx_cost, transaction); Self::get_transaction_cost(&mut tx_cost, transaction, feature_set); tx_cost.account_data_size = Self::calculate_account_data_size(transaction); @@ -69,8 +69,26 @@ impl CostModel { ) } - fn get_signature_cost(transaction: &SanitizedTransaction) -> u64 { - transaction.signatures().len() as u64 * SIGNATURE_COST + fn get_signature_cost(tx_cost: &mut UsageCostDetails, transaction: &SanitizedTransaction) { + let signatures_count_detail = transaction.message().get_signature_details(); + tx_cost.num_transaction_signatures = signatures_count_detail.num_transaction_signatures(); + tx_cost.num_secp256k1_instruction_signatures = + signatures_count_detail.num_secp256k1_instruction_signatures(); + tx_cost.num_ed25519_instruction_signatures = + signatures_count_detail.num_ed25519_instruction_signatures(); + tx_cost.signature_cost = signatures_count_detail + .num_transaction_signatures() + .saturating_mul(SIGNATURE_COST) + .saturating_add( + signatures_count_detail + .num_secp256k1_instruction_signatures() + .saturating_mul(SECP256K1_VERIFY_COST), + ) + .saturating_add( + signatures_count_detail + .num_ed25519_instruction_signatures() + .saturating_mul(ED25519_VERIFY_COST), + ); } fn get_writable_accounts(transaction: &SanitizedTransaction) -> Vec { diff --git a/cost-model/src/cost_tracker.rs b/cost-model/src/cost_tracker.rs index e4f1b917d74..33b0172eacd 100644 --- a/cost-model/src/cost_tracker.rs +++ b/cost-model/src/cost_tracker.rs @@ -62,6 +62,9 @@ pub struct CostTracker { /// The amount of total account data size remaining. If `Some`, then do not add transactions /// that would cause `account_data_size` to exceed this limit. account_data_size_limit: Option, + transaction_signature_count: u64, + secp256k1_instruction_signature_count: u64, + ed25519_instruction_signature_count: u64, } impl Default for CostTracker { @@ -82,6 +85,9 @@ impl Default for CostTracker { transaction_count: 0, account_data_size: 0, account_data_size_limit: None, + transaction_signature_count: 0, + secp256k1_instruction_signature_count: 0, + ed25519_instruction_signature_count: 0, } } } @@ -167,6 +173,21 @@ impl CostTracker { ("costliest_account", costliest_account.to_string(), String), ("costliest_account_cost", costliest_account_cost as i64, i64), ("account_data_size", self.account_data_size, i64), + ( + "transaction_signature_count", + self.transaction_signature_count, + i64 + ), + ( + "secp256k1_instruction_signature_count", + self.secp256k1_instruction_signature_count, + i64 + ), + ( + "ed25519_instruction_signature_count", + self.ed25519_instruction_signature_count, + i64 + ), ); } @@ -186,7 +207,9 @@ impl CostTracker { if self.vote_cost.saturating_add(cost) > self.vote_cost_limit { return Err(CostTrackerError::WouldExceedVoteMaxLimit); } - } else if self.block_cost.saturating_add(cost) > self.block_cost_limit { + } + + if self.block_cost.saturating_add(cost) > self.block_cost_limit { // check against the total package cost return Err(CostTrackerError::WouldExceedBlockMaxLimit); } @@ -232,6 +255,18 @@ impl CostTracker { self.add_transaction_execution_cost(tx_cost, tx_cost.sum()); saturating_add_assign!(self.account_data_size, tx_cost.account_data_size()); saturating_add_assign!(self.transaction_count, 1); + saturating_add_assign!( + self.transaction_signature_count, + tx_cost.num_transaction_signatures() + ); + saturating_add_assign!( + self.secp256k1_instruction_signature_count, + tx_cost.num_secp256k1_instruction_signatures() + ); + saturating_add_assign!( + self.ed25519_instruction_signature_count, + tx_cost.num_ed25519_instruction_signatures() + ); } fn remove_transaction_cost(&mut self, tx_cost: &TransactionCost) { @@ -241,6 +276,15 @@ impl CostTracker { .account_data_size .saturating_sub(tx_cost.account_data_size()); self.transaction_count = self.transaction_count.saturating_sub(1); + self.transaction_signature_count = self + .transaction_signature_count + .saturating_sub(tx_cost.num_transaction_signatures()); + self.secp256k1_instruction_signature_count = self + .secp256k1_instruction_signature_count + .saturating_sub(tx_cost.num_secp256k1_instruction_signatures()); + self.ed25519_instruction_signature_count = self + .ed25519_instruction_signature_count + .saturating_sub(tx_cost.num_ed25519_instruction_signatures()); } /// Apply additional actual execution units to cost_tracker diff --git a/cost-model/src/transaction_cost.rs b/cost-model/src/transaction_cost.rs index e765eee3bc7..170be03e037 100644 --- a/cost-model/src/transaction_cost.rs +++ b/cost-model/src/transaction_cost.rs @@ -87,6 +87,27 @@ impl TransactionCost { Self::Transaction(usage_cost) => &usage_cost.writable_accounts, } } + + pub fn num_transaction_signatures(&self) -> u64 { + match self { + Self::SimpleVote { .. } => 1, + Self::Transaction(usage_cost) => usage_cost.num_transaction_signatures, + } + } + + pub fn num_secp256k1_instruction_signatures(&self) -> u64 { + match self { + Self::SimpleVote { .. } => 0, + Self::Transaction(usage_cost) => usage_cost.num_secp256k1_instruction_signatures, + } + } + + pub fn num_ed25519_instruction_signatures(&self) -> u64 { + match self { + Self::SimpleVote { .. } => 0, + Self::Transaction(usage_cost) => usage_cost.num_ed25519_instruction_signatures, + } + } } const MAX_WRITABLE_ACCOUNTS: usize = 256; @@ -102,6 +123,9 @@ pub struct UsageCostDetails { pub bpf_execution_cost: u64, pub loaded_accounts_data_size_cost: u64, pub account_data_size: u64, + pub num_transaction_signatures: u64, + pub num_secp256k1_instruction_signatures: u64, + pub num_ed25519_instruction_signatures: u64, } impl Default for UsageCostDetails { @@ -115,6 +139,9 @@ impl Default for UsageCostDetails { bpf_execution_cost: 0u64, loaded_accounts_data_size_cost: 0u64, account_data_size: 0u64, + num_transaction_signatures: 0u64, + num_secp256k1_instruction_signatures: 0u64, + num_ed25519_instruction_signatures: 0u64, } } } @@ -133,6 +160,10 @@ impl PartialEq for UsageCostDetails { && self.bpf_execution_cost == other.bpf_execution_cost && self.loaded_accounts_data_size_cost == other.loaded_accounts_data_size_cost && self.account_data_size == other.account_data_size + && self.num_transaction_signatures == other.num_transaction_signatures + && self.num_secp256k1_instruction_signatures + == other.num_secp256k1_instruction_signatures + && self.num_ed25519_instruction_signatures == other.num_ed25519_instruction_signatures && to_hash_set(&self.writable_accounts) == to_hash_set(&other.writable_accounts) } } diff --git a/docs/src/api/http.md b/docs/src/api/http.md index 63163fbfa4c..9edf2c792cd 100644 --- a/docs/src/api/http.md +++ b/docs/src/api/http.md @@ -154,13 +154,11 @@ Some methods support providing a `filters` object to enable pre-filtering the da Although not a JSON RPC API, a `GET /health` at the RPC HTTP Endpoint provides a health-check mechanism for use by load balancers or other network infrastructure. This request will always return a HTTP 200 OK response with a body of -"ok", "behind" or "unknown" based on the following conditions: +"ok", "behind" or "unknown": -1. If one or more `--known-validator` arguments are provided to `solana-validator` - "ok" is returned - when the node has within `HEALTH_CHECK_SLOT_DISTANCE` slots of the highest - known validator, otherwise "behind". "unknown" is returned when no slot - information from known validators is not yet available. -2. "ok" is always returned if no known validators are provided. +- `ok`: The node is within `HEALTH_CHECK_SLOT_DISTANCE` slots from the latest cluster confirmed slot +- `behind { distance }`: The node is behind `distance` slots from the latest cluster confirmed slot where `distance > HEALTH_CHECK_SLOT_DISTANCE` +- `unknown`: The node is unable to determine where it stands in relation to the cluster ## JSON RPC API Reference diff --git a/docs/src/api/methods/_getHealth.mdx b/docs/src/api/methods/_getHealth.mdx index 482a2ff62a0..ceb30cc40fa 100644 --- a/docs/src/api/methods/_getHealth.mdx +++ b/docs/src/api/methods/_getHealth.mdx @@ -12,13 +12,8 @@ import { ## getHealth -Returns the current health of the node. - -:::caution -If one or more `--known-validator` arguments are provided to `solana-validator` - "ok" is returned -when the node has within `HEALTH_CHECK_SLOT_DISTANCE` slots of the highest known validator, -otherwise an error is returned. "ok" is always returned if no known validators are provided. -::: +Returns the current health of the node. A healthy node is one that is within +`HEALTH_CHECK_SLOT_DISTANCE` slots of the latest cluster confirmed slot. diff --git a/docs/src/cli/install-solana-cli-tools.md b/docs/src/cli/install-solana-cli-tools.md index 26a4ede5af6..db0b30b607e 100644 --- a/docs/src/cli/install-solana-cli-tools.md +++ b/docs/src/cli/install-solana-cli-tools.md @@ -54,7 +54,7 @@ Please update your PATH environment variable to include the solana programs: solana --version ``` -- After a successful install, `solana-install update` may be used to easily +- After a successful install, `agave-install update` may be used to easily update the Solana software to a newer version at any time. --- @@ -72,7 +72,7 @@ solana --version installer into a temporary directory: ```bash -cmd /c "curl https://release.solana.com/LATEST_SOLANA_RELEASE_VERSION/solana-install-init-x86_64-pc-windows-msvc.exe --output C:\solana-install-tmp\solana-install-init.exe --create-dirs" +cmd /c "curl https://release.solana.com/LATEST_SOLANA_RELEASE_VERSION/agave-install-init-x86_64-pc-windows-msvc.exe --output C:\agave-install-tmp\agave-install-init.exe --create-dirs" ``` - Copy and paste the following command, then press Enter to install the latest @@ -80,7 +80,7 @@ cmd /c "curl https://release.solana.com/LATEST_SOLANA_RELEASE_VERSION/solana-ins to allow the program to run. ```bash -C:\solana-install-tmp\solana-install-init.exe LATEST_SOLANA_RELEASE_VERSION +C:\agave-install-tmp\agave-install-init.exe LATEST_SOLANA_RELEASE_VERSION ``` - When the installer is finished, press Enter. @@ -95,12 +95,12 @@ C:\solana-install-tmp\solana-install-init.exe LATEST_SOLANA_RELEASE_VERSION solana --version ``` -- After a successful install, `solana-install update` may be used to easily +- After a successful install, `agave-install update` may be used to easily update the Solana software to a newer version at any time. ## Download Prebuilt Binaries -If you would rather not use `solana-install` to manage the install, you can +If you would rather not use `agave-install` to manage the install, you can manually download and install the binaries. ### Linux @@ -161,7 +161,7 @@ You can then run the following command to obtain the same result as with prebuilt binaries: ```bash -solana-install init +agave-install init ``` ## Use Homebrew diff --git a/docs/src/cluster/bench-tps.md b/docs/src/cluster/bench-tps.md index d913f9e5f16..35978cdd096 100644 --- a/docs/src/cluster/bench-tps.md +++ b/docs/src/cluster/bench-tps.md @@ -108,7 +108,7 @@ For example Generally we are using `debug` for infrequent debug messages, `trace` for potentially frequent messages and `info` for performance-related logging. -You can also attach to a running process with GDB. The leader's process is named _solana-validator_: +You can also attach to a running process with GDB. The leader's process is named _agave-validator_: ```bash sudo gdb diff --git a/docs/src/clusters.md b/docs/src/clusters.md index 5d59e7ea6cc..08102c68a8a 100644 --- a/docs/src/clusters.md +++ b/docs/src/clusters.md @@ -39,10 +39,10 @@ export SOLANA_METRICS_CONFIG="host=https://metrics.solana.com:8086,db=devnet,u=s solana config set --url https://api.devnet.solana.com ``` -##### Example `solana-validator` command-line +##### Example `agave-validator` command-line ```bash -$ solana-validator \ +$ agave-validator \ --identity validator-keypair.json \ --vote-account vote-account-keypair.json \ --known-validator dv1ZAGvdsz5hHLwWXsVnM94hWf1pjbKVau1QVkaMJ92 \ @@ -91,10 +91,10 @@ export SOLANA_METRICS_CONFIG="host=https://metrics.solana.com:8086,db=tds,u=test solana config set --url https://api.testnet.solana.com ``` -##### Example `solana-validator` command-line +##### Example `agave-validator` command-line ```bash -$ solana-validator \ +$ agave-validator \ --identity validator-keypair.json \ --vote-account vote-account-keypair.json \ --known-validator 5D1fNXzvv5NjV1ysLjirC4WY92RNsVH18vjmcszZd8on \ @@ -143,10 +143,10 @@ export SOLANA_METRICS_CONFIG="host=https://metrics.solana.com:8086,db=mainnet-be solana config set --url https://api.mainnet-beta.solana.com ``` -##### Example `solana-validator` command-line +##### Example `agave-validator` command-line ```bash -$ solana-validator \ +$ agave-validator \ --identity ~/validator-keypair.json \ --vote-account ~/vote-account-keypair.json \ --known-validator 7Np41oeYqPefeNQEHSv1UDhYrehxin3NStELsSKCT4K2 \ diff --git a/docs/src/developing/backwards-compatibility.md b/docs/src/developing/backwards-compatibility.md index 4a3c60b8e12..0fdc388ea2d 100644 --- a/docs/src/developing/backwards-compatibility.md +++ b/docs/src/developing/backwards-compatibility.md @@ -76,7 +76,7 @@ Major releases: - [`solana-program`](https://docs.rs/solana-program/) - Rust SDK for writing programs - [`solana-client`](https://docs.rs/solana-client/) - Rust client for connecting to RPC API - [`solana-cli-config`](https://docs.rs/solana-cli-config/) - Rust client for managing Solana CLI config files -- [`solana-geyser-plugin-interface`](https://docs.rs/solana-geyser-plugin-interface/) - Rust interface for developing Solana Geyser plugins. +- [`agave-geyser-plugin-interface`](https://docs.rs/agave-geyser-plugin-interface/) - Rust interface for developing Solana Geyser plugins. Patch releases: diff --git a/docs/src/developing/on-chain-programs/debugging.md b/docs/src/developing/on-chain-programs/debugging.md index fdbb1aebaa8..700985f2ee9 100644 --- a/docs/src/developing/on-chain-programs/debugging.md +++ b/docs/src/developing/on-chain-programs/debugging.md @@ -113,15 +113,15 @@ To turn on SBF interpreter trace messages in a local cluster configure the ## Source level debugging Source level debugging of on-chain programs written in Rust or C can -be done using the `program run` subcommand of `solana-ledger-tool`, +be done using the `program run` subcommand of `agave-ledger-tool`, and lldb, distributed with Solana Rust and Clang compiler binary package platform-tools. -The `solana-ledger-tool program run` subcommand loads a compiled +The `agave-ledger-tool program run` subcommand loads a compiled on-chain program, executes it in RBPF virtual machine and runs a gdb server that accepts incoming connections from LLDB or GDB. Once lldb -is connected to `solana-ledger-tool` gdbserver, it can control -execution of an on-chain program. Run `solana-ledger-tool program run +is connected to `agave-ledger-tool` gdbserver, it can control +execution of an on-chain program. Run `agave-ledger-tool program run --help` for an example of specifying input data for parameters of the program entrypoint function. @@ -131,7 +131,7 @@ loadable files, one a usual loadable module with the extension `.so`, and another the same loadable module but containing Dwarf debug information, a file with extension `.debug`. -To execute a program in debugger, run `solana-ledger-tool program run` +To execute a program in debugger, run `agave-ledger-tool program run` with `-e debugger` command line option. For example, a crate named 'helloworld' is compiled and an executable program is built in `target/deploy` directory. There should be three files in that @@ -139,27 +139,27 @@ directory - helloworld-keypair.json -- a keypair for deploying the program, - helloworld.debug -- a binary file containing debug information, - helloworld.so -- an executable file loadable into the virtual machine. -The command line for running `solana-ledger-tool` would be something like this +The command line for running `agave-ledger-tool` would be something like this ``` -solana-ledger-tool program run -l test-ledger -e debugger target/deploy/helloworld.so +agave-ledger-tool program run -l test-ledger -e debugger target/deploy/helloworld.so ``` -Note that `solana-ledger-tool` always loads a ledger database. Most +Note that `agave-ledger-tool` always loads a ledger database. Most on-chain programs interact with a ledger in some manner. Even if for debugging purpose a ledger is not needed, it has to be provided to -`solana-ledger-tool`. A minimal ledger database can be created by +`agave-ledger-tool`. A minimal ledger database can be created by running `solana-test-validator`, which creates a ledger in `test-ledger` subdirectory. -In debugger mode `solana-ledger-tool program run` loads an `.so` file and +In debugger mode `agave-ledger-tool program run` loads an `.so` file and starts listening for an incoming connection from a debugger ``` Waiting for a Debugger connection on "127.0.0.1:9001"... ``` -To connect to `solana-ledger-tool` and execute the program, run lldb. For +To connect to `agave-ledger-tool` and execute the program, run lldb. For debugging rust programs it may be beneficial to run solana-lldb wrapper to lldb, i.e. at a new shell prompt (other than the one used -to start `solana-ledger-tool`) run the command +to start `agave-ledger-tool`) run the command ``` solana-lldb @@ -181,7 +181,7 @@ If the debugger finds the file, it will print something like this Current executable set to '/path/helloworld.debug' (bpf). ``` -Now, connect to the gdb server that `solana-ledger-tool` implements, and +Now, connect to the gdb server that `agave-ledger-tool` implements, and debug the program as usual. Enter the following command at lldb prompt ``` (lldb) gdb-remote 127.0.0.1:9001 @@ -230,13 +230,13 @@ First file is `tasks.json` with the following content { "label": "solana-debugger", "type": "shell", - "command": "solana-ledger-tool program run -l test-ledger -e debugger ${workspaceFolder}/target/deploy/helloworld.so" + "command": "agave-ledger-tool program run -l test-ledger -e debugger ${workspaceFolder}/target/deploy/helloworld.so" } ] } ``` The first task is to build the on-chain program using cargo-build-sbf -utility. The second task is to run `solana-ledger-tool program run` in debugger mode. +utility. The second task is to run `agave-ledger-tool program run` in debugger mode. Another file is `launch.json` with the following content ``` @@ -254,12 +254,12 @@ Another file is `launch.json` with the following content } ``` This file specifies how to run debugger and to connect it to the gdb -server implemented by `solana-ledger-tool`. +server implemented by `agave-ledger-tool`. To start debugging a program, first build it by running the build task. The next step is to run `solana-debugger` task. The tasks specified in `tasks.json` file are started from `Terminal >> Run Task...` menu of -VSCode. When `solana-ledger-tool` is running and listening from incoming +VSCode. When `agave-ledger-tool` is running and listening from incoming connections, it's time to start the debugger. Launch it from VSCode `Run and Debug` menu. If everything is set up correctly, VSCode will start a debugging session and the program execution should stop on diff --git a/docs/src/developing/on-chain-programs/deploying.md b/docs/src/developing/on-chain-programs/deploying.md index 2f0d095444d..111ee98f8b7 100644 --- a/docs/src/developing/on-chain-programs/deploying.md +++ b/docs/src/developing/on-chain-programs/deploying.md @@ -56,7 +56,7 @@ As a data point of the number of accounts and potential data stored on-chain, be 5. **BPF Program Loader v2**: 191 accounts 6. **BPF Program Loader v1**: 150 accounts -> _Note: this data was pulled with a modified `solana-ledger-tool` built from this branch: [https://github.com/jstarry/solana/tree/large-account-stats](https://github.com/jstarry/solana/tree/large-account-stats)_ +> _Note: this data was pulled with a modified `agave-ledger-tool` built from this branch: [https://github.com/jstarry/solana/tree/large-account-stats](https://github.com/jstarry/solana/tree/large-account-stats)_ ### Reclaiming buffer accounts diff --git a/docs/src/developing/plugins/geyser-plugins.md b/docs/src/developing/plugins/geyser-plugins.md index 3ea07473a61..41e1b655e94 100644 --- a/docs/src/developing/plugins/geyser-plugins.md +++ b/docs/src/developing/plugins/geyser-plugins.md @@ -22,20 +22,20 @@ implementation for the PostgreSQL database. ### Important Crates: -- [`solana-geyser-plugin-interface`] — This crate defines the plugin +- [`agave-geyser-plugin-interface`] — This crate defines the plugin interfaces. - [`solana-accountsdb-plugin-postgres`] — The crate for the referential plugin implementation for the PostgreSQL database. -[`solana-geyser-plugin-interface`]: https://docs.rs/solana-geyser-plugin-interface +[`agave-geyser-plugin-interface`]: https://docs.rs/agave-geyser-plugin-interface [`solana-accountsdb-plugin-postgres`]: https://docs.rs/solana-accountsdb-plugin-postgres [`solana-sdk`]: https://docs.rs/solana-sdk [`solana-transaction-status`]: https://docs.rs/solana-transaction-status ## The Plugin Interface -The Plugin interface is declared in [`solana-geyser-plugin-interface`]. It +The Plugin interface is declared in [`agave-geyser-plugin-interface`]. It is defined by the trait `GeyserPlugin`. The plugin should implement the trait and expose a "C" function `_create_plugin` to return the pointer to this trait. For example, in the referential implementation, the following code @@ -164,7 +164,7 @@ please refer to [`solana-sdk`] and [`solana-transaction-status`] The `slot` points to the slot the transaction is executed at. For more details, please refer to the Rust documentation in -[`solana-geyser-plugin-interface`]. +[`agave-geyser-plugin-interface`]. ## Example PostgreSQL Plugin diff --git a/docs/src/getstarted/local.md b/docs/src/getstarted/local.md index 3a4358cac20..580c335749b 100644 --- a/docs/src/getstarted/local.md +++ b/docs/src/getstarted/local.md @@ -48,7 +48,7 @@ Confirm you have the desired version of `solana` installed by running: solana --version ``` -After a successful install, `solana-install update` may be used to easily update the Solana software to a newer version at any time. +After a successful install, `agave-install update` may be used to easily update the Solana software to a newer version at any time. @@ -70,7 +70,7 @@ After a successful install, `solana-install update` may be used to easily update installer into a temporary directory: ```bash -cmd /c "curl https://release.solana.com/stable/solana-install-init-x86_64-pc-windows-msvc.exe --output C:\solana-install-tmp\solana-install-init.exe --create-dirs" +cmd /c "curl https://release.solana.com/stable/agave-install-init-x86_64-pc-windows-msvc.exe --output C:\agave-install-tmp\agave-install-init.exe --create-dirs" ``` - Copy and paste the following command, then press Enter to install the latest @@ -78,7 +78,7 @@ cmd /c "curl https://release.solana.com/stable/solana-install-init-x86_64-pc-win to allow the program to run. ```bash -C:\solana-install-tmp\solana-install-init.exe stable +C:\agave-install-tmp\agave-install-init.exe stable ``` - When the installer is finished, press Enter. @@ -91,7 +91,7 @@ C:\solana-install-tmp\solana-install-init.exe stable solana --version ``` -After a successful install, `solana-install update` may be used to easily update the Solana software to a newer version at any time. +After a successful install, `agave-install update` may be used to easily update the Solana software to a newer version at any time. diff --git a/docs/src/implemented-proposals/installer.md b/docs/src/implemented-proposals/installer.md index a3ad797171c..c052aa7b4e5 100644 --- a/docs/src/implemented-proposals/installer.md +++ b/docs/src/implemented-proposals/installer.md @@ -13,16 +13,16 @@ This document proposes an easy to use software install and updater that can be u The easiest install method for supported platforms: ```bash -$ curl -sSf https://raw.githubusercontent.com/solana-labs/solana/v1.0.0/install/solana-install-init.sh | sh +$ curl -sSf https://raw.githubusercontent.com/solana-labs/solana/v1.0.0/install/agave-install-init.sh | sh ``` -This script will check github for the latest tagged release and download and run the `solana-install-init` binary from there. +This script will check github for the latest tagged release and download and run the `agave-install-init` binary from there. If additional arguments need to be specified during the installation, the following shell syntax is used: ```bash -$ init_args=.... # arguments for `solana-install-init ...` -$ curl -sSf https://raw.githubusercontent.com/solana-labs/solana/v1.0.0/install/solana-install-init.sh | sh -s - ${init_args} +$ init_args=.... # arguments for `agave-install-init ...` +$ curl -sSf https://raw.githubusercontent.com/solana-labs/solana/v1.0.0/install/agave-install-init.sh | sh -s - ${init_args} ``` ### Fetch and run a pre-built installer from a Github release @@ -30,9 +30,9 @@ $ curl -sSf https://raw.githubusercontent.com/solana-labs/solana/v1.0.0/install/ With a well-known release URL, a pre-built binary can be obtained for supported platforms: ```bash -$ curl -o solana-install-init https://github.com/solana-labs/solana/releases/download/v1.0.0/solana-install-init-x86_64-apple-darwin -$ chmod +x ./solana-install-init -$ ./solana-install-init --help +$ curl -o agave-install-init https://github.com/solana-labs/solana/releases/download/v1.0.0/agave-install-init-x86_64-apple-darwin +$ chmod +x ./agave-install-init +$ ./agave-install-init --help ``` ### Build and run the installer from source @@ -51,16 +51,16 @@ Given a solana release tarball \(as created by `ci/publish-tarball.sh`\) that ha ```bash $ solana-keygen new -o update-manifest.json # <-- only generated once, the public key is shared with users -$ solana-install deploy http://example.com/path/to/solana-release.tar.bz2 update-manifest.json +$ agave-install deploy http://example.com/path/to/solana-release.tar.bz2 update-manifest.json ``` ### Run a validator node that auto updates itself ```bash -$ solana-install init --pubkey 92DMonmBYXwEMHJ99c9ceRSpAmk9v6i3RdvDdXaVcrfj # <-- pubkey is obtained from whoever is deploying the updates -$ export PATH=~/.local/share/solana-install/bin:$PATH +$ agave-install init --pubkey 92DMonmBYXwEMHJ99c9ceRSpAmk9v6i3RdvDdXaVcrfj # <-- pubkey is obtained from whoever is deploying the updates +$ export PATH=~/.local/share/agave-install/bin:$PATH $ solana-keygen ... # <-- runs the latest solana-keygen -$ solana-install run solana-validator ... # <-- runs a validator, restarting it as necessary when an update is applied +$ agave-install run agave-validator ... # <-- runs a validator, restarting it as necessary when an update is applied ``` ## On-chain Update Manifest @@ -87,9 +87,9 @@ pub struct SignedUpdateManifest { } ``` -Note that the `manifest` field itself contains a corresponding signature \(`manifest_signature`\) to guard against man-in-the-middle attacks between the `solana-install` tool and the solana cluster RPC API. +Note that the `manifest` field itself contains a corresponding signature \(`manifest_signature`\) to guard against man-in-the-middle attacks between the `agave-install` tool and the solana cluster RPC API. -To guard against rollback attacks, `solana-install` will refuse to install an update with an older `timestamp_secs` than what is currently installed. +To guard against rollback attacks, `agave-install` will refuse to install an update with an older `timestamp_secs` than what is currently installed. ## Release Archive Contents @@ -101,17 +101,17 @@ A release archive is expected to be a tar file compressed with bzip2 with the fo - `/bin/` -- directory containing available programs in the release. - `solana-install` will symlink this directory to + `agave-install` will symlink this directory to - `~/.local/share/solana-install/bin` for use by the `PATH` environment + `~/.local/share/agave-install/bin` for use by the `PATH` environment variable. - `...` -- any additional files and directories are permitted -## solana-install Tool +## agave-install Tool -The `solana-install` tool is used by the user to install and update their cluster software. +The `agave-install` tool is used by the user to install and update their cluster software. It manages the following files and directories in the user's home directory: @@ -122,11 +122,11 @@ It manages the following files and directories in the user's home directory: ### Command-line Interface ```text -solana-install 0.16.0 +agave-install 0.16.0 The solana cluster software installer USAGE: - solana-install [OPTIONS] + agave-install [OPTIONS] FLAGS: -h, --help Prints help information @@ -145,11 +145,11 @@ SUBCOMMANDS: ``` ```text -solana-install-init +agave-install-init initializes a new installation USAGE: - solana-install init [OPTIONS] + agave-install init [OPTIONS] FLAGS: -h, --help Prints help information @@ -161,11 +161,11 @@ OPTIONS: ``` ```text -solana-install info +agave-install info displays information about the current installation USAGE: - solana-install info [FLAGS] + agave-install info [FLAGS] FLAGS: -h, --help Prints help information @@ -173,11 +173,11 @@ FLAGS: ``` ```text -solana-install deploy +agave-install deploy deploys a new update USAGE: - solana-install deploy + agave-install deploy FLAGS: -h, --help Prints help information @@ -188,22 +188,22 @@ ARGS: ``` ```text -solana-install update +agave-install update checks for an update, and if available downloads and applies it USAGE: - solana-install update + agave-install update FLAGS: -h, --help Prints help information ``` ```text -solana-install run +agave-install run Runs a program while periodically checking and applying software updates USAGE: - solana-install run [program_arguments]... + agave-install run [program_arguments]... FLAGS: -h, --help Prints help information diff --git a/docs/src/implemented-proposals/rpc-transaction-history.md b/docs/src/implemented-proposals/rpc-transaction-history.md index c8eb878eae4..d728254412b 100644 --- a/docs/src/implemented-proposals/rpc-transaction-history.md +++ b/docs/src/implemented-proposals/rpc-transaction-history.md @@ -68,7 +68,7 @@ results of BigTable queries more complicated but is not a significant issue. ## Data Population The ongoing population of instance data will occur on an epoch cadence through the -use of a new `solana-ledger-tool` command that will convert rocksdb data for a +use of a new `agave-ledger-tool` command that will convert rocksdb data for a given slot range into the instance schema. The same process will be run once, manually, to backfill the existing ledger diff --git a/docs/src/integrations/exchange.md b/docs/src/integrations/exchange.md index 2e8d70f9cc6..9de6d517fd6 100644 --- a/docs/src/integrations/exchange.md +++ b/docs/src/integrations/exchange.md @@ -28,7 +28,7 @@ To run an api node: 2. Start the validator with at least the following parameters: ```bash -solana-validator \ +agave-validator \ --ledger \ --identity \ --entrypoint \ @@ -44,14 +44,14 @@ solana-validator \ Customize `--ledger` to your desired ledger storage location, and `--rpc-port` to the port you want to expose. The `--entrypoint` and `--expected-genesis-hash` parameters are all specific to the cluster you are joining. -[Current parameters for Mainnet Beta](../clusters.md#example-solana-validator-command-line-2) +[Current parameters for Mainnet Beta](../clusters.md#example-agave-validator-command-line-2) The `--limit-ledger-size` parameter allows you to specify how many ledger [shreds](../terminology.md#shred) your node retains on disk. If you do not include this parameter, the validator will keep the entire ledger until it runs out of disk space. The default value attempts to keep the ledger disk usage under 500GB. More or less disk usage may be requested by adding an argument to -`--limit-ledger-size` if desired. Check `solana-validator --help` for the +`--limit-ledger-size` if desired. Check `agave-validator --help` for the default limit value used by `--limit-ledger-size`. More information about selecting a custom limit value is [available here](https://github.com/solana-labs/solana/blob/583cec922b6107e0f85c7e14cb5e642bc7dfb340/core/src/ledger_cleanup_service.rs#L15-L26). @@ -70,16 +70,16 @@ ensure you miss as little data as possible. Running the solana software as a systemd service is one great option. For monitoring, we provide -[`solana-watchtower`](https://github.com/solana-labs/solana/blob/master/watchtower/README.md), -which can monitor your validator and detect with the `solana-validator` process +[`agave-watchtower`](https://github.com/solana-labs/solana/blob/master/watchtower/README.md), +which can monitor your validator and detect with the `agave-validator` process is unhealthy. It can directly be configured to alert you via Slack, Telegram, -Discord, or Twillio. For details, run `solana-watchtower --help`. +Discord, or Twillio. For details, run `agave-watchtower --help`. ```bash -solana-watchtower --validator-identity +agave-watchtower --validator-identity ``` -> You can find more information about the [best practices for Solana Watchtower](../validator/best-practices/monitoring.md#solana-watchtower) here in the docs. +> You can find more information about the [best practices for Solana Watchtower](../validator/best-practices/monitoring.md#agave-watchtower) here in the docs. #### New Software Release Announcements @@ -103,7 +103,7 @@ known validators. This snapshot reflects the current state of the chain, but does not contain the complete historical ledger. If one of your node exits and boots from a new snapshot, there may be a gap in the ledger on that node. In order to prevent this issue, add the `--no-snapshot-fetch` parameter to your -`solana-validator` command to receive historical ledger data instead of a +`agave-validator` command to receive historical ledger data instead of a snapshot. Do not pass the `--no-snapshot-fetch` parameter on your initial boot as it's not diff --git a/docs/src/integrations/retrying-transactions.md b/docs/src/integrations/retrying-transactions.md index c2d7ff24be2..29cdeafa0c0 100644 --- a/docs/src/integrations/retrying-transactions.md +++ b/docs/src/integrations/retrying-transactions.md @@ -106,7 +106,7 @@ according to three ports: unable to process all transactions For more information on the TPU, please refer to -[this excellent writeup by Jito Labs](https://jito-labs.medium.com/solana-validator-101-transaction-processing-90bcdc271143). +[this excellent writeup by Jito Labs](https://jito-labs.medium.com/agave-validator-101-transaction-processing-90bcdc271143). ## How Transactions Get Dropped diff --git a/docs/src/running-validator/restart-cluster.md b/docs/src/running-validator/restart-cluster.md index 4039f69a6b4..4823ae2fd5c 100644 --- a/docs/src/running-validator/restart-cluster.md +++ b/docs/src/running-validator/restart-cluster.md @@ -5,7 +5,7 @@ In Solana 1.14 or greater, run the following command to output the latest optimistically confirmed slot your validator observed: ```bash -solana-ledger-tool -l ledger latest-optimistic-slots +agave-ledger-tool -l ledger latest-optimistic-slots ``` In Solana 1.13 or less, the latest optimistically confirmed can be found by looking for the more recent occurrence of @@ -28,11 +28,11 @@ instead. ### Step 4. Create a new snapshot for slot `SLOT_X` with a hard fork at slot `SLOT_X` ```bash -$ solana-ledger-tool -l --snapshot-archive-path --incremental-snapshot-archive-path create-snapshot SLOT_X --hard-fork SLOT_X +$ agave-ledger-tool -l --snapshot-archive-path --incremental-snapshot-archive-path create-snapshot SLOT_X --hard-fork SLOT_X ``` The snapshots directory should now contain the new snapshot. -`solana-ledger-tool create-snapshot` will also output the new shred version, and bank hash value, +`agave-ledger-tool create-snapshot` will also output the new shred version, and bank hash value, call this NEW_SHRED_VERSION and NEW_BANK_HASH respectively. Adjust your validator's arguments: @@ -62,7 +62,7 @@ Post something like the following to #announcements (adjusting the text as appro > 2. a. Preferred method, start from your local ledger with: > > ```bash -> solana-validator +> agave-validator > --wait-for-supermajority SLOT_X # <-- NEW! IMPORTANT! REMOVE AFTER THIS RESTART > --expected-bank-hash NEW_BANK_HASH # <-- NEW! IMPORTANT! REMOVE AFTER THIS RESTART > --hard-fork SLOT_X # <-- NEW! IMPORTANT! REMOVE AFTER THIS RESTART @@ -78,7 +78,7 @@ Post something like the following to #announcements (adjusting the text as appro > b. If your validator doesn't have ledger up to slot SLOT_X or if you have deleted your ledger, have it instead download a snapshot with: > > ```bash -> solana-validator +> agave-validator > --wait-for-supermajority SLOT_X # <-- NEW! IMPORTANT! REMOVE AFTER THIS RESTART > --expected-bank-hash NEW_BANK_HASH # <-- NEW! IMPORTANT! REMOVE AFTER THIS RESTART > --entrypoint entrypoint.testnet.solana.com:8001 @@ -89,7 +89,7 @@ Post something like the following to #announcements (adjusting the text as appro > ... # <-- your other --identity/--vote-account/etc arguments > ``` > -> You can check for which slots your ledger has with: `solana-ledger-tool -l path/to/ledger bounds` +> You can check for which slots your ledger has with: `agave-ledger-tool -l path/to/ledger bounds` > > 3. Wait until 80% of the stake comes online > @@ -116,7 +116,7 @@ and create a new snapshot with additional `--destake-vote-account ` arguments for each of the non-responsive validator's vote account address ```bash -$ solana-ledger-tool -l ledger create-snapshot SLOT_X ledger --hard-fork SLOT_X \ +$ agave-ledger-tool -l ledger create-snapshot SLOT_X ledger --hard-fork SLOT_X \ --destake-vote-account \ --destake-vote-account \ . diff --git a/docs/src/running-validator/validator-failover.md b/docs/src/running-validator/validator-failover.md index 34968b73640..1329dd58b22 100644 --- a/docs/src/running-validator/validator-failover.md +++ b/docs/src/running-validator/validator-failover.md @@ -82,11 +82,11 @@ For more information on etcd TLS setup, please refer to https://etcd.io/docs/v3.5/op-guide/security/#example-2-client-to-server-authentication-with-https-client-certificates ### Primary Validator -The following additional `solana-validator` parameters are required to enable +The following additional `agave-validator` parameters are required to enable tower storage into etcd: ``` -solana-validator ... \ +agave-validator ... \ --tower-storage etcd \ --etcd-cacert-file certs/etcd-ca.pem \ --etcd-cert-file certs/validator.pem \ @@ -100,7 +100,7 @@ that your etcd endpoint remain accessible at all times. ### Secondary Validator Configure the secondary validator like the primary with the exception of the -following `solana-validator` command-line argument changes: +following `agave-validator` command-line argument changes: * Generate and use a secondary validator identity: `--identity secondary-validator-keypair.json` * Add `--no-check-vote-account` * Add `--authorized-voter validator-keypair.json` (where @@ -111,8 +111,8 @@ When both validators are running normally and caught up to the cluster, a failover from primary to secondary can be triggered by running the following command on the secondary validator: ```bash -$ solana-validator wait-for-restart-window --identity validator-keypair.json \ - && solana-validator set-identity validator-keypair.json +$ agave-validator wait-for-restart-window --identity validator-keypair.json \ + && agave-validator set-identity validator-keypair.json ``` The secondary validator will acquire a lock on the tower in etcd to ensure @@ -128,7 +128,7 @@ exit. However if/when the secondary validator restarts, it will do so using the secondary validator identity and thus the restart cycle is broken. ## Triggering a failover via monitoring -Monitoring of your choosing can invoke the `solana-validator set-identity +Monitoring of your choosing can invoke the `agave-validator set-identity validator-keypair.json` command mentioned in the previous section. It is not necessary to guarantee the primary validator has halted before failing diff --git a/docs/src/running-validator/validator-stake.md b/docs/src/running-validator/validator-stake.md index ae430ae9675..71c9cd21301 100644 --- a/docs/src/running-validator/validator-stake.md +++ b/docs/src/running-validator/validator-stake.md @@ -3,7 +3,7 @@ title: Staking --- **By default your validator will have no stake.** This means it will be -ineligible to become leader. +ineligible to become leader, and unable to land votes. ## Monitoring Catch Up @@ -55,8 +55,25 @@ but only one re-delegation is permitted per epoch: solana delegate-stake ~/validator-stake-keypair.json ~/some-other-vote-account-keypair.json ``` -Assuming the node is voting, now you're up and running and generating validator -rewards. Rewards are paid automatically on epoch boundaries. +## Validator Stake Warm-up + +To combat various attacks on consensus, new stake delegations are subject to +a [warm-up](/staking/stake-accounts#delegation-warmup-and-cooldown) +period. + +Monitor a validator's stake during warmup by: + +- View your vote account:`solana vote-account ~/vote-account-keypair.json` This displays the current state of all the votes the validator has submitted to the network. +- View your stake account, the delegation preference and details of your stake:`solana stake-account ~/validator-stake-keypair.json` +- `solana validators` displays the current active stake of all validators, including yours +- `solana stake-history` shows the history of stake warming up and cooling down over recent epochs +- Look for log messages on your validator indicating your next leader slot: `[2019-09-27T20:16:00.319721164Z INFO solana_core::replay_stage] voted and reset PoH at tick height ####. My next leader slot is ####` +- Once your stake is warmed up, you will see a stake balance listed for your validator by running `solana validators` + +## Validator Rewards + +Once your stake is warmed up, and assuming the node is voting, you will now be +generating validator rewards. Rewards are paid automatically on epoch boundaries. The rewards lamports earned are split between your stake account and the vote account according to the commission rate set in the vote account. Rewards can @@ -76,21 +93,6 @@ before submitting a transaction. Learn more about [transaction fees here](../implemented-proposals/transaction-fees.md). -## Validator Stake Warm-up - -To combat various attacks on consensus, new stake delegations are subject to -a [warm-up](/staking/stake-accounts#delegation-warmup-and-cooldown) -period. - -Monitor a validator's stake during warmup by: - -- View your vote account:`solana vote-account ~/vote-account-keypair.json` This displays the current state of all the votes the validator has submitted to the network. -- View your stake account, the delegation preference and details of your stake:`solana stake-account ~/validator-stake-keypair.json` -- `solana validators` displays the current active stake of all validators, including yours -- `solana stake-history` shows the history of stake warming up and cooling down over recent epochs -- Look for log messages on your validator indicating your next leader slot: `[2019-09-27T20:16:00.319721164Z INFO solana_core::replay_stage] voted and reset PoH at tick height ####. My next leader slot is ####` -- Once your stake is warmed up, you will see a stake balance listed for your validator by running `solana validators` - ## Monitor Your Staked Validator Confirm your validator becomes a [leader](../terminology.md#leader) diff --git a/docs/src/running-validator/validator-start.md b/docs/src/running-validator/validator-start.md index d30533abd54..5a3f5ca3402 100644 --- a/docs/src/running-validator/validator-start.md +++ b/docs/src/running-validator/validator-start.md @@ -29,7 +29,7 @@ detail on cluster activity. ## Enabling CUDA If your machine has a GPU with CUDA installed \(Linux-only currently\), include -the `--cuda` argument to `solana-validator`. +the `--cuda` argument to `agave-validator`. When your validator is started look for the following log message to indicate that CUDA is enabled: `"[ solana::validator] CUDA is enabled"` @@ -44,7 +44,7 @@ the following commands. #### **Optimize sysctl knobs** ```bash -sudo bash -c "cat >/etc/sysctl.d/21-solana-validator.conf </etc/sysctl.d/21-agave-validator.conf <` -argument to `solana-validator`. You can specify multiple ones by repeating the argument `--known-validator --known-validator `. +argument to `agave-validator`. You can specify multiple ones by repeating the argument `--known-validator --known-validator `. This has two effects, one is when the validator is booting with `--only-known-rpc`, it will only ask that set of known nodes for downloading genesis and snapshot data. Another is that in combination with the `--halt-on-known-validators-accounts-hash-mismatch` option, it will monitor the merkle root hash of the entire accounts state of other known nodes on gossip and if the hashes produce any mismatch, @@ -274,13 +280,13 @@ account state divergence. Connect to the cluster by running: ```bash -solana-validator \ +agave-validator \ --identity ~/validator-keypair.json \ --vote-account ~/vote-account-keypair.json \ --rpc-port 8899 \ --entrypoint entrypoint.devnet.solana.com:8001 \ --limit-ledger-size \ - --log ~/solana-validator.log + --log ~/agave-validator.log ``` To force validator logging to the console add a `--log -` argument, otherwise @@ -293,7 +299,7 @@ The ledger will be placed in the `ledger/` directory by default, use the > [paper wallet seed phrase](../wallet-guide/paper-wallet.md) > for your `--identity` and/or > `--authorized-voter` keypairs. To use these, pass the respective argument as -> `solana-validator --identity ASK ... --authorized-voter ASK ...` +> `agave-validator --identity ASK ... --authorized-voter ASK ...` > and you will be prompted to enter your seed phrases and optional passphrase. Confirm your validator is connected to the network by opening a new terminal and @@ -309,7 +315,7 @@ If your validator is connected, its public key and IP address will appear in the By default the validator will dynamically select available network ports in the 8000-10000 range, and may be overridden with `--dynamic-port-range`. For -example, `solana-validator --dynamic-port-range 11000-11020 ...` will restrict +example, `agave-validator --dynamic-port-range 11000-11020 ...` will restrict the validator to ports 11000-11020. ### Limiting ledger size to conserve disk space @@ -363,8 +369,8 @@ WantedBy=multi-user.target ``` Now create `/home/sol/bin/validator.sh` to include the desired -`solana-validator` command-line. Ensure that the 'exec' command is used to -start the validator process (i.e. "exec solana-validator ..."). This is +`agave-validator` command-line. Ensure that the 'exec' command is used to +start the validator process (i.e. "exec agave-validator ..."). This is important because without it, logrotate will end up killing the validator every time the logs are rotated. @@ -391,14 +397,14 @@ to be reverted and the issue reproduced before help can be provided. #### Log rotation -The validator log file, as specified by `--log ~/solana-validator.log`, can get +The validator log file, as specified by `--log ~/agave-validator.log`, can get very large over time and it's recommended that log rotation be configured. The validator will re-open its log file when it receives the `USR1` signal, which is the basic primitive that enables log rotation. If the validator is being started by a wrapper shell script, it is important to -launch the process with `exec` (`exec solana-validator ...`) when using logrotate. +launch the process with `exec` (`exec agave-validator ...`) when using logrotate. This will prevent the `USR1` signal from being sent to the script's process instead of the validator's, which will kill them both. @@ -406,13 +412,13 @@ instead of the validator's, which will kill them both. An example setup for the `logrotate`, which assumes that the validator is running as a systemd service called `sol.service` and writes a log file at -/home/sol/solana-validator.log: +/home/sol/agave-validator.log: ```bash # Setup log rotation cat > logrotate.sol < @@ -72,14 +72,14 @@ Next, in your browser, go to `https://api.telegram.org/bot/getUp The response should be in JSON. Search for the string `"chat":` in the JSON. The `id` value of that chat is your `TELEGRAM_CHAT_ID`. It will be a negative number like: `-781559558`. Remember to include the negative sign! If you cannot find `"chat":` in the JSON, then you may have to remove the bot from your chat group and add it again. -With your Telegram chat id in hand, export the environment variable where you plan to run `solana-watchtower`: +With your Telegram chat id in hand, export the environment variable where you plan to run `agave-watchtower`: ``` export TELEGRAM_CHAT_ID= ``` -#### Restart solana-watchtower +#### Restart agave-watchtower -Once your environment variables are set, restart `solana-watchtower`. You should see output about your validator. +Once your environment variables are set, restart `agave-watchtower`. You should see output about your validator. To test that your Telegram configuration is working properly, you could stop your validator briefly until it is labeled as delinquent. Up to a minute after the validator is delinquent, you should receive a message in the Telegram group from your bot. Start the validator again and verify that you get another message in your Telegram group from the bot. The message should say `all clear`. \ No newline at end of file diff --git a/docs/src/validator/best-practices/operations.md b/docs/src/validator/best-practices/operations.md index 0588fc9ee90..7560f77994d 100644 --- a/docs/src/validator/best-practices/operations.md +++ b/docs/src/validator/best-practices/operations.md @@ -13,10 +13,10 @@ The Solana validator community holds regular educational workshops. You can watc ## Help with the validator command line -From within the Solana CLI, you can execute the `solana-validator` command with the `--help` flag to get a better understanding of the flags and sub commands available. +From within the Solana CLI, you can execute the `agave-validator` command with the `--help` flag to get a better understanding of the flags and sub commands available. ``` -solana-validator --help +agave-validator --help ``` ## Restarting your validator @@ -31,10 +31,10 @@ solana leader-schedule Based on the current slot and the leader schedule, you can calculate open time windows where your validator is not expected to produce blocks. -Assuming you are ready to restart, you may use the `solana-validator exit` command. The command exits your validator process when an appropriate idle time window is reached. Assuming that you have systemd implemented for your validator process, the validator should restart automatically after the exit. See the below help command for details: +Assuming you are ready to restart, you may use the `agave-validator exit` command. The command exits your validator process when an appropriate idle time window is reached. Assuming that you have systemd implemented for your validator process, the validator should restart automatically after the exit. See the below help command for details: ``` -solana-validator exit --help +agave-validator exit --help ``` ## Upgrading @@ -45,27 +45,27 @@ There are many ways to upgrade the [Solana software](../../cli/install-solana-cl ### Building From Source -It is a best practice to always build your Solana binaries from source. If you build from source, you are certain that the code you are building has not been tampered with before the binary was created. You may also be able to optimize your `solana-validator` binary to your specific hardware. +It is a best practice to always build your Solana binaries from source. If you build from source, you are certain that the code you are building has not been tampered with before the binary was created. You may also be able to optimize your `agave-validator` binary to your specific hardware. If you build from source on the validator machine (or a machine with the same CPU), you can target your specific architecture using the `-march` flag. Refer to the Solana docs for [instructions on building from source](../../cli/install-solana-cli-tools.md#build-from-source). -### solana-install +### agave-install -If you are not comfortable building from source, or you need to quickly install a new version to test something out, you could instead try using the `solana-install` command. +If you are not comfortable building from source, or you need to quickly install a new version to test something out, you could instead try using the `agave-install` command. Assuming you want to install Solana version `1.14.17`, you would execute the following: ``` -solana-install init 1.14.17 +agave-install init 1.14.17 ``` -This command downloads the executable for `1.14.17` and installs it into a `.local` directory. You can also look at `solana-install --help` for more options. +This command downloads the executable for `1.14.17` and installs it into a `.local` directory. You can also look at `agave-install --help` for more options. > **Note** this command only works if you already have the solana cli installed. If you do not have the cli installed, refer to [install solana cli tools](../../cli/install-solana-cli-tools.md) ### Restart -For all install methods, the validator process will need to be restarted before the newly installed version is in use. Use `solana-validator exit` to restart your validator process. +For all install methods, the validator process will need to be restarted before the newly installed version is in use. Use `agave-validator exit` to restart your validator process. ### Verifying version @@ -79,13 +79,13 @@ grep -B1 'Starting validator with' Validators operators who have not experienced significant downtime (multiple hours of downtime), should avoid downloading snapshots. It is important for the health of the cluster as well as your validator history to maintain the local ledger. Therefore, you should not download a new snapshot any time your validator is offline or experiences an issue. Downloading a snapshot should only be reserved for occasions when you do not have local state. Prolonged downtime or the first install of a new validator are examples of times when you may not have state locally. In other cases such as restarts for upgrades, a snapshot download should be avoided. -To avoid downloading a snapshot on restart, add the following flag to the `solana-validator` command: +To avoid downloading a snapshot on restart, add the following flag to the `agave-validator` command: ``` --no-snapshot-fetch ``` -If you use this flag with the `solana-validator` command, make sure that you run `solana catchup ` after your validator starts to make sure that the validator is catching up in a reasonable time. After some time (potentially a few hours), if it appears that your validator continues to fall behind, then you may have to download a new snapshot. +If you use this flag with the `agave-validator` command, make sure that you run `solana catchup ` after your validator starts to make sure that the validator is catching up in a reasonable time. After some time (potentially a few hours), if it appears that your validator continues to fall behind, then you may have to download a new snapshot. ### Downloading Snapshots @@ -122,13 +122,13 @@ Once you have a local snapshot, you can restart your validator with the `--no-sn ## Regularly Check Account Balances -It is important that you do not accidentally run out of funds in your identity account, as your node will stop voting. It is also important to note that this account keypair is the most vulnerable of the three keypairs in a vote account because the keypair for the identity account is stored on your validator when running the `solana-validator` software. How much SOL you should store there is up to you. As a best practice, make sure to check the account regularly and refill or deduct from it as needed. To check the account balance do: +It is important that you do not accidentally run out of funds in your identity account, as your node will stop voting. It is also important to note that this account keypair is the most vulnerable of the three keypairs in a vote account because the keypair for the identity account is stored on your validator when running the `agave-validator` software. How much SOL you should store there is up to you. As a best practice, make sure to check the account regularly and refill or deduct from it as needed. To check the account balance do: ``` solana balance validator-keypair.json ``` -> **Note** `solana-watchtower` can monitor for a minimum validator identity balance. See [monitoring best practices](./monitoring.md) for details. +> **Note** `agave-watchtower` can monitor for a minimum validator identity balance. See [monitoring best practices](./monitoring.md) for details. ## Withdrawing From The Vote Account diff --git a/docs/src/validator/get-started/setup-a-validator.md b/docs/src/validator/get-started/setup-a-validator.md index 6598400bda5..a745798cd23 100644 --- a/docs/src/validator/get-started/setup-a-validator.md +++ b/docs/src/validator/get-started/setup-a-validator.md @@ -245,7 +245,7 @@ Your system will need to be tuned in order to run properly. Your validator may n #### **Optimize sysctl knobs** ```bash -sudo bash -c "cat >/etc/sysctl.d/21-solana-validator.conf </etc/sysctl.d/21-agave-validator.conf < For more explanation on the flags used in the command, refer to the `solana-validator --help` command +> For more explanation on the flags used in the command, refer to the `agave-validator --help` command ``` #!/bin/bash -exec solana-validator \ +exec agave-validator \ --identity /home/sol/validator-keypair.json \ --known-validator 5D1fNXzvv5NjV1ysLjirC4WY92RNsVH18vjmcszZd8on \ --known-validator dDzy5SR3AXdYWVqbDEkVFdvSPCtS9ihF5kJkHCtXoFs \ @@ -67,4 +67,19 @@ The identities of the [known validators](../../running-validator/validator-start Additional examples of other Solana cluster specific validator commands can be found on the [Clusters](../../clusters.md) page. -Keep in mind, you will still need to customize these commands to operate as an RPC node, as well other operator specific configuration settings. \ No newline at end of file +Keep in mind, you will still need to customize these commands to operate as an RPC node, as well other operator specific configuration settings. + +## Account indexing + +As the number of populated accounts on the cluster grows, account-data RPC +requests that scan the entire account set -- like +[`getProgramAccounts`](../../api/http#getprogramaccounts) and +[SPL-token-specific requests](../../api/http#gettokenaccountsbydelegate) -- +may perform poorly. If your validator needs to support any of these requests, +you can use the `--account-index` parameter to activate one or more in-memory +account indexes that significantly improve RPC performance by indexing accounts +by the key field. Currently supports the following parameter values: + +- `program-id`: each account indexed by its owning program; used by [getProgramAccounts](../../api/http#getprogramaccounts) +- `spl-token-mint`: each SPL token account indexed by its token Mint; used by [getTokenAccountsByDelegate](../../api/http#gettokenaccountsbydelegate), and [getTokenLargestAccounts](../../api/http#gettokenlargestaccounts) +- `spl-token-owner`: each SPL token account indexed by the token-owner address; used by [getTokenAccountsByOwner](../../api/http#gettokenaccountsbyowner), and [getProgramAccounts](../../api/http#getprogramaccounts) requests that include an spl-token-owner filter. diff --git a/dos/src/main.rs b/dos/src/main.rs index 8e6c3c5b2b1..6a7968669f4 100644 --- a/dos/src/main.rs +++ b/dos/src/main.rs @@ -755,7 +755,7 @@ fn run_dos( } fn main() { - solana_logger::setup_with_default("solana=info"); + solana_logger::setup_with_default_filter(); let cmd_params = build_cli_parameters(); let (nodes, client) = if !cmd_params.skip_gossip { diff --git a/faucet/src/bin/faucet.rs b/faucet/src/bin/faucet.rs index 8e45ef98155..56cc7542623 100644 --- a/faucet/src/bin/faucet.rs +++ b/faucet/src/bin/faucet.rs @@ -19,7 +19,7 @@ use { async fn main() { let default_keypair = solana_cli_config::Config::default().keypair_path; - solana_logger::setup_with_default("solana=info"); + solana_logger::setup_with_default_filter(); solana_metrics::set_panic_hook("faucet", /*version:*/ None); let matches = App::new(crate_name!()) .about(crate_description!()) diff --git a/frozen-abi/Cargo.toml b/frozen-abi/Cargo.toml index 3121b6968eb..898b6d9b205 100644 --- a/frozen-abi/Cargo.toml +++ b/frozen-abi/Cargo.toml @@ -38,6 +38,7 @@ cc = { workspace = true, features = ["jobserver", "parallel"] } [target.'cfg(not(target_os = "solana"))'.dev-dependencies] solana-logger = { workspace = true } +bitflags = { workspace = true } [build-dependencies] rustc_version = { workspace = true } diff --git a/frozen-abi/src/abi_digester.rs b/frozen-abi/src/abi_digester.rs index 0d0886daae7..b014efd2ba1 100644 --- a/frozen-abi/src/abi_digester.rs +++ b/frozen-abi/src/abi_digester.rs @@ -17,7 +17,7 @@ pub struct AbiDigester { data_types: std::rc::Rc>>, depth: usize, for_enum: bool, - opaque_scope: Option, + opaque_type_matcher: Option, } pub type DigestResult = Result; @@ -70,7 +70,7 @@ impl AbiDigester { data_types: std::rc::Rc::new(std::cell::RefCell::new(vec![])), for_enum: false, depth: 0, - opaque_scope: None, + opaque_type_matcher: None, } } @@ -81,16 +81,16 @@ impl AbiDigester { data_types: self.data_types.clone(), depth: self.depth, for_enum: false, - opaque_scope: self.opaque_scope.clone(), + opaque_type_matcher: self.opaque_type_matcher.clone(), } } - pub fn create_new_opaque(&self, top_scope: &str) -> Self { + pub fn create_new_opaque(&self, type_matcher: &str) -> Self { Self { data_types: self.data_types.clone(), depth: self.depth, for_enum: false, - opaque_scope: Some(top_scope.to_owned()), + opaque_type_matcher: Some(type_matcher.to_owned()), } } @@ -103,7 +103,7 @@ impl AbiDigester { data_types: self.data_types.clone(), depth, for_enum: false, - opaque_scope: self.opaque_scope.clone(), + opaque_type_matcher: self.opaque_type_matcher.clone(), }) } @@ -116,15 +116,15 @@ impl AbiDigester { data_types: self.data_types.clone(), depth, for_enum: true, - opaque_scope: self.opaque_scope.clone(), + opaque_type_matcher: self.opaque_type_matcher.clone(), }) } pub fn digest_data(&mut self, value: &T) -> DigestResult { let type_name = normalize_type_name(type_name::()); if type_name.ends_with("__SerializeWith") - || (self.opaque_scope.is_some() - && type_name.starts_with(self.opaque_scope.as_ref().unwrap())) + || (self.opaque_type_matcher.is_some() + && type_name.contains(self.opaque_type_matcher.as_ref().unwrap())) { // we can't use the AbiEnumVisitor trait for these cases. value.serialize(self.create_new()) @@ -661,6 +661,34 @@ mod tests { #[frozen_abi(digest = "9PMdHRb49BpkywrmPoJyZWMsEmf5E1xgmsFGkGmea5RW")] type TestBitVec = bv::BitVec; + mod bitflags_abi { + use crate::abi_example::{AbiExample, EvenAsOpaque, IgnoreAsHelper}; + + bitflags::bitflags! { + #[frozen_abi(digest = "HhKNkaeAd7AohTb8S8sPKjAWwzxWY2DPz5FvkWmx5bSH")] + #[derive(Serialize, Deserialize)] + struct TestFlags: u8 { + const TestBit = 0b0000_0001; + } + } + + impl AbiExample for TestFlags { + fn example() -> Self { + Self::empty() + } + } + + impl IgnoreAsHelper for TestFlags {} + // This (EvenAsOpaque) marker trait is needed for bitflags-generated types because we can't + // impl AbiExample for its private type: + // thread '...TestFlags_frozen_abi...' panicked at ...: + // derive or implement AbiExample/AbiEnumVisitor for + // solana_frozen_abi::abi_digester::tests::_::InternalBitFlags + impl EvenAsOpaque for TestFlags { + const TYPE_NAME_MATCHER: &'static str = "::_::InternalBitFlags"; + } + } + mod skip_should_be_same { #[frozen_abi(digest = "4LbuvQLX78XPbm4hqqZcHFHpseDJcw4qZL9EUZXSi2Ss")] #[derive(Serialize, AbiExample)] @@ -691,4 +719,12 @@ mod tests { Variant2(u8, u16, #[serde(skip)] u32), } } + + #[frozen_abi(digest = "B1PcwZdUfGnxaRid9e6ZwkST3NZ2KUEYobA1DkxWrYLP")] + #[derive(Serialize, AbiExample)] + struct TestArcWeak(std::sync::Weak); + + #[frozen_abi(digest = "4R8uCLR1BVU1aFgkSaNyKcFD1FeM6rGdsjbJBFpnqx4v")] + #[derive(Serialize, AbiExample)] + struct TestRcWeak(std::rc::Weak); } diff --git a/frozen-abi/src/abi_example.rs b/frozen-abi/src/abi_example.rs index c7765c4a573..ab68c6ff25e 100644 --- a/frozen-abi/src/abi_example.rs +++ b/frozen-abi/src/abi_example.rs @@ -6,6 +6,24 @@ use { std::any::type_name, }; +// The most important trait for the abi digesting. This trait is used to create any complexities of +// object graph to generate the abi digest. The frozen abi test harness calls T::example() to +// instantiate the tested root type and traverses its fields recursively, abusing the +// serde::serialize(). +// +// This trait applicability is similar to the Default trait. That means all referenced types must +// implement this trait. AbiExample is implemented for almost all common types in this file. +// +// When implementing AbiExample manually, you need to return a _minimally-populated_ value +// from it to actually generate a meaningful digest. This impl semantics is unlike Default, which +// usually returns something empty. See actual impls for inspiration. +// +// The requirement of AbiExample impls even applies to those types of `#[serde(skip)]`-ed fields. +// That's because the abi digesting needs a properly initialized object to enter into the +// serde::serialize() to begin with, even knowning they aren't used for serialization and thus abi +// digest. Luckily, `#[serde(skip)]`-ed fields' AbiExample impls can just delegate to T::default(), +// exploiting the nature of this artificial impl requirement as an exception from the usual +// AbiExample semantics. pub trait AbiExample: Sized { fn example() -> Self; } @@ -137,25 +155,12 @@ tuple_example_impls! { } } -// Source: https://github.com/rust-lang/rust/blob/ba18875557aabffe386a2534a1aa6118efb6ab88/src/libcore/array/mod.rs#L417 -macro_rules! array_example_impls { - {$n:expr, $t:ident $($ts:ident)*} => { - impl AbiExample for [T; $n] where T: AbiExample { - fn example() -> Self { - [$t::example(), $($ts::example()),*] - } - } - array_example_impls!{($n - 1), $($ts)*} - }; - {$n:expr,} => { - impl AbiExample for [T; $n] { - fn example() -> Self { [] } - } - }; +impl AbiExample for [T; N] { + fn example() -> Self { + std::array::from_fn(|_| T::example()) + } } -array_example_impls! {32, T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T} - // Source: https://github.com/rust-lang/rust/blob/ba18875557aabffe386a2534a1aa6118efb6ab88/src/libcore/default.rs#L137 macro_rules! example_impls { ($t:ty, $v:expr) => { @@ -232,7 +237,14 @@ impl AbiExample for BitVec { } impl IgnoreAsHelper for BitVec {} -impl EvenAsOpaque for BitVec {} +// This (EvenAsOpaque) marker trait is needed for BitVec because we can't impl AbiExample for its +// private type: +// thread '...TestBitVec_frozen_abi...' panicked at ...: +// derive or implement AbiExample/AbiEnumVisitor for +// bv::bit_vec::inner::Inner +impl EvenAsOpaque for BitVec { + const TYPE_NAME_MATCHER: &'static str = "bv::bit_vec::inner::"; +} pub(crate) fn normalize_type_name(type_name: &str) -> String { type_name.chars().filter(|c| *c != '&').collect() @@ -329,6 +341,23 @@ impl AbiExample for std::sync::Arc { } } +// When T is weakly owned by the likes of `std::{sync, rc}::Weak`s, we need to uphold the ownership +// of T in some way at least during abi digesting... However, there's no easy way. Stashing them +// into static is confronted with Send/Sync issue. Stashing them into thread_local is confronted +// with not enough (T + 'static) lifetime bound.. So, just leak the examples. This should be +// tolerated, considering ::example() should ever be called inside tests, not in production code... +fn leak_and_inhibit_drop<'a, T>(t: T) -> &'a mut T { + Box::leak(Box::new(t)) +} + +impl AbiExample for std::sync::Weak { + fn example() -> Self { + info!("AbiExample for (Arc's Weak): {}", type_name::()); + // leaking is needed otherwise Arc::upgrade() will always return None... + std::sync::Arc::downgrade(leak_and_inhibit_drop(std::sync::Arc::new(T::example()))) + } +} + impl AbiExample for std::rc::Rc { fn example() -> Self { info!("AbiExample for (Rc): {}", type_name::()); @@ -336,6 +365,14 @@ impl AbiExample for std::rc::Rc { } } +impl AbiExample for std::rc::Weak { + fn example() -> Self { + info!("AbiExample for (Rc's Weak): {}", type_name::()); + // leaking is needed otherwise Rc::upgrade() will always return None... + std::rc::Rc::downgrade(leak_and_inhibit_drop(std::rc::Rc::new(T::example()))) + } +} + impl AbiExample for std::sync::Mutex { fn example() -> Self { info!("AbiExample for (Mutex): {}", type_name::()); @@ -457,6 +494,13 @@ impl AbiExample for std::path::PathBuf { } } +#[cfg(not(target_os = "solana"))] +impl AbiExample for std::time::SystemTime { + fn example() -> Self { + std::time::SystemTime::UNIX_EPOCH + } +} + use std::net::{IpAddr, Ipv4Addr, SocketAddr}; impl AbiExample for SocketAddr { fn example() -> Self { @@ -470,13 +514,22 @@ impl AbiExample for IpAddr { } } -// This is a control flow indirection needed for digesting all variants of an enum +// This is a control flow indirection needed for digesting all variants of an enum. +// +// All of types (including non-enums) will be processed by this trait, albeit the +// name of this trait. +// User-defined enums usually just need to impl this with namesake derive macro (AbiEnumVisitor). +// +// Note that sometimes this indirection doesn't work for various reasons. For that end, there are +// hacks with marker traits (IgnoreAsHelper/EvenAsOpaque). pub trait AbiEnumVisitor: Serialize { fn visit_for_abi(&self, digester: &mut AbiDigester) -> DigestResult; } pub trait IgnoreAsHelper {} -pub trait EvenAsOpaque {} +pub trait EvenAsOpaque { + const TYPE_NAME_MATCHER: &'static str; +} impl AbiEnumVisitor for T { default fn visit_for_abi(&self, _digester: &mut AbiDigester) -> DigestResult { @@ -489,7 +542,9 @@ impl AbiEnumVisitor for T { impl AbiEnumVisitor for T { default fn visit_for_abi(&self, digester: &mut AbiDigester) -> DigestResult { - info!("AbiEnumVisitor for (default): {}", type_name::()); + info!("AbiEnumVisitor for T: {}", type_name::()); + // not calling self.serialize(...) is intentional here as the most generic impl + // consider IgnoreAsHelper and EvenAsOpaque if you're stuck on this.... T::example() .serialize(digester.create_new()) .map_err(DigestError::wrap_by_type::) @@ -501,7 +556,7 @@ impl AbiEnumVisitor for T { // relevant test: TestVecEnum impl AbiEnumVisitor for &T { default fn visit_for_abi(&self, digester: &mut AbiDigester) -> DigestResult { - info!("AbiEnumVisitor for (&default): {}", type_name::()); + info!("AbiEnumVisitor for &T: {}", type_name::()); // Don't call self.visit_for_abi(...) to avoid the infinite recursion! T::visit_for_abi(self, digester) } @@ -521,9 +576,13 @@ impl AbiEnumVisitor for &T { // inability of implementing AbiExample for private structs from other crates impl AbiEnumVisitor for &T { default fn visit_for_abi(&self, digester: &mut AbiDigester) -> DigestResult { - info!("AbiEnumVisitor for (IgnoreAsOpaque): {}", type_name::()); - let top_scope = type_name::().split("::").next().unwrap(); - self.serialize(digester.create_new_opaque(top_scope)) + let type_name = type_name::(); + let matcher = T::TYPE_NAME_MATCHER; + info!( + "AbiEnumVisitor for (EvenAsOpaque): {}: matcher: {}", + type_name, matcher + ); + self.serialize(digester.create_new_opaque(matcher)) .map_err(DigestError::wrap_by_type::) } } diff --git a/geyser-plugin-interface/Cargo.toml b/geyser-plugin-interface/Cargo.toml index af99758b47d..56f42fd4612 100644 --- a/geyser-plugin-interface/Cargo.toml +++ b/geyser-plugin-interface/Cargo.toml @@ -1,7 +1,7 @@ [package] -name = "solana-geyser-plugin-interface" +name = "agave-geyser-plugin-interface" description = "The Solana Geyser plugin interface." -documentation = "https://docs.rs/solana-geyser-plugin-interface" +documentation = "https://docs.rs/agave-geyser-plugin-interface" version = { workspace = true } authors = { workspace = true } repository = { workspace = true } diff --git a/geyser-plugin-manager/Cargo.toml b/geyser-plugin-manager/Cargo.toml index 9b4468eddae..2c8efb06228 100644 --- a/geyser-plugin-manager/Cargo.toml +++ b/geyser-plugin-manager/Cargo.toml @@ -10,6 +10,7 @@ license = { workspace = true } edition = { workspace = true } [dependencies] +agave-geyser-plugin-interface = { workspace = true } bs58 = { workspace = true } crossbeam-channel = { workspace = true } @@ -21,7 +22,6 @@ log = { workspace = true } serde_json = { workspace = true } solana-accounts-db = { workspace = true } solana-entry = { workspace = true } -solana-geyser-plugin-interface = { workspace = true } solana-ledger = { workspace = true } solana-measure = { workspace = true } solana-metrics = { workspace = true } diff --git a/geyser-plugin-manager/src/accounts_update_notifier.rs b/geyser-plugin-manager/src/accounts_update_notifier.rs index 7c7e3370fc0..90ab0b7998a 100644 --- a/geyser-plugin-manager/src/accounts_update_notifier.rs +++ b/geyser-plugin-manager/src/accounts_update_notifier.rs @@ -1,14 +1,14 @@ /// Module responsible for notifying plugins of account updates use { crate::geyser_plugin_manager::GeyserPluginManager, + agave_geyser_plugin_interface::geyser_plugin_interface::{ + ReplicaAccountInfoV3, ReplicaAccountInfoVersions, + }, log::*, solana_accounts_db::{ account_storage::meta::StoredAccountMeta, accounts_update_notifier_interface::AccountsUpdateNotifierInterface, }, - solana_geyser_plugin_interface::geyser_plugin_interface::{ - ReplicaAccountInfoV3, ReplicaAccountInfoVersions, - }, solana_measure::measure::Measure, solana_metrics::*, solana_sdk::{ diff --git a/geyser-plugin-manager/src/block_metadata_notifier.rs b/geyser-plugin-manager/src/block_metadata_notifier.rs index ab56cf3be81..b23cc002220 100644 --- a/geyser-plugin-manager/src/block_metadata_notifier.rs +++ b/geyser-plugin-manager/src/block_metadata_notifier.rs @@ -3,11 +3,11 @@ use { block_metadata_notifier_interface::BlockMetadataNotifier, geyser_plugin_manager::GeyserPluginManager, }, - log::*, - solana_accounts_db::stake_rewards::RewardInfo, - solana_geyser_plugin_interface::geyser_plugin_interface::{ + agave_geyser_plugin_interface::geyser_plugin_interface::{ ReplicaBlockInfoV3, ReplicaBlockInfoVersions, }, + log::*, + solana_accounts_db::stake_rewards::RewardInfo, solana_measure::measure::Measure, solana_metrics::*, solana_sdk::{clock::UnixTimestamp, pubkey::Pubkey}, diff --git a/geyser-plugin-manager/src/entry_notifier.rs b/geyser-plugin-manager/src/entry_notifier.rs index ce6c3239c09..69a61ff70ae 100644 --- a/geyser-plugin-manager/src/entry_notifier.rs +++ b/geyser-plugin-manager/src/entry_notifier.rs @@ -1,11 +1,11 @@ /// Module responsible for notifying plugins about entries use { crate::geyser_plugin_manager::GeyserPluginManager, - log::*, - solana_entry::entry::EntrySummary, - solana_geyser_plugin_interface::geyser_plugin_interface::{ + agave_geyser_plugin_interface::geyser_plugin_interface::{ ReplicaEntryInfo, ReplicaEntryInfoVersions, }, + log::*, + solana_entry::entry::EntrySummary, solana_ledger::entry_notifier_interface::EntryNotifier, solana_measure::measure::Measure, solana_metrics::*, diff --git a/geyser-plugin-manager/src/geyser_plugin_manager.rs b/geyser-plugin-manager/src/geyser_plugin_manager.rs index 0698cf1a656..81579f8fd9d 100644 --- a/geyser-plugin-manager/src/geyser_plugin_manager.rs +++ b/geyser-plugin-manager/src/geyser_plugin_manager.rs @@ -1,15 +1,51 @@ use { + agave_geyser_plugin_interface::geyser_plugin_interface::GeyserPlugin, jsonrpc_core::{ErrorCode, Result as JsonRpcResult}, jsonrpc_server_utils::tokio::sync::oneshot::Sender as OneShotSender, libloading::Library, log::*, - solana_geyser_plugin_interface::geyser_plugin_interface::GeyserPlugin, - std::path::Path, + std::{ + ops::{Deref, DerefMut}, + path::Path, + }, }; +#[derive(Debug)] +pub struct LoadedGeyserPlugin { + name: String, + plugin: Box, +} + +impl LoadedGeyserPlugin { + pub fn new(plugin: Box, name: Option) -> Self { + Self { + name: name.unwrap_or_else(|| plugin.name().to_owned()), + plugin, + } + } + + pub fn name(&self) -> &str { + &self.name + } +} + +impl Deref for LoadedGeyserPlugin { + type Target = Box; + + fn deref(&self) -> &Self::Target { + &self.plugin + } +} + +impl DerefMut for LoadedGeyserPlugin { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.plugin + } +} + #[derive(Default, Debug)] pub struct GeyserPluginManager { - pub plugins: Vec>, + pub plugins: Vec, libs: Vec, } @@ -177,6 +213,22 @@ impl GeyserPluginManager { data: None, })?; + // Then see if a plugin with this name already exists. If so, abort + if self + .plugins + .iter() + .any(|plugin| plugin.name().eq(new_plugin.name())) + { + return Err(jsonrpc_core::Error { + code: ErrorCode::InvalidRequest, + message: format!( + "There already exists a plugin named {} loaded, while reloading {name}. Did not load requested plugin", + new_plugin.name() + ), + data: None, + }); + } + // Attempt to on_load with new plugin match new_plugin.on_load(new_parsed_config_file) { // On success, push plugin and library @@ -244,13 +296,13 @@ pub enum GeyserPluginManagerError { #[error("Invalid plugin path")] InvalidPluginPath, - #[error("Cannot load plugin shared library")] + #[error("Cannot load plugin shared library (error: {0})")] PluginLoadError(String), #[error("The geyser plugin {0} is already loaded shared library")] PluginAlreadyLoaded(String), - #[error("The GeyserPlugin on_load method failed")] + #[error("The GeyserPlugin on_load method failed (error: {0})")] PluginStartError(String), } @@ -264,7 +316,7 @@ pub enum GeyserPluginManagerError { #[cfg(not(test))] pub(crate) fn load_plugin_from_config( geyser_plugin_config_file: &Path, -) -> Result<(Box, Library, &str), GeyserPluginManagerError> { +) -> Result<(LoadedGeyserPlugin, Library, &str), GeyserPluginManagerError> { use std::{fs::File, io::Read, path::PathBuf}; type PluginConstructor = unsafe fn() -> *mut dyn GeyserPlugin; use libloading::Symbol; @@ -307,6 +359,8 @@ pub(crate) fn load_plugin_from_config( libpath = config_dir.join(libpath); } + let plugin_name = result["name"].as_str().map(|s| s.to_owned()); + let config_file = geyser_plugin_config_file .as_os_str() .to_str() @@ -321,7 +375,11 @@ pub(crate) fn load_plugin_from_config( let plugin_raw = constructor(); (Box::from_raw(plugin_raw), lib) }; - Ok((plugin, lib, config_file)) + Ok(( + LoadedGeyserPlugin::new(plugin, plugin_name), + lib, + config_file, + )) } #[cfg(test)] @@ -337,7 +395,7 @@ const TESTPLUGIN2_CONFIG: &str = "TESTPLUGIN2_CONFIG"; #[cfg(test)] pub(crate) fn load_plugin_from_config( geyser_plugin_config_file: &Path, -) -> Result<(Box, Library, &str), GeyserPluginManagerError> { +) -> Result<(LoadedGeyserPlugin, Library, &str), GeyserPluginManagerError> { if geyser_plugin_config_file.ends_with(TESTPLUGIN_CONFIG) { Ok(tests::dummy_plugin_and_library( tests::TestPlugin, @@ -359,19 +417,19 @@ pub(crate) fn load_plugin_from_config( mod tests { use { crate::geyser_plugin_manager::{ - GeyserPluginManager, TESTPLUGIN2_CONFIG, TESTPLUGIN_CONFIG, + GeyserPluginManager, LoadedGeyserPlugin, TESTPLUGIN2_CONFIG, TESTPLUGIN_CONFIG, }, + agave_geyser_plugin_interface::geyser_plugin_interface::GeyserPlugin, libloading::Library, - solana_geyser_plugin_interface::geyser_plugin_interface::GeyserPlugin, std::sync::{Arc, RwLock}, }; pub(super) fn dummy_plugin_and_library( plugin: P, config_path: &'static str, - ) -> (Box, Library, &'static str) { + ) -> (LoadedGeyserPlugin, Library, &'static str) { ( - Box::new(plugin), + LoadedGeyserPlugin::new(Box::new(plugin), None), Library::from(libloading::os::unix::Library::this()), config_path, ) diff --git a/geyser-plugin-manager/src/slot_status_notifier.rs b/geyser-plugin-manager/src/slot_status_notifier.rs index 587abe2f79d..1557bb2d4d8 100644 --- a/geyser-plugin-manager/src/slot_status_notifier.rs +++ b/geyser-plugin-manager/src/slot_status_notifier.rs @@ -1,7 +1,7 @@ use { crate::geyser_plugin_manager::GeyserPluginManager, + agave_geyser_plugin_interface::geyser_plugin_interface::SlotStatus, log::*, - solana_geyser_plugin_interface::geyser_plugin_interface::SlotStatus, solana_measure::measure::Measure, solana_metrics::*, solana_sdk::clock::Slot, diff --git a/geyser-plugin-manager/src/transaction_notifier.rs b/geyser-plugin-manager/src/transaction_notifier.rs index ab821e81104..b757c1202b3 100644 --- a/geyser-plugin-manager/src/transaction_notifier.rs +++ b/geyser-plugin-manager/src/transaction_notifier.rs @@ -1,10 +1,10 @@ /// Module responsible for notifying plugins of transactions use { crate::geyser_plugin_manager::GeyserPluginManager, - log::*, - solana_geyser_plugin_interface::geyser_plugin_interface::{ + agave_geyser_plugin_interface::geyser_plugin_interface::{ ReplicaTransactionInfoV2, ReplicaTransactionInfoVersions, }, + log::*, solana_measure::measure::Measure, solana_metrics::*, solana_rpc::transaction_notifier_interface::TransactionNotifier, diff --git a/gossip/benches/crds_gossip_pull.rs b/gossip/benches/crds_gossip_pull.rs index eaed9b67116..35be66b4bad 100644 --- a/gossip/benches/crds_gossip_pull.rs +++ b/gossip/benches/crds_gossip_pull.rs @@ -52,6 +52,6 @@ fn bench_build_crds_filters(bencher: &mut Bencher) { let crds = RwLock::new(crds); bencher.iter(|| { let filters = crds_gossip_pull.build_crds_filters(&thread_pool, &crds, MAX_BLOOM_SIZE); - assert_eq!(filters.len(), 128); + assert_eq!(filters.len(), 16); }); } diff --git a/gossip/src/cluster_info.rs b/gossip/src/cluster_info.rs index b0b99b1c02d..10bc5901d57 100644 --- a/gossip/src/cluster_info.rs +++ b/gossip/src/cluster_info.rs @@ -52,8 +52,9 @@ use { solana_ledger::shred::Shred, solana_measure::measure::Measure, solana_net_utils::{ - bind_common, bind_common_in_range, bind_in_range, bind_two_in_range_with_offset, - find_available_port_in_range, multi_bind_in_range, PortRange, + bind_common, bind_common_in_range, bind_in_range, bind_in_range_with_config, + bind_more_with_config, bind_two_in_range_with_offset_and_config, + find_available_port_in_range, multi_bind_in_range, PortRange, SocketConfig, }, solana_perf::{ data_budget::DataBudget, @@ -2078,7 +2079,7 @@ impl ClusterInfo { score }; let score = match response.data { - CrdsData::LegacyContactInfo(_) => 2 * score, + CrdsData::LegacyContactInfo(_) | CrdsData::ContactInfo(_) => 2 * score, _ => score, }; ((addr, response), score) @@ -2211,7 +2212,6 @@ impl ClusterInfo { { let _st = ScopedTimer::from(&self.stats.process_pull_response); self.gossip.process_pull_responses( - from, filtered_pulls, filtered_pulls_expired_timeout, failed_inserts, @@ -2804,10 +2804,12 @@ pub struct Sockets { pub serve_repair: UdpSocket, pub serve_repair_quic: UdpSocket, pub ancestor_hashes_requests: UdpSocket, - pub tpu_quic: UdpSocket, - pub tpu_forwards_quic: UdpSocket, + pub tpu_quic: Vec, + pub tpu_forwards_quic: Vec, } +const QUIC_ENDPOINTS: usize = 10; + #[derive(Debug)] pub struct Node { pub info: ContactInfo, @@ -2825,15 +2827,44 @@ impl Node { let unspecified_bind_addr = format!("{:?}:0", IpAddr::V4(Ipv4Addr::UNSPECIFIED)); let port_range = (1024, 65535); - let ((_tpu_port, tpu), (_tpu_quic_port, tpu_quic)) = - bind_two_in_range_with_offset(localhost_ip_addr, port_range, QUIC_PORT_OFFSET).unwrap(); + let udp_config = SocketConfig { + reuseaddr: false, + reuseport: false, + }; + let quic_config = SocketConfig { + reuseaddr: false, + reuseport: true, + }; + let ((tpu_port, tpu), (_tpu_quic_port, tpu_quic)) = + bind_two_in_range_with_offset_and_config( + localhost_ip_addr, + port_range, + QUIC_PORT_OFFSET, + udp_config.clone(), + quic_config.clone(), + ) + .unwrap(); + let tpu_quic = + bind_more_with_config(tpu_quic, QUIC_ENDPOINTS, quic_config.clone()).unwrap(); + let port_range = (tpu_port + 1, port_range.1); let (gossip_port, (gossip, ip_echo)) = bind_common_in_range(localhost_ip_addr, port_range).unwrap(); let gossip_addr = SocketAddr::new(localhost_ip_addr, gossip_port); let tvu = UdpSocket::bind(&localhost_bind_addr).unwrap(); let tvu_quic = UdpSocket::bind(&localhost_bind_addr).unwrap(); - let ((_tpu_forwards_port, tpu_forwards), (_tpu_forwards_quic_port, tpu_forwards_quic)) = - bind_two_in_range_with_offset(localhost_ip_addr, port_range, QUIC_PORT_OFFSET).unwrap(); + let port_range = (gossip_port + 1, port_range.1); + let ((tpu_forwards_port, tpu_forwards), (_tpu_forwards_quic_port, tpu_forwards_quic)) = + bind_two_in_range_with_offset_and_config( + localhost_ip_addr, + port_range, + QUIC_PORT_OFFSET, + udp_config, + quic_config.clone(), + ) + .unwrap(); + let tpu_forwards_quic = + bind_more_with_config(tpu_forwards_quic, QUIC_ENDPOINTS, quic_config).unwrap(); + let port_range = (tpu_forwards_port + 1, port_range.1); let tpu_vote = UdpSocket::bind(&localhost_bind_addr).unwrap(); let repair = UdpSocket::bind(&localhost_bind_addr).unwrap(); let rpc_port = find_available_port_in_range(localhost_ip_addr, port_range).unwrap(); @@ -2920,7 +2951,19 @@ impl Node { } } fn bind(bind_ip_addr: IpAddr, port_range: PortRange) -> (u16, UdpSocket) { - bind_in_range(bind_ip_addr, port_range).expect("Failed to bind") + let config = SocketConfig { + reuseaddr: false, + reuseport: false, + }; + Self::bind_with_config(bind_ip_addr, port_range, config) + } + + fn bind_with_config( + bind_ip_addr: IpAddr, + port_range: PortRange, + config: SocketConfig, + ) -> (u16, UdpSocket) { + bind_in_range_with_config(bind_ip_addr, port_range, config).expect("Failed to bind") } pub fn new_single_bind( @@ -2933,10 +2976,38 @@ impl Node { Self::get_gossip_port(gossip_addr, port_range, bind_ip_addr); let (tvu_port, tvu) = Self::bind(bind_ip_addr, port_range); let (tvu_quic_port, tvu_quic) = Self::bind(bind_ip_addr, port_range); + let udp_config = SocketConfig { + reuseaddr: false, + reuseport: false, + }; + let quic_config = SocketConfig { + reuseaddr: false, + reuseport: true, + }; let ((tpu_port, tpu), (_tpu_quic_port, tpu_quic)) = - bind_two_in_range_with_offset(bind_ip_addr, port_range, QUIC_PORT_OFFSET).unwrap(); + bind_two_in_range_with_offset_and_config( + bind_ip_addr, + port_range, + QUIC_PORT_OFFSET, + udp_config.clone(), + quic_config.clone(), + ) + .unwrap(); + let tpu_quic = + bind_more_with_config(tpu_quic, QUIC_ENDPOINTS, quic_config.clone()).unwrap(); + let port_range = (tpu_port + 1, port_range.1); let ((tpu_forwards_port, tpu_forwards), (_tpu_forwards_quic_port, tpu_forwards_quic)) = - bind_two_in_range_with_offset(bind_ip_addr, port_range, QUIC_PORT_OFFSET).unwrap(); + bind_two_in_range_with_offset_and_config( + bind_ip_addr, + port_range, + QUIC_PORT_OFFSET, + udp_config, + quic_config.clone(), + ) + .unwrap(); + let tpu_forwards_quic = + bind_more_with_config(tpu_forwards_quic, QUIC_ENDPOINTS, quic_config).unwrap(); + let port_range = (tpu_forwards_port + 1, port_range.1); let (tpu_vote_port, tpu_vote) = Self::bind(bind_ip_addr, port_range); let (_, retransmit_socket) = Self::bind(bind_ip_addr, port_range); let (_, repair) = Self::bind(bind_ip_addr, port_range); @@ -3014,25 +3085,40 @@ impl Node { let (tvu_port, tvu_sockets) = multi_bind_in_range(bind_ip_addr, port_range, 8).expect("tvu multi_bind"); let (tvu_quic_port, tvu_quic) = Self::bind(bind_ip_addr, port_range); + let port_range = (tvu_quic_port + 1, port_range.1); let (tpu_port, tpu_sockets) = multi_bind_in_range(bind_ip_addr, port_range, 32).expect("tpu multi_bind"); - let (_tpu_port_quic, tpu_quic) = Self::bind( + let quic_config = SocketConfig { + reuseaddr: false, + reuseport: true, + }; + let port_range = (tpu_port + 1, port_range.1); + let (tpu_port_quic, tpu_quic) = Self::bind_with_config( bind_ip_addr, (tpu_port + QUIC_PORT_OFFSET, tpu_port + QUIC_PORT_OFFSET + 1), + quic_config.clone(), ); + let tpu_quic = + bind_more_with_config(tpu_quic, QUIC_ENDPOINTS, quic_config.clone()).unwrap(); + let port_range = (tpu_port_quic + 1, port_range.1); let (tpu_forwards_port, tpu_forwards_sockets) = multi_bind_in_range(bind_ip_addr, port_range, 8).expect("tpu_forwards multi_bind"); - let (_tpu_forwards_port_quic, tpu_forwards_quic) = Self::bind( + let port_range = (tpu_forwards_port + 1, port_range.1); + let (tpu_forwards_port_quic, tpu_forwards_quic) = Self::bind_with_config( bind_ip_addr, ( tpu_forwards_port + QUIC_PORT_OFFSET, tpu_forwards_port + QUIC_PORT_OFFSET + 1, ), + quic_config.clone(), ); + let tpu_forwards_quic = + bind_more_with_config(tpu_forwards_quic, QUIC_ENDPOINTS, quic_config.clone()).unwrap(); + let port_range = (tpu_forwards_port_quic + 1, port_range.1); let (tpu_vote_port, tpu_vote_sockets) = multi_bind_in_range(bind_ip_addr, port_range, 1).expect("tpu_vote multi_bind"); @@ -3127,6 +3213,7 @@ fn filter_on_shred_version( if crds.get_shred_version(from) == Some(self_shred_version) { values.retain(|value| match &value.data { // Allow contact-infos so that shred-versions are updated. + CrdsData::ContactInfo(_) => true, CrdsData::LegacyContactInfo(_) => true, CrdsData::NodeInstance(_) => true, // Only retain values with the same shred version. @@ -3136,6 +3223,7 @@ fn filter_on_shred_version( values.retain(|value| match &value.data { // Allow node to update its own contact info in case their // shred-version changes + CrdsData::ContactInfo(node) => node.pubkey() == from, CrdsData::LegacyContactInfo(node) => node.pubkey() == from, CrdsData::NodeInstance(_) => true, _ => false, @@ -3154,6 +3242,11 @@ fn filter_on_shred_version( { Some(msg) } + CrdsData::ContactInfo(node) + if node.shred_version() == 0 || node.shred_version() == self_shred_version => + { + Some(msg) + } _ => { stats.skip_pull_shred_version.add_relaxed(1); None @@ -3192,7 +3285,6 @@ mod tests { }, itertools::izip, solana_ledger::shred::Shredder, - solana_net_utils::MINIMUM_VALIDATOR_PORT_RANGE_WIDTH, solana_sdk::signature::{Keypair, Signer}, solana_vote_program::{vote_instruction, vote_state::Vote}, std::{ @@ -3661,11 +3753,8 @@ mod tests { fn new_with_external_ip_test_gossip() { // Can't use VALIDATOR_PORT_RANGE because if this test runs in parallel with others, the // port returned by `bind_in_range()` might be snatched up before `Node::new_with_external_ip()` runs - let port_range = ( - VALIDATOR_PORT_RANGE.1 + MINIMUM_VALIDATOR_PORT_RANGE_WIDTH, - VALIDATOR_PORT_RANGE.1 + (2 * MINIMUM_VALIDATOR_PORT_RANGE_WIDTH), - ); - + let (start, end) = VALIDATOR_PORT_RANGE; + let port_range = (end, end + (end - start)); let ip = IpAddr::V4(Ipv4Addr::LOCALHOST); let port = bind_in_range(ip, port_range).expect("Failed to bind").0; let node = Node::new_with_external_ip( diff --git a/gossip/src/crds_gossip.rs b/gossip/src/crds_gossip.rs index 41a0e4c9ab4..015deed1d2a 100644 --- a/gossip/src/crds_gossip.rs +++ b/gossip/src/crds_gossip.rs @@ -274,7 +274,6 @@ impl CrdsGossip { /// Process a pull response. pub fn process_pull_responses( &self, - from: &Pubkey, responses: Vec, responses_expired_timeout: Vec, failed_inserts: Vec, @@ -283,7 +282,6 @@ impl CrdsGossip { ) { self.pull.process_pull_responses( &self.crds, - from, responses, responses_expired_timeout, failed_inserts, diff --git a/gossip/src/crds_gossip_pull.rs b/gossip/src/crds_gossip_pull.rs index 3e69192f2ac..191406dd672 100644 --- a/gossip/src/crds_gossip_pull.rs +++ b/gossip/src/crds_gossip_pull.rs @@ -18,7 +18,7 @@ use { crds::{Crds, GossipRoute, VersionedCrdsValue}, crds_gossip, crds_gossip_error::CrdsGossipError, - crds_value::{CrdsData, CrdsValue}, + crds_value::CrdsValue, legacy_contact_info::LegacyContactInfo as ContactInfo, ping_pong::PingCache, }, @@ -53,8 +53,6 @@ use { pub const CRDS_GOSSIP_PULL_CRDS_TIMEOUT_MS: u64 = 15000; // Retention period of hashes of received outdated values. const FAILED_INSERTS_RETENTION_MS: u64 = 20_000; -// Maximum number of pull requests to send out each time around. -const MAX_NUM_PULL_REQUESTS: usize = 1024; pub const FALSE_RATE: f64 = 0.1f64; pub const KEYS: f64 = 8f64; @@ -143,19 +141,26 @@ impl CrdsFilter { /// A vector of crds filters that together hold a complete set of Hashes. struct CrdsFilterSet { - filters: Vec>, + filters: Vec>>, mask_bits: u32, } impl CrdsFilterSet { - fn new(num_items: usize, max_bytes: usize) -> Self { + fn new(rng: &mut R, num_items: usize, max_bytes: usize) -> Self { + const SAMPLE_RATE: usize = 8; + const MAX_NUM_FILTERS: usize = 1024; let max_bits = (max_bytes * 8) as f64; let max_items = CrdsFilter::max_items(max_bits, FALSE_RATE, KEYS); let mask_bits = CrdsFilter::mask_bits(num_items as f64, max_items); - let filters = - repeat_with(|| Bloom::random(max_items as usize, FALSE_RATE, max_bits as usize).into()) - .take(1 << mask_bits) - .collect(); + let mut filters: Vec<_> = repeat_with(|| None).take(1usize << mask_bits).collect(); + let mut indices: Vec<_> = (0..filters.len()).collect(); + let size = (filters.len() + SAMPLE_RATE - 1) / SAMPLE_RATE; + for _ in 0..MAX_NUM_FILTERS.min(size) { + let k = rng.gen_range(0..indices.len()); + let k = indices.swap_remove(k); + let filter = Bloom::random(max_items as usize, FALSE_RATE, max_bits as usize); + filters[k] = Some(AtomicBloom::::from(filter)); + } Self { filters, mask_bits } } @@ -167,7 +172,9 @@ impl CrdsFilterSet { .unwrap_or_default(), ) .unwrap(); - self.filters[index].add(&hash_value); + if let Some(filter) = &self.filters[index] { + filter.add(&hash_value); + } } } @@ -177,10 +184,12 @@ impl From for Vec { cfs.filters .into_iter() .enumerate() - .map(|(seed, filter)| CrdsFilter { - filter: filter.into(), - mask: CrdsFilter::compute_mask(seed as u64, mask_bits), - mask_bits, + .filter_map(|(seed, filter)| { + Some(CrdsFilter { + filter: Bloom::::from(filter?), + mask: CrdsFilter::compute_mask(seed as u64, mask_bits), + mask_bits, + }) }) .collect() } @@ -269,14 +278,7 @@ impl CrdsGossipPull { if nodes.is_empty() { return Err(CrdsGossipError::NoPeers); } - let mut filters = self.build_crds_filters(thread_pool, crds, bloom_size); - if filters.len() > MAX_NUM_PULL_REQUESTS { - for i in 0..MAX_NUM_PULL_REQUESTS { - let j = rng.gen_range(i..filters.len()); - filters.swap(i, j); - } - filters.truncate(MAX_NUM_PULL_REQUESTS); - } + let filters = self.build_crds_filters(thread_pool, crds, bloom_size); // Associate each pull-request filter with a randomly selected peer. let dist = WeightedIndex::new(weights).unwrap(); let nodes = repeat_with(|| nodes[dist.sample(&mut rng)].clone()); @@ -360,7 +362,6 @@ impl CrdsGossipPull { pub(crate) fn process_pull_responses( &self, crds: &RwLock, - from: &Pubkey, responses: Vec, responses_expired_timeout: Vec, failed_inserts: Vec, @@ -382,7 +383,6 @@ impl CrdsGossipPull { } stats.success += num_inserts; self.num_pulls.fetch_add(num_inserts, Ordering::Relaxed); - owners.insert(*from); for owner in owners { crds.update_record_timestamp(&owner, now); } @@ -427,7 +427,7 @@ impl CrdsGossipPull { let crds = crds.read().unwrap(); let num_items = crds.len() + crds.num_purged() + failed_inserts.len(); let num_items = MIN_NUM_BLOOM_ITEMS.max(num_items); - let filters = CrdsFilterSet::new(num_items, bloom_size); + let filters = CrdsFilterSet::new(&mut rand::thread_rng(), num_items, bloom_size); thread_pool.install(|| { crds.par_values() .with_min_len(PAR_MIN_LENGTH) @@ -488,11 +488,6 @@ impl CrdsGossipPull { let out: Vec<_> = crds .filter_bitmask(filter.mask, filter.mask_bits) .filter(pred) - .filter(|entry| { - // Exclude the new ContactInfo from the pull responses - // until the cluster has upgraded. - !matches!(&entry.value.data, CrdsData::ContactInfo(_)) - }) .map(|entry| entry.value.clone()) .take(output_size_limit.load(Ordering::Relaxed).max(0) as usize) .collect(); @@ -543,7 +538,6 @@ impl CrdsGossipPull { fn process_pull_response( &self, crds: &RwLock, - from: &Pubkey, timeouts: &CrdsTimeouts, response: Vec, now: u64, @@ -553,7 +547,6 @@ impl CrdsGossipPull { self.filter_pull_responses(crds, timeouts, response, now, &mut stats); self.process_pull_responses( crds, - from, versioned, versioned_expired_timeout, failed_inserts, @@ -673,45 +666,61 @@ pub(crate) mod tests { #[test] fn test_crds_filter_set_add() { - let crds_filter_set = - CrdsFilterSet::new(/*num_items=*/ 9672788, /*max_bytes=*/ 8196); - let hash_values: Vec<_> = repeat_with(Hash::new_unique).take(1024).collect(); + let mut rng = rand::thread_rng(); + let crds_filter_set = CrdsFilterSet::new( + &mut rng, /*num_items=*/ 59672788, /*max_bytes=*/ 8196, + ); + let hash_values: Vec<_> = repeat_with(|| { + let buf: [u8; 32] = rng.gen(); + solana_sdk::hash::hashv(&[&buf]) + }) + .take(1024) + .collect(); + assert_eq!(crds_filter_set.filters.len(), 8192); for hash_value in &hash_values { crds_filter_set.add(*hash_value); } let filters: Vec = crds_filter_set.into(); + let mut num_hits = 0; assert_eq!(filters.len(), 1024); for hash_value in hash_values { - let mut num_hits = 0; + let mut hit = false; let mut false_positives = 0; for filter in &filters { if filter.test_mask(&hash_value) { num_hits += 1; + assert!(!hit); + hit = true; assert!(filter.contains(&hash_value)); assert!(filter.filter.contains(&hash_value)); } else if filter.filter.contains(&hash_value) { false_positives += 1; } } - assert_eq!(num_hits, 1); assert!(false_positives < 5); } + assert!(num_hits > 96, "num_hits: {num_hits}"); } #[test] fn test_crds_filter_set_new() { // Validates invariances required by CrdsFilterSet::get in the // vector of filters generated by CrdsFilterSet::new. - let filters: Vec = - CrdsFilterSet::new(/*num_items=*/ 55345017, /*max_bytes=*/ 4098).into(); - assert_eq!(filters.len(), 16384); + let filters = CrdsFilterSet::new( + &mut rand::thread_rng(), + 55345017, // num_items + 4098, // max_bytes + ); + assert_eq!(filters.filters.len(), 16384); + let filters = Vec::::from(filters); + assert_eq!(filters.len(), 1024); let mask_bits = filters[0].mask_bits; let right_shift = 64 - mask_bits; let ones = !0u64 >> mask_bits; - for (i, filter) in filters.iter().enumerate() { + for filter in &filters { // Check that all mask_bits are equal. assert_eq!(mask_bits, filter.mask_bits); - assert_eq!(i as u64, filter.mask >> right_shift); + assert!((0..16384).contains(&(filter.mask >> right_shift))); assert_eq!(ones, ones & filter.mask); } } @@ -744,7 +753,7 @@ pub(crate) mod tests { let crds = RwLock::new(crds); assert!(num_inserts > 30_000, "num inserts: {num_inserts}"); let filters = crds_gossip_pull.build_crds_filters(&thread_pool, &crds, MAX_BLOOM_SIZE); - assert_eq!(filters.len(), MIN_NUM_BLOOM_FILTERS.max(32)); + assert_eq!(filters.len(), MIN_NUM_BLOOM_FILTERS.max(4)); let crds = crds.read().unwrap(); let purged: Vec<_> = thread_pool.install(|| crds.purged().collect()); let hash_values: Vec<_> = crds.values().map(|v| v.value_hash).chain(purged).collect(); @@ -755,21 +764,24 @@ pub(crate) mod tests { "hash_values.len(): {}", hash_values.len() ); + let mut num_hits = 0; let mut false_positives = 0; for hash_value in hash_values { - let mut num_hits = 0; + let mut hit = false; for filter in &filters { if filter.test_mask(&hash_value) { num_hits += 1; + assert!(!hit); + hit = true; assert!(filter.contains(&hash_value)); assert!(filter.filter.contains(&hash_value)); } else if filter.filter.contains(&hash_value) { false_positives += 1; } } - assert_eq!(num_hits, 1); } - assert!(false_positives < 150_000, "fp: {false_positives}"); + assert!(num_hits > 4000, "num_hits: {num_hits}"); + assert!(false_positives < 20_000, "fp: {false_positives}"); } #[test] @@ -1196,7 +1208,6 @@ pub(crate) mod tests { let failed = node .process_pull_response( &node_crds, - &node_pubkey, &node.make_timeouts(node_pubkey, &HashMap::new(), Duration::default()), rsp.into_iter().flatten().collect(), 1, @@ -1313,7 +1324,8 @@ pub(crate) mod tests { } #[test] fn test_crds_filter_complete_set_add_mask() { - let mut filters: Vec = CrdsFilterSet::new(1000, 10).into(); + let mut filters = + Vec::::from(CrdsFilterSet::new(&mut rand::thread_rng(), 1000, 10)); assert!(filters.iter().all(|f| f.mask_bits > 0)); let mut h: Hash = Hash::default(); // rev to make the hash::default() miss on the first few test_masks @@ -1375,14 +1387,8 @@ pub(crate) mod tests { ); // inserting a fresh value should be fine. assert_eq!( - node.process_pull_response( - &node_crds, - &peer_pubkey, - &timeouts, - vec![peer_entry.clone()], - 1, - ) - .0, + node.process_pull_response(&node_crds, &timeouts, vec![peer_entry.clone()], 1,) + .0, 0 ); @@ -1394,7 +1400,6 @@ pub(crate) mod tests { assert_eq!( node.process_pull_response( &node_crds, - &peer_pubkey, &timeouts, vec![peer_entry.clone(), unstaked_peer_entry], node.crds_timeout + 100, @@ -1408,7 +1413,6 @@ pub(crate) mod tests { assert_eq!( node.process_pull_response( &node_crds, - &peer_pubkey, &timeouts, vec![peer_entry], node.crds_timeout + 1, @@ -1425,7 +1429,6 @@ pub(crate) mod tests { assert_eq!( node.process_pull_response( &node_crds, - &peer_pubkey, &timeouts, vec![peer_vote.clone()], node.crds_timeout + 1, @@ -1439,7 +1442,6 @@ pub(crate) mod tests { assert_eq!( node.process_pull_response( &node_crds, - &peer_pubkey, &timeouts, vec![peer_vote], node.crds_timeout + 2, diff --git a/gossip/src/crds_gossip_push.rs b/gossip/src/crds_gossip_push.rs index 345c9eaf172..72ffc30a486 100644 --- a/gossip/src/crds_gossip_push.rs +++ b/gossip/src/crds_gossip_push.rs @@ -16,7 +16,7 @@ use { cluster_info::{Ping, CRDS_UNIQUE_PUBKEY_CAPACITY}, crds::{Crds, CrdsError, Cursor, GossipRoute}, crds_gossip, - crds_value::{CrdsData, CrdsValue}, + crds_value::CrdsValue, ping_pong::PingCache, push_active_set::PushActiveSet, received_cache::ReceivedCache, @@ -191,11 +191,6 @@ impl CrdsGossipPush { let crds = crds.read().unwrap(); let entries = crds .get_entries(crds_cursor.deref_mut()) - .filter(|entry| { - // Exclude the new ContactInfo from outgoing push messages - // until the cluster has upgraded. - !matches!(&entry.value.data, CrdsData::ContactInfo(_)) - }) .map(|entry| &entry.value) .filter(|value| wallclock_window.contains(&value.wallclock())); for value in entries { diff --git a/gossip/src/duplicate_shred_handler.rs b/gossip/src/duplicate_shred_handler.rs index 366eef0913a..ba95178bc88 100644 --- a/gossip/src/duplicate_shred_handler.rs +++ b/gossip/src/duplicate_shred_handler.rs @@ -271,15 +271,12 @@ mod tests { let my_pubkey = my_keypair.pubkey(); let genesis_config_info = create_genesis_config_with_leader(10_000, &my_pubkey, 10_000); let GenesisConfigInfo { genesis_config, .. } = genesis_config_info; - let bank_forks = BankForks::new(Bank::new_for_tests(&genesis_config)); + let bank_forks = BankForks::new_rw_arc(Bank::new_for_tests(&genesis_config)); let leader_schedule_cache = Arc::new(LeaderScheduleCache::new_from_bank( - &bank_forks.working_bank(), + &bank_forks.read().unwrap().working_bank(), )); - let mut duplicate_shred_handler = DuplicateShredHandler::new( - blockstore.clone(), - leader_schedule_cache, - Arc::new(RwLock::new(bank_forks)), - ); + let mut duplicate_shred_handler = + DuplicateShredHandler::new(blockstore.clone(), leader_schedule_cache, bank_forks); let chunks = create_duplicate_proof( my_keypair.clone(), None, @@ -340,13 +337,12 @@ mod tests { let my_pubkey = my_keypair.pubkey(); let genesis_config_info = create_genesis_config_with_leader(10_000, &my_pubkey, 10_000); let GenesisConfigInfo { genesis_config, .. } = genesis_config_info; - let bank_forks = BankForks::new(Bank::new_for_tests(&genesis_config)); + let bank_forks = BankForks::new_rw_arc(Bank::new_for_tests(&genesis_config)); let leader_schedule_cache = Arc::new(LeaderScheduleCache::new_from_bank( - &bank_forks.working_bank(), + &bank_forks.read().unwrap().working_bank(), )); - let bank_forks_ptr = Arc::new(RwLock::new(bank_forks)); let mut duplicate_shred_handler = - DuplicateShredHandler::new(blockstore.clone(), leader_schedule_cache, bank_forks_ptr); + DuplicateShredHandler::new(blockstore.clone(), leader_schedule_cache, bank_forks); let start_slot: Slot = 1; // This proof will not be accepted because num_chunks is too large. diff --git a/gossip/src/main.rs b/gossip/src/main.rs index 226fab8d9d4..1f31195f431 100644 --- a/gossip/src/main.rs +++ b/gossip/src/main.rs @@ -326,7 +326,7 @@ fn process_rpc_url( } fn main() -> Result<(), Box> { - solana_logger::setup_with_default("solana=info"); + solana_logger::setup_with_default_filter(); let matches = parse_matches(); let socket_addr_space = SocketAddrSpace::new(matches.is_present("allow_private_addr")); diff --git a/gossip/tests/crds_gossip.rs b/gossip/tests/crds_gossip.rs index 827da50390c..74415ec3c8f 100644 --- a/gossip/tests/crds_gossip.rs +++ b/gossip/tests/crds_gossip.rs @@ -575,7 +575,6 @@ fn network_run_pull( .gossip .filter_pull_responses(&timeouts, rsp, now, &mut stats); node.gossip.process_pull_responses( - &from, vers, vers_expired_timeout, failed_inserts, diff --git a/gossip/tests/gossip.rs b/gossip/tests/gossip.rs index d9abeec31b5..569f7c480df 100644 --- a/gossip/tests/gossip.rs +++ b/gossip/tests/gossip.rs @@ -309,7 +309,7 @@ pub fn cluster_info_scale() { vec![100; vote_keypairs.len()], ); let bank0 = Bank::new_for_tests(&genesis_config_info.genesis_config); - let bank_forks = Arc::new(RwLock::new(BankForks::new(bank0))); + let bank_forks = BankForks::new_rw_arc(bank0); let nodes: Vec<_> = vote_keypairs .into_iter() diff --git a/install/Cargo.toml b/install/Cargo.toml index 588d4315df5..c40a0ee6e9e 100644 --- a/install/Cargo.toml +++ b/install/Cargo.toml @@ -1,7 +1,7 @@ [package] -name = "solana-install" +name = "agave-install" description = "The solana cluster software installer" -documentation = "https://docs.rs/solana-install" +documentation = "https://docs.rs/agave-install" version = { workspace = true } authors = { workspace = true } repository = { workspace = true } diff --git a/install/solana-install-init.sh b/install/agave-install-init.sh similarity index 86% rename from install/solana-install-init.sh rename to install/agave-install-init.sh index db36dc61e2f..cf2d1babf3c 100755 --- a/install/solana-install-init.sh +++ b/install/agave-install-init.sh @@ -10,25 +10,25 @@ # except according to those terms. # This is just a little script that can be downloaded from the internet to -# install solana-install. It just does platform detection, downloads the installer +# install agave-install. It just does platform detection, downloads the installer # and runs it. { # this ensures the entire script is downloaded # if [ -z "$SOLANA_DOWNLOAD_ROOT" ]; then - SOLANA_DOWNLOAD_ROOT="https://github.com/solana-labs/solana/releases/download/" + SOLANA_DOWNLOAD_ROOT="https://github.com/anza-xyz/agave/releases/download/" fi -GH_LATEST_RELEASE="https://api.github.com/repos/solana-labs/solana/releases/latest" +GH_LATEST_RELEASE="https://api.github.com/repos/anza-xyz/agave/releases/latest" set -e usage() { cat 1>&2 < --pubkey + agave-install-init [FLAGS] [OPTIONS] --data_dir --pubkey FLAGS: -h, --help Prints help information @@ -81,7 +81,7 @@ main() { esac TARGET="${_cputype}-${_ostype}" - temp_dir="$(mktemp -d 2>/dev/null || ensure mktemp -d -t solana-install-init)" + temp_dir="$(mktemp -d 2>/dev/null || ensure mktemp -d -t agave-install-init)" ensure mkdir -p "$temp_dir" # Check for SOLANA_RELEASE environment variable override. Otherwise fetch @@ -101,8 +101,8 @@ main() { fi fi - download_url="$SOLANA_DOWNLOAD_ROOT/$release/solana-install-init-$TARGET" - solana_install_init="$temp_dir/solana-install-init" + download_url="$SOLANA_DOWNLOAD_ROOT/$release/agave-install-init-$TARGET" + solana_install_init="$temp_dir/agave-install-init" printf 'downloading %s installer\n' "$release" 1>&2 @@ -111,7 +111,7 @@ main() { ensure chmod u+x "$solana_install_init" if [ ! -x "$solana_install_init" ]; then printf '%s\n' "Cannot execute $solana_install_init (likely because of mounting /tmp as noexec)." 1>&2 - printf '%s\n' "Please copy the file to a location where you can execute binaries and run ./solana-install-init." 1>&2 + printf '%s\n' "Please copy the file to a location where you can execute binaries and run ./agave-install-init." 1>&2 exit 1 fi @@ -130,7 +130,7 @@ main() { } err() { - printf 'solana-install-init: %s\n' "$1" >&2 + printf 'agave-install-init: %s\n' "$1" >&2 exit 1 } diff --git a/install/install-help.sh b/install/install-help.sh index 9fb08afa6d1..7604777e378 100755 --- a/install/install-help.sh +++ b/install/install-help.sh @@ -4,11 +4,11 @@ set -e cd "$(dirname "$0")"/.. cargo="$(readlink -f "./cargo")" -"$cargo" build --package solana-install +"$cargo" build --package agave-install export PATH=$PWD/target/debug:$PATH echo "\`\`\`manpage" -solana-install --help +agave-install --help echo "\`\`\`" echo "" @@ -16,7 +16,7 @@ commands=(init info deploy update run) for x in "${commands[@]}"; do echo "\`\`\`manpage" - solana-install "${x}" --help + agave-install "${x}" --help echo "\`\`\`" echo "" done diff --git a/install/src/bin/solana-install-init.rs b/install/src/bin/agave-install-init.rs similarity index 92% rename from install/src/bin/solana-install-init.rs rename to install/src/bin/agave-install-init.rs index ec888d8f452..84c154ac12b 100644 --- a/install/src/bin/solana-install-init.rs +++ b/install/src/bin/agave-install-init.rs @@ -16,7 +16,7 @@ fn press_enter() { } fn main() { - solana_install::main_init().unwrap_or_else(|err| { + agave_install::main_init().unwrap_or_else(|err| { println!("Error: {err}"); press_enter(); exit(1); diff --git a/install/src/command.rs b/install/src/command.rs index ac53f5fe2b5..996799835fa 100644 --- a/install/src/command.rs +++ b/install/src/command.rs @@ -541,7 +541,7 @@ pub fn init( explicit_release: Option, ) -> Result<(), String> { let config = { - // Write new config file only if different, so that running |solana-install init| + // Write new config file only if different, so that running |agave-install init| // repeatedly doesn't unnecessarily re-download let mut current_config = Config::load(config_file).unwrap_or_default(); current_config.current_update_manifest = None; @@ -573,7 +573,7 @@ pub fn init( fn github_release_download_url(release_semver: &str) -> String { format!( - "https://github.com/solana-labs/solana/releases/download/v{}/solana-release-{}.tar.bz2", + "https://github.com/anza-xyz/agave/releases/download/v{}/solana-release-{}.tar.bz2", release_semver, crate::build_env::TARGET ) @@ -581,7 +581,7 @@ fn github_release_download_url(release_semver: &str) -> String { fn release_channel_download_url(release_channel: &str) -> String { format!( - "https://release.solana.com/{}/solana-release-{}.tar.bz2", + "https://release.anza.xyz/{}/solana-release-{}.tar.bz2", release_channel, crate::build_env::TARGET ) @@ -589,7 +589,7 @@ fn release_channel_download_url(release_channel: &str) -> String { fn release_channel_version_url(release_channel: &str) -> String { format!( - "https://release.solana.com/{}/solana-release-{}.yml", + "https://release.anza.xyz/{}/solana-release-{}.yml", release_channel, crate::build_env::TARGET ) @@ -871,7 +871,7 @@ fn check_for_newer_github_release( prerelease_allowed: bool, ) -> Result, String> { let client = reqwest::blocking::Client::builder() - .user_agent("solana-install") + .user_agent("agave-install") .build() .map_err(|err| err.to_string())?; @@ -906,7 +906,7 @@ fn check_for_newer_github_release( while page == 1 || releases.len() == PER_PAGE { let url = reqwest::Url::parse_with_params( - "https://api.github.com/repos/solana-labs/solana/releases", + "https://api.github.com/repos/anza-xyz/agave/releases", &[ ("per_page", &format!("{PER_PAGE}")), ("page", &format!("{page}")), diff --git a/install/src/lib.rs b/install/src/lib.rs index 159317edd2e..a28b963d65f 100644 --- a/install/src/lib.rs +++ b/install/src/lib.rs @@ -281,7 +281,7 @@ pub fn main() -> Result<(), String> { pub fn main_init() -> Result<(), String> { solana_logger::setup(); - let matches = App::new("solana-install-init") + let matches = App::new("agave-install-init") .about("Initializes a new installation") .version(solana_version::version!()) .arg({ diff --git a/install/src/main.rs b/install/src/main.rs index c7b15aa6a67..245f09825dd 100644 --- a/install/src/main.rs +++ b/install/src/main.rs @@ -1,3 +1,3 @@ fn main() -> Result<(), String> { - solana_install::main() + agave_install::main() } diff --git a/ledger-tool/Cargo.toml b/ledger-tool/Cargo.toml index fb387773c14..66b4b551064 100644 --- a/ledger-tool/Cargo.toml +++ b/ledger-tool/Cargo.toml @@ -1,7 +1,7 @@ [package] -name = "solana-ledger-tool" +name = "agave-ledger-tool" description = "Blockchain, Rebuilt for Scale" -documentation = "https://docs.rs/solana-ledger-tool" +documentation = "https://docs.rs/agave-ledger-tool" version = { workspace = true } authors = { workspace = true } repository = { workspace = true } diff --git a/ledger-tool/src/bigtable.rs b/ledger-tool/src/bigtable.rs index 6f0f3e8829e..59cf809a2c0 100644 --- a/ledger-tool/src/bigtable.rs +++ b/ledger-tool/src/bigtable.rs @@ -126,7 +126,7 @@ async fn block( BlockEncodingOptions { transaction_details: TransactionDetails::Full, show_rewards: true, - max_supported_transaction_version: None, + max_supported_transaction_version: Some(0), }, ) .map_err(|err| match err { @@ -585,6 +585,7 @@ async fn get_bigtable( credential_type: CredentialType::Filepath(Some(args.crediential_path.unwrap())), instance_name: args.instance_name, app_profile_id: args.app_profile_id, + max_message_size: solana_storage_bigtable::DEFAULT_MAX_MESSAGE_SIZE, }, ) .await diff --git a/ledger-tool/src/ledger_utils.rs b/ledger-tool/src/ledger_utils.rs index 6514312bc5d..e96034077f0 100644 --- a/ledger-tool/src/ledger_utils.rs +++ b/ledger-tool/src/ledger_utils.rs @@ -137,14 +137,14 @@ pub fn load_and_process_ledger( } let account_paths = if let Some(account_paths) = arg_matches.value_of("account_paths") { - // If this blockstore access is Primary, no other process (solana-validator) can hold + // If this blockstore access is Primary, no other process (agave-validator) can hold // Primary access. So, allow a custom accounts path without worry of wiping the accounts - // of solana-validator. + // of agave-validator. if !blockstore.is_primary_access() { // Attempt to open the Blockstore in Primary access; if successful, no other process // was holding Primary so allow things to proceed with custom accounts path. Release - // the Primary access instead of holding it to give priority to solana-validator over - // solana-ledger-tool should solana-validator start before we've finished. + // the Primary access instead of holding it to give priority to agave-validator over + // agave-ledger-tool should agave-validator start before we've finished. info!( "Checking if another process currently holding Primary access to {:?}", blockstore.ledger_path() diff --git a/ledger-tool/src/main.rs b/ledger-tool/src/main.rs index 2fa26528497..f7b25ec2796 100644 --- a/ledger-tool/src/main.rs +++ b/ledger-tool/src/main.rs @@ -1081,7 +1081,7 @@ fn main() { const DEFAULT_MAX_FULL_SNAPSHOT_ARCHIVES_TO_RETAIN: usize = std::usize::MAX; const DEFAULT_MAX_INCREMENTAL_SNAPSHOT_ARCHIVES_TO_RETAIN: usize = std::usize::MAX; - solana_logger::setup_with_default("solana=info"); + solana_logger::setup_with_default_filter(); let starting_slot_arg = Arg::with_name("starting_slot") .long("starting-slot") @@ -1132,7 +1132,8 @@ fn main() { .validator(is_parsable::) .takes_value(true) .default_value("0") - .help("How many accounts to add to stress the system. Accounts are ignored in operations related to correctness."); + .help("How many accounts to add to stress the system. Accounts are ignored in operations related to correctness.") + .hidden(hidden_unless_forced()); let accounts_filler_size = Arg::with_name("accounts_filler_size") .long("accounts-filler-size") .value_name("BYTES") @@ -1140,7 +1141,8 @@ fn main() { .takes_value(true) .default_value("0") .requires("accounts_filler_count") - .help("Size per filler account in bytes."); + .help("Size per filler account in bytes.") + .hidden(hidden_unless_forced()); let account_paths_arg = Arg::with_name("account_paths") .long("accounts") .value_name("PATHS") @@ -2691,7 +2693,11 @@ fn main() { } if write_bank_file { let working_bank = bank_forks.read().unwrap().working_bank(); - let _ = bank_hash_details::write_bank_hash_details_file(&working_bank); + bank_hash_details::write_bank_hash_details_file(&working_bank) + .map_err(|err| { + warn!("Unable to write bank hash_details file: {err}"); + }) + .ok(); } exit_signal.store(true, Ordering::Relaxed); system_monitor_service.join().unwrap(); diff --git a/ledger-tool/src/program.rs b/ledger-tool/src/program.rs index 4acad738160..ba86b1de995 100644 --- a/ledger-tool/src/program.rs +++ b/ledger-tool/src/program.rs @@ -552,7 +552,7 @@ pub fn program(ledger_path: &Path, matches: &ArgMatches<'_>) { .clone(), ); for key in cached_account_keys { - loaded_programs.replenish(key, bank.load_program(&key, false)); + loaded_programs.replenish(key, bank.load_program(&key, false, None)); debug!("Loaded program {}", key); } invoke_context.programs_loaded_for_tx_batch = &loaded_programs; diff --git a/ledger/src/bank_forks_utils.rs b/ledger/src/bank_forks_utils.rs index b46d950adba..c75380581fc 100644 --- a/ledger/src/bank_forks_utils.rs +++ b/ledger/src/bank_forks_utils.rs @@ -264,6 +264,7 @@ fn bank_forks_from_snapshot( process_options.shrink_ratio, process_options.accounts_db_test_hash_calculation, process_options.accounts_db_skip_shrink, + process_options.accounts_db_force_initial_clean, process_options.verify_index, process_options.accounts_db_config.clone(), accounts_update_notifier, @@ -357,8 +358,5 @@ fn bank_forks_from_snapshot( incremental: incremental_snapshot_hash, }; - ( - Arc::new(RwLock::new(BankForks::new(bank))), - starting_snapshot_hashes, - ) + (BankForks::new_rw_arc(bank), starting_snapshot_hashes) } diff --git a/ledger/src/bigtable_upload.rs b/ledger/src/bigtable_upload.rs index 3db5f8eebbe..be28ee8a070 100644 --- a/ledger/src/bigtable_upload.rs +++ b/ledger/src/bigtable_upload.rs @@ -138,7 +138,7 @@ pub async fn upload_confirmed_blocks( "No blocks between {} and {} need to be uploaded to bigtable", starting_slot, ending_slot ); - return Ok(last_blockstore_slot); + return Ok(ending_slot); } let last_slot = *blocks_to_upload.last().unwrap(); info!( diff --git a/ledger/src/bigtable_upload_service.rs b/ledger/src/bigtable_upload_service.rs index 3149eb96a32..0ffb02aac24 100644 --- a/ledger/src/bigtable_upload_service.rs +++ b/ledger/src/bigtable_upload_service.rs @@ -117,7 +117,7 @@ impl BigTableUploadService { )); match result { - Ok(last_slot_uploaded) => start_slot = last_slot_uploaded, + Ok(last_slot_uploaded) => start_slot = last_slot_uploaded.saturating_add(1), Err(err) => { warn!("bigtable: upload_confirmed_blocks: {}", err); std::thread::sleep(std::time::Duration::from_secs(2)); diff --git a/ledger/src/blockstore.rs b/ledger/src/blockstore.rs index b4426aa3678..bd1d19b7d33 100644 --- a/ledger/src/blockstore.rs +++ b/ledger/src/blockstore.rs @@ -211,6 +211,7 @@ pub struct Blockstore { program_costs_cf: LedgerColumn, bank_hash_cf: LedgerColumn, optimistic_slots_cf: LedgerColumn, + merkle_root_meta_cf: LedgerColumn, last_root: RwLock, insert_shreds_lock: Mutex<()>, new_shreds_signals: Mutex>>, @@ -312,6 +313,7 @@ impl Blockstore { let program_costs_cf = db.column(); let bank_hash_cf = db.column(); let optimistic_slots_cf = db.column(); + let merkle_root_meta_cf = db.column(); let db = Arc::new(db); @@ -365,6 +367,7 @@ impl Blockstore { program_costs_cf, bank_hash_cf, optimistic_slots_cf, + merkle_root_meta_cf, new_shreds_signals: Mutex::default(), completed_slots_senders: Mutex::default(), shred_timing_point_sender: None, @@ -734,6 +737,7 @@ impl Blockstore { self.program_costs_cf.submit_rocksdb_cf_metrics(); self.bank_hash_cf.submit_rocksdb_cf_metrics(); self.optimistic_slots_cf.submit_rocksdb_cf_metrics(); + self.merkle_root_meta_cf.submit_rocksdb_cf_metrics(); } fn try_shred_recovery( @@ -1127,18 +1131,25 @@ impl Blockstore { // Clear this slot as a next slot from parent if let Some(parent_slot) = slot_meta.parent_slot { - let mut parent_slot_meta = self + if let Some(mut parent_slot_meta) = self .meta(parent_slot) .expect("Couldn't fetch from SlotMeta column family") - .expect("Unconfirmed slot should have had parent slot set"); - // .retain() is a linear scan; however, next_slots should - // only contain several elements so this isn't so bad - parent_slot_meta - .next_slots - .retain(|&next_slot| next_slot != slot); - self.meta_cf - .put(parent_slot, &parent_slot_meta) - .expect("Couldn't insert into SlotMeta column family"); + { + // .retain() is a linear scan; however, next_slots should + // only contain several elements so this isn't so bad + parent_slot_meta + .next_slots + .retain(|&next_slot| next_slot != slot); + self.meta_cf + .put(parent_slot, &parent_slot_meta) + .expect("Couldn't insert into SlotMeta column family"); + } else { + error!( + "Parent slot meta {} for child {} is missing or cleaned up. + Falling back to orphan repair to remedy the situation", + parent_slot, slot + ); + } } // Reinsert parts of `slot_meta` that are important to retain, like the `next_slots` // field. @@ -2310,14 +2321,16 @@ impl Blockstore { let (lock, lowest_available_slot) = self.ensure_lowest_cleanup_slot(); for transaction_status_cf_primary_index in 0..=1 { - let index_iterator = self.transaction_status_cf.iter(IteratorMode::From( - ( - transaction_status_cf_primary_index, - signature, - lowest_available_slot, - ), - IteratorDirection::Forward, - ))?; + let index_iterator = + self.transaction_status_cf + .iter_current_index_filtered(IteratorMode::From( + ( + transaction_status_cf_primary_index, + signature, + lowest_available_slot, + ), + IteratorDirection::Forward, + ))?; for ((i, sig, slot), _data) in index_iterator { counter += 1; if i != transaction_status_cf_primary_index || sig != signature { @@ -2459,15 +2472,17 @@ impl Blockstore { let mut signatures: Vec<(Slot, Signature)> = vec![]; for transaction_status_cf_primary_index in 0..=1 { - let index_iterator = self.address_signatures_cf.iter(IteratorMode::From( - ( - transaction_status_cf_primary_index, - pubkey, - start_slot.max(lowest_available_slot), - Signature::default(), - ), - IteratorDirection::Forward, - ))?; + let index_iterator = + self.address_signatures_cf + .iter_current_index_filtered(IteratorMode::From( + ( + transaction_status_cf_primary_index, + pubkey, + start_slot.max(lowest_available_slot), + Signature::default(), + ), + IteratorDirection::Forward, + ))?; for ((i, address, slot, signature), _) in index_iterator { if i != transaction_status_cf_primary_index || slot > end_slot || address != pubkey { @@ -2494,15 +2509,17 @@ impl Blockstore { let (lock, lowest_available_slot) = self.ensure_lowest_cleanup_slot(); let mut signatures: Vec<(Slot, Signature)> = vec![]; for transaction_status_cf_primary_index in 0..=1 { - let index_iterator = self.address_signatures_cf.iter(IteratorMode::From( - ( - transaction_status_cf_primary_index, - pubkey, - slot.max(lowest_available_slot), - Signature::default(), - ), - IteratorDirection::Forward, - ))?; + let index_iterator = + self.address_signatures_cf + .iter_current_index_filtered(IteratorMode::From( + ( + transaction_status_cf_primary_index, + pubkey, + slot.max(lowest_available_slot), + Signature::default(), + ), + IteratorDirection::Forward, + ))?; for ((i, address, transaction_slot, signature), _) in index_iterator { if i != transaction_status_cf_primary_index || transaction_slot > slot @@ -2661,10 +2678,12 @@ impl Blockstore { let mut starting_primary_index_iter_timer = Measure::start("starting_primary_index_iter"); if slot > next_max_slot { - let mut starting_iterator = self.address_signatures_cf.iter(IteratorMode::From( - (starting_primary_index, address, slot, Signature::default()), - IteratorDirection::Reverse, - ))?; + let mut starting_iterator = + self.address_signatures_cf + .iter_current_index_filtered(IteratorMode::From( + (starting_primary_index, address, slot, Signature::default()), + IteratorDirection::Reverse, + ))?; // Iterate through starting_iterator until limit is reached while address_signatures.len() < limit { @@ -2697,10 +2716,12 @@ impl Blockstore { // Iterate through next_iterator until limit is reached let mut next_primary_index_iter_timer = Measure::start("next_primary_index_iter_timer"); - let mut next_iterator = self.address_signatures_cf.iter(IteratorMode::From( - (next_primary_index, address, slot, Signature::default()), - IteratorDirection::Reverse, - ))?; + let mut next_iterator = + self.address_signatures_cf + .iter_current_index_filtered(IteratorMode::From( + (next_primary_index, address, slot, Signature::default()), + IteratorDirection::Reverse, + ))?; while address_signatures.len() < limit { if let Some(((i, key_address, slot, signature), _)) = next_iterator.next() { // Skip next_max_slot, which is already included diff --git a/ledger/src/blockstore/blockstore_purge.rs b/ledger/src/blockstore/blockstore_purge.rs index 090096d17e9..92f9453eabb 100644 --- a/ledger/src/blockstore/blockstore_purge.rs +++ b/ledger/src/blockstore/blockstore_purge.rs @@ -214,6 +214,10 @@ impl Blockstore { & self .db .delete_range_cf::(&mut write_batch, from_slot, to_slot) + .is_ok() + & self + .db + .delete_range_cf::(&mut write_batch, from_slot, to_slot) .is_ok(); let mut w_active_transaction_status_index = self.active_transaction_status_index.write().unwrap(); @@ -337,6 +341,10 @@ impl Blockstore { .db .delete_file_in_range_cf::(from_slot, to_slot) .is_ok() + & self + .db + .delete_file_in_range_cf::(from_slot, to_slot) + .is_ok() } /// Purges special columns (using a non-Slot primary-index) exactly, by diff --git a/ledger/src/blockstore_db.rs b/ledger/src/blockstore_db.rs index 25f68b8ef65..566e4cf18c9 100644 --- a/ledger/src/blockstore_db.rs +++ b/ledger/src/blockstore_db.rs @@ -2,6 +2,7 @@ pub use rocksdb::Direction as IteratorDirection; use { crate::{ blockstore_meta, + blockstore_meta::MerkleRootMeta, blockstore_metrics::{ maybe_enable_rocksdb_perf, report_rocksdb_read_perf, report_rocksdb_write_perf, BlockstoreRocksDbColumnFamilyMetrics, PerfSamplingStatus, PERF_METRIC_OP_NAME_GET, @@ -34,7 +35,7 @@ use { }, solana_storage_proto::convert::generated, std::{ - collections::HashMap, + collections::{HashMap, HashSet}, ffi::{CStr, CString}, fs, marker::PhantomData, @@ -103,6 +104,8 @@ const BLOCK_HEIGHT_CF: &str = "block_height"; const PROGRAM_COSTS_CF: &str = "program_costs"; /// Column family for optimistic slots const OPTIMISTIC_SLOTS_CF: &str = "optimistic_slots"; +/// Column family for merkle roots +const MERKLE_ROOT_META_CF: &str = "merkle_root_meta"; #[derive(Error, Debug)] pub enum BlockstoreError { @@ -323,6 +326,19 @@ pub mod columns { /// * value type: [`blockstore_meta::OptimisticSlotMetaVersioned`] pub struct OptimisticSlots; + #[derive(Debug)] + /// The merkle root meta column + /// + /// Each merkle shred is part of a merkle tree for + /// its FEC set. This column stores that merkle root and associated + /// meta information about the first shred received. + /// + /// Its index type is (Slot, fec_set_index). + /// + /// * index type: `crate::shred::ErasureSetId` `(Slot, fec_set_index: u32)` + /// * value type: [`blockstore_meta::MerkleRootMeta`]` + pub struct MerkleRootMeta; + // When adding a new column ... // - Add struct below and implement `Column` and `ColumnName` traits // - Add descriptor in Rocks::cf_descriptors() and name in Rocks::columns() @@ -376,15 +392,12 @@ impl Rocks { } let oldest_slot = OldestSlot::default(); let column_options = options.column_options.clone(); + let cf_descriptors = Self::cf_descriptors(path, &options, &oldest_slot); // Open the database let db = match access_type { AccessType::Primary | AccessType::PrimaryForMaintenance => Rocks { - db: DB::open_cf_descriptors( - &db_options, - path, - Self::cf_descriptors(&options, &oldest_slot), - )?, + db: DB::open_cf_descriptors(&db_options, path, cf_descriptors)?, access_type, oldest_slot, column_options, @@ -397,14 +410,14 @@ impl Rocks { "Opening Rocks with secondary (read only) access at: {:?}", secondary_path ); - info!("This secondary access could temporarily degrade other accesses, such as by solana-validator"); + info!("This secondary access could temporarily degrade other accesses, such as by agave-validator"); Rocks { db: DB::open_cf_descriptors_as_secondary( &db_options, path, &secondary_path, - Self::cf_descriptors(&options, &oldest_slot), + cf_descriptors, )?, access_type, oldest_slot, @@ -418,7 +431,17 @@ impl Rocks { Ok(db) } + /// Create the column family (CF) descriptors necessary to open the database. + /// + /// In order to open a RocksDB database with Primary access, all columns must be opened. So, + /// in addition to creating descriptors for all of the expected columns, also create + /// descriptors for columns that were discovered but are otherwise unknown to the software. + /// + /// One case where columns could be unknown is if a RocksDB database is modified with a newer + /// software version that adds a new column, and then also opened with an older version that + /// did not have knowledge of that new column. fn cf_descriptors( + path: &Path, options: &BlockstoreOptions, oldest_slot: &OldestSlot, ) -> Vec { @@ -426,7 +449,7 @@ impl Rocks { let (cf_descriptor_shred_data, cf_descriptor_shred_code) = new_cf_descriptor_pair_shreds::(options, oldest_slot); - vec![ + let mut cf_descriptors = vec![ new_cf_descriptor::(options, oldest_slot), new_cf_descriptor::(options, oldest_slot), new_cf_descriptor::(options, oldest_slot), @@ -447,7 +470,53 @@ impl Rocks { new_cf_descriptor::(options, oldest_slot), new_cf_descriptor::(options, oldest_slot), new_cf_descriptor::(options, oldest_slot), - ] + new_cf_descriptor::(options, oldest_slot), + ]; + + // If the access type is Secondary, we don't need to open all of the + // columns so we can just return immediately. + match options.access_type { + AccessType::Secondary => { + return cf_descriptors; + } + AccessType::Primary | AccessType::PrimaryForMaintenance => {} + } + + // Attempt to detect the column families that are present. It is not a + // fatal error if we cannot, for example, if the Blockstore is brand + // new and will be created by the call to Rocks::open(). + let detected_cfs = match DB::list_cf(&Options::default(), path) { + Ok(detected_cfs) => detected_cfs, + Err(err) => { + warn!("Unable to detect Rocks columns: {err:?}"); + vec![] + } + }; + // The default column is handled automatically, we don't need to create + // a descriptor for it + const DEFAULT_COLUMN_NAME: &str = "default"; + let known_cfs: HashSet<_> = cf_descriptors + .iter() + .map(|cf_descriptor| cf_descriptor.name().to_string()) + .chain(std::iter::once(DEFAULT_COLUMN_NAME.to_string())) + .collect(); + detected_cfs.iter().for_each(|cf_name| { + if known_cfs.get(cf_name.as_str()).is_none() { + info!("Detected unknown column {cf_name}, opening column with basic options"); + // This version of the software was unaware of the column, so + // it is fair to assume that we will not attempt to read or + // write the column. So, set some bare bones settings to avoid + // using extra resources on this unknown column. + let mut options = Options::default(); + // Lower the default to avoid unnecessary allocations + options.set_write_buffer_size(1024 * 1024); + // Disable compactions to avoid any modifications to the column + options.set_disable_auto_compactions(true); + cf_descriptors.push(ColumnFamilyDescriptor::new(cf_name, options)); + } + }); + + cf_descriptors } fn columns() -> Vec<&'static str> { @@ -474,6 +543,7 @@ impl Rocks { BlockHeight::NAME, ProgramCosts::NAME, OptimisticSlots::NAME, + MerkleRootMeta::NAME, ] } @@ -730,6 +800,19 @@ impl Column for T { } } +#[derive(Debug)] +pub enum IndexError { + UnpackError, +} + +/// Helper trait to transition primary indexes out from the columns that are using them. This +/// abbreviated trait assists in iterating past data with new keys. It will be modified and +/// expanded in a future version to support writing with the new key and reading both key types. +pub trait ColumnIndexDeprecation: Column { + const CURRENT_INDEX_LEN: usize; + fn try_current_index(key: &[u8]) -> std::result::Result; +} + impl Column for columns::TransactionStatus { type Index = (u64, Signature, Slot); @@ -742,14 +825,8 @@ impl Column for columns::TransactionStatus { } fn index(key: &[u8]) -> (u64, Signature, Slot) { - if key.len() != 80 { - Self::as_index(0) - } else { - let index = BigEndian::read_u64(&key[0..8]); - let signature = Signature::try_from(&key[8..72]).unwrap(); - let slot = BigEndian::read_u64(&key[72..80]); - (index, signature, slot) - } + ::try_current_index(key) + .unwrap_or_else(|_| Self::as_index(0)) } fn primary_index(index: Self::Index) -> u64 { @@ -771,6 +848,20 @@ impl ProtobufColumn for columns::TransactionStatus { type Type = generated::TransactionStatusMeta; } +impl ColumnIndexDeprecation for columns::TransactionStatus { + const CURRENT_INDEX_LEN: usize = 80; + + fn try_current_index(key: &[u8]) -> std::result::Result { + if key.len() != Self::CURRENT_INDEX_LEN { + return Err(IndexError::UnpackError); + } + let primary_index = BigEndian::read_u64(&key[0..8]); + let signature = Signature::try_from(&key[8..72]).unwrap(); + let slot = BigEndian::read_u64(&key[72..80]); + Ok((primary_index, signature, slot)) + } +} + impl Column for columns::AddressSignatures { type Index = (u64, Pubkey, Slot, Signature); @@ -784,11 +875,8 @@ impl Column for columns::AddressSignatures { } fn index(key: &[u8]) -> (u64, Pubkey, Slot, Signature) { - let index = BigEndian::read_u64(&key[0..8]); - let pubkey = Pubkey::try_from(&key[8..40]).unwrap(); - let slot = BigEndian::read_u64(&key[40..48]); - let signature = Signature::try_from(&key[48..112]).unwrap(); - (index, pubkey, slot, signature) + ::try_current_index(key) + .unwrap_or_else(|_| Self::as_index(0)) } fn primary_index(index: Self::Index) -> u64 { @@ -807,6 +895,21 @@ impl ColumnName for columns::AddressSignatures { const NAME: &'static str = ADDRESS_SIGNATURES_CF; } +impl ColumnIndexDeprecation for columns::AddressSignatures { + const CURRENT_INDEX_LEN: usize = 112; + + fn try_current_index(key: &[u8]) -> std::result::Result { + if key.len() != Self::CURRENT_INDEX_LEN { + return Err(IndexError::UnpackError); + } + let primary_index = BigEndian::read_u64(&key[0..8]); + let pubkey = Pubkey::try_from(&key[8..40]).unwrap(); + let slot = BigEndian::read_u64(&key[40..48]); + let signature = Signature::try_from(&key[48..112]).unwrap(); + Ok((primary_index, pubkey, slot, signature)) + } +} + impl Column for columns::TransactionMemos { type Index = Signature; @@ -1073,6 +1176,39 @@ impl TypedColumn for columns::OptimisticSlots { type Type = blockstore_meta::OptimisticSlotMetaVersioned; } +impl Column for columns::MerkleRootMeta { + type Index = (Slot, /*fec_set_index:*/ u32); + + fn index(key: &[u8]) -> Self::Index { + let slot = BigEndian::read_u64(&key[..8]); + let fec_set_index = BigEndian::read_u32(&key[8..]); + + (slot, fec_set_index) + } + + fn key((slot, fec_set_index): Self::Index) -> Vec { + let mut key = vec![0; 12]; + BigEndian::write_u64(&mut key[..8], slot); + BigEndian::write_u32(&mut key[8..], fec_set_index); + key + } + + fn primary_index((slot, _fec_set_index): Self::Index) -> Slot { + slot + } + + fn as_index(slot: Slot) -> Self::Index { + (slot, 0) + } +} + +impl ColumnName for columns::MerkleRootMeta { + const NAME: &'static str = MERKLE_ROOT_META_CF; +} +impl TypedColumn for columns::MerkleRootMeta { + type Type = MerkleRootMeta; +} + #[derive(Debug)] pub struct Database { backend: Arc, @@ -1617,6 +1753,23 @@ where } } +impl LedgerColumn +where + C: ColumnIndexDeprecation + ColumnName, +{ + pub(crate) fn iter_current_index_filtered( + &self, + iterator_mode: IteratorMode, + ) -> Result)> + '_> { + let cf = self.handle(); + let iter = self.backend.iterator_cf::(cf, iterator_mode); + Ok(iter.filter_map(|pair| { + let (key, value) = pair.unwrap(); + C::try_current_index(&key).ok().map(|index| (index, value)) + })) + } +} + impl<'a> WriteBatch<'a> { pub fn put_bytes(&mut self, key: C::Index, bytes: &[u8]) -> Result<()> { self.write_batch @@ -1933,7 +2086,9 @@ fn should_enable_compression() -> bool { #[cfg(test)] pub mod tests { - use {super::*, crate::blockstore_db::columns::ShredData}; + use { + super::*, crate::blockstore_db::columns::ShredData, std::path::PathBuf, tempfile::tempdir, + }; #[test] fn test_compaction_filter() { @@ -1985,6 +2140,7 @@ pub mod tests { #[test] fn test_cf_names_and_descriptors_equal_length() { + let path = PathBuf::default(); let options = BlockstoreOptions::default(); let oldest_slot = OldestSlot::default(); // The names and descriptors don't need to be in the same order for our use cases; @@ -1992,7 +2148,7 @@ pub mod tests { // should update both lists. assert_eq!( Rocks::columns().len(), - Rocks::cf_descriptors(&options, &oldest_slot).len() + Rocks::cf_descriptors(&path, &options, &oldest_slot).len() ); } @@ -2016,4 +2172,47 @@ pub mod tests { }); assert!(!should_enable_cf_compaction("something else")); } + + #[test] + fn test_open_unknown_columns() { + solana_logger::setup(); + + let temp_dir = tempdir().unwrap(); + let db_path = temp_dir.path(); + + // Open with Primary to create the new database + { + let options = BlockstoreOptions { + access_type: AccessType::Primary, + enforce_ulimit_nofile: false, + ..BlockstoreOptions::default() + }; + let mut rocks = Rocks::open(db_path, options).unwrap(); + + // Introduce a new column that will not be known + rocks + .db + .create_cf("new_column", &Options::default()) + .unwrap(); + } + + // Opening with either Secondary or Primary access should succeed, + // even though the Rocks code is unaware of "new_column" + { + let options = BlockstoreOptions { + access_type: AccessType::Secondary, + enforce_ulimit_nofile: false, + ..BlockstoreOptions::default() + }; + let _ = Rocks::open(db_path, options).unwrap(); + } + { + let options = BlockstoreOptions { + access_type: AccessType::Primary, + enforce_ulimit_nofile: false, + ..BlockstoreOptions::default() + }; + let _ = Rocks::open(db_path, options).unwrap(); + } + } } diff --git a/ledger/src/blockstore_meta.rs b/ledger/src/blockstore_meta.rs index 79954ee96b6..6e618154f20 100644 --- a/ledger/src/blockstore_meta.rs +++ b/ledger/src/blockstore_meta.rs @@ -138,6 +138,16 @@ pub(crate) struct ErasureConfig { num_coding: usize, } +#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)] +pub struct MerkleRootMeta { + /// The merkle root, `None` for legacy shreds + merkle_root: Option, + /// The first received shred index + first_received_shred_index: u32, + /// The shred type of the first received shred + first_received_shred_type: ShredType, +} + #[derive(Deserialize, Serialize)] pub struct DuplicateSlotProof { #[serde(with = "serde_bytes")] @@ -396,6 +406,35 @@ impl ErasureMeta { } } +#[allow(dead_code)] +impl MerkleRootMeta { + pub(crate) fn from_shred(shred: &Shred) -> Self { + Self { + // An error here after the shred has already sigverified + // can only indicate that the leader is sending + // legacy or malformed shreds. We should still store + // `None` for those cases in blockstore, as a later + // shred that contains a proper merkle root would constitute + // a valid duplicate shred proof. + merkle_root: shred.merkle_root().ok(), + first_received_shred_index: shred.index(), + first_received_shred_type: shred.shred_type(), + } + } + + pub(crate) fn merkle_root(&self) -> Option { + self.merkle_root + } + + pub(crate) fn first_received_shred_index(&self) -> u32 { + self.first_received_shred_index + } + + pub(crate) fn first_received_shred_type(&self) -> ShredType { + self.first_received_shred_type + } +} + impl DuplicateSlotProof { pub(crate) fn new(shred1: Vec, shred2: Vec) -> Self { DuplicateSlotProof { shred1, shred2 } diff --git a/ledger/src/blockstore_processor.rs b/ledger/src/blockstore_processor.rs index f5a8836087d..ff30c87b7e2 100644 --- a/ledger/src/blockstore_processor.rs +++ b/ledger/src/blockstore_processor.rs @@ -610,6 +610,7 @@ pub struct ProcessOptions { pub allow_dead_slots: bool, pub accounts_db_test_hash_calculation: bool, pub accounts_db_skip_shrink: bool, + pub accounts_db_force_initial_clean: bool, pub accounts_db_config: Option, pub verify_index: bool, pub shrink_ratio: AccountShrinkThreshold, @@ -714,7 +715,7 @@ pub(crate) fn process_blockstore_for_bank_0( accounts_update_notifier, exit, ); - let bank_forks = Arc::new(RwLock::new(BankForks::new(bank0))); + let bank_forks = BankForks::new_rw_arc(bank0); info!("Processing ledger for slot 0..."); process_bank_0( @@ -1554,6 +1555,11 @@ fn load_frozen_forks( root = new_root_bank.slot(); leader_schedule_cache.set_root(new_root_bank); + new_root_bank + .loaded_programs_cache + .write() + .unwrap() + .prune(root, new_root_bank.epoch()); let _ = bank_forks.write().unwrap().set_root( root, accounts_background_request_sender, @@ -2988,7 +2994,7 @@ pub mod tests { ] } - declare_process_instruction!(mock_processor_ok, 1, |_invoke_context| { + declare_process_instruction!(MockBuiltinOk, 1, |_invoke_context| { // Always succeeds Ok(()) }); @@ -2996,7 +3002,7 @@ pub mod tests { let mock_program_id = solana_sdk::pubkey::new_rand(); let mut bank = Bank::new_for_tests(&genesis_config); - bank.add_mockup_builtin(mock_program_id, mock_processor_ok); + bank.add_mockup_builtin(mock_program_id, MockBuiltinOk::vm); let tx = Transaction::new_signed_with_payer( &[Instruction::new_with_bincode( @@ -3017,7 +3023,7 @@ pub mod tests { let bankhash_ok = bank.hash(); assert!(result.is_ok()); - declare_process_instruction!(mock_processor_err, 1, |invoke_context| { + declare_process_instruction!(MockBuiltinErr, 1, |invoke_context| { let instruction_errors = get_instruction_errors(); let err = invoke_context @@ -3037,7 +3043,7 @@ pub mod tests { (0..get_instruction_errors().len()).for_each(|err| { let mut bank = Bank::new_for_tests(&genesis_config); - bank.add_mockup_builtin(mock_program_id, mock_processor_err); + bank.add_mockup_builtin(mock_program_id, MockBuiltinErr::vm); let tx = Transaction::new_signed_with_payer( &[Instruction::new_with_bincode( @@ -3553,8 +3559,8 @@ pub mod tests { blockstore.set_roots([3, 5].iter()).unwrap(); // Set up bank1 - let mut bank_forks = BankForks::new(Bank::new_for_tests(&genesis_config)); - let bank0 = bank_forks.get(0).unwrap(); + let bank_forks = BankForks::new_rw_arc(Bank::new_for_tests(&genesis_config)); + let bank0 = bank_forks.read().unwrap().get(0).unwrap(); let opts = ProcessOptions { run_verification: true, accounts_db_test_hash_calculation: true, @@ -3563,7 +3569,11 @@ pub mod tests { let recyclers = VerifyRecyclers::default(); process_bank_0(&bank0, &blockstore, &opts, &recyclers, None, None); let bank0_last_blockhash = bank0.last_blockhash(); - let bank1 = bank_forks.insert(Bank::new_from_parent(bank0, &Pubkey::default(), 1)); + let bank1 = + bank_forks + .write() + .unwrap() + .insert(Bank::new_from_parent(bank0, &Pubkey::default(), 1)); confirm_full_slot( &blockstore, &bank1, @@ -3576,7 +3586,7 @@ pub mod tests { &mut ExecuteTimings::default(), ) .unwrap(); - bank_forks.set_root( + bank_forks.write().unwrap().set_root( 1, &solana_runtime::accounts_background_service::AbsRequestSender::default(), None, @@ -3585,7 +3595,6 @@ pub mod tests { let leader_schedule_cache = LeaderScheduleCache::new_from_bank(&bank1); // Test process_blockstore_from_root() from slot 1 onwards - let bank_forks = RwLock::new(bank_forks); process_blockstore_from_root( &blockstore, &bank_forks, diff --git a/ledger/src/shred.rs b/ledger/src/shred.rs index 5fda160e29b..ebb0b791e56 100644 --- a/ledger/src/shred.rs +++ b/ledger/src/shred.rs @@ -334,6 +334,7 @@ impl Shred { dispatch!(pub(crate) fn erasure_shard_index(&self) -> Result); dispatch!(pub fn into_payload(self) -> Vec); + dispatch!(pub fn merkle_root(&self) -> Result); dispatch!(pub fn payload(&self) -> &Vec); dispatch!(pub fn sanitize(&self) -> Result<(), Error>); @@ -893,6 +894,7 @@ pub fn should_discard_shred( root: Slot, max_slot: Slot, shred_version: u16, + should_drop_legacy_shreds: impl Fn(Slot) -> bool, stats: &mut ShredFetchStats, ) -> bool { debug_assert!(root < max_slot); @@ -967,7 +969,11 @@ pub fn should_discard_shred( } } match shred_variant { - ShredVariant::LegacyCode | ShredVariant::LegacyData => (), + ShredVariant::LegacyCode | ShredVariant::LegacyData => { + if should_drop_legacy_shreds(slot) { + return true; + } + } ShredVariant::MerkleCode(_) => { stats.num_shreds_merkle_code = stats.num_shreds_merkle_code.saturating_add(1); } @@ -1171,6 +1177,7 @@ mod tests { root, max_slot, shred_version, + |_| false, // should_drop_legacy_shreds &mut stats )); assert_eq!(stats, ShredFetchStats::default()); @@ -1181,6 +1188,7 @@ mod tests { root, max_slot, shred_version, + |_| false, // should_drop_legacy_shreds &mut stats )); assert_eq!(stats.index_overrun, 1); @@ -1191,6 +1199,7 @@ mod tests { root, max_slot, shred_version, + |_| false, // should_drop_legacy_shreds &mut stats )); assert_eq!(stats.index_overrun, 2); @@ -1201,6 +1210,7 @@ mod tests { root, max_slot, shred_version, + |_| false, // should_drop_legacy_shreds &mut stats )); assert_eq!(stats.index_overrun, 3); @@ -1211,6 +1221,7 @@ mod tests { root, max_slot, shred_version, + |_| false, // should_drop_legacy_shreds &mut stats )); assert_eq!(stats.index_overrun, 4); @@ -1221,6 +1232,7 @@ mod tests { root, max_slot, shred_version, + |_| false, // should_drop_legacy_shreds &mut stats )); assert_eq!(stats.bad_parent_offset, 1); @@ -1241,6 +1253,7 @@ mod tests { root, max_slot, shred_version, + |_| false, // should_drop_legacy_shreds &mut stats )); @@ -1260,6 +1273,7 @@ mod tests { root, max_slot, shred_version, + |_| false, // should_drop_legacy_shreds &mut stats )); assert_eq!(1, stats.index_out_of_bounds); @@ -1280,6 +1294,7 @@ mod tests { root, max_slot, shred_version, + |_| false, // should_drop_legacy_shreds &mut stats )); packet.buffer_mut()[OFFSET_OF_SHRED_VARIANT] = u8::MAX; @@ -1289,6 +1304,7 @@ mod tests { root, max_slot, shred_version, + |_| false, // should_drop_legacy_shreds &mut stats )); assert_eq!(1, stats.bad_shred_type); @@ -1300,6 +1316,7 @@ mod tests { root, max_slot, shred_version, + |_| false, // should_drop_legacy_shreds &mut stats )); assert_eq!(1, stats.bad_shred_type); diff --git a/ledger/src/shred/merkle.rs b/ledger/src/shred/merkle.rs index 4f1cd22111e..8ed51a6653b 100644 --- a/ledger/src/shred/merkle.rs +++ b/ledger/src/shred/merkle.rs @@ -154,7 +154,7 @@ impl ShredData { Ok(Self::SIZE_OF_HEADERS + Self::capacity(proof_size)?) } - fn merkle_root(&self) -> Result { + pub(super) fn merkle_root(&self) -> Result { let proof_size = self.proof_size()?; let index = self.erasure_shard_index()?; let proof_offset = Self::proof_offset(proof_size)?; @@ -266,7 +266,7 @@ impl ShredCode { Ok(Self::SIZE_OF_HEADERS + Self::capacity(proof_size)?) } - fn merkle_root(&self) -> Result { + pub(super) fn merkle_root(&self) -> Result { let proof_size = self.proof_size()?; let index = self.erasure_shard_index()?; let proof_offset = Self::proof_offset(proof_size)?; @@ -821,7 +821,8 @@ pub(super) fn make_shreds_from_data( } } let now = Instant::now(); - let erasure_batch_size = shredder::get_erasure_batch_size(DATA_SHREDS_PER_FEC_BLOCK); + let erasure_batch_size = + shredder::get_erasure_batch_size(DATA_SHREDS_PER_FEC_BLOCK, is_last_in_slot); let proof_size = get_proof_size(erasure_batch_size); let data_buffer_size = ShredData::capacity(proof_size)?; let chunk_size = DATA_SHREDS_PER_FEC_BLOCK * data_buffer_size; @@ -872,7 +873,8 @@ pub(super) fn make_shreds_from_data( let data_buffer_size = ShredData::capacity(proof_size).ok()?; let num_data_shreds = (data.len() + data_buffer_size - 1) / data_buffer_size; let num_data_shreds = num_data_shreds.max(1); - let erasure_batch_size = shredder::get_erasure_batch_size(num_data_shreds); + let erasure_batch_size = + shredder::get_erasure_batch_size(num_data_shreds, is_last_in_slot); (proof_size == get_proof_size(erasure_batch_size)) .then_some((proof_size, data_buffer_size)) }) @@ -932,7 +934,8 @@ pub(super) fn make_shreds_from_data( .scan(next_code_index, |next_code_index, chunk| { let out = Some(*next_code_index); let num_data_shreds = chunk.len(); - let erasure_batch_size = shredder::get_erasure_batch_size(num_data_shreds); + let erasure_batch_size = + shredder::get_erasure_batch_size(num_data_shreds, is_last_in_slot); let num_coding_shreds = erasure_batch_size - num_data_shreds; *next_code_index += num_coding_shreds as u32; out @@ -945,7 +948,13 @@ pub(super) fn make_shreds_from_data( .into_iter() .zip(next_code_index) .map(|(shreds, next_code_index)| { - make_erasure_batch(keypair, shreds, next_code_index, reed_solomon_cache) + make_erasure_batch( + keypair, + shreds, + next_code_index, + is_last_in_slot, + reed_solomon_cache, + ) }) .collect() } else { @@ -954,7 +963,13 @@ pub(super) fn make_shreds_from_data( .into_par_iter() .zip(next_code_index) .map(|(shreds, next_code_index)| { - make_erasure_batch(keypair, shreds, next_code_index, reed_solomon_cache) + make_erasure_batch( + keypair, + shreds, + next_code_index, + is_last_in_slot, + reed_solomon_cache, + ) }) .collect() }) @@ -969,10 +984,11 @@ fn make_erasure_batch( keypair: &Keypair, shreds: Vec, next_code_index: u32, + is_last_in_slot: bool, reed_solomon_cache: &ReedSolomonCache, ) -> Result, Error> { let num_data_shreds = shreds.len(); - let erasure_batch_size = shredder::get_erasure_batch_size(num_data_shreds); + let erasure_batch_size = shredder::get_erasure_batch_size(num_data_shreds, is_last_in_slot); let num_coding_shreds = erasure_batch_size - num_data_shreds; let proof_size = get_proof_size(erasure_batch_size); debug_assert!(shreds @@ -1056,7 +1072,10 @@ mod test { itertools::Itertools, rand::{seq::SliceRandom, CryptoRng, Rng}, rayon::ThreadPoolBuilder, - solana_sdk::signature::{Keypair, Signer}, + solana_sdk::{ + packet::PACKET_DATA_SIZE, + signature::{Keypair, Signer}, + }, std::{cmp::Ordering, iter::repeat_with}, test_case::test_case, }; @@ -1124,8 +1143,7 @@ mod test { assert_eq!(entry, &bytes[..SIZE_OF_MERKLE_PROOF_ENTRY]); } - fn run_merkle_tree_round_trip(size: usize) { - let mut rng = rand::thread_rng(); + fn run_merkle_tree_round_trip(rng: &mut R, size: usize) { let nodes = repeat_with(|| rng.gen::<[u8; 32]>()).map(Hash::from); let nodes: Vec<_> = nodes.take(size).collect(); let tree = make_merkle_tree(nodes.clone()); @@ -1145,8 +1163,9 @@ mod test { #[test] fn test_merkle_tree_round_trip() { - for size in [1, 2, 3, 4, 5, 6, 7, 8, 9, 19, 37, 64, 79] { - run_merkle_tree_round_trip(size); + let mut rng = rand::thread_rng(); + for size in 1..=143 { + run_merkle_tree_round_trip(&mut rng, size); } } @@ -1327,32 +1346,49 @@ mod test { } } - #[test_case(0)] - #[test_case(15600)] - #[test_case(31200)] - #[test_case(46800)] - fn test_make_shreds_from_data(data_size: usize) { + #[test_case(0, false)] + #[test_case(0, true)] + #[test_case(15600, false)] + #[test_case(15600, true)] + #[test_case(31200, false)] + #[test_case(31200, true)] + #[test_case(46800, false)] + #[test_case(46800, true)] + fn test_make_shreds_from_data(data_size: usize, is_last_in_slot: bool) { let mut rng = rand::thread_rng(); let data_size = data_size.saturating_sub(16); let reed_solomon_cache = ReedSolomonCache::default(); for data_size in data_size..data_size + 32 { - run_make_shreds_from_data(&mut rng, data_size, &reed_solomon_cache); + run_make_shreds_from_data(&mut rng, data_size, is_last_in_slot, &reed_solomon_cache); } } - #[test] - fn test_make_shreds_from_data_rand() { + #[test_case(false)] + #[test_case(true)] + fn test_make_shreds_from_data_rand(is_last_in_slot: bool) { let mut rng = rand::thread_rng(); let reed_solomon_cache = ReedSolomonCache::default(); for _ in 0..32 { let data_size = rng.gen_range(0..31200 * 7); - run_make_shreds_from_data(&mut rng, data_size, &reed_solomon_cache); + run_make_shreds_from_data(&mut rng, data_size, is_last_in_slot, &reed_solomon_cache); + } + } + + #[ignore] + #[test_case(false)] + #[test_case(true)] + fn test_make_shreds_from_data_paranoid(is_last_in_slot: bool) { + let mut rng = rand::thread_rng(); + let reed_solomon_cache = ReedSolomonCache::default(); + for data_size in 0..=PACKET_DATA_SIZE * 4 * 64 { + run_make_shreds_from_data(&mut rng, data_size, is_last_in_slot, &reed_solomon_cache); } } fn run_make_shreds_from_data( rng: &mut R, data_size: usize, + is_last_in_slot: bool, reed_solomon_cache: &ReedSolomonCache, ) { let thread_pool = ThreadPoolBuilder::new().num_threads(2).build().unwrap(); @@ -1373,7 +1409,7 @@ mod test { parent_slot, shred_version, reference_tick, - true, // is_last_in_slot + is_last_in_slot, next_shred_index, next_code_index, reed_solomon_cache, @@ -1480,14 +1516,17 @@ mod test { .flags .contains(ShredFlags::LAST_SHRED_IN_SLOT)) .count(), - 1 + if is_last_in_slot { 1 } else { 0 } + ); + assert_eq!( + data_shreds + .last() + .unwrap() + .data_header + .flags + .contains(ShredFlags::LAST_SHRED_IN_SLOT), + is_last_in_slot ); - assert!(data_shreds - .last() - .unwrap() - .data_header - .flags - .contains(ShredFlags::LAST_SHRED_IN_SLOT)); // Assert that data shreds can be recovered from coding shreds. let recovered_data_shreds: Vec<_> = shreds .iter() diff --git a/ledger/src/shred/shred_code.rs b/ledger/src/shred/shred_code.rs index ba85d92af25..0ad97a0f729 100644 --- a/ledger/src/shred/shred_code.rs +++ b/ledger/src/shred/shred_code.rs @@ -6,7 +6,7 @@ use { CodingShredHeader, Error, ShredCommonHeader, ShredType, SignedData, DATA_SHREDS_PER_FEC_BLOCK, MAX_DATA_SHREDS_PER_SLOT, SIZE_OF_NONCE, }, - solana_sdk::{clock::Slot, packet::PACKET_DATA_SIZE, signature::Signature}, + solana_sdk::{clock::Slot, hash::Hash, packet::PACKET_DATA_SIZE, signature::Signature}, static_assertions::const_assert_eq, }; @@ -47,6 +47,13 @@ impl ShredCode { } } + pub(super) fn merkle_root(&self) -> Result { + match self { + Self::Legacy(_) => Err(Error::InvalidShredType), + Self::Merkle(shred) => shred.merkle_root(), + } + } + pub(super) fn new_from_parity_shard( slot: Slot, index: u32, diff --git a/ledger/src/shred/shred_data.rs b/ledger/src/shred/shred_data.rs index 9bf2c0bf05f..ecb40367b4e 100644 --- a/ledger/src/shred/shred_data.rs +++ b/ledger/src/shred/shred_data.rs @@ -7,7 +7,7 @@ use { DataShredHeader, Error, ShredCommonHeader, ShredFlags, ShredType, ShredVariant, SignedData, MAX_DATA_SHREDS_PER_SLOT, }, - solana_sdk::{clock::Slot, signature::Signature}, + solana_sdk::{clock::Slot, hash::Hash, signature::Signature}, }; #[derive(Clone, Debug, Eq, PartialEq)] @@ -41,6 +41,13 @@ impl ShredData { } } + pub(super) fn merkle_root(&self) -> Result { + match self { + Self::Legacy(_) => Err(Error::InvalidShredType), + Self::Merkle(shred) => shred.merkle_root(), + } + } + pub(super) fn new_from_data( slot: Slot, index: u32, diff --git a/ledger/src/shredder.rs b/ledger/src/shredder.rs index 1a597c41f98..f3203876de7 100644 --- a/ledger/src/shredder.rs +++ b/ledger/src/shredder.rs @@ -207,7 +207,13 @@ impl Shredder { .iter() .scan(next_code_index, |next_code_index, chunk| { let num_data_shreds = chunk.len(); - let erasure_batch_size = get_erasure_batch_size(num_data_shreds); + let is_last_in_slot = chunk + .last() + .copied() + .map(Shred::last_in_slot) + .unwrap_or(true); + let erasure_batch_size = + get_erasure_batch_size(num_data_shreds, is_last_in_slot); *next_code_index += (erasure_batch_size - num_data_shreds) as u32; Some(*next_code_index) }), @@ -276,7 +282,12 @@ impl Shredder { && shred.version() == version && shred.fec_set_index() == fec_set_index)); let num_data = data.len(); - let num_coding = get_erasure_batch_size(num_data) + let is_last_in_slot = data + .last() + .map(Borrow::borrow) + .map(Shred::last_in_slot) + .unwrap_or(true); + let num_coding = get_erasure_batch_size(num_data, is_last_in_slot) .checked_sub(num_data) .unwrap(); assert!(num_coding > 0); @@ -434,11 +445,16 @@ impl Default for ReedSolomonCache { } /// Maps number of data shreds in each batch to the erasure batch size. -pub(crate) fn get_erasure_batch_size(num_data_shreds: usize) -> usize { - ERASURE_BATCH_SIZE +pub(crate) fn get_erasure_batch_size(num_data_shreds: usize, is_last_in_slot: bool) -> usize { + let erasure_batch_size = ERASURE_BATCH_SIZE .get(num_data_shreds) .copied() - .unwrap_or(2 * num_data_shreds) + .unwrap_or(2 * num_data_shreds); + if is_last_in_slot { + erasure_batch_size.max(2 * DATA_SHREDS_PER_FEC_BLOCK) + } else { + erasure_batch_size + } } // Returns offsets to fec_set_index when spliting shreds into erasure batches. @@ -518,17 +534,19 @@ mod tests { }) .collect(); + let is_last_in_slot = true; let size = serialized_size(&entries).unwrap() as usize; // Integer division to ensure we have enough shreds to fit all the data let data_buffer_size = ShredData::capacity(/*merkle_proof_size:*/ None).unwrap(); let num_expected_data_shreds = (size + data_buffer_size - 1) / data_buffer_size; let num_expected_coding_shreds = - get_erasure_batch_size(num_expected_data_shreds) - num_expected_data_shreds; + get_erasure_batch_size(num_expected_data_shreds, is_last_in_slot) + - num_expected_data_shreds; let start_index = 0; let (data_shreds, coding_shreds) = shredder.entries_to_shreds( &keypair, &entries, - true, // is_last_in_slot + is_last_in_slot, start_index, // next_shred_index start_index, // next_code_index true, // merkle_variant @@ -792,7 +810,7 @@ mod tests { assert_eq!(data_shreds.len(), num_data_shreds); assert_eq!( num_coding_shreds, - get_erasure_batch_size(num_data_shreds) - num_data_shreds + get_erasure_batch_size(num_data_shreds, is_last_in_slot) - num_data_shreds ); let all_shreds = data_shreds @@ -1189,7 +1207,10 @@ mod tests { .iter() .group_by(|shred| shred.fec_set_index()) .into_iter() - .map(|(_, chunk)| get_erasure_batch_size(chunk.count())) + .map(|(_, chunk)| { + let chunk: Vec<_> = chunk.collect(); + get_erasure_batch_size(chunk.len(), chunk.last().unwrap().last_in_slot()) + }) .sum(); assert_eq!(coding_shreds.len(), num_shreds - data_shreds.len()); } @@ -1232,9 +1253,10 @@ mod tests { #[test] fn test_max_shreds_per_slot() { for num_data_shreds in 32..128 { - let num_coding_shreds = get_erasure_batch_size(num_data_shreds) - .checked_sub(num_data_shreds) - .unwrap(); + let num_coding_shreds = + get_erasure_batch_size(num_data_shreds, /*is_last_in_slot:*/ false) + .checked_sub(num_data_shreds) + .unwrap(); assert!( MAX_DATA_SHREDS_PER_SLOT * num_coding_shreds <= MAX_CODE_SHREDS_PER_SLOT * num_data_shreds diff --git a/ledger/src/use_snapshot_archives_at_startup.rs b/ledger/src/use_snapshot_archives_at_startup.rs index e34abfb7779..584b2e4a7d3 100644 --- a/ledger/src/use_snapshot_archives_at_startup.rs +++ b/ledger/src/use_snapshot_archives_at_startup.rs @@ -38,7 +38,7 @@ pub mod cli { If there is no state already on disk, startup will fail. \ Note, this will use the latest state available, \ which may be newer than the latest snapshot archive. \ - Specifying \"when-newest\" will use snapshot-related state \ + \nSpecifying \"when-newest\" will use snapshot-related state \ already on disk unless there are snapshot archives newer than it. \ This can happen if a new snapshot archive is downloaded \ while the node is stopped."; diff --git a/local-cluster/src/local_cluster.rs b/local-cluster/src/local_cluster.rs index d180a4abaf0..76cf85176d4 100644 --- a/local-cluster/src/local_cluster.rs +++ b/local-cluster/src/local_cluster.rs @@ -293,7 +293,7 @@ impl LocalCluster { socket_addr_space, DEFAULT_TPU_USE_QUIC, DEFAULT_TPU_CONNECTION_POOL_SIZE, - DEFAULT_TPU_ENABLE_UDP, + true, Arc::new(RwLock::new(None)), ) .expect("assume successful validator start"); diff --git a/local-cluster/src/validator_configs.rs b/local-cluster/src/validator_configs.rs index 70211b5dac6..627c5797cd8 100644 --- a/local-cluster/src/validator_configs.rs +++ b/local-cluster/src/validator_configs.rs @@ -51,6 +51,7 @@ pub fn safe_clone_config(config: &ValidatorConfig) -> ValidatorConfig { warp_slot: config.warp_slot, accounts_db_test_hash_calculation: config.accounts_db_test_hash_calculation, accounts_db_skip_shrink: config.accounts_db_skip_shrink, + accounts_db_force_initial_clean: config.accounts_db_force_initial_clean, tpu_coalesce: config.tpu_coalesce, staked_nodes_overrides: config.staked_nodes_overrides.clone(), validator_exit: Arc::new(RwLock::new(Exit::default())), diff --git a/local-cluster/tests/local_cluster.rs b/local-cluster/tests/local_cluster.rs index 658fdf0de3b..c654efdd35d 100644 --- a/local-cluster/tests/local_cluster.rs +++ b/local-cluster/tests/local_cluster.rs @@ -2315,13 +2315,13 @@ fn test_hard_fork_with_gap_in_roots() { ); // create hard-forked snapshot only for validator a, emulating the manual cluster restart - // procedure with `solana-ledger-tool create-snapshot` + // procedure with `agave-ledger-tool create-snapshot` let genesis_slot = 0; { let blockstore_a = Blockstore::open(&val_a_ledger_path).unwrap(); create_snapshot_to_hard_fork(&blockstore_a, hard_fork_slot, vec![hard_fork_slot]); - // Intentionally make solana-validator unbootable by replaying blocks from the genesis to + // Intentionally make agave-validator unbootable by replaying blocks from the genesis to // ensure the hard-forked snapshot is used always. Otherwise, we couldn't create a gap // in the ledger roots column family reliably. // There was a bug which caused the hard-forked snapshot at an unrooted slot to forget @@ -2558,6 +2558,7 @@ fn run_test_load_program_accounts_partition(scan_commitment: CommitmentConfig) { #[test] #[serial] +#[ignore] fn test_rpc_block_subscribe() { let total_stake = 100 * DEFAULT_NODE_STAKE; let leader_stake = total_stake; @@ -2917,24 +2918,26 @@ fn setup_transfer_scan_threads( .get_latest_blockhash_with_commitment(CommitmentConfig::processed()) .unwrap(); for i in 0..starting_keypairs_.len() { - client - .async_transfer( - 1, - &starting_keypairs_[i], - &target_keypairs_[i].pubkey(), - blockhash, - ) - .unwrap(); + let result = client.async_transfer( + 1, + &starting_keypairs_[i], + &target_keypairs_[i].pubkey(), + blockhash, + ); + if result.is_err() { + debug!("Failed in transfer for starting keypair: {:?}", result); + } } for i in 0..starting_keypairs_.len() { - client - .async_transfer( - 1, - &target_keypairs_[i], - &starting_keypairs_[i].pubkey(), - blockhash, - ) - .unwrap(); + let result = client.async_transfer( + 1, + &target_keypairs_[i], + &starting_keypairs_[i].pubkey(), + blockhash, + ); + if result.is_err() { + debug!("Failed in transfer for starting keypair: {:?}", result); + } } } }) diff --git a/logger/src/lib.rs b/logger/src/lib.rs index 7b9ae30d881..aa8dc5ca1db 100644 --- a/logger/src/lib.rs +++ b/logger/src/lib.rs @@ -10,6 +10,8 @@ lazy_static! { Arc::new(RwLock::new(env_logger::Logger::from_default_env())); } +pub const DEFAULT_FILTER: &str = "solana=info,agave=info"; + struct LoggerShim {} impl log::Log for LoggerShim { @@ -49,6 +51,11 @@ pub fn setup_with_default(filter: &str) { replace_logger(logger); } +// Configures logging with the `DEFAULT_FILTER` if RUST_LOG is not set +pub fn setup_with_default_filter() { + setup_with_default(DEFAULT_FILTER); +} + // Configures logging with the default filter "error" if RUST_LOG is not set pub fn setup() { setup_with_default("error"); diff --git a/multinode-demo/bootstrap-validator.sh b/multinode-demo/bootstrap-validator.sh index f69c05d1ed3..98d40e9adf3 100755 --- a/multinode-demo/bootstrap-validator.sh +++ b/multinode-demo/bootstrap-validator.sh @@ -14,9 +14,9 @@ if [[ "$SOLANA_GPU_MISSING" -eq 1 ]]; then fi if [[ -n $SOLANA_CUDA ]]; then - program=$solana_validator_cuda + program=$agave_validator_cuda else - program=$solana_validator + program=$agave_validator fi no_restart=0 diff --git a/multinode-demo/common.sh b/multinode-demo/common.sh index 9ae9331cb7a..db43dd15cff 100644 --- a/multinode-demo/common.sh +++ b/multinode-demo/common.sh @@ -30,7 +30,11 @@ if [[ -n $USE_INSTALL || ! -f "$SOLANA_ROOT"/Cargo.toml ]]; then if [[ -z $program ]]; then printf "solana" else - printf "solana-%s" "$program" + if [[ $program == "validator" || $program == "ledger-tool" || $program == "watchtower" || $program == "install" ]]; then + printf "agave-%s" "$program" + else + printf "solana-%s" "$program" + fi fi } else @@ -40,6 +44,8 @@ else if [[ -z $program ]]; then crate="cli" program="solana" + elif [[ $program == "validator" || $program == "ledger-tool" || $program == "watchtower" || $program == "install" ]]; then + program="agave-$program" else program="solana-$program" fi @@ -63,8 +69,8 @@ fi solana_bench_tps=$(solana_program bench-tps) solana_faucet=$(solana_program faucet) -solana_validator=$(solana_program validator) -solana_validator_cuda="$solana_validator --cuda" +agave_validator=$(solana_program validator) +agave_validator_cuda="$agave_validator --cuda" solana_genesis=$(solana_program genesis) solana_gossip=$(solana_program gossip) solana_keygen=$(solana_program keygen) diff --git a/multinode-demo/validator.sh b/multinode-demo/validator.sh index 9090055b908..a122b8523b5 100755 --- a/multinode-demo/validator.sh +++ b/multinode-demo/validator.sh @@ -64,7 +64,7 @@ while [[ -n $1 ]]; do elif [[ $1 = --no-airdrop ]]; then airdrops_enabled=0 shift - # solana-validator options + # agave-validator options elif [[ $1 = --expected-genesis-hash ]]; then args+=("$1" "$2") shift 2 @@ -267,9 +267,9 @@ if [[ $maybeRequireTower = true ]]; then fi if [[ -n $SOLANA_CUDA ]]; then - program=$solana_validator_cuda + program=$agave_validator_cuda else - program=$solana_validator + program=$agave_validator fi set -e diff --git a/net-utils/src/lib.rs b/net-utils/src/lib.rs index 1ff48173def..5e71a13d61d 100644 --- a/net-utils/src/lib.rs +++ b/net-utils/src/lib.rs @@ -381,14 +381,35 @@ pub fn is_host_port(string: String) -> Result<(), String> { parse_host_port(&string).map(|_| ()) } +#[derive(Clone, Debug, Default)] +pub struct SocketConfig { + pub reuseaddr: bool, + pub reuseport: bool, +} + #[cfg(any(windows, target_os = "ios"))] fn udp_socket(_reuseaddr: bool) -> io::Result { let sock = Socket::new(Domain::IPV4, Type::DGRAM, None)?; Ok(sock) } +#[cfg(any(windows, target_os = "ios"))] +fn udp_socket_with_config(_config: SocketConfig) -> io::Result { + let sock = Socket::new(Domain::IPV4, Type::DGRAM, None)?; + Ok(sock) +} + #[cfg(not(any(windows, target_os = "ios")))] fn udp_socket(reuseaddr: bool) -> io::Result { + let config = SocketConfig { + reuseaddr, + reuseport: false, + }; + udp_socket_with_config(config) +} + +#[cfg(not(any(windows, target_os = "ios")))] +fn udp_socket_with_config(config: SocketConfig) -> io::Result { use { nix::sys::socket::{ setsockopt, @@ -396,14 +417,21 @@ fn udp_socket(reuseaddr: bool) -> io::Result { }, std::os::unix::io::AsRawFd, }; + let SocketConfig { + reuseaddr, + mut reuseport, + } = config; let sock = Socket::new(Domain::IPV4, Type::DGRAM, None)?; let sock_fd = sock.as_raw_fd(); + // best effort, i.e. ignore setsockopt() errors, we'll get the failure in caller if reuseaddr { - // best effort, i.e. ignore errors here, we'll get the failure in caller - setsockopt(sock_fd, ReusePort, &true).ok(); setsockopt(sock_fd, ReuseAddr, &true).ok(); + reuseport = true; + } + if reuseport { + setsockopt(sock_fd, ReusePort, &true).ok(); } Ok(sock) @@ -427,7 +455,16 @@ pub fn bind_common_in_range( } pub fn bind_in_range(ip_addr: IpAddr, range: PortRange) -> io::Result<(u16, UdpSocket)> { - let sock = udp_socket(false)?; + let config = SocketConfig::default(); + bind_in_range_with_config(ip_addr, range, config) +} + +pub fn bind_in_range_with_config( + ip_addr: IpAddr, + range: PortRange, + config: SocketConfig, +) -> io::Result<(u16, UdpSocket)> { + let sock = udp_socket_with_config(config)?; for port in range.0..range.1 { let addr = SocketAddr::new(ip_addr, port); @@ -481,8 +518,12 @@ pub fn multi_bind_in_range( port }; // drop the probe, port should be available... briefly. + let config = SocketConfig { + reuseaddr: true, + reuseport: true, + }; for _ in 0..num { - let sock = bind_to(ip_addr, port, true); + let sock = bind_to_with_config(ip_addr, port, config.clone()); if let Ok(sock) = sock { sockets.push(sock); } else { @@ -503,7 +544,19 @@ pub fn multi_bind_in_range( } pub fn bind_to(ip_addr: IpAddr, port: u16, reuseaddr: bool) -> io::Result { - let sock = udp_socket(reuseaddr)?; + let config = SocketConfig { + reuseaddr, + reuseport: false, + }; + bind_to_with_config(ip_addr, port, config) +} + +pub fn bind_to_with_config( + ip_addr: IpAddr, + port: u16, + config: SocketConfig, +) -> io::Result { + let sock = udp_socket_with_config(config)?; let addr = SocketAddr::new(ip_addr, port); @@ -516,7 +569,20 @@ pub fn bind_common( port: u16, reuseaddr: bool, ) -> io::Result<(UdpSocket, TcpListener)> { - let sock = udp_socket(reuseaddr)?; + let config = SocketConfig { + reuseaddr, + reuseport: false, + }; + bind_common_with_config(ip_addr, port, config) +} + +// binds both a UdpSocket and a TcpListener +pub fn bind_common_with_config( + ip_addr: IpAddr, + port: u16, + config: SocketConfig, +) -> io::Result<(UdpSocket, TcpListener)> { + let sock = udp_socket_with_config(config)?; let addr = SocketAddr::new(ip_addr, port); let sock_addr = SockAddr::from(addr); @@ -528,6 +594,18 @@ pub fn bind_two_in_range_with_offset( ip_addr: IpAddr, range: PortRange, offset: u16, +) -> io::Result<((u16, UdpSocket), (u16, UdpSocket))> { + let sock1_config = SocketConfig::default(); + let sock2_config = SocketConfig::default(); + bind_two_in_range_with_offset_and_config(ip_addr, range, offset, sock1_config, sock2_config) +} + +pub fn bind_two_in_range_with_offset_and_config( + ip_addr: IpAddr, + range: PortRange, + offset: u16, + sock1_config: SocketConfig, + sock2_config: SocketConfig, ) -> io::Result<((u16, UdpSocket), (u16, UdpSocket))> { if range.1.saturating_sub(range.0) < offset { return Err(io::Error::new( @@ -536,9 +614,11 @@ pub fn bind_two_in_range_with_offset( )); } for port in range.0..range.1 { - if let Ok(first_bind) = bind_to(ip_addr, port, false) { + if let Ok(first_bind) = bind_to_with_config(ip_addr, port, sock1_config.clone()) { if range.1.saturating_sub(port) >= offset { - if let Ok(second_bind) = bind_to(ip_addr, port + offset, false) { + if let Ok(second_bind) = + bind_to_with_config(ip_addr, port + offset, sock2_config.clone()) + { return Ok(( (first_bind.local_addr().unwrap().port(), first_bind), (second_bind.local_addr().unwrap().port(), second_bind), @@ -578,6 +658,19 @@ pub fn find_available_port_in_range(ip_addr: IpAddr, range: PortRange) -> io::Re } } +pub fn bind_more_with_config( + socket: UdpSocket, + num: usize, + config: SocketConfig, +) -> io::Result> { + let addr = socket.local_addr().unwrap(); + let ip = addr.ip(); + let port = addr.port(); + std::iter::once(Ok(socket)) + .chain((1..num).map(|_| bind_to_with_config(ip, port, config.clone()))) + .collect() +} + #[cfg(test)] mod tests { use {super::*, std::net::Ipv4Addr}; @@ -681,8 +774,12 @@ mod tests { let ip_addr = IpAddr::V4(Ipv4Addr::UNSPECIFIED); assert_eq!(bind_in_range(ip_addr, (2000, 2001)).unwrap().0, 2000); let ip_addr = IpAddr::V4(Ipv4Addr::UNSPECIFIED); - let x = bind_to(ip_addr, 2002, true).unwrap(); - let y = bind_to(ip_addr, 2002, true).unwrap(); + let config = SocketConfig { + reuseaddr: true, + reuseport: true, + }; + let x = bind_to_with_config(ip_addr, 2002, config.clone()).unwrap(); + let y = bind_to_with_config(ip_addr, 2002, config).unwrap(); assert_eq!( x.local_addr().unwrap().port(), y.local_addr().unwrap().port() diff --git a/net/net.sh b/net/net.sh index 1106fe8e90a..531d36b5165 100755 --- a/net/net.sh +++ b/net/net.sh @@ -120,7 +120,7 @@ Operate a configured testnet sanity/start-specific options: -F - Discard validator nodes that didn't bootup successfully - -o noInstallCheck - Skip solana-install sanity + -o noInstallCheck - Skip agave-install sanity -o rejectExtraNodes - Require the exact number of nodes stop-specific options: @@ -136,7 +136,7 @@ Operate a configured testnet --netem-cmd - Optional command argument to netem. Default is "add". Use "cleanup" to remove rules. update-specific options: - --platform linux|osx|windows - Deploy the tarball using 'solana-install deploy ...' for the + --platform linux|osx|windows - Deploy the tarball using 'agave-install deploy ...' for the given platform (multiple platforms may be specified) (-t option must be supplied as well) @@ -511,11 +511,11 @@ deployUpdate() { declare bootstrapLeader=${validatorIpList[0]} for updatePlatform in $updatePlatforms; do - echo "--- Deploying solana-install update: $updatePlatform" + echo "--- Deploying agave-install update: $updatePlatform" ( set -x - scripts/solana-install-update-manifest-keypair.sh "$updatePlatform" + scripts/agave-install-update-manifest-keypair.sh "$updatePlatform" timeout 30s scp "${sshOptions[@]}" \ update_manifest_keypair.json "$bootstrapLeader:solana/update_manifest_keypair.json" @@ -560,7 +560,7 @@ prepareDeploy() { if [[ -n $releaseChannel ]]; then echo "Downloading release from channel: $releaseChannel" rm -f "$SOLANA_ROOT"/solana-release.tar.bz2 - declare updateDownloadUrl=https://release.solana.com/"$releaseChannel"/solana-release-x86_64-unknown-linux-gnu.tar.bz2 + declare updateDownloadUrl=https://release.anza.xyz/"$releaseChannel"/solana-release-x86_64-unknown-linux-gnu.tar.bz2 ( set -x curl -L -I "$updateDownloadUrl" diff --git a/net/remote/remote-deploy-update.sh b/net/remote/remote-deploy-update.sh index dd772927c0e..3a71cf57251 100755 --- a/net/remote/remote-deploy-update.sh +++ b/net/remote/remote-deploy-update.sh @@ -35,6 +35,6 @@ loadConfigFile PATH="$HOME"/.cargo/bin:"$PATH" set -x -scripts/solana-install-deploy.sh \ +scripts/agave-install-deploy.sh \ --keypair config/faucet.json \ localhost "$releaseChannel" "$updatePlatform" diff --git a/net/remote/remote-node.sh b/net/remote/remote-node.sh index aeb920bd50b..b7d224088da 100755 --- a/net/remote/remote-node.sh +++ b/net/remote/remote-node.sh @@ -121,7 +121,7 @@ cat >> ~/solana/on-reboot < system-stats.pid if ${GPU_CUDA_OK} && [[ -e /dev/nvidia0 ]]; then - echo Selecting solana-validator-cuda + echo Selecting agave-validator-cuda export SOLANA_CUDA=1 elif ${GPU_FAIL_IF_NONE} ; then echo "Expected GPU, found none!" @@ -257,13 +257,13 @@ EOF if [[ -n "$maybeWarpSlot" ]]; then # shellcheck disable=SC2086 # Do not want to quote $maybeWarSlot - solana-ledger-tool -l config/bootstrap-validator create-snapshot 0 config/bootstrap-validator $maybeWarpSlot + agave-ledger-tool -l config/bootstrap-validator create-snapshot 0 config/bootstrap-validator $maybeWarpSlot fi - solana-ledger-tool -l config/bootstrap-validator shred-version --max-genesis-archive-unpacked-size 1073741824 | tee config/shred-version + agave-ledger-tool -l config/bootstrap-validator shred-version --max-genesis-archive-unpacked-size 1073741824 | tee config/shred-version if [[ -n "$maybeWaitForSupermajority" ]]; then - bankHash=$(solana-ledger-tool -l config/bootstrap-validator bank-hash --halt-at-slot 0) + bankHash=$(agave-ledger-tool -l config/bootstrap-validator bank-hash --halt-at-slot 0) extraNodeArgs="$extraNodeArgs --expected-bank-hash $bankHash" echo "$bankHash" > config/bank-hash fi diff --git a/net/remote/remote-sanity.sh b/net/remote/remote-sanity.sh index 8c36e99ffdf..91dae4b5733 100755 --- a/net/remote/remote-sanity.sh +++ b/net/remote/remote-sanity.sh @@ -65,7 +65,7 @@ local|tar|skip) export USE_INSTALL=1 solana_cli=solana solana_gossip=solana-gossip - solana_install=solana-install + solana_install=agave-install ;; *) echo "Unknown deployment method: $deployMethod" @@ -122,7 +122,7 @@ else fi if $installCheck && [[ -r update_manifest_keypair.json ]]; then - echo "--- $sanityTargetIp: solana-install test" + echo "--- $sanityTargetIp: agave-install test" ( set -x diff --git a/notifier/src/lib.rs b/notifier/src/lib.rs index a3692257724..75406d2fbda 100644 --- a/notifier/src/lib.rs +++ b/notifier/src/lib.rs @@ -19,7 +19,7 @@ /// /// To receive a Twilio SMS notification on failure, having a Twilio account, /// and a sending number owned by that account, -/// define environment variable before running `solana-watchtower`: +/// define environment variable before running `agave-watchtower`: /// ```bash /// export TWILIO_CONFIG='ACCOUNT=,TOKEN=,TO=,FROM=' /// ``` @@ -208,7 +208,7 @@ impl Notifier { NotificationType::Resolve { ref incident } => incident.clone().to_string(), }; - let data = json!({"payload":{"summary":msg,"source":"solana-watchtower","severity":"critical"},"routing_key":routing_key,"event_action":event_action,"dedup_key":dedup_key}); + let data = json!({"payload":{"summary":msg,"source":"agave-watchtower","severity":"critical"},"routing_key":routing_key,"event_action":event_action,"dedup_key":dedup_key}); let url = "https://events.pagerduty.com/v2/enqueue"; if let Err(err) = self.client.post(url).json(&data).send() { diff --git a/perf/Cargo.toml b/perf/Cargo.toml index aea478da078..b62484f4249 100644 --- a/perf/Cargo.toml +++ b/perf/Cargo.toml @@ -21,6 +21,8 @@ log = { workspace = true } rand = { workspace = true } rayon = { workspace = true } serde = { workspace = true } +solana-frozen-abi = { workspace = true } +solana-frozen-abi-macro = { workspace = true } solana-metrics = { workspace = true } solana-rayon-threadlimit = { workspace = true } solana-sdk = { workspace = true } @@ -40,6 +42,9 @@ rand_chacha = { workspace = true } solana-logger = { workspace = true } test-case = { workspace = true } +[build-dependencies] +rustc_version = { workspace = true } + [[bench]] name = "sigverify" diff --git a/perf/build.rs b/perf/build.rs index 025c71008f0..4925ee898eb 100644 --- a/perf/build.rs +++ b/perf/build.rs @@ -1,3 +1,6 @@ +extern crate rustc_version; +use rustc_version::{version_meta, Channel}; + fn main() { #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] { @@ -8,4 +11,27 @@ fn main() { println!("cargo:rustc-cfg=build_target_feature_avx2"); } } + + // Copied and adapted from + // https://github.com/Kimundi/rustc-version-rs/blob/1d692a965f4e48a8cb72e82cda953107c0d22f47/README.md#example + // Licensed under Apache-2.0 + MIT + match version_meta().unwrap().channel { + Channel::Stable => { + println!("cargo:rustc-cfg=RUSTC_WITHOUT_SPECIALIZATION"); + } + Channel::Beta => { + println!("cargo:rustc-cfg=RUSTC_WITHOUT_SPECIALIZATION"); + } + Channel::Nightly => { + println!("cargo:rustc-cfg=RUSTC_WITH_SPECIALIZATION"); + } + Channel::Dev => { + println!("cargo:rustc-cfg=RUSTC_WITH_SPECIALIZATION"); + // See https://github.com/solana-labs/solana/issues/11055 + // We may be running the custom `rust-bpf-builder` toolchain, + // which currently needs `#![feature(proc_macro_hygiene)]` to + // be applied. + println!("cargo:rustc-cfg=RUSTC_NEEDS_PROC_MACRO_HYGIENE"); + } + } } diff --git a/perf/src/cuda_runtime.rs b/perf/src/cuda_runtime.rs index a2986af1813..5b44099aecb 100644 --- a/perf/src/cuda_runtime.rs +++ b/perf/src/cuda_runtime.rs @@ -54,7 +54,7 @@ fn unpin(mem: *mut T) { // A vector wrapper where the underlying memory can be // page-pinned. Controlled by flags in case user only wants // to pin in certain circumstances. -#[derive(Debug, Default, Serialize, Deserialize)] +#[derive(Debug, Default, Serialize, Deserialize, AbiExample)] pub struct PinnedVec { x: Vec, pinned: bool, diff --git a/perf/src/lib.rs b/perf/src/lib.rs index 8d277d7ad69..83cefe1f319 100644 --- a/perf/src/lib.rs +++ b/perf/src/lib.rs @@ -1,3 +1,4 @@ +#![cfg_attr(RUSTC_WITH_SPECIALIZATION, feature(min_specialization))] pub mod cuda_runtime; pub mod data_budget; pub mod deduper; @@ -23,6 +24,9 @@ extern crate assert_matches; #[macro_use] extern crate solana_metrics; +#[macro_use] +extern crate solana_frozen_abi_macro; + fn is_rosetta_emulated() -> bool { #[cfg(target_os = "macos")] { diff --git a/perf/src/packet.rs b/perf/src/packet.rs index b030f04dae8..fbb8a437d6b 100644 --- a/perf/src/packet.rs +++ b/perf/src/packet.rs @@ -18,7 +18,7 @@ pub const NUM_PACKETS: usize = 1024 * 8; pub const PACKETS_PER_BATCH: usize = 64; pub const NUM_RCVMMSGS: usize = 64; -#[derive(Debug, Default, Clone, Serialize, Deserialize)] +#[derive(Debug, Default, Clone, Serialize, Deserialize, AbiExample)] pub struct PacketBatch { packets: PinnedVec, } diff --git a/perf/src/recycler.rs b/perf/src/recycler.rs index 27c47d0df45..87c44399e7f 100644 --- a/perf/src/recycler.rs +++ b/perf/src/recycler.rs @@ -57,6 +57,15 @@ impl Default for RecyclerX { } } +#[cfg(RUSTC_WITH_SPECIALIZATION)] +impl solana_frozen_abi::abi_example::AbiExample + for RecyclerX> +{ + fn example() -> Self { + Self::default() + } +} + pub trait Reset { fn reset(&mut self); fn warm(&mut self, size_hint: usize); diff --git a/program-runtime/src/invoke_context.rs b/program-runtime/src/invoke_context.rs index 12f82300d78..2b1c009b0cf 100644 --- a/program-runtime/src/invoke_context.rs +++ b/program-runtime/src/invoke_context.rs @@ -13,9 +13,10 @@ use { solana_measure::measure::Measure, solana_rbpf::{ ebpf::MM_HEAP_START, - elf::SBPFVersion, + error::{EbpfError, ProgramResult}, memory_region::MemoryMapping, - vm::{BuiltinFunction, Config, ContextObject, ProgramResult}, + program::{BuiltinFunction, SBPFVersion}, + vm::{Config, ContextObject, EbpfVm}, }, solana_sdk::{ account::{AccountSharedData, ReadableAccount}, @@ -44,44 +45,46 @@ use { }, }; -pub type ProcessInstructionWithContext = BuiltinFunction>; +pub type BuiltinFunctionWithContext = BuiltinFunction>; /// Adapter so we can unify the interfaces of built-in programs and syscalls #[macro_export] macro_rules! declare_process_instruction { ($process_instruction:ident, $cu_to_consume:expr, |$invoke_context:ident| $inner:tt) => { - pub fn $process_instruction( - invoke_context: &mut $crate::invoke_context::InvokeContext, - _arg0: u64, - _arg1: u64, - _arg2: u64, - _arg3: u64, - _arg4: u64, - _memory_mapping: &mut $crate::solana_rbpf::memory_region::MemoryMapping, - result: &mut $crate::solana_rbpf::vm::ProgramResult, - ) { - fn process_instruction_inner( - $invoke_context: &mut $crate::invoke_context::InvokeContext, - ) -> std::result::Result<(), solana_sdk::instruction::InstructionError> { - $inner + $crate::solana_rbpf::declare_builtin_function!( + $process_instruction, + fn rust( + invoke_context: &mut $crate::invoke_context::InvokeContext, + _arg0: u64, + _arg1: u64, + _arg2: u64, + _arg3: u64, + _arg4: u64, + _memory_mapping: &mut $crate::solana_rbpf::memory_region::MemoryMapping, + ) -> std::result::Result> { + fn process_instruction_inner( + $invoke_context: &mut $crate::invoke_context::InvokeContext, + ) -> std::result::Result<(), solana_sdk::instruction::InstructionError> { + $inner + } + let consumption_result = if $cu_to_consume > 0 + && invoke_context + .feature_set + .is_active(&solana_sdk::feature_set::native_programs_consume_cu::id()) + { + invoke_context.consume_checked($cu_to_consume) + } else { + Ok(()) + }; + consumption_result + .and_then(|_| { + process_instruction_inner(invoke_context) + .map(|_| 0) + .map_err(|err| Box::new(err) as Box) + }) + .into() } - let consumption_result = if $cu_to_consume > 0 - && invoke_context - .feature_set - .is_active(&solana_sdk::feature_set::native_programs_consume_cu::id()) - { - invoke_context.consume_checked($cu_to_consume) - } else { - Ok(()) - }; - *result = consumption_result - .and_then(|_| { - process_instruction_inner(invoke_context) - .map(|_| 0) - .map_err(|err| Box::new(err) as Box) - }) - .into(); - } + ); }; } @@ -746,11 +749,11 @@ impl<'a> InvokeContext<'a> { .programs_loaded_for_tx_batch .find(&builtin_id) .ok_or(InstructionError::UnsupportedProgramId)?; - let process_instruction = match &entry.program { + let function = match &entry.program { LoadedProgramType::Builtin(program) => program .get_function_registry() .lookup_by_key(ENTRYPOINT_KEY) - .map(|(_name, process_instruction)| process_instruction), + .map(|(_name, function)| function), _ => None, } .ok_or(InstructionError::UnsupportedProgramId)?; @@ -762,31 +765,41 @@ impl<'a> InvokeContext<'a> { let logger = self.get_log_collector(); stable_log::program_invoke(&logger, &program_id, self.get_stack_height()); let pre_remaining_units = self.get_remaining(); + // In program-runtime v2 we will create this VM instance only once per transaction. + // `program_runtime_environment_v2.get_config()` will be used instead of `mock_config`. + // For now, only built-ins are invoked from here, so the VM and its Config are irrelevant. let mock_config = Config::default(); - let mut mock_memory_mapping = - MemoryMapping::new(Vec::new(), &mock_config, &SBPFVersion::V2).unwrap(); - let mut result = ProgramResult::Ok(0); - process_instruction( + let empty_memory_mapping = + MemoryMapping::new(Vec::new(), &mock_config, &SBPFVersion::V1).unwrap(); + let mut vm = EbpfVm::new( + self.programs_loaded_for_tx_batch + .environments + .program_runtime_v2 + .clone(), + &SBPFVersion::V1, // Removes lifetime tracking unsafe { std::mem::transmute::<&mut InvokeContext, &mut InvokeContext>(self) }, + empty_memory_mapping, 0, - 0, - 0, - 0, - 0, - &mut mock_memory_mapping, - &mut result, ); - let result = match result { + vm.invoke_function(function); + let result = match vm.program_result { ProgramResult::Ok(_) => { stable_log::program_success(&logger, &program_id); Ok(()) } - ProgramResult::Err(err) => { - stable_log::program_failure(&logger, &program_id, err.as_ref()); - if let Some(err) = err.downcast_ref::() { - Err(err.clone()) + ProgramResult::Err(ref err) => { + if let EbpfError::SyscallError(syscall_error) = err { + if let Some(instruction_err) = syscall_error.downcast_ref::() + { + stable_log::program_failure(&logger, &program_id, instruction_err); + Err(instruction_err.clone()) + } else { + stable_log::program_failure(&logger, &program_id, syscall_error); + Err(InstructionError::ProgramFailedToComplete) + } } else { + stable_log::program_failure(&logger, &program_id, err); Err(InstructionError::ProgramFailedToComplete) } } @@ -889,7 +902,7 @@ impl<'a> InvokeContext<'a> { pub fn get_syscall_context(&self) -> Result<&SyscallContext, InstructionError> { self.syscall_context .last() - .and_then(|syscall_context| syscall_context.as_ref()) + .and_then(std::option::Option::as_ref) .ok_or(InstructionError::CallDepth) } @@ -979,7 +992,7 @@ pub fn mock_process_instruction, instruction_account_metas: Vec, expected_result: Result<(), InstructionError>, - process_instruction: ProcessInstructionWithContext, + builtin_function: BuiltinFunctionWithContext, mut pre_adjustments: F, mut post_adjustments: G, ) -> Vec { @@ -1014,7 +1027,7 @@ pub fn mock_process_instruction>>; +pub const MAX_LOADED_ENTRY_COUNT: usize = 256; pub const DELAY_VISIBILITY_SLOT_OFFSET: Slot = 1; /// Relationship between two fork IDs @@ -48,13 +53,21 @@ pub enum BlockRelation { pub trait ForkGraph { /// Returns the BlockRelation of A to B fn relationship(&self, a: Slot, b: Slot) -> BlockRelation; + + /// Returns the epoch of the given slot + fn slot_epoch(&self, _slot: Slot) -> Option { + Some(0) + } } /// Provides information about current working slot, and its ancestors pub trait WorkingSlot { - /// Returns the current slot value + /// Returns the current slot fn current_slot(&self) -> Slot; + /// Returns the epoch of the current slot + fn current_epoch(&self) -> Epoch; + /// Returns true if the `other` slot is an ancestor of self, false otherwise fn is_ancestor(&self, other: Slot) -> bool; } @@ -62,17 +75,17 @@ pub trait WorkingSlot { #[derive(Default)] pub enum LoadedProgramType { /// Tombstone for undeployed, closed or unloadable programs - FailedVerification(Arc>>), + FailedVerification(ProgramRuntimeEnvironment), #[default] Closed, DelayVisibility, /// Successfully verified but not currently compiled, used to track usage statistics when a compiled program is evicted from memory. - Unloaded(Arc>>), + Unloaded(ProgramRuntimeEnvironment), LegacyV0(Executable>), LegacyV1(Executable>), Typed(Executable>), #[cfg(test)] - TestLoaded(Arc>>), + TestLoaded(ProgramRuntimeEnvironment), Builtin(BuiltinProgram>), } @@ -95,6 +108,23 @@ impl Debug for LoadedProgramType { } } +impl LoadedProgramType { + /// Returns a reference to its environment if it has one + pub fn get_environment(&self) -> Option<&ProgramRuntimeEnvironment> { + match self { + LoadedProgramType::LegacyV0(program) + | LoadedProgramType::LegacyV1(program) + | LoadedProgramType::Typed(program) => Some(program.get_loader()), + LoadedProgramType::FailedVerification(env) | LoadedProgramType::Unloaded(env) => { + Some(env) + } + #[cfg(test)] + LoadedProgramType::TestLoaded(environment) => Some(environment), + _ => None, + } + } +} + #[derive(Debug, Default)] pub struct LoadedProgram { /// The program of this entry @@ -109,20 +139,34 @@ pub struct LoadedProgram { pub maybe_expiration_slot: Option, /// How often this entry was used by a transaction pub tx_usage_counter: AtomicU64, - /// How often this entry was used by a transaction + /// How often this entry was used by an instruction pub ix_usage_counter: AtomicU64, } +/// Global cache statistics for [LoadedPrograms]. #[derive(Debug, Default)] pub struct Stats { + /// a program was already in the cache pub hits: AtomicU64, + /// a program was not found and loaded instead pub misses: AtomicU64, + /// a compiled executable was unloaded pub evictions: HashMap, + /// an unloaded program was loaded again (opposite of eviction) + pub reloads: AtomicU64, + /// a program was loaded or un/re/deployed pub insertions: AtomicU64, + /// a program was loaded but can not be extracted on its own fork anymore + pub lost_insertions: AtomicU64, + /// a program which was already in the cache was reloaded by mistake pub replacements: AtomicU64, + /// a program was only used once before being unloaded pub one_hit_wonders: AtomicU64, - pub prunes: AtomicU64, - pub expired: AtomicU64, + /// a program became unreachable in the fork graph because of rerooting + pub prunes_orphan: AtomicU64, + /// a program got pruned because it was not recompiled for the next epoch + pub prunes_environment: AtomicU64, + /// the [SecondLevel] was empty because all slot versions got pruned pub empty_entries: AtomicU64, } @@ -131,12 +175,14 @@ impl Stats { pub fn submit(&self, slot: Slot) { let hits = self.hits.load(Ordering::Relaxed); let misses = self.misses.load(Ordering::Relaxed); + let evictions: u64 = self.evictions.values().sum(); + let reloads = self.reloads.load(Ordering::Relaxed); let insertions = self.insertions.load(Ordering::Relaxed); + let lost_insertions = self.lost_insertions.load(Ordering::Relaxed); let replacements = self.replacements.load(Ordering::Relaxed); let one_hit_wonders = self.one_hit_wonders.load(Ordering::Relaxed); - let evictions: u64 = self.evictions.values().sum(); - let prunes = self.prunes.load(Ordering::Relaxed); - let expired = self.expired.load(Ordering::Relaxed); + let prunes_orphan = self.prunes_orphan.load(Ordering::Relaxed); + let prunes_environment = self.prunes_environment.load(Ordering::Relaxed); let empty_entries = self.empty_entries.load(Ordering::Relaxed); datapoint_info!( "loaded-programs-cache-stats", @@ -144,16 +190,18 @@ impl Stats { ("hits", hits, i64), ("misses", misses, i64), ("evictions", evictions, i64), + ("reloads", reloads, i64), ("insertions", insertions, i64), - ("replacements", replacements, i64), + ("lost_insertions", lost_insertions, i64), + ("replace_entry", replacements, i64), ("one_hit_wonders", one_hit_wonders, i64), - ("prunes", prunes, i64), - ("evict_expired", expired, i64), - ("evict_empty_entries", empty_entries, i64), + ("prunes_orphan", prunes_orphan, i64), + ("prunes_environment", prunes_environment, i64), + ("empty_entries", empty_entries, i64), ); debug!( - "Loaded Programs Cache Stats -- Hits: {}, Misses: {}, Evictions: {}, Insertions: {}, Replacements: {}, One-Hit-Wonders: {}, Prunes: {}, Expired: {}, Empty: {}", - hits, misses, evictions, insertions, replacements, one_hit_wonders, prunes, expired, empty_entries + "Loaded Programs Cache Stats -- Hits: {}, Misses: {}, Evictions: {}, Reloads: {}, Insertions: {} Lost-Insertions: {}, Replacements: {}, One-Hit-Wonders: {}, Prunes-Orphan: {}, Prunes-Environment: {}, Empty: {}", + hits, misses, evictions, reloads, insertions, lost_insertions, replacements, one_hit_wonders, prunes_orphan, prunes_environment, empty_entries ); if log_enabled!(log::Level::Trace) && !self.evictions.is_empty() { let mut evictions = self.evictions.iter().collect::>(); @@ -221,7 +269,7 @@ impl LoadedProgram { /// Creates a new user program pub fn new( loader_key: &Pubkey, - program_runtime_environment: Arc>>, + program_runtime_environment: ProgramRuntimeEnvironment, deployment_slot: Slot, effective_slot: Slot, maybe_expiration_slot: Option, @@ -331,22 +379,14 @@ impl LoadedProgram { } pub fn to_unloaded(&self) -> Option { - let env = match &self.program { - LoadedProgramType::LegacyV0(program) - | LoadedProgramType::LegacyV1(program) - | LoadedProgramType::Typed(program) => program.get_loader().clone(), - #[cfg(test)] - LoadedProgramType::TestLoaded(env) => env.clone(), - _ => return None, - }; Some(Self { - program: LoadedProgramType::Unloaded(env), + program: LoadedProgramType::Unloaded(self.program.get_environment()?.clone()), account_size: self.account_size, deployment_slot: self.deployment_slot, effective_slot: self.effective_slot, maybe_expiration_slot: self.maybe_expiration_slot, tx_usage_counter: AtomicU64::new(self.tx_usage_counter.load(Ordering::Relaxed)), - ix_usage_counter: AtomicU64::new(self.tx_usage_counter.load(Ordering::Relaxed)), + ix_usage_counter: AtomicU64::new(self.ix_usage_counter.load(Ordering::Relaxed)), }) } @@ -354,11 +394,11 @@ impl LoadedProgram { pub fn new_builtin( deployment_slot: Slot, account_size: usize, - entrypoint: ProcessInstructionWithContext, + builtin_function: BuiltinFunctionWithContext, ) -> Self { let mut function_registry = FunctionRegistry::default(); function_registry - .register_function_hashed(*b"entrypoint", entrypoint) + .register_function_hashed(*b"entrypoint", builtin_function) .unwrap(); Self { deployment_slot, @@ -407,10 +447,10 @@ impl LoadedProgram { #[derive(Clone, Debug)] pub struct ProgramRuntimeEnvironments { - /// Globally shared RBPF config and syscall registry - pub program_runtime_v1: Arc>>, + /// Globally shared RBPF config and syscall registry for runtime V1 + pub program_runtime_v1: ProgramRuntimeEnvironment, /// Globally shared RBPF config and syscall registry for runtime V2 - pub program_runtime_v2: Arc>>, + pub program_runtime_v2: ProgramRuntimeEnvironment, } impl Default for ProgramRuntimeEnvironments { @@ -426,15 +466,84 @@ impl Default for ProgramRuntimeEnvironments { } } +#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)] +pub struct LoadingTaskCookie(u64); + +impl LoadingTaskCookie { + fn new() -> Self { + Self(0) + } + + fn update(&mut self) { + let LoadingTaskCookie(cookie) = self; + *cookie = cookie.wrapping_add(1); + } +} + +/// Prevents excessive polling during cooperative loading #[derive(Debug, Default)] -pub struct LoadedPrograms { +pub struct LoadingTaskWaiter { + cookie: Mutex, + cond: Condvar, +} + +impl LoadingTaskWaiter { + pub fn new() -> Self { + Self { + cookie: Mutex::new(LoadingTaskCookie::new()), + cond: Condvar::new(), + } + } + + pub fn cookie(&self) -> LoadingTaskCookie { + *self.cookie.lock().unwrap() + } + + pub fn notify(&self) { + let mut cookie = self.cookie.lock().unwrap(); + cookie.update(); + self.cond.notify_all(); + } + + pub fn wait(&self, cookie: LoadingTaskCookie) -> LoadingTaskCookie { + let cookie_guard = self.cookie.lock().unwrap(); + *self + .cond + .wait_while(cookie_guard, |current_cookie| *current_cookie == cookie) + .unwrap() + } +} + +#[derive(Debug, Default)] +struct SecondLevel { + slot_versions: Vec>, + /// Contains the bank and TX batch a program at this address is currently being loaded + cooperative_loading_lock: Option<(Slot, std::thread::ThreadId)>, +} + +#[derive(Debug)] +pub struct LoadedPrograms { /// A two level index: /// - /// Pubkey is the address of a program, multiple versions can coexists simultaneously under the same address (in different slots). - entries: HashMap>>, + /// The first level is for the address at which programs are deployed and the second level for the slot (and thus also fork). + entries: HashMap, + /// The slot of the last rerooting + pub latest_root_slot: Slot, + /// The epoch of the last rerooting + pub latest_root_epoch: Epoch, + /// Environments of the current epoch pub environments: ProgramRuntimeEnvironments, - latest_root: Slot, + /// Anticipated replacement for `environments` at the next epoch + /// + /// This is `None` during most of an epoch, and only `Some` around the boundaries (at the end and beginning of an epoch). + /// More precisely, it starts with the recompilation phase a few hundred slots before the epoch boundary, + /// and it ends with the first rerooting after the epoch boundary. + pub upcoming_environments: Option, + /// List of loaded programs which should be recompiled before the next epoch (but don't have to). + pub programs_to_recompile: Vec<(Pubkey, Arc)>, pub stats: Stats, + pub fork_graph: Option>>, + pub loading_task_waiter: Arc, } #[derive(Clone, Debug, Default)] @@ -444,12 +553,7 @@ pub struct LoadedProgramsForTxBatch { entries: HashMap>, slot: Slot, pub environments: ProgramRuntimeEnvironments, -} - -pub struct ExtractedPrograms { - pub loaded: LoadedProgramsForTxBatch, - pub missing: Vec<(Pubkey, u64)>, - pub unloaded: Vec<(Pubkey, u64)>, + pub hit_max_limit: bool, } impl LoadedProgramsForTxBatch { @@ -458,6 +562,7 @@ impl LoadedProgramsForTxBatch { entries: HashMap::new(), slot, environments, + hit_max_limit: false, } } @@ -511,153 +616,197 @@ pub enum LoadedProgramMatchCriteria { NoCriteria, } -impl LoadedPrograms { +impl LoadedPrograms { + pub fn new(root_slot: Slot, root_epoch: Epoch) -> Self { + Self { + entries: HashMap::new(), + latest_root_slot: root_slot, + latest_root_epoch: root_epoch, + environments: ProgramRuntimeEnvironments::default(), + upcoming_environments: None, + programs_to_recompile: Vec::default(), + stats: Stats::default(), + fork_graph: None, + loading_task_waiter: Arc::new(LoadingTaskWaiter::default()), + } + } + + pub fn set_fork_graph(&mut self, fork_graph: Arc>) { + self.fork_graph = Some(fork_graph); + } + + /// Returns the current environments depending on the given epoch + pub fn get_environments_for_epoch(&self, epoch: Epoch) -> &ProgramRuntimeEnvironments { + if epoch != self.latest_root_epoch { + if let Some(upcoming_environments) = self.upcoming_environments.as_ref() { + return upcoming_environments; + } + } + &self.environments + } + /// Refill the cache with a single entry. It's typically called during transaction loading, /// when the cache doesn't contain the entry corresponding to program `key`. - /// The function dedupes the cache, in case some other thread replenished the entry in parallel. pub fn replenish( &mut self, key: Pubkey, entry: Arc, ) -> (bool, Arc) { - let second_level = self.entries.entry(key).or_default(); - let index = second_level - .iter() - .position(|at| at.effective_slot >= entry.effective_slot); - if let Some((existing, entry_index)) = - index.and_then(|index| second_level.get(index).map(|value| (value, index))) - { - if existing.deployment_slot == entry.deployment_slot - && existing.effective_slot == entry.effective_slot - { - if matches!(existing.program, LoadedProgramType::Unloaded(_)) { - // The unloaded program is getting reloaded + let slot_versions = &mut self.entries.entry(key).or_default().slot_versions; + match slot_versions.binary_search_by(|at| { + at.effective_slot + .cmp(&entry.effective_slot) + .then(at.deployment_slot.cmp(&entry.deployment_slot)) + }) { + Ok(index) => { + let existing = slot_versions.get_mut(index).unwrap(); + if std::mem::discriminant(&existing.program) + != std::mem::discriminant(&entry.program) + { // Copy over the usage counter to the new entry - let mut usage_count = existing.tx_usage_counter.load(Ordering::Relaxed); - saturating_add_assign!( - usage_count, - entry.tx_usage_counter.load(Ordering::Relaxed) + entry.tx_usage_counter.fetch_add( + existing.tx_usage_counter.load(Ordering::Relaxed), + Ordering::Relaxed, ); - entry.tx_usage_counter.store(usage_count, Ordering::Relaxed); - entry.ix_usage_counter.store( + entry.ix_usage_counter.fetch_add( existing.ix_usage_counter.load(Ordering::Relaxed), Ordering::Relaxed, ); - second_level.remove(entry_index); - } else if existing.is_tombstone() != entry.is_tombstone() { - // Either the old entry is tombstone and the new one is not. - // (Let's give the new entry a chance). - // Or, the old entry is not a tombstone and the new one is a tombstone. - // (Remove the old entry, as the tombstone makes it obsolete). - second_level.remove(entry_index); + self.stats.reloads.fetch_add(1, Ordering::Relaxed); + *existing = entry.clone(); + (false, entry) } else { + // Something is wrong, I can feel it ... self.stats.replacements.fetch_add(1, Ordering::Relaxed); - return (true, existing.clone()); + (true, existing.clone()) } } + Err(index) => { + self.stats.insertions.fetch_add(1, Ordering::Relaxed); + slot_versions.insert(index, entry.clone()); + (false, entry) + } } - self.stats.insertions.fetch_add(1, Ordering::Relaxed); - second_level.insert(index.unwrap_or(second_level.len()), entry.clone()); - (false, entry) } /// Assign the program `entry` to the given `key` in the cache. /// This is typically called when a deployed program is managed (un-/re-/deployed) via /// loader instructions. Because of the cooldown, entires can not have the same /// deployment_slot and effective_slot. - pub fn assign_program(&mut self, key: Pubkey, entry: Arc) -> Arc { + pub fn assign_program( + &mut self, + key: Pubkey, + entry: Arc, + ) -> (bool, Arc) { let (was_occupied, entry) = self.replenish(key, entry); debug_assert!(!was_occupied); - entry + (was_occupied, entry) } - /// On the epoch boundary this removes all programs of the outdated feature set - pub fn prune_feature_set_transition(&mut self) { + pub fn prune_by_deployment_slot(&mut self, slot: Slot) { for second_level in self.entries.values_mut() { - second_level.retain(|entry| { - let retain = match &entry.program { - LoadedProgramType::Builtin(_) - | LoadedProgramType::DelayVisibility - | LoadedProgramType::Closed => true, - LoadedProgramType::LegacyV0(program) | LoadedProgramType::LegacyV1(program) - if Arc::ptr_eq( - program.get_loader(), - &self.environments.program_runtime_v1, - ) => - { - true - } - LoadedProgramType::Unloaded(environment) - | LoadedProgramType::FailedVerification(environment) - if Arc::ptr_eq(environment, &self.environments.program_runtime_v1) - || Arc::ptr_eq(environment, &self.environments.program_runtime_v2) => - { - true - } - LoadedProgramType::Typed(program) - if Arc::ptr_eq( - program.get_loader(), - &self.environments.program_runtime_v2, - ) => - { - true - } - _ => false, - }; - if !retain { - self.stats.prunes.fetch_add(1, Ordering::Relaxed); - } - retain - }); + second_level + .slot_versions + .retain(|entry| entry.deployment_slot != slot); } self.remove_programs_with_no_entries(); } - pub fn prune_by_deployment_slot(&mut self, slot: Slot) { - self.entries.retain(|_key, second_level| { - *second_level = second_level - .iter() - .filter(|entry| entry.deployment_slot != slot) - .cloned() - .collect(); - !second_level.is_empty() - }); - self.remove_programs_with_no_entries(); - } - - /// Before rerooting the blockstore this removes all programs of orphan forks - pub fn prune(&mut self, fork_graph: &F, new_root: Slot) { - let previous_root = self.latest_root; - self.entries.retain(|_key, second_level| { + /// Before rerooting the blockstore this removes all superfluous entries + pub fn prune(&mut self, new_root_slot: Slot, new_root_epoch: Epoch) { + let Some(fork_graph) = self.fork_graph.clone() else { + error!("Program cache doesn't have fork graph."); + return; + }; + let Ok(fork_graph) = fork_graph.read() else { + error!("Failed to lock fork graph for reading."); + return; + }; + let mut recompilation_phase_ends = false; + if self.latest_root_epoch != new_root_epoch { + self.latest_root_epoch = new_root_epoch; + if let Some(upcoming_environments) = self.upcoming_environments.take() { + recompilation_phase_ends = true; + self.environments = upcoming_environments; + self.programs_to_recompile.clear(); + } + } + for second_level in self.entries.values_mut() { + // Remove entries un/re/deployed on orphan forks let mut first_ancestor_found = false; - *second_level = second_level + let mut first_ancestor_env = None; + second_level.slot_versions = second_level + .slot_versions .iter() .rev() .filter(|entry| { - let relation = fork_graph.relationship(entry.deployment_slot, new_root); - if entry.deployment_slot >= new_root { + let relation = fork_graph.relationship(entry.deployment_slot, new_root_slot); + if entry.deployment_slot >= new_root_slot { matches!(relation, BlockRelation::Equal | BlockRelation::Descendant) - } else if !first_ancestor_found - && (matches!(relation, BlockRelation::Ancestor) - || entry.deployment_slot <= previous_root) + } else if matches!(relation, BlockRelation::Ancestor) + || entry.deployment_slot <= self.latest_root_slot { - first_ancestor_found = true; - first_ancestor_found + if !first_ancestor_found { + first_ancestor_found = true; + first_ancestor_env = entry.program.get_environment(); + return true; + } + // Do not prune the entry if the runtime environment of the entry is different + // than the entry that was previously found (stored in first_ancestor_env). + // Different environment indicates that this entry might belong to an older + // epoch that had a different environment (e.g. different feature set). + // Once the root moves to the new/current epoch, the entry will get pruned. + // But, until then the entry might still be getting used by an older slot. + if let Some(entry_env) = entry.program.get_environment() { + if let Some(env) = first_ancestor_env { + if !Arc::ptr_eq(entry_env, env) { + return true; + } + } + } + self.stats.prunes_orphan.fetch_add(1, Ordering::Relaxed); + false } else { - self.stats.prunes.fetch_add(1, Ordering::Relaxed); + self.stats.prunes_orphan.fetch_add(1, Ordering::Relaxed); false } }) + .filter(|entry| { + // Remove expired + if let Some(expiration) = entry.maybe_expiration_slot { + if expiration <= new_root_slot { + return false; + } + } + // Remove outdated environment of previous feature set + if recompilation_phase_ends + && !Self::matches_environment(entry, &self.environments) + { + self.stats + .prunes_environment + .fetch_add(1, Ordering::Relaxed); + return false; + } + true + }) .cloned() .collect(); - second_level.reverse(); - !second_level.is_empty() - }); - - self.remove_expired_entries(new_root); + second_level.slot_versions.reverse(); + } self.remove_programs_with_no_entries(); + debug_assert!(self.latest_root_slot <= new_root_slot); + self.latest_root_slot = new_root_slot; + } - self.latest_root = std::cmp::max(self.latest_root, new_root); + fn matches_environment( + entry: &Arc, + environments: &ProgramRuntimeEnvironments, + ) -> bool { + let Some(environment) = entry.program.get_environment() else { + return true; + }; + Arc::ptr_eq(environment, &environments.program_runtime_v1) + || Arc::ptr_eq(environment, &environments.program_runtime_v2) } fn matches_loaded_program_criteria( @@ -695,82 +844,134 @@ impl LoadedPrograms { /// Extracts a subset of the programs relevant to a transaction batch /// and returns which program accounts the accounts DB needs to load. pub fn extract( - &self, + &mut self, working_slot: &S, - keys: impl Iterator, - ) -> ExtractedPrograms { - let mut missing = Vec::new(); - let mut unloaded = Vec::new(); - let found = keys - .filter_map(|(key, (match_criteria, count))| { - if let Some(second_level) = self.entries.get(&key) { - for entry in second_level.iter().rev() { - let current_slot = working_slot.current_slot(); - if entry.deployment_slot <= self.latest_root - || entry.deployment_slot == current_slot - || working_slot.is_ancestor(entry.deployment_slot) - { - if !Self::is_entry_usable(entry, current_slot, &match_criteria) { - missing.push((key, count)); - return None; - } + search_for: &mut Vec<(Pubkey, (LoadedProgramMatchCriteria, u64))>, + loaded_programs_for_tx_batch: &mut LoadedProgramsForTxBatch, + is_first_round: bool, + ) -> Option<(Pubkey, u64)> { + let mut cooperative_loading_task = None; + search_for.retain(|(key, (match_criteria, usage_count))| { + if let Some(second_level) = self.entries.get_mut(key) { + for entry in second_level.slot_versions.iter().rev() { + let is_ancestor = if let Some(fork_graph) = &self.fork_graph { + fork_graph + .read() + .map(|fork_graph_r| { + matches!( + fork_graph_r.relationship( + entry.deployment_slot, + loaded_programs_for_tx_batch.slot + ), + BlockRelation::Ancestor + ) + }) + .unwrap_or(false) + } else { + working_slot.is_ancestor(entry.deployment_slot) + }; - if let LoadedProgramType::Unloaded(environment) = &entry.program { - if Arc::ptr_eq(environment, &self.environments.program_runtime_v1) - || Arc::ptr_eq( - environment, - &self.environments.program_runtime_v2, - ) - { - // if the environment hasn't changed since the entry was unloaded. - unloaded.push((key, count)); - } else { - missing.push((key, count)); + if entry.deployment_slot <= self.latest_root_slot + || entry.deployment_slot == loaded_programs_for_tx_batch.slot + || is_ancestor + { + let entry_to_return = + if loaded_programs_for_tx_batch.slot >= entry.effective_slot { + if !Self::is_entry_usable( + entry, + loaded_programs_for_tx_batch.slot, + match_criteria, + ) || !Self::matches_environment( + entry, + &loaded_programs_for_tx_batch.environments, + ) { + break; + } + + if let LoadedProgramType::Unloaded(_environment) = &entry.program { + break; } - return None; - } - if current_slot >= entry.effective_slot { - let mut usage_count = - entry.tx_usage_counter.load(Ordering::Relaxed); - saturating_add_assign!(usage_count, count); - entry.tx_usage_counter.store(usage_count, Ordering::Relaxed); - return Some((key, entry.clone())); - } else if entry.is_implicit_delay_visibility_tombstone(current_slot) { + entry.clone() + } else if entry.is_implicit_delay_visibility_tombstone( + loaded_programs_for_tx_batch.slot, + ) { // Found a program entry on the current fork, but it's not effective // yet. It indicates that the program has delayed visibility. Return // the tombstone to reflect that. - return Some(( - key, - Arc::new(LoadedProgram::new_tombstone( - entry.deployment_slot, - LoadedProgramType::DelayVisibility, - )), - )); - } - } + Arc::new(LoadedProgram::new_tombstone( + entry.deployment_slot, + LoadedProgramType::DelayVisibility, + )) + } else { + continue; + }; + entry_to_return + .tx_usage_counter + .fetch_add(*usage_count, Ordering::Relaxed); + loaded_programs_for_tx_batch + .entries + .insert(*key, entry_to_return); + return false; } } - missing.push((key, count)); - None - }) - .collect::>>(); - - self.stats - .misses - .fetch_add(missing.len() as u64, Ordering::Relaxed); - self.stats - .hits - .fetch_add(found.len() as u64, Ordering::Relaxed); - ExtractedPrograms { - loaded: LoadedProgramsForTxBatch { - entries: found, - slot: working_slot.current_slot(), - environments: self.environments.clone(), - }, - missing, - unloaded, + } + if cooperative_loading_task.is_none() { + // We have not selected a task so far + let second_level = self.entries.entry(*key).or_default(); + if second_level.cooperative_loading_lock.is_none() { + // Select this missing entry which is not selected by any other TX batch yet + cooperative_loading_task = Some((*key, *usage_count)); + second_level.cooperative_loading_lock = + Some((working_slot.current_slot(), std::thread::current().id())); + } + } + true + }); + if is_first_round { + self.stats + .misses + .fetch_add(search_for.len() as u64, Ordering::Relaxed); + self.stats.hits.fetch_add( + loaded_programs_for_tx_batch.entries.len() as u64, + Ordering::Relaxed, + ); + } + cooperative_loading_task + } + + /// Called by Bank::replenish_program_cache() for each program that is done loading. + pub fn finish_cooperative_loading_task( + &mut self, + slot: Slot, + key: Pubkey, + loaded_program: Arc, + ) -> bool { + let second_level = self.entries.entry(key).or_default(); + debug_assert_eq!( + second_level.cooperative_loading_lock, + Some((slot, std::thread::current().id())) + ); + second_level.cooperative_loading_lock = None; + // Check that it will be visible to our own fork once inserted + if loaded_program.deployment_slot > self.latest_root_slot + && !self + .fork_graph + .as_ref() + .and_then(|fork_graph| fork_graph.read().ok()) + .map(|fork_graph_r| { + matches!( + fork_graph_r.relationship(loaded_program.deployment_slot, slot), + BlockRelation::Equal | BlockRelation::Ancestor + ) + }) + .unwrap_or(true) + { + self.stats.lost_insertions.fetch_add(1, Ordering::Relaxed); } + let (was_replaced, _) = self.assign_program(key, loaded_program); + self.loading_task_waiter.notify(); + was_replaced } pub fn merge(&mut self, tx_batch_cache: &LoadedProgramsForTxBatch) { @@ -789,8 +990,10 @@ impl LoadedPrograms { ) -> Vec<(Pubkey, Arc)> { self.entries .iter() - .flat_map(|(id, list)| { - list.iter() + .flat_map(|(id, second_level)| { + second_level + .slot_versions + .iter() .filter_map(move |program| match program.program { LoadedProgramType::LegacyV0(_) | LoadedProgramType::LegacyV1(_) if include_program_runtime_v1 => @@ -816,7 +1019,6 @@ impl LoadedPrograms { .len() .saturating_sub(shrink_to.apply_to(MAX_LOADED_ENTRY_COUNT)); self.unload_program_entries(sorted_candidates.iter().take(num_to_unload)); - self.remove_programs_with_no_entries(); } /// Removes all the entries at the given keys, if they exist @@ -826,27 +1028,9 @@ impl LoadedPrograms { } } - fn remove_expired_entries(&mut self, current_slot: Slot) { - for entry in self.entries.values_mut() { - entry.retain(|program| { - program - .maybe_expiration_slot - .map(|expiration| { - if expiration > current_slot { - true - } else { - self.stats.expired.fetch_add(1, Ordering::Relaxed); - false - } - }) - .unwrap_or(true) - }); - } - } - fn unload_program(&mut self, id: &Pubkey) { - if let Some(entries) = self.entries.get_mut(id) { - entries.iter_mut().for_each(|entry| { + if let Some(second_level) = self.entries.get_mut(id) { + for entry in second_level.slot_versions.iter_mut() { if let Some(unloaded) = entry.to_unloaded() { *entry = Arc::new(unloaded); self.stats @@ -855,7 +1039,7 @@ impl LoadedPrograms { .and_modify(|c| saturating_add_assign!(*c, 1)) .or_insert(1); } - }); + } } } @@ -869,8 +1053,12 @@ impl LoadedPrograms { remove: impl Iterator)>, ) { for (id, program) in remove { - if let Some(entries) = self.entries.get_mut(id) { - if let Some(candidate) = entries.iter_mut().find(|entry| entry == &program) { + if let Some(second_level) = self.entries.get_mut(id) { + if let Some(candidate) = second_level + .slot_versions + .iter_mut() + .find(|entry| entry == &program) + { if let Some(unloaded) = candidate.to_unloaded() { if candidate.tx_usage_counter.load(Ordering::Relaxed) == 1 { self.stats.one_hit_wonders.fetch_add(1, Ordering::Relaxed); @@ -889,7 +1077,10 @@ impl LoadedPrograms { fn remove_programs_with_no_entries(&mut self) { let num_programs_before_removal = self.entries.len(); - self.entries.retain(|_, programs| !programs.is_empty()); + self.entries.retain(|_, second_level| { + !second_level.slot_versions.is_empty() + || second_level.cooperative_loading_lock.is_some() + }); if self.entries.len() < num_programs_before_removal { self.stats.empty_entries.fetch_add( num_programs_before_removal.saturating_sub(self.entries.len()) as u64, @@ -908,10 +1099,10 @@ impl solana_frozen_abi::abi_example::AbiExample for LoadedProgram { } #[cfg(RUSTC_WITH_SPECIALIZATION)] -impl solana_frozen_abi::abi_example::AbiExample for LoadedPrograms { +impl solana_frozen_abi::abi_example::AbiExample for LoadedPrograms { fn example() -> Self { // LoadedPrograms isn't serializable by definition. - Self::default() + Self::new(Slot::default(), Epoch::default()) } } @@ -919,23 +1110,72 @@ impl solana_frozen_abi::abi_example::AbiExample for LoadedPrograms { mod tests { use { crate::loaded_programs::{ - BlockRelation, ExtractedPrograms, ForkGraph, LoadedProgram, LoadedProgramMatchCriteria, - LoadedProgramType, LoadedPrograms, LoadedProgramsForTxBatch, WorkingSlot, - DELAY_VISIBILITY_SLOT_OFFSET, + BlockRelation, ForkGraph, LoadedProgram, LoadedProgramMatchCriteria, LoadedProgramType, + LoadedPrograms, LoadedProgramsForTxBatch, ProgramRuntimeEnvironment, + ProgramRuntimeEnvironments, WorkingSlot, DELAY_VISIBILITY_SLOT_OFFSET, }, assert_matches::assert_matches, percentage::Percentage, - solana_rbpf::vm::BuiltinProgram, - solana_sdk::{clock::Slot, pubkey::Pubkey}, + solana_rbpf::program::BuiltinProgram, + solana_sdk::{ + clock::{Epoch, Slot}, + pubkey::Pubkey, + }, std::{ ops::ControlFlow, sync::{ atomic::{AtomicU64, Ordering}, - Arc, + Arc, RwLock, }, }, }; + static MOCK_ENVIRONMENT: std::sync::OnceLock = + std::sync::OnceLock::::new(); + + fn new_mock_cache() -> LoadedPrograms { + let mut cache = LoadedPrograms::new(0, 0); + + cache.environments.program_runtime_v1 = MOCK_ENVIRONMENT + .get_or_init(|| Arc::new(BuiltinProgram::new_mock())) + .clone(); + cache + } + + fn new_test_loaded_program(deployment_slot: Slot, effective_slot: Slot) -> Arc { + new_test_loaded_program_with_usage(deployment_slot, effective_slot, AtomicU64::default()) + } + + fn new_test_loaded_program_with_usage( + deployment_slot: Slot, + effective_slot: Slot, + usage_counter: AtomicU64, + ) -> Arc { + new_test_loaded_program_with_usage_and_expiry( + deployment_slot, + effective_slot, + usage_counter, + None, + ) + } + + fn new_test_loaded_program_with_usage_and_expiry( + deployment_slot: Slot, + effective_slot: Slot, + usage_counter: AtomicU64, + expiry: Option, + ) -> Arc { + Arc::new(LoadedProgram { + program: LoadedProgramType::TestLoaded(MOCK_ENVIRONMENT.get().unwrap().clone()), + account_size: 0, + deployment_slot, + effective_slot, + maybe_expiration_slot: expiry, + tx_usage_counter: usage_counter, + ix_usage_counter: AtomicU64::default(), + }) + } + fn new_test_builtin_program(deployment_slot: Slot, effective_slot: Slot) -> Arc { Arc::new(LoadedProgram { program: LoadedProgramType::Builtin(BuiltinProgram::new_mock()), @@ -948,17 +1188,19 @@ mod tests { }) } - fn set_tombstone( - cache: &mut LoadedPrograms, + fn set_tombstone( + cache: &mut LoadedPrograms, key: Pubkey, slot: Slot, reason: LoadedProgramType, ) -> Arc { - cache.assign_program(key, Arc::new(LoadedProgram::new_tombstone(slot, reason))) + cache + .assign_program(key, Arc::new(LoadedProgram::new_tombstone(slot, reason))) + .1 } - fn insert_unloaded_program( - cache: &mut LoadedPrograms, + fn insert_unloaded_program( + cache: &mut LoadedPrograms, key: Pubkey, slot: Slot, ) -> Arc { @@ -980,15 +1222,17 @@ mod tests { cache.replenish(key, unloaded).1 } - fn num_matching_entries

(cache: &LoadedPrograms, predicate: P) -> usize + fn num_matching_entries(cache: &LoadedPrograms, predicate: P) -> usize where P: Fn(&LoadedProgramType) -> bool, + FG: ForkGraph, { cache .entries .values() - .map(|programs| { - programs + .map(|second_level| { + second_level + .slot_versions .iter() .filter(|program| predicate(&program.program)) .count() @@ -999,9 +1243,8 @@ mod tests { #[test] fn test_eviction() { let mut programs = vec![]; - let mut num_total_programs: usize = 0; - let mut cache = LoadedPrograms::default(); + let mut cache = new_mock_cache::(); let program1 = Pubkey::new_unique(); let program1_deployment_slots = [0, 10, 20]; @@ -1019,7 +1262,6 @@ mod tests { AtomicU64::new(usage_counter), ), ); - num_total_programs += 1; programs.push((program1, *deployment_slot, usage_counter)); }); @@ -1053,7 +1295,6 @@ mod tests { AtomicU64::new(usage_counter), ), ); - num_total_programs += 1; programs.push((program2, *deployment_slot, usage_counter)); }); @@ -1086,7 +1327,6 @@ mod tests { AtomicU64::new(usage_counter), ), ); - num_total_programs += 1; programs.push((program3, *deployment_slot, usage_counter)); }); @@ -1132,8 +1372,8 @@ mod tests { let unloaded = cache .entries .iter() - .flat_map(|(id, cached_programs)| { - cached_programs.iter().filter_map(|program| { + .flat_map(|(id, second_level)| { + second_level.slot_versions.iter().filter_map(|program| { matches!(program.program, LoadedProgramType::Unloaded(_)) .then_some((*id, program.tx_usage_counter.load(Ordering::Relaxed))) }) @@ -1167,7 +1407,7 @@ mod tests { #[test] fn test_usage_count_of_unloaded_program() { - let mut cache = LoadedPrograms::default(); + let mut cache = new_mock_cache::(); let program = Pubkey::new_unique(); let num_total_programs = 6; @@ -1186,8 +1426,8 @@ mod tests { }); assert_eq!(num_unloaded, 1); - cache.entries.values().for_each(|programs| { - programs.iter().for_each(|program| { + cache.entries.values().for_each(|second_level| { + second_level.slot_versions.iter().for_each(|program| { if matches!(program.program, LoadedProgramType::Unloaded(_)) { // Test that the usage counter is retained for the unloaded program assert_eq!(program.tx_usage_counter.load(Ordering::Relaxed), 10); @@ -1204,8 +1444,8 @@ mod tests { new_test_loaded_program_with_usage(0, 2, AtomicU64::new(0)), ); - cache.entries.values().for_each(|programs| { - programs.iter().for_each(|program| { + cache.entries.values().for_each(|second_level| { + second_level.slot_versions.iter().for_each(|program| { if matches!(program.program, LoadedProgramType::Unloaded(_)) && program.deployment_slot == 0 && program.effective_slot == 2 @@ -1219,7 +1459,7 @@ mod tests { #[test] fn test_replace_tombstones() { - let mut cache = LoadedPrograms::default(); + let mut cache = new_mock_cache::(); let program1 = Pubkey::new_unique(); let env = Arc::new(BuiltinProgram::new_mock()); set_tombstone( @@ -1251,7 +1491,7 @@ mod tests { assert_eq!(tombstone.deployment_slot, 100); assert_eq!(tombstone.effective_slot, 100); - let mut cache = LoadedPrograms::default(); + let mut cache = new_mock_cache::(); let program1 = Pubkey::new_unique(); let tombstone = set_tombstone( &mut cache, @@ -1263,8 +1503,8 @@ mod tests { .entries .get(&program1) .expect("Failed to find the entry"); - assert_eq!(second_level.len(), 1); - assert!(second_level.get(0).unwrap().is_tombstone()); + assert_eq!(second_level.slot_versions.len(), 1); + assert!(second_level.slot_versions.first().unwrap().is_tombstone()); assert_eq!(tombstone.deployment_slot, 10); assert_eq!(tombstone.effective_slot, 10); @@ -1279,8 +1519,8 @@ mod tests { .entries .get(&program2) .expect("Failed to find the entry"); - assert_eq!(second_level.len(), 1); - assert!(!second_level.get(0).unwrap().is_tombstone()); + assert_eq!(second_level.slot_versions.len(), 1); + assert!(!second_level.slot_versions.first().unwrap().is_tombstone()); let tombstone = set_tombstone( &mut cache, @@ -1292,9 +1532,9 @@ mod tests { .entries .get(&program2) .expect("Failed to find the entry"); - assert_eq!(second_level.len(), 2); - assert!(!second_level.get(0).unwrap().is_tombstone()); - assert!(second_level.get(1).unwrap().is_tombstone()); + assert_eq!(second_level.slot_versions.len(), 2); + assert!(!second_level.slot_versions.first().unwrap().is_tombstone()); + assert!(second_level.slot_versions.get(1).unwrap().is_tombstone()); assert!(tombstone.is_tombstone()); assert_eq!(tombstone.deployment_slot, 60); assert_eq!(tombstone.effective_slot, 60); @@ -1311,48 +1551,134 @@ mod tests { #[test] fn test_prune_empty() { - let mut cache = LoadedPrograms::default(); - let fork_graph = TestForkGraph { + let mut cache = new_mock_cache::(); + let fork_graph = Arc::new(RwLock::new(TestForkGraph { relation: BlockRelation::Unrelated, - }; + })); - cache.prune(&fork_graph, 0); + cache.set_fork_graph(fork_graph); + + cache.prune(0, 0); assert!(cache.entries.is_empty()); - cache.prune(&fork_graph, 10); + cache.prune(10, 0); assert!(cache.entries.is_empty()); - let fork_graph = TestForkGraph { + let mut cache = new_mock_cache::(); + let fork_graph = Arc::new(RwLock::new(TestForkGraph { relation: BlockRelation::Ancestor, - }; + })); + + cache.set_fork_graph(fork_graph); - cache.prune(&fork_graph, 0); + cache.prune(0, 0); assert!(cache.entries.is_empty()); - cache.prune(&fork_graph, 10); + cache.prune(10, 0); assert!(cache.entries.is_empty()); - let fork_graph = TestForkGraph { + let mut cache = new_mock_cache::(); + let fork_graph = Arc::new(RwLock::new(TestForkGraph { relation: BlockRelation::Descendant, - }; + })); + + cache.set_fork_graph(fork_graph); - cache.prune(&fork_graph, 0); + cache.prune(0, 0); assert!(cache.entries.is_empty()); - cache.prune(&fork_graph, 10); + cache.prune(10, 0); assert!(cache.entries.is_empty()); - let fork_graph = TestForkGraph { + let mut cache = new_mock_cache::(); + let fork_graph = Arc::new(RwLock::new(TestForkGraph { relation: BlockRelation::Unknown, - }; + })); + cache.set_fork_graph(fork_graph); - cache.prune(&fork_graph, 0); + cache.prune(0, 0); assert!(cache.entries.is_empty()); - cache.prune(&fork_graph, 10); + cache.prune(10, 0); assert!(cache.entries.is_empty()); } + #[test] + fn test_prune_different_env() { + let mut cache = new_mock_cache::(); + + let fork_graph = Arc::new(RwLock::new(TestForkGraph { + relation: BlockRelation::Ancestor, + })); + + cache.set_fork_graph(fork_graph); + + let program1 = Pubkey::new_unique(); + let loaded_program = new_test_loaded_program(10, 10); + let (existing, program) = cache.replenish(program1, loaded_program.clone()); + assert!(!existing); + assert_eq!(program, loaded_program); + + let new_env = Arc::new(BuiltinProgram::new_mock()); + cache.upcoming_environments = Some(ProgramRuntimeEnvironments { + program_runtime_v1: new_env.clone(), + program_runtime_v2: new_env.clone(), + }); + let updated_program = Arc::new(LoadedProgram { + program: LoadedProgramType::TestLoaded(new_env.clone()), + account_size: 0, + deployment_slot: 20, + effective_slot: 20, + maybe_expiration_slot: None, + tx_usage_counter: AtomicU64::default(), + ix_usage_counter: AtomicU64::default(), + }); + let (existing, program) = cache.replenish(program1, updated_program.clone()); + assert!(!existing); + assert_eq!(program, updated_program); + + // Test that there are 2 entries for the program + assert_eq!( + cache + .entries + .get(&program1) + .expect("failed to find the program") + .slot_versions + .len(), + 2 + ); + + cache.prune(21, cache.latest_root_epoch); + + // Test that prune didn't remove the entry, since environments are different. + assert_eq!( + cache + .entries + .get(&program1) + .expect("failed to find the program") + .slot_versions + .len(), + 2 + ); + + cache.prune(22, cache.latest_root_epoch.saturating_add(1)); + + let second_level = cache + .entries + .get(&program1) + .expect("failed to find the program"); + // Test that prune removed 1 entry, since epoch changed + assert_eq!(second_level.slot_versions.len(), 1); + + let entry = second_level + .slot_versions + .first() + .expect("Failed to get the program") + .clone(); + // Test that the correct entry remains in the cache + assert_eq!(entry, updated_program); + } + #[derive(Default)] struct TestForkGraphSpecific { forks: Vec>, @@ -1394,102 +1720,47 @@ mod tests { } } - struct TestWorkingSlot { - slot: Slot, - fork: Vec, - slot_pos: usize, - } - - impl TestWorkingSlot { - fn new(slot: Slot, fork: &[Slot]) -> Self { - let mut fork = fork.to_vec(); - fork.sort(); - let slot_pos = fork - .iter() - .position(|current| *current == slot) - .expect("The fork didn't have the slot in it"); - TestWorkingSlot { - slot, - fork, - slot_pos, - } - } - - fn update_slot(&mut self, slot: Slot) { - self.slot = slot; - self.slot_pos = self - .fork - .iter() - .position(|current| *current == slot) - .expect("The fork didn't have the slot in it"); - } - } + struct TestWorkingSlot(pub Slot); impl WorkingSlot for TestWorkingSlot { fn current_slot(&self) -> Slot { - self.slot + self.0 } - fn is_ancestor(&self, other: Slot) -> bool { - self.fork - .iter() - .position(|current| *current == other) - .map(|other_pos| other_pos < self.slot_pos) - .unwrap_or(false) + fn current_epoch(&self) -> Epoch { + 0 } - } - fn new_test_loaded_program(deployment_slot: Slot, effective_slot: Slot) -> Arc { - new_test_loaded_program_with_usage(deployment_slot, effective_slot, AtomicU64::default()) - } - - fn new_test_loaded_program_with_usage( - deployment_slot: Slot, - effective_slot: Slot, - usage_counter: AtomicU64, - ) -> Arc { - new_test_loaded_program_with_usage_and_expiry( - deployment_slot, - effective_slot, - usage_counter, - None, - ) - } - - fn new_test_loaded_program_with_usage_and_expiry( - deployment_slot: Slot, - effective_slot: Slot, - usage_counter: AtomicU64, - expiry: Option, - ) -> Arc { - let env = Arc::new(BuiltinProgram::new_mock()); - Arc::new(LoadedProgram { - program: LoadedProgramType::TestLoaded(env), - account_size: 0, - deployment_slot, - effective_slot, - maybe_expiration_slot: expiry, - tx_usage_counter: usage_counter, - ix_usage_counter: AtomicU64::default(), - }) + fn is_ancestor(&self, _other: Slot) -> bool { + false + } } fn match_slot( - table: &LoadedProgramsForTxBatch, + extracted: &LoadedProgramsForTxBatch, program: &Pubkey, deployment_slot: Slot, working_slot: Slot, ) -> bool { - assert_eq!(table.slot, working_slot); - table - .find(program) + assert_eq!(extracted.slot, working_slot); + extracted + .entries + .get(program) .map(|entry| entry.deployment_slot == deployment_slot) .unwrap_or(false) } + fn match_missing( + missing: &[(Pubkey, (LoadedProgramMatchCriteria, u64))], + program: &Pubkey, + _reload: bool, + ) -> bool { + missing.iter().any(|(key, _)| key == program) + } + #[test] fn test_fork_extract_and_prune() { - let mut cache = LoadedPrograms::default(); + let mut cache = new_mock_cache::(); // Fork graph created for the test // 0 @@ -1508,9 +1779,12 @@ mod tests { let mut fork_graph = TestForkGraphSpecific::default(); fork_graph.insert_fork(&[0, 10, 20, 22]); - fork_graph.insert_fork(&[0, 5, 11, 15, 16, 19, 21, 23]); + fork_graph.insert_fork(&[0, 5, 11, 15, 16, 18, 19, 21, 23]); fork_graph.insert_fork(&[0, 5, 11, 25, 27]); + let fork_graph = Arc::new(RwLock::new(fork_graph)); + cache.set_fork_graph(fork_graph); + let program1 = Pubkey::new_unique(); assert!(!cache.replenish(program1, new_test_loaded_program(0, 1)).0); assert!(!cache.replenish(program1, new_test_loaded_program(10, 11)).0); @@ -1562,136 +1836,100 @@ mod tests { // 23 // Testing fork 0 - 10 - 12 - 22 with current slot at 22 - let working_slot = TestWorkingSlot::new(22, &[0, 10, 20, 22]); - let ExtractedPrograms { - loaded: found, - missing, - unloaded, - } = cache.extract( - &working_slot, - vec![ - (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), - (program2, (LoadedProgramMatchCriteria::NoCriteria, 2)), - (program3, (LoadedProgramMatchCriteria::NoCriteria, 3)), - (program4, (LoadedProgramMatchCriteria::NoCriteria, 4)), - ] - .into_iter(), - ); + let mut missing = vec![ + (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), + (program2, (LoadedProgramMatchCriteria::NoCriteria, 2)), + (program3, (LoadedProgramMatchCriteria::NoCriteria, 3)), + (program4, (LoadedProgramMatchCriteria::NoCriteria, 4)), + ]; + let mut extracted = LoadedProgramsForTxBatch::new(22, cache.environments.clone()); + cache.extract(&TestWorkingSlot(22), &mut missing, &mut extracted, true); - assert!(match_slot(&found, &program1, 20, 22)); - assert!(match_slot(&found, &program4, 0, 22)); + assert!(match_slot(&extracted, &program1, 20, 22)); + assert!(match_slot(&extracted, &program4, 0, 22)); - assert!(missing.contains(&(program2, 2))); - assert!(missing.contains(&(program3, 3))); - assert!(unloaded.is_empty()); + assert!(match_missing(&missing, &program2, false)); + assert!(match_missing(&missing, &program3, false)); // Testing fork 0 - 5 - 11 - 15 - 16 with current slot at 16 - let mut working_slot = TestWorkingSlot::new(15, &[0, 5, 11, 15, 16, 18, 19, 23]); - let ExtractedPrograms { - loaded: found, - missing, - unloaded, - } = cache.extract( - &working_slot, - vec![ - (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), - (program2, (LoadedProgramMatchCriteria::NoCriteria, 1)), - (program3, (LoadedProgramMatchCriteria::NoCriteria, 1)), - (program4, (LoadedProgramMatchCriteria::NoCriteria, 1)), - ] - .into_iter(), - ); - - assert!(match_slot(&found, &program1, 0, 15)); - assert!(match_slot(&found, &program2, 11, 15)); + let mut missing = vec![ + (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), + (program2, (LoadedProgramMatchCriteria::NoCriteria, 1)), + (program3, (LoadedProgramMatchCriteria::NoCriteria, 1)), + (program4, (LoadedProgramMatchCriteria::NoCriteria, 1)), + ]; + let mut extracted = LoadedProgramsForTxBatch::new(15, cache.environments.clone()); + cache.extract(&TestWorkingSlot(15), &mut missing, &mut extracted, true); + + assert!(match_slot(&extracted, &program1, 0, 15)); + assert!(match_slot(&extracted, &program2, 11, 15)); // The effective slot of program4 deployed in slot 15 is 19. So it should not be usable in slot 16. // A delay visibility tombstone should be returned here. - let tombstone = found.find(&program4).expect("Failed to find the tombstone"); + let tombstone = extracted + .find(&program4) + .expect("Failed to find the tombstone"); assert_matches!(tombstone.program, LoadedProgramType::DelayVisibility); assert_eq!(tombstone.deployment_slot, 15); - assert!(missing.contains(&(program3, 1))); - assert!(unloaded.is_empty()); + assert!(match_missing(&missing, &program3, false)); // Testing the same fork above, but current slot is now 18 (equal to effective slot of program4). - working_slot.update_slot(18); - let ExtractedPrograms { - loaded: found, - missing, - unloaded, - } = cache.extract( - &working_slot, - vec![ - (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), - (program2, (LoadedProgramMatchCriteria::NoCriteria, 1)), - (program3, (LoadedProgramMatchCriteria::NoCriteria, 1)), - (program4, (LoadedProgramMatchCriteria::NoCriteria, 1)), - ] - .into_iter(), - ); - - assert!(match_slot(&found, &program1, 0, 18)); - assert!(match_slot(&found, &program2, 11, 18)); + let mut missing = vec![ + (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), + (program2, (LoadedProgramMatchCriteria::NoCriteria, 1)), + (program3, (LoadedProgramMatchCriteria::NoCriteria, 1)), + (program4, (LoadedProgramMatchCriteria::NoCriteria, 1)), + ]; + let mut extracted = LoadedProgramsForTxBatch::new(18, cache.environments.clone()); + cache.extract(&TestWorkingSlot(18), &mut missing, &mut extracted, true); + + assert!(match_slot(&extracted, &program1, 0, 18)); + assert!(match_slot(&extracted, &program2, 11, 18)); // The effective slot of program4 deployed in slot 15 is 18. So it should be usable in slot 18. - assert!(match_slot(&found, &program4, 15, 18)); + assert!(match_slot(&extracted, &program4, 15, 18)); - assert!(missing.contains(&(program3, 1))); - assert!(unloaded.is_empty()); + assert!(match_missing(&missing, &program3, false)); // Testing the same fork above, but current slot is now 23 (future slot than effective slot of program4). - working_slot.update_slot(23); - let ExtractedPrograms { - loaded: found, - missing, - unloaded, - } = cache.extract( - &working_slot, - vec![ - (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), - (program2, (LoadedProgramMatchCriteria::NoCriteria, 1)), - (program3, (LoadedProgramMatchCriteria::NoCriteria, 1)), - (program4, (LoadedProgramMatchCriteria::NoCriteria, 1)), - ] - .into_iter(), - ); - - assert!(match_slot(&found, &program1, 0, 23)); - assert!(match_slot(&found, &program2, 11, 23)); + let mut missing = vec![ + (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), + (program2, (LoadedProgramMatchCriteria::NoCriteria, 1)), + (program3, (LoadedProgramMatchCriteria::NoCriteria, 1)), + (program4, (LoadedProgramMatchCriteria::NoCriteria, 1)), + ]; + let mut extracted = LoadedProgramsForTxBatch::new(23, cache.environments.clone()); + cache.extract(&TestWorkingSlot(23), &mut missing, &mut extracted, true); + + assert!(match_slot(&extracted, &program1, 0, 23)); + assert!(match_slot(&extracted, &program2, 11, 23)); // The effective slot of program4 deployed in slot 15 is 19. So it should be usable in slot 23. - assert!(match_slot(&found, &program4, 15, 23)); + assert!(match_slot(&extracted, &program4, 15, 23)); - assert!(missing.contains(&(program3, 1))); - assert!(unloaded.is_empty()); + assert!(match_missing(&missing, &program3, false)); // Testing fork 0 - 5 - 11 - 15 - 16 with current slot at 11 - let working_slot = TestWorkingSlot::new(11, &[0, 5, 11, 15, 16]); - let ExtractedPrograms { - loaded: found, - missing, - unloaded, - } = cache.extract( - &working_slot, - vec![ - (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), - (program2, (LoadedProgramMatchCriteria::NoCriteria, 1)), - (program3, (LoadedProgramMatchCriteria::NoCriteria, 1)), - (program4, (LoadedProgramMatchCriteria::NoCriteria, 1)), - ] - .into_iter(), - ); - - assert!(match_slot(&found, &program1, 0, 11)); + let mut missing = vec![ + (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), + (program2, (LoadedProgramMatchCriteria::NoCriteria, 1)), + (program3, (LoadedProgramMatchCriteria::NoCriteria, 1)), + (program4, (LoadedProgramMatchCriteria::NoCriteria, 1)), + ]; + let mut extracted = LoadedProgramsForTxBatch::new(11, cache.environments.clone()); + cache.extract(&TestWorkingSlot(11), &mut missing, &mut extracted, true); + + assert!(match_slot(&extracted, &program1, 0, 11)); // program2 was updated at slot 11, but is not effective till slot 12. The result should contain a tombstone. - let tombstone = found.find(&program2).expect("Failed to find the tombstone"); + let tombstone = extracted + .find(&program2) + .expect("Failed to find the tombstone"); assert_matches!(tombstone.program, LoadedProgramType::DelayVisibility); assert_eq!(tombstone.deployment_slot, 11); - assert!(match_slot(&found, &program4, 5, 11)); + assert!(match_slot(&extracted, &program4, 5, 11)); - assert!(missing.contains(&(program3, 1))); - assert!(unloaded.is_empty()); + assert!(match_missing(&missing, &program3, false)); // The following is a special case, where there's an expiration slot let test_program = Arc::new(LoadedProgram { @@ -1706,61 +1944,45 @@ mod tests { assert!(!cache.replenish(program4, test_program).0); // Testing fork 0 - 5 - 11 - 15 - 16 - 19 - 21 - 23 with current slot at 19 - let working_slot = TestWorkingSlot::new(19, &[0, 5, 11, 15, 16, 18, 19, 21, 23]); - let ExtractedPrograms { - loaded: found, - missing, - unloaded, - } = cache.extract( - &working_slot, - vec![ - (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), - (program2, (LoadedProgramMatchCriteria::NoCriteria, 1)), - (program3, (LoadedProgramMatchCriteria::NoCriteria, 1)), - (program4, (LoadedProgramMatchCriteria::NoCriteria, 1)), - ] - .into_iter(), - ); - - assert!(match_slot(&found, &program1, 0, 19)); - assert!(match_slot(&found, &program2, 11, 19)); + let mut missing = vec![ + (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), + (program2, (LoadedProgramMatchCriteria::NoCriteria, 1)), + (program3, (LoadedProgramMatchCriteria::NoCriteria, 1)), + (program4, (LoadedProgramMatchCriteria::NoCriteria, 1)), + ]; + let mut extracted = LoadedProgramsForTxBatch::new(19, cache.environments.clone()); + cache.extract(&TestWorkingSlot(19), &mut missing, &mut extracted, true); + + assert!(match_slot(&extracted, &program1, 0, 19)); + assert!(match_slot(&extracted, &program2, 11, 19)); // Program4 deployed at slot 19 should not be expired yet - assert!(match_slot(&found, &program4, 19, 19)); + assert!(match_slot(&extracted, &program4, 19, 19)); - assert!(missing.contains(&(program3, 1))); - assert!(unloaded.is_empty()); + assert!(match_missing(&missing, &program3, false)); // Testing fork 0 - 5 - 11 - 15 - 16 - 19 - 21 - 23 with current slot at 21 // This would cause program4 deployed at slot 19 to be expired. - let working_slot = TestWorkingSlot::new(21, &[0, 5, 11, 15, 16, 18, 19, 21, 23]); - let ExtractedPrograms { - loaded: found, - missing, - unloaded, - } = cache.extract( - &working_slot, - vec![ - (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), - (program2, (LoadedProgramMatchCriteria::NoCriteria, 1)), - (program3, (LoadedProgramMatchCriteria::NoCriteria, 1)), - (program4, (LoadedProgramMatchCriteria::NoCriteria, 1)), - ] - .into_iter(), - ); + let mut missing = vec![ + (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), + (program2, (LoadedProgramMatchCriteria::NoCriteria, 1)), + (program3, (LoadedProgramMatchCriteria::NoCriteria, 1)), + (program4, (LoadedProgramMatchCriteria::NoCriteria, 1)), + ]; + let mut extracted = LoadedProgramsForTxBatch::new(21, cache.environments.clone()); + cache.extract(&TestWorkingSlot(21), &mut missing, &mut extracted, true); - assert!(match_slot(&found, &program1, 0, 21)); - assert!(match_slot(&found, &program2, 11, 21)); + assert!(match_slot(&extracted, &program1, 0, 21)); + assert!(match_slot(&extracted, &program2, 11, 21)); - assert!(missing.contains(&(program3, 1))); - assert!(missing.contains(&(program4, 1))); - assert!(unloaded.is_empty()); + assert!(match_missing(&missing, &program3, false)); + assert!(match_missing(&missing, &program4, false)); // Remove the expired entry to let the rest of the test continue - if let Some(programs) = cache.entries.get_mut(&program4) { - programs.pop(); + if let Some(second_level) = cache.entries.get_mut(&program4) { + second_level.slot_versions.pop(); } - cache.prune(&fork_graph, 5); + cache.prune(5, 0); // Fork graph after pruning // 0 @@ -1778,54 +2000,38 @@ mod tests { // 23 // Testing fork 11 - 15 - 16- 19 - 22 with root at 5 and current slot at 22 - let working_slot = TestWorkingSlot::new(22, &[5, 11, 15, 16, 19, 22, 23]); - let ExtractedPrograms { - loaded: found, - missing, - unloaded, - } = cache.extract( - &working_slot, - vec![ - (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), - (program2, (LoadedProgramMatchCriteria::NoCriteria, 1)), - (program3, (LoadedProgramMatchCriteria::NoCriteria, 1)), - (program4, (LoadedProgramMatchCriteria::NoCriteria, 1)), - ] - .into_iter(), - ); + let mut missing = vec![ + (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), + (program2, (LoadedProgramMatchCriteria::NoCriteria, 1)), + (program3, (LoadedProgramMatchCriteria::NoCriteria, 1)), + (program4, (LoadedProgramMatchCriteria::NoCriteria, 1)), + ]; + let mut extracted = LoadedProgramsForTxBatch::new(21, cache.environments.clone()); + cache.extract(&TestWorkingSlot(21), &mut missing, &mut extracted, true); // Since the fork was pruned, we should not find the entry deployed at slot 20. - assert!(match_slot(&found, &program1, 0, 22)); - assert!(match_slot(&found, &program2, 11, 22)); - assert!(match_slot(&found, &program4, 15, 22)); + assert!(match_slot(&extracted, &program1, 0, 21)); + assert!(match_slot(&extracted, &program2, 11, 21)); + assert!(match_slot(&extracted, &program4, 15, 21)); - assert!(missing.contains(&(program3, 1))); - assert!(unloaded.is_empty()); + assert!(match_missing(&missing, &program3, false)); // Testing fork 0 - 5 - 11 - 25 - 27 with current slot at 27 - let working_slot = TestWorkingSlot::new(27, &[11, 25, 27]); - let ExtractedPrograms { - loaded: found, - missing: _, - unloaded, - } = cache.extract( - &working_slot, - vec![ - (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), - (program2, (LoadedProgramMatchCriteria::NoCriteria, 1)), - (program3, (LoadedProgramMatchCriteria::NoCriteria, 1)), - (program4, (LoadedProgramMatchCriteria::NoCriteria, 1)), - ] - .into_iter(), - ); - - assert!(unloaded.is_empty()); - assert!(match_slot(&found, &program1, 0, 27)); - assert!(match_slot(&found, &program2, 11, 27)); - assert!(match_slot(&found, &program3, 25, 27)); - assert!(match_slot(&found, &program4, 5, 27)); - - cache.prune(&fork_graph, 15); + let mut missing = vec![ + (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), + (program2, (LoadedProgramMatchCriteria::NoCriteria, 1)), + (program3, (LoadedProgramMatchCriteria::NoCriteria, 1)), + (program4, (LoadedProgramMatchCriteria::NoCriteria, 1)), + ]; + let mut extracted = LoadedProgramsForTxBatch::new(27, cache.environments.clone()); + cache.extract(&TestWorkingSlot(27), &mut missing, &mut extracted, true); + + assert!(match_slot(&extracted, &program1, 0, 27)); + assert!(match_slot(&extracted, &program2, 11, 27)); + assert!(match_slot(&extracted, &program3, 25, 27)); + assert!(match_slot(&extracted, &program4, 5, 27)); + + cache.prune(15, 0); // Fork graph after pruning // 0 @@ -1843,34 +2049,26 @@ mod tests { // 23 // Testing fork 16, 19, 23, with root at 15, current slot at 23 - let working_slot = TestWorkingSlot::new(23, &[16, 19, 23]); - let ExtractedPrograms { - loaded: found, - missing, - unloaded, - } = cache.extract( - &working_slot, - vec![ - (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), - (program2, (LoadedProgramMatchCriteria::NoCriteria, 1)), - (program3, (LoadedProgramMatchCriteria::NoCriteria, 1)), - (program4, (LoadedProgramMatchCriteria::NoCriteria, 1)), - ] - .into_iter(), - ); - - assert!(match_slot(&found, &program1, 0, 23)); - assert!(match_slot(&found, &program2, 11, 23)); - assert!(match_slot(&found, &program4, 15, 23)); + let mut missing = vec![ + (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), + (program2, (LoadedProgramMatchCriteria::NoCriteria, 1)), + (program3, (LoadedProgramMatchCriteria::NoCriteria, 1)), + (program4, (LoadedProgramMatchCriteria::NoCriteria, 1)), + ]; + let mut extracted = LoadedProgramsForTxBatch::new(23, cache.environments.clone()); + cache.extract(&TestWorkingSlot(23), &mut missing, &mut extracted, true); + + assert!(match_slot(&extracted, &program1, 0, 23)); + assert!(match_slot(&extracted, &program2, 11, 23)); + assert!(match_slot(&extracted, &program4, 15, 23)); // program3 was deployed on slot 25, which has been pruned - assert!(missing.contains(&(program3, 1))); - assert!(unloaded.is_empty()); + assert!(match_missing(&missing, &program3, false)); } #[test] fn test_extract_using_deployment_slot() { - let mut cache = LoadedPrograms::default(); + let mut cache = new_mock_cache::(); // Fork graph created for the test // 0 @@ -1889,9 +2087,12 @@ mod tests { let mut fork_graph = TestForkGraphSpecific::default(); fork_graph.insert_fork(&[0, 10, 20, 22]); - fork_graph.insert_fork(&[0, 5, 11, 15, 16, 19, 21, 23]); + fork_graph.insert_fork(&[0, 5, 11, 12, 15, 16, 18, 19, 21, 23]); fork_graph.insert_fork(&[0, 5, 11, 25, 27]); + let fork_graph = Arc::new(RwLock::new(fork_graph)); + cache.set_fork_graph(fork_graph); + let program1 = Pubkey::new_unique(); assert!(!cache.replenish(program1, new_test_loaded_program(0, 1)).0); assert!(!cache.replenish(program1, new_test_loaded_program(20, 21)).0); @@ -1904,58 +2105,43 @@ mod tests { assert!(!cache.replenish(program3, new_test_loaded_program(25, 26)).0); // Testing fork 0 - 5 - 11 - 15 - 16 - 19 - 21 - 23 with current slot at 19 - let working_slot = TestWorkingSlot::new(12, &[0, 5, 11, 12, 15, 16, 18, 19, 21, 23]); - let ExtractedPrograms { - loaded: found, - missing, - unloaded, - } = cache.extract( - &working_slot, - vec![ - (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), - (program2, (LoadedProgramMatchCriteria::NoCriteria, 1)), - (program3, (LoadedProgramMatchCriteria::NoCriteria, 1)), - ] - .into_iter(), - ); + let mut missing = vec![ + (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), + (program2, (LoadedProgramMatchCriteria::NoCriteria, 1)), + (program3, (LoadedProgramMatchCriteria::NoCriteria, 1)), + ]; + let mut extracted = LoadedProgramsForTxBatch::new(12, cache.environments.clone()); + cache.extract(&TestWorkingSlot(12), &mut missing, &mut extracted, true); - assert!(match_slot(&found, &program1, 0, 12)); - assert!(match_slot(&found, &program2, 11, 12)); + assert!(match_slot(&extracted, &program1, 0, 12)); + assert!(match_slot(&extracted, &program2, 11, 12)); - assert!(missing.contains(&(program3, 1))); - assert!(unloaded.is_empty()); + assert!(match_missing(&missing, &program3, false)); // Test the same fork, but request the program modified at a later slot than what's in the cache. - let ExtractedPrograms { - loaded: found, - missing, - unloaded, - } = cache.extract( - &working_slot, - vec![ - ( - program1, - (LoadedProgramMatchCriteria::DeployedOnOrAfterSlot(5), 1), - ), - ( - program2, - (LoadedProgramMatchCriteria::DeployedOnOrAfterSlot(5), 1), - ), - (program3, (LoadedProgramMatchCriteria::NoCriteria, 1)), - ] - .into_iter(), - ); + let mut missing = vec![ + ( + program1, + (LoadedProgramMatchCriteria::DeployedOnOrAfterSlot(5), 1), + ), + ( + program2, + (LoadedProgramMatchCriteria::DeployedOnOrAfterSlot(5), 1), + ), + (program3, (LoadedProgramMatchCriteria::NoCriteria, 1)), + ]; + let mut extracted = LoadedProgramsForTxBatch::new(12, cache.environments.clone()); + cache.extract(&TestWorkingSlot(12), &mut missing, &mut extracted, true); - assert!(match_slot(&found, &program2, 11, 12)); + assert!(match_slot(&extracted, &program2, 11, 12)); - assert!(missing.contains(&(program1, 1))); - assert!(missing.contains(&(program3, 1))); - assert!(unloaded.is_empty()); + assert!(match_missing(&missing, &program1, false)); + assert!(match_missing(&missing, &program3, false)); } #[test] fn test_extract_unloaded() { - let mut cache = LoadedPrograms::default(); + let mut cache = new_mock_cache::(); // Fork graph created for the test // 0 @@ -1977,6 +2163,9 @@ mod tests { fork_graph.insert_fork(&[0, 5, 11, 15, 16, 19, 21, 23]); fork_graph.insert_fork(&[0, 5, 11, 25, 27]); + let fork_graph = Arc::new(RwLock::new(fork_graph)); + cache.set_fork_graph(fork_graph); + let program1 = Pubkey::new_unique(); assert!(!cache.replenish(program1, new_test_loaded_program(0, 1)).0); assert!(!cache.replenish(program1, new_test_loaded_program(20, 21)).0); @@ -2006,75 +2195,51 @@ mod tests { ); // Testing fork 0 - 5 - 11 - 15 - 16 - 19 - 21 - 23 with current slot at 19 - let working_slot = TestWorkingSlot::new(19, &[0, 5, 11, 12, 15, 16, 18, 19, 21, 23]); - let ExtractedPrograms { - loaded: found, - missing, - unloaded, - } = cache.extract( - &working_slot, - vec![ - (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), - (program2, (LoadedProgramMatchCriteria::NoCriteria, 1)), - (program3, (LoadedProgramMatchCriteria::NoCriteria, 1)), - ] - .into_iter(), - ); + let mut missing = vec![ + (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), + (program2, (LoadedProgramMatchCriteria::NoCriteria, 1)), + (program3, (LoadedProgramMatchCriteria::NoCriteria, 1)), + ]; + let mut extracted = LoadedProgramsForTxBatch::new(19, cache.environments.clone()); + cache.extract(&TestWorkingSlot(19), &mut missing, &mut extracted, true); - assert!(match_slot(&found, &program1, 0, 19)); - assert!(match_slot(&found, &program2, 11, 19)); + assert!(match_slot(&extracted, &program1, 0, 19)); + assert!(match_slot(&extracted, &program2, 11, 19)); - assert!(missing.contains(&(program3, 1))); - assert!(unloaded.is_empty()); + assert!(match_missing(&missing, &program3, false)); // Testing fork 0 - 5 - 11 - 25 - 27 with current slot at 27 - let working_slot = TestWorkingSlot::new(27, &[0, 5, 11, 25, 27]); - let ExtractedPrograms { - loaded: found, - missing, - unloaded, - } = cache.extract( - &working_slot, - vec![ - (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), - (program2, (LoadedProgramMatchCriteria::NoCriteria, 1)), - (program3, (LoadedProgramMatchCriteria::NoCriteria, 1)), - ] - .into_iter(), - ); + let mut missing = vec![ + (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), + (program2, (LoadedProgramMatchCriteria::NoCriteria, 1)), + (program3, (LoadedProgramMatchCriteria::NoCriteria, 1)), + ]; + let mut extracted = LoadedProgramsForTxBatch::new(27, cache.environments.clone()); + cache.extract(&TestWorkingSlot(27), &mut missing, &mut extracted, true); - assert!(match_slot(&found, &program1, 0, 27)); - assert!(match_slot(&found, &program2, 11, 27)); + assert!(match_slot(&extracted, &program1, 0, 27)); + assert!(match_slot(&extracted, &program2, 11, 27)); - assert!(unloaded.contains(&(program3, 1))); - assert!(missing.is_empty()); + assert!(match_missing(&missing, &program3, true)); // Testing fork 0 - 10 - 20 - 22 with current slot at 22 - let working_slot = TestWorkingSlot::new(22, &[0, 10, 20, 22]); - let ExtractedPrograms { - loaded: found, - missing, - unloaded, - } = cache.extract( - &working_slot, - vec![ - (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), - (program2, (LoadedProgramMatchCriteria::NoCriteria, 1)), - (program3, (LoadedProgramMatchCriteria::NoCriteria, 1)), - ] - .into_iter(), - ); + let mut missing = vec![ + (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), + (program2, (LoadedProgramMatchCriteria::NoCriteria, 1)), + (program3, (LoadedProgramMatchCriteria::NoCriteria, 1)), + ]; + let mut extracted = LoadedProgramsForTxBatch::new(22, cache.environments.clone()); + cache.extract(&TestWorkingSlot(22), &mut missing, &mut extracted, true); - assert!(match_slot(&found, &program1, 20, 22)); + assert!(match_slot(&extracted, &program1, 20, 22)); - assert!(missing.contains(&(program2, 1))); - assert!(missing.contains(&(program3, 1))); - assert!(unloaded.is_empty()); + assert!(match_missing(&missing, &program2, false)); + assert!(match_missing(&missing, &program3, true)); } #[test] fn test_prune_expired() { - let mut cache = LoadedPrograms::default(); + let mut cache = new_mock_cache::(); // Fork graph created for the test // 0 @@ -2093,8 +2258,10 @@ mod tests { let mut fork_graph = TestForkGraphSpecific::default(); fork_graph.insert_fork(&[0, 10, 20, 22]); - fork_graph.insert_fork(&[0, 5, 11, 15, 16, 19, 21, 23]); + fork_graph.insert_fork(&[0, 5, 11, 12, 15, 16, 18, 19, 21, 23]); fork_graph.insert_fork(&[0, 5, 11, 25, 27]); + let fork_graph = Arc::new(RwLock::new(fork_graph)); + cache.set_fork_graph(fork_graph); let program1 = Pubkey::new_unique(); assert!(!cache.replenish(program1, new_test_loaded_program(10, 11)).0); @@ -2120,50 +2287,34 @@ mod tests { assert!(!cache.replenish(program1, test_program).0); // Testing fork 0 - 5 - 11 - 15 - 16 - 19 - 21 - 23 with current slot at 19 - let working_slot = TestWorkingSlot::new(12, &[0, 5, 11, 12, 15, 16, 18, 19, 21, 23]); - let ExtractedPrograms { - loaded: found, - missing, - unloaded, - } = cache.extract( - &working_slot, - vec![ - (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), - (program2, (LoadedProgramMatchCriteria::NoCriteria, 1)), - (program3, (LoadedProgramMatchCriteria::NoCriteria, 1)), - ] - .into_iter(), - ); + let mut missing = vec![ + (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), + (program2, (LoadedProgramMatchCriteria::NoCriteria, 1)), + (program3, (LoadedProgramMatchCriteria::NoCriteria, 1)), + ]; + let mut extracted = LoadedProgramsForTxBatch::new(12, cache.environments.clone()); + cache.extract(&TestWorkingSlot(12), &mut missing, &mut extracted, true); // Program1 deployed at slot 11 should not be expired yet - assert!(match_slot(&found, &program1, 11, 12)); - assert!(match_slot(&found, &program2, 11, 12)); + assert!(match_slot(&extracted, &program1, 11, 12)); + assert!(match_slot(&extracted, &program2, 11, 12)); - assert!(missing.contains(&(program3, 1))); - assert!(unloaded.is_empty()); + assert!(match_missing(&missing, &program3, false)); // Testing fork 0 - 5 - 11 - 12 - 15 - 16 - 19 - 21 - 23 with current slot at 15 // This would cause program4 deployed at slot 15 to be expired. - let working_slot = TestWorkingSlot::new(15, &[0, 5, 11, 15, 16, 18, 19, 21, 23]); - let ExtractedPrograms { - loaded: found, - missing, - unloaded, - } = cache.extract( - &working_slot, - vec![ - (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), - (program2, (LoadedProgramMatchCriteria::NoCriteria, 1)), - (program3, (LoadedProgramMatchCriteria::NoCriteria, 1)), - ] - .into_iter(), - ); - assert!(unloaded.is_empty()); + let mut missing = vec![ + (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), + (program2, (LoadedProgramMatchCriteria::NoCriteria, 1)), + (program3, (LoadedProgramMatchCriteria::NoCriteria, 1)), + ]; + let mut extracted = LoadedProgramsForTxBatch::new(15, cache.environments.clone()); + cache.extract(&TestWorkingSlot(15), &mut missing, &mut extracted, true); - assert!(match_slot(&found, &program2, 11, 15)); + assert!(match_slot(&extracted, &program2, 11, 15)); - assert!(missing.contains(&(program1, 1))); - assert!(missing.contains(&(program3, 1))); + assert!(match_missing(&missing, &program1, false)); + assert!(match_missing(&missing, &program3, false)); // Test that the program still exists in the cache, even though it is expired. assert_eq!( @@ -2171,29 +2322,34 @@ mod tests { .entries .get(&program1) .expect("Didn't find program1") + .slot_versions .len(), 3 ); // New root 5 should not evict the expired entry for program1 - cache.prune(&fork_graph, 5); + cache.prune(5, 0); assert_eq!( cache .entries .get(&program1) .expect("Didn't find program1") + .slot_versions .len(), 1 ); + // Unlock the cooperative loading lock so that the subsequent prune can do its job + cache.finish_cooperative_loading_task(15, program1, new_test_loaded_program(0, 1)); + // New root 15 should evict the expired entry for program1 - cache.prune(&fork_graph, 15); + cache.prune(15, 0); assert!(cache.entries.get(&program1).is_none()); } #[test] fn test_fork_prune_find_first_ancestor() { - let mut cache = LoadedPrograms::default(); + let mut cache = new_mock_cache::(); // Fork graph created for the test // 0 @@ -2208,29 +2364,23 @@ mod tests { let mut fork_graph = TestForkGraphSpecific::default(); fork_graph.insert_fork(&[0, 10, 20]); fork_graph.insert_fork(&[0, 5]); + let fork_graph = Arc::new(RwLock::new(fork_graph)); + cache.set_fork_graph(fork_graph); let program1 = Pubkey::new_unique(); assert!(!cache.replenish(program1, new_test_loaded_program(0, 1)).0); assert!(!cache.replenish(program1, new_test_loaded_program(5, 6)).0); - cache.prune(&fork_graph, 10); + cache.prune(10, 0); - let working_slot = TestWorkingSlot::new(20, &[0, 10, 20]); - let ExtractedPrograms { - loaded: found, - missing: _, - unloaded, - } = cache.extract( - &working_slot, - vec![(program1, (LoadedProgramMatchCriteria::NoCriteria, 1))].into_iter(), - ); - assert!(unloaded.is_empty()); + let mut missing = vec![(program1, (LoadedProgramMatchCriteria::NoCriteria, 1))]; + let mut extracted = LoadedProgramsForTxBatch::new(20, cache.environments.clone()); + cache.extract(&TestWorkingSlot(20), &mut missing, &mut extracted, true); // The cache should have the program deployed at slot 0 assert_eq!( - found - .entries - .get(&program1) + extracted + .find(&program1) .expect("Did not find the program") .deployment_slot, 0 @@ -2239,7 +2389,7 @@ mod tests { #[test] fn test_prune_by_deployment_slot() { - let mut cache = LoadedPrograms::default(); + let mut cache = new_mock_cache::(); // Fork graph created for the test // 0 @@ -2253,7 +2403,9 @@ mod tests { // deployed at slot 0. let mut fork_graph = TestForkGraphSpecific::default(); fork_graph.insert_fork(&[0, 10, 20]); - fork_graph.insert_fork(&[0, 5]); + fork_graph.insert_fork(&[0, 5, 6]); + let fork_graph = Arc::new(RwLock::new(fork_graph)); + cache.set_fork_graph(fork_graph); let program1 = Pubkey::new_unique(); assert!(!cache.replenish(program1, new_test_loaded_program(0, 1)).0); @@ -2262,129 +2414,95 @@ mod tests { let program2 = Pubkey::new_unique(); assert!(!cache.replenish(program2, new_test_loaded_program(10, 11)).0); - let working_slot = TestWorkingSlot::new(20, &[0, 10, 20]); - let ExtractedPrograms { - loaded: found, - missing: _, - unloaded: _, - } = cache.extract( - &working_slot, - vec![ - (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), - (program2, (LoadedProgramMatchCriteria::NoCriteria, 1)), - ] - .into_iter(), - ); + let mut missing = vec![ + (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), + (program2, (LoadedProgramMatchCriteria::NoCriteria, 1)), + ]; + let mut extracted = LoadedProgramsForTxBatch::new(20, cache.environments.clone()); + cache.extract(&TestWorkingSlot(20), &mut missing, &mut extracted, true); - assert!(match_slot(&found, &program1, 0, 20)); - assert!(match_slot(&found, &program2, 10, 20)); - - let working_slot = TestWorkingSlot::new(6, &[0, 5, 6]); - let ExtractedPrograms { - loaded: found, - missing, - unloaded: _, - } = cache.extract( - &working_slot, - vec![ - (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), - (program2, (LoadedProgramMatchCriteria::NoCriteria, 1)), - ] - .into_iter(), - ); + assert!(match_slot(&extracted, &program1, 0, 20)); + assert!(match_slot(&extracted, &program2, 10, 20)); - assert!(match_slot(&found, &program1, 5, 6)); - assert!(missing.contains(&(program2, 1))); + let mut missing = vec![ + (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), + (program2, (LoadedProgramMatchCriteria::NoCriteria, 1)), + ]; + let mut extracted = LoadedProgramsForTxBatch::new(6, cache.environments.clone()); + cache.extract(&TestWorkingSlot(6), &mut missing, &mut extracted, true); + + assert!(match_slot(&extracted, &program1, 5, 6)); + assert!(match_missing(&missing, &program2, false)); // Pruning slot 5 will remove program1 entry deployed at slot 5. // On fork chaining from slot 5, the entry deployed at slot 0 will become visible. cache.prune_by_deployment_slot(5); - let working_slot = TestWorkingSlot::new(20, &[0, 10, 20]); - let ExtractedPrograms { - loaded: found, - missing: _, - unloaded: _, - } = cache.extract( - &working_slot, - vec![ - (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), - (program2, (LoadedProgramMatchCriteria::NoCriteria, 1)), - ] - .into_iter(), - ); + let mut missing = vec![ + (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), + (program2, (LoadedProgramMatchCriteria::NoCriteria, 1)), + ]; + let mut extracted = LoadedProgramsForTxBatch::new(20, cache.environments.clone()); + cache.extract(&TestWorkingSlot(20), &mut missing, &mut extracted, true); - assert!(match_slot(&found, &program1, 0, 20)); - assert!(match_slot(&found, &program2, 10, 20)); - - let working_slot = TestWorkingSlot::new(6, &[0, 5, 6]); - let ExtractedPrograms { - loaded: found, - missing, - unloaded: _, - } = cache.extract( - &working_slot, - vec![ - (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), - (program2, (LoadedProgramMatchCriteria::NoCriteria, 1)), - ] - .into_iter(), - ); + assert!(match_slot(&extracted, &program1, 0, 20)); + assert!(match_slot(&extracted, &program2, 10, 20)); - assert!(match_slot(&found, &program1, 0, 6)); - assert!(missing.contains(&(program2, 1))); + let mut missing = vec![ + (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), + (program2, (LoadedProgramMatchCriteria::NoCriteria, 1)), + ]; + let mut extracted = LoadedProgramsForTxBatch::new(6, cache.environments.clone()); + cache.extract(&TestWorkingSlot(6), &mut missing, &mut extracted, true); + + assert!(match_slot(&extracted, &program1, 0, 6)); + assert!(match_missing(&missing, &program2, false)); // Pruning slot 10 will remove program2 entry deployed at slot 10. // As there is no other entry for program2, extract() will return it as missing. cache.prune_by_deployment_slot(10); - let working_slot = TestWorkingSlot::new(20, &[0, 10, 20]); - let ExtractedPrograms { - loaded: found, - missing: _, - unloaded: _, - } = cache.extract( - &working_slot, - vec![ - (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), - (program2, (LoadedProgramMatchCriteria::NoCriteria, 1)), - ] - .into_iter(), - ); + let mut missing = vec![ + (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), + (program2, (LoadedProgramMatchCriteria::NoCriteria, 1)), + ]; + let mut extracted = LoadedProgramsForTxBatch::new(20, cache.environments.clone()); + cache.extract(&TestWorkingSlot(20), &mut missing, &mut extracted, true); - assert!(match_slot(&found, &program1, 0, 20)); - assert!(missing.contains(&(program2, 1))); + assert!(match_slot(&extracted, &program1, 0, 20)); + assert!(match_missing(&missing, &program2, false)); } #[test] fn test_usable_entries_for_slot() { + new_mock_cache::(); let tombstone = Arc::new(LoadedProgram::new_tombstone(0, LoadedProgramType::Closed)); - assert!(LoadedPrograms::is_entry_usable( + assert!(LoadedPrograms::::is_entry_usable( &tombstone, 0, &LoadedProgramMatchCriteria::NoCriteria )); - assert!(LoadedPrograms::is_entry_usable( + assert!(LoadedPrograms::::is_entry_usable( &tombstone, 1, &LoadedProgramMatchCriteria::Tombstone )); - assert!(LoadedPrograms::is_entry_usable( + assert!(LoadedPrograms::::is_entry_usable( &tombstone, 1, &LoadedProgramMatchCriteria::NoCriteria )); - assert!(LoadedPrograms::is_entry_usable( + assert!(LoadedPrograms::::is_entry_usable( &tombstone, 1, &LoadedProgramMatchCriteria::DeployedOnOrAfterSlot(0) )); - assert!(!LoadedPrograms::is_entry_usable( + assert!(!LoadedPrograms::::is_entry_usable( &tombstone, 1, &LoadedProgramMatchCriteria::DeployedOnOrAfterSlot(1) @@ -2392,31 +2510,31 @@ mod tests { let program = new_test_loaded_program(0, 1); - assert!(LoadedPrograms::is_entry_usable( + assert!(LoadedPrograms::::is_entry_usable( &program, 0, &LoadedProgramMatchCriteria::NoCriteria )); - assert!(!LoadedPrograms::is_entry_usable( + assert!(!LoadedPrograms::::is_entry_usable( &program, 1, &LoadedProgramMatchCriteria::Tombstone )); - assert!(LoadedPrograms::is_entry_usable( + assert!(LoadedPrograms::::is_entry_usable( &program, 1, &LoadedProgramMatchCriteria::NoCriteria )); - assert!(LoadedPrograms::is_entry_usable( + assert!(LoadedPrograms::::is_entry_usable( &program, 1, &LoadedProgramMatchCriteria::DeployedOnOrAfterSlot(0) )); - assert!(!LoadedPrograms::is_entry_usable( + assert!(!LoadedPrograms::::is_entry_usable( &program, 1, &LoadedProgramMatchCriteria::DeployedOnOrAfterSlot(1) @@ -2429,37 +2547,37 @@ mod tests { Some(2), )); - assert!(LoadedPrograms::is_entry_usable( + assert!(LoadedPrograms::::is_entry_usable( &program, 0, &LoadedProgramMatchCriteria::NoCriteria )); - assert!(LoadedPrograms::is_entry_usable( + assert!(LoadedPrograms::::is_entry_usable( &program, 1, &LoadedProgramMatchCriteria::NoCriteria )); - assert!(!LoadedPrograms::is_entry_usable( + assert!(!LoadedPrograms::::is_entry_usable( &program, 1, &LoadedProgramMatchCriteria::Tombstone )); - assert!(!LoadedPrograms::is_entry_usable( + assert!(!LoadedPrograms::::is_entry_usable( &program, 2, &LoadedProgramMatchCriteria::NoCriteria )); - assert!(LoadedPrograms::is_entry_usable( + assert!(LoadedPrograms::::is_entry_usable( &program, 1, &LoadedProgramMatchCriteria::DeployedOnOrAfterSlot(0) )); - assert!(!LoadedPrograms::is_entry_usable( + assert!(!LoadedPrograms::::is_entry_usable( &program, 1, &LoadedProgramMatchCriteria::DeployedOnOrAfterSlot(1) diff --git a/program-runtime/src/message_processor.rs b/program-runtime/src/message_processor.rs index 80bfaf16e97..77735f7f50b 100644 --- a/program-runtime/src/message_processor.rs +++ b/program-runtime/src/message_processor.rs @@ -222,7 +222,7 @@ mod tests { ChangeData { data: u8 }, } - declare_process_instruction!(process_instruction, 1, |invoke_context| { + declare_process_instruction!(MockBuiltin, 1, |invoke_context| { let transaction_context = &invoke_context.transaction_context; let instruction_context = transaction_context.get_current_instruction_context()?; let instruction_data = instruction_context.get_instruction_data(); @@ -274,7 +274,7 @@ mod tests { let mut programs_loaded_for_tx_batch = LoadedProgramsForTxBatch::default(); programs_loaded_for_tx_batch.replenish( mock_system_program_id, - Arc::new(LoadedProgram::new_builtin(0, 0, process_instruction)), + Arc::new(LoadedProgram::new_builtin(0, 0, MockBuiltin::vm)), ); let account_keys = (0..transaction_context.get_number_of_accounts()) .map(|index| { @@ -438,7 +438,7 @@ mod tests { DoWork { lamports: u64, data: u8 }, } - declare_process_instruction!(process_instruction, 1, |invoke_context| { + declare_process_instruction!(MockBuiltin, 1, |invoke_context| { let transaction_context = &invoke_context.transaction_context; let instruction_context = transaction_context.get_current_instruction_context()?; let instruction_data = instruction_context.get_instruction_data(); @@ -507,7 +507,7 @@ mod tests { let mut programs_loaded_for_tx_batch = LoadedProgramsForTxBatch::default(); programs_loaded_for_tx_batch.replenish( mock_program_id, - Arc::new(LoadedProgram::new_builtin(0, 0, process_instruction)), + Arc::new(LoadedProgram::new_builtin(0, 0, MockBuiltin::vm)), ); let account_metas = vec![ AccountMeta::new( @@ -655,7 +655,7 @@ mod tests { #[test] fn test_precompile() { let mock_program_id = Pubkey::new_unique(); - declare_process_instruction!(process_instruction, 1, |_invoke_context| { + declare_process_instruction!(MockBuiltin, 1, |_invoke_context| { Err(InstructionError::Custom(0xbabb1e)) }); @@ -695,7 +695,7 @@ mod tests { let mut programs_loaded_for_tx_batch = LoadedProgramsForTxBatch::default(); programs_loaded_for_tx_batch.replenish( mock_program_id, - Arc::new(LoadedProgram::new_builtin(0, 0, process_instruction)), + Arc::new(LoadedProgram::new_builtin(0, 0, MockBuiltin::vm)), ); let mut programs_modified_by_tx = LoadedProgramsForTxBatch::default(); let mut programs_updated_only_for_global_cache = LoadedProgramsForTxBatch::default(); diff --git a/program-runtime/src/stable_log.rs b/program-runtime/src/stable_log.rs index 9ba7542e9c0..748c4d76392 100644 --- a/program-runtime/src/stable_log.rs +++ b/program-runtime/src/stable_log.rs @@ -101,10 +101,10 @@ pub fn program_success(log_collector: &Option>>, progra /// ```notrust /// "Program

failed: " /// ``` -pub fn program_failure( +pub fn program_failure( log_collector: &Option>>, program_id: &Pubkey, - err: &dyn std::error::Error, + err: &E, ) { ic_logger_msg!(log_collector, "Program {} failed: {}", program_id, err); } diff --git a/program-runtime/src/timings.rs b/program-runtime/src/timings.rs index 0e2e4956a55..8eeb9c5a005 100644 --- a/program-runtime/src/timings.rs +++ b/program-runtime/src/timings.rs @@ -300,13 +300,6 @@ impl ThreadExecuteTimings { } pub fn accumulate(&mut self, other: &ThreadExecuteTimings) { - self.execute_timings.saturating_add_in_place( - ExecuteTimingType::TotalBatchesLen, - *other - .execute_timings - .metrics - .index(ExecuteTimingType::TotalBatchesLen), - ); self.execute_timings.accumulate(&other.execute_timings); saturating_add_assign!(self.total_thread_us, other.total_thread_us); saturating_add_assign!( diff --git a/program-test/Cargo.toml b/program-test/Cargo.toml index 87a9c88487a..c4ab4507b27 100644 --- a/program-test/Cargo.toml +++ b/program-test/Cargo.toml @@ -27,6 +27,7 @@ solana-program-runtime = { workspace = true } solana-runtime = { workspace = true } solana-sdk = { workspace = true } solana-vote-program = { workspace = true } +solana_rbpf = { workspace = true } test-case = { workspace = true } thiserror = { workspace = true } tokio = { workspace = true, features = ["full"] } diff --git a/program-test/src/lib.rs b/program-test/src/lib.rs index 4cc8fc9ba21..c3252d05e40 100644 --- a/program-test/src/lib.rs +++ b/program-test/src/lib.rs @@ -13,7 +13,7 @@ use { solana_banks_server::banks_server::start_local_server, solana_bpf_loader_program::serialization::serialize_parameters, solana_program_runtime::{ - compute_budget::ComputeBudget, ic_msg, invoke_context::ProcessInstructionWithContext, + compute_budget::ComputeBudget, ic_msg, invoke_context::BuiltinFunctionWithContext, loaded_programs::LoadedProgram, stable_log, timings::ExecuteTimings, }, solana_runtime::{ @@ -66,6 +66,10 @@ pub use { solana_banks_client::{BanksClient, BanksClientError}, solana_banks_interface::BanksTransactionResultWithMetadata, solana_program_runtime::invoke_context::InvokeContext, + solana_rbpf::{ + error::EbpfError, + vm::{get_runtime_environment_key, EbpfVm}, + }, solana_sdk::transaction_context::IndexOfAccount, }; @@ -94,10 +98,10 @@ fn get_invoke_context<'a, 'b>() -> &'a mut InvokeContext<'b> { unsafe { transmute::(ptr) } } -pub fn builtin_process_instruction( - process_instruction: solana_sdk::entrypoint::ProcessInstruction, +pub fn invoke_builtin_function( + builtin_function: solana_sdk::entrypoint::ProcessInstruction, invoke_context: &mut InvokeContext, -) -> Result<(), Box> { +) -> Result> { set_invoke_context(invoke_context); let transaction_context = &invoke_context.transaction_context; @@ -131,9 +135,10 @@ pub fn builtin_process_instruction( unsafe { deserialize(&mut parameter_bytes.as_slice_mut()[0] as *mut u8) }; // Execute the program - process_instruction(program_id, &account_infos, instruction_data).map_err(|err| { - let err: Box = Box::new(InstructionError::from(u64::from(err))); - stable_log::program_failure(&log_collector, program_id, err.as_ref()); + builtin_function(program_id, &account_infos, instruction_data).map_err(|err| { + let err = InstructionError::from(u64::from(err)); + stable_log::program_failure(&log_collector, program_id, &err); + let err: Box = Box::new(err); err })?; stable_log::program_success(&log_collector, program_id); @@ -170,21 +175,24 @@ pub fn builtin_process_instruction( } } - Ok(()) + Ok(0) } /// Converts a `solana-program`-style entrypoint into the runtime's entrypoint style, for /// use with `ProgramTest::add_program` #[macro_export] macro_rules! processor { - ($process_instruction:expr) => { - Some( - |invoke_context, _arg0, _arg1, _arg2, _arg3, _arg4, _memory_mapping, result| { - *result = $crate::builtin_process_instruction($process_instruction, invoke_context) - .map(|_| 0) + ($builtin_function:expr) => { + Some(|vm, _arg0, _arg1, _arg2, _arg3, _arg4| { + let vm = unsafe { + &mut *((vm as *mut u64).offset(-($crate::get_runtime_environment_key() as isize)) + as *mut $crate::EbpfVm<$crate::InvokeContext>) + }; + vm.program_result = + $crate::invoke_builtin_function($builtin_function, vm.context_object_pointer) + .map_err(|err| $crate::EbpfError::SyscallError(err)) .into(); - }, - ) + }) }; } @@ -507,10 +515,10 @@ impl ProgramTest { pub fn new( program_name: &str, program_id: Pubkey, - process_instruction: Option, + builtin_function: Option, ) -> Self { let mut me = Self::default(); - me.add_program(program_name, program_id, process_instruction); + me.add_program(program_name, program_id, builtin_function); me } @@ -601,13 +609,13 @@ impl ProgramTest { /// `program_name` will also be used to locate the SBF shared object in the current or fixtures /// directory. /// - /// If `process_instruction` is provided, the natively built-program may be used instead of the + /// If `builtin_function` is provided, the natively built-program may be used instead of the /// SBF shared object depending on the `BPF_OUT_DIR` environment variable. pub fn add_program( &mut self, program_name: &str, program_id: Pubkey, - process_instruction: Option, + builtin_function: Option, ) { let add_bpf = |this: &mut ProgramTest, program_file: PathBuf| { let data = read_file(&program_file); @@ -681,7 +689,7 @@ impl ProgramTest { }; let program_file = find_file(&format!("{program_name}.so")); - match (self.prefer_bpf, program_file, process_instruction) { + match (self.prefer_bpf, program_file, builtin_function) { // If SBF is preferred (i.e., `test-sbf` is invoked) and a BPF shared object exists, // use that as the program data. (true, Some(file), _) => add_bpf(self, file), @@ -690,8 +698,8 @@ impl ProgramTest { // processor function as is. // // TODO: figure out why tests hang if a processor panics when running native code. - (false, _, Some(process)) => { - self.add_builtin_program(program_name, program_id, process) + (false, _, Some(builtin_function)) => { + self.add_builtin_program(program_name, program_id, builtin_function) } // Invalid: `test-sbf` invocation with no matching SBF shared object. @@ -714,13 +722,13 @@ impl ProgramTest { &mut self, program_name: &str, program_id: Pubkey, - process_instruction: ProcessInstructionWithContext, + builtin_function: BuiltinFunctionWithContext, ) { info!("\"{}\" builtin program", program_name); self.builtin_programs.push(( program_id, program_name.to_string(), - LoadedProgram::new_builtin(0, program_name.len(), process_instruction), + LoadedProgram::new_builtin(0, program_name.len(), builtin_function), )); } @@ -839,7 +847,7 @@ impl ProgramTest { }; let slot = bank.slot(); let last_blockhash = bank.last_blockhash(); - let bank_forks = Arc::new(RwLock::new(BankForks::new(bank))); + let bank_forks = BankForks::new_rw_arc(bank); let block_commitment_cache = Arc::new(RwLock::new( BlockCommitmentCache::new_for_tests_with_slots(slot, slot), )); diff --git a/program-test/src/programs.rs b/program-test/src/programs.rs index ed96be7644f..8d9a42790f7 100644 --- a/program-test/src/programs.rs +++ b/program-test/src/programs.rs @@ -30,7 +30,7 @@ static SPL_PROGRAMS: &[(Pubkey, Pubkey, &[u8])] = &[ ( spl_token_2022::ID, solana_sdk::bpf_loader_upgradeable::ID, - include_bytes!("programs/spl_token_2022-0.9.0.so"), + include_bytes!("programs/spl_token_2022-1.0.0.so"), ), ( spl_memo_1_0::ID, diff --git a/program-test/src/programs/spl_token_2022-0.9.0.so b/program-test/src/programs/spl_token_2022-0.9.0.so deleted file mode 100644 index 805d3ed1a41..00000000000 Binary files a/program-test/src/programs/spl_token_2022-0.9.0.so and /dev/null differ diff --git a/program-test/src/programs/spl_token_2022-1.0.0.so b/program-test/src/programs/spl_token_2022-1.0.0.so new file mode 100755 index 00000000000..796fafc4cc1 Binary files /dev/null and b/program-test/src/programs/spl_token_2022-1.0.0.so differ diff --git a/programs/address-lookup-table-tests/tests/common.rs b/programs/address-lookup-table-tests/tests/common.rs index 48b80199312..064244858cd 100644 --- a/programs/address-lookup-table-tests/tests/common.rs +++ b/programs/address-lookup-table-tests/tests/common.rs @@ -1,6 +1,5 @@ #![allow(dead_code)] use { - solana_address_lookup_table_program::processor::process_instruction, solana_program_test::*, solana_sdk::{ account::AccountSharedData, @@ -20,7 +19,11 @@ use { }; pub async fn setup_test_context() -> ProgramTestContext { - let program_test = ProgramTest::new("", id(), Some(process_instruction)); + let program_test = ProgramTest::new( + "", + id(), + Some(solana_address_lookup_table_program::processor::Entrypoint::vm), + ); program_test.start_with_context().await } diff --git a/programs/address-lookup-table-tests/tests/create_lookup_table_ix.rs b/programs/address-lookup-table-tests/tests/create_lookup_table_ix.rs index 183de53e313..39ff9aea660 100644 --- a/programs/address-lookup-table-tests/tests/create_lookup_table_ix.rs +++ b/programs/address-lookup-table-tests/tests/create_lookup_table_ix.rs @@ -1,7 +1,6 @@ use { assert_matches::assert_matches, common::{assert_ix_error, overwrite_slot_hashes_with_slots, setup_test_context}, - solana_address_lookup_table_program::processor::process_instruction, solana_program_test::*, solana_sdk::{ address_lookup_table::{ @@ -23,7 +22,11 @@ use { mod common; pub async fn setup_test_context_without_authority_feature() -> ProgramTestContext { - let mut program_test = ProgramTest::new("", id(), Some(process_instruction)); + let mut program_test = ProgramTest::new( + "", + id(), + Some(solana_address_lookup_table_program::processor::Entrypoint::vm), + ); program_test.deactivate_feature( feature_set::relax_authority_signer_check_for_lookup_table_creation::id(), ); diff --git a/programs/address-lookup-table/src/lib.rs b/programs/address-lookup-table/src/lib.rs index 11d9b4b0dd3..737ec32c8f6 100644 --- a/programs/address-lookup-table/src/lib.rs +++ b/programs/address-lookup-table/src/lib.rs @@ -2,13 +2,14 @@ #![cfg_attr(RUSTC_WITH_SPECIALIZATION, feature(specialization))] #![cfg_attr(RUSTC_NEEDS_PROC_MACRO_HYGIENE, feature(proc_macro_hygiene))] +#[cfg(not(target_os = "solana"))] pub mod processor; #[deprecated( since = "1.17.0", - note = "Please use `solana_sdk::address_lookup_table` instead" + note = "Please use `solana_program::address_lookup_table` instead" )] -pub use solana_sdk::address_lookup_table::{ +pub use solana_program::address_lookup_table::{ error, instruction, program::{check_id, id, ID}, state, diff --git a/programs/address-lookup-table/src/processor.rs b/programs/address-lookup-table/src/processor.rs index 6f71b293d03..4db568c71a1 100644 --- a/programs/address-lookup-table/src/processor.rs +++ b/programs/address-lookup-table/src/processor.rs @@ -21,29 +21,25 @@ use { pub const DEFAULT_COMPUTE_UNITS: u64 = 750; -declare_process_instruction!( - process_instruction, - DEFAULT_COMPUTE_UNITS, - |invoke_context| { - let transaction_context = &invoke_context.transaction_context; - let instruction_context = transaction_context.get_current_instruction_context()?; - let instruction_data = instruction_context.get_instruction_data(); - match limited_deserialize(instruction_data)? { - ProgramInstruction::CreateLookupTable { - recent_slot, - bump_seed, - } => Processor::create_lookup_table(invoke_context, recent_slot, bump_seed), - ProgramInstruction::FreezeLookupTable => Processor::freeze_lookup_table(invoke_context), - ProgramInstruction::ExtendLookupTable { new_addresses } => { - Processor::extend_lookup_table(invoke_context, new_addresses) - } - ProgramInstruction::DeactivateLookupTable => { - Processor::deactivate_lookup_table(invoke_context) - } - ProgramInstruction::CloseLookupTable => Processor::close_lookup_table(invoke_context), - } +declare_process_instruction!(Entrypoint, DEFAULT_COMPUTE_UNITS, |invoke_context| { + let transaction_context = &invoke_context.transaction_context; + let instruction_context = transaction_context.get_current_instruction_context()?; + let instruction_data = instruction_context.get_instruction_data(); + match limited_deserialize(instruction_data)? { + ProgramInstruction::CreateLookupTable { + recent_slot, + bump_seed, + } => Processor::create_lookup_table(invoke_context, recent_slot, bump_seed), + ProgramInstruction::FreezeLookupTable => Processor::freeze_lookup_table(invoke_context), + ProgramInstruction::ExtendLookupTable { new_addresses } => { + Processor::extend_lookup_table(invoke_context, new_addresses) + } + ProgramInstruction::DeactivateLookupTable => { + Processor::deactivate_lookup_table(invoke_context) + } + ProgramInstruction::CloseLookupTable => Processor::close_lookup_table(invoke_context), } -); +}); fn checked_add(a: usize, b: usize) -> Result { a.checked_add(b).ok_or(InstructionError::ArithmeticOverflow) diff --git a/programs/bpf-loader-tests/tests/common.rs b/programs/bpf-loader-tests/tests/common.rs index eeaf957a7e1..99cae212c7f 100644 --- a/programs/bpf-loader-tests/tests/common.rs +++ b/programs/bpf-loader-tests/tests/common.rs @@ -1,7 +1,6 @@ #![allow(dead_code)] use { - solana_bpf_loader_program::process_instruction, solana_program_test::*, solana_sdk::{ account::AccountSharedData, @@ -15,7 +14,7 @@ use { }; pub async fn setup_test_context() -> ProgramTestContext { - let program_test = ProgramTest::new("", id(), Some(process_instruction)); + let program_test = ProgramTest::new("", id(), Some(solana_bpf_loader_program::Entrypoint::vm)); program_test.start_with_context().await } diff --git a/programs/bpf_loader/Cargo.toml b/programs/bpf_loader/Cargo.toml index 16a52c07928..48d771b8656 100644 --- a/programs/bpf_loader/Cargo.toml +++ b/programs/bpf_loader/Cargo.toml @@ -27,6 +27,7 @@ assert_matches = { workspace = true } memoffset = { workspace = true } rand = { workspace = true } solana-sdk = { workspace = true, features = ["dev-context-only-utils"] } +test-case = { workspace = true } [lib] crate-type = ["lib"] diff --git a/programs/bpf_loader/src/lib.rs b/programs/bpf_loader/src/lib.rs index 82c62374640..25e69ae62db 100644 --- a/programs/bpf_loader/src/lib.rs +++ b/programs/bpf_loader/src/lib.rs @@ -18,12 +18,14 @@ use { }, solana_rbpf::{ aligned_memory::AlignedMemory, + declare_builtin_function, ebpf::{self, HOST_ALIGN, MM_HEAP_START}, elf::Executable, - error::EbpfError, + error::{EbpfError, ProgramResult}, memory_region::{AccessType, MemoryCowCallback, MemoryMapping, MemoryRegion}, + program::BuiltinProgram, verifier::RequisiteVerifier, - vm::{BuiltinProgram, ContextObject, EbpfVm, ProgramResult}, + vm::{ContextObject, EbpfVm}, }, solana_sdk::{ account::WritableAccount, @@ -268,7 +270,7 @@ pub fn create_vm<'a, 'b>( trace_log: Vec::new(), })?; Ok(EbpfVm::new( - program.get_config(), + program.get_loader().clone(), program.get_sbpf_version(), invoke_context, memory_mapping, @@ -320,7 +322,7 @@ macro_rules! create_vm { macro_rules! mock_create_vm { ($vm:ident, $additional_regions:expr, $accounts_metadata:expr, $invoke_context:expr $(,)?) => { let loader = std::sync::Arc::new(BuiltinProgram::new_mock()); - let function_registry = solana_rbpf::elf::FunctionRegistry::default(); + let function_registry = solana_rbpf::program::FunctionRegistry::default(); let executable = solana_rbpf::elf::Executable::::from_text_bytes( &[0x95, 0, 0, 0, 0, 0, 0, 0], loader, @@ -374,20 +376,22 @@ fn create_memory_mapping<'a, 'b, C: ContextObject>( }) } -pub fn process_instruction( - invoke_context: &mut InvokeContext, - _arg0: u64, - _arg1: u64, - _arg2: u64, - _arg3: u64, - _arg4: u64, - _memory_mapping: &mut MemoryMapping, - result: &mut ProgramResult, -) { - *result = process_instruction_inner(invoke_context).into(); -} +declare_builtin_function!( + Entrypoint, + fn rust( + invoke_context: &mut InvokeContext, + _arg0: u64, + _arg1: u64, + _arg2: u64, + _arg3: u64, + _arg4: u64, + _memory_mapping: &mut MemoryMapping, + ) -> Result> { + process_instruction_inner(invoke_context) + } +); -fn process_instruction_inner( +pub fn process_instruction_inner( invoke_context: &mut InvokeContext, ) -> Result> { let log_collector = invoke_context.get_log_collector(); @@ -505,7 +509,11 @@ fn process_instruction_inner( if native_programs_consume_cu { invoke_context.consume_checked(DEFAULT_LOADER_COMPUTE_UNITS)?; } - process_loader_instruction(invoke_context) + ic_logger_msg!( + log_collector, + "BPF loader management instructions are no longer supported" + ); + Err(InstructionError::UnsupportedProgramId) } else if bpf_loader_deprecated::check_id(program_id) { if native_programs_consume_cu { invoke_context.consume_checked(DEPRECATED_LOADER_COMPUTE_UNITS)?; @@ -1468,7 +1476,7 @@ fn common_close_account( Ok(()) } -fn process_loader_instruction(invoke_context: &mut InvokeContext) -> Result<(), InstructionError> { +fn _process_loader_instruction(invoke_context: &mut InvokeContext) -> Result<(), InstructionError> { let transaction_context = &invoke_context.transaction_context; let instruction_context = transaction_context.get_current_instruction_context()?; let instruction_data = instruction_context.get_instruction_data(); @@ -1619,13 +1627,12 @@ fn execute<'a, 'b: 'a>( } ProgramResult::Err(mut error) => { if direct_mapping { - if let Some(EbpfError::AccessViolation( - _pc, + if let EbpfError::AccessViolation( AccessType::Store, address, _size, _section_name, - )) = error.downcast_ref() + ) = error { // If direct_mapping is enabled and a program tries to write to a readonly // region we'll get a memory access violation. Map it to a more specific @@ -1633,7 +1640,7 @@ fn execute<'a, 'b: 'a>( if let Some((instruction_account_index, _)) = account_region_addrs .iter() .enumerate() - .find(|(_, vm_region)| vm_region.contains(address)) + .find(|(_, vm_region)| vm_region.contains(&address)) { let transaction_context = &invoke_context.transaction_context; let instruction_context = @@ -1644,17 +1651,21 @@ fn execute<'a, 'b: 'a>( instruction_account_index as IndexOfAccount, )?; - error = Box::new(if account.is_executable() { + error = EbpfError::SyscallError(Box::new(if account.is_executable() { InstructionError::ExecutableDataModified } else if account.is_writable() { InstructionError::ExternalAccountDataModified } else { InstructionError::ReadonlyDataModified - }) + })); } } } - Err(error) + Err(if let EbpfError::SyscallError(err) = error { + err + } else { + error.into() + }) } _ => Ok(()), } @@ -1802,7 +1813,7 @@ mod tests { transaction_accounts, instruction_accounts, expected_result, - super::process_instruction, + Entrypoint::vm, |invoke_context| { test_utils::load_all_invoked_programs(invoke_context); }, @@ -1822,6 +1833,7 @@ mod tests { program_account } + #[ignore] #[test] fn test_bpf_loader_write() { let loader_id = bpf_loader::id(); @@ -1889,6 +1901,7 @@ mod tests { ); } + #[ignore] #[test] fn test_bpf_loader_finalize() { let loader_id = bpf_loader::id(); @@ -1953,6 +1966,7 @@ mod tests { ); } + #[ignore] #[test] fn test_bpf_loader_invoke_main() { let loader_id = bpf_loader::id(); @@ -2021,7 +2035,7 @@ mod tests { vec![(program_id, program_account.clone())], Vec::new(), Err(InstructionError::ProgramFailedToComplete), - super::process_instruction, + Entrypoint::vm, |invoke_context| { invoke_context.mock_set_remaining(0); test_utils::load_all_invoked_programs(invoke_context); @@ -2567,7 +2581,7 @@ mod tests { transaction_accounts, instruction_accounts, expected_result, - super::process_instruction, + Entrypoint::vm, |_invoke_context| {}, |_invoke_context| {}, ) diff --git a/programs/bpf_loader/src/syscalls/cpi.rs b/programs/bpf_loader/src/syscalls/cpi.rs index 0240ca65b0d..e9c4bd91bee 100644 --- a/programs/bpf_loader/src/syscalls/cpi.rs +++ b/programs/bpf_loader/src/syscalls/cpi.rs @@ -1,6 +1,6 @@ use { super::*, - crate::{declare_syscall, serialization::account_data_region_memory_state}, + crate::serialization::account_data_region_memory_state, scopeguard::defer, solana_program_runtime::invoke_context::SerializedAccountMetadata, solana_rbpf::{ @@ -106,6 +106,7 @@ impl<'a, 'b> CallerAccount<'a, 'b> { fn from_account_info( invoke_context: &InvokeContext, memory_mapping: &'b MemoryMapping<'a>, + is_disable_cpi_setting_executable_and_rent_epoch_active: bool, _vm_addr: u64, account_info: &AccountInfo, account_metadata: &SerializedAccountMetadata, @@ -257,8 +258,16 @@ impl<'a, 'b> CallerAccount<'a, 'b> { vm_data_addr, ref_to_len_in_vm, serialized_len_ptr, - executable: account_info.executable, - rent_epoch: account_info.rent_epoch, + executable: if is_disable_cpi_setting_executable_and_rent_epoch_active { + false + } else { + account_info.executable + }, + rent_epoch: if is_disable_cpi_setting_executable_and_rent_epoch_active { + 0 + } else { + account_info.rent_epoch + }, }) } @@ -266,6 +275,7 @@ impl<'a, 'b> CallerAccount<'a, 'b> { fn from_sol_account_info( invoke_context: &InvokeContext, memory_mapping: &'b MemoryMapping<'a>, + is_disable_cpi_setting_executable_and_rent_epoch_active: bool, vm_addr: u64, account_info: &SolAccountInfo, account_metadata: &SerializedAccountMetadata, @@ -391,8 +401,16 @@ impl<'a, 'b> CallerAccount<'a, 'b> { vm_data_addr: account_info.data_addr, ref_to_len_in_vm, serialized_len_ptr, - executable: account_info.executable, - rent_epoch: account_info.rent_epoch, + executable: if is_disable_cpi_setting_executable_and_rent_epoch_active { + false + } else { + account_info.executable + }, + rent_epoch: if is_disable_cpi_setting_executable_and_rent_epoch_active { + 0 + } else { + account_info.rent_epoch + }, }) } @@ -437,10 +455,10 @@ trait SyscallInvokeSigned { ) -> Result, Error>; } -declare_syscall!( +declare_builtin_function!( /// Cross-program invocation called from Rust SyscallInvokeSignedRust, - fn inner_call( + fn rust( invoke_context: &mut InvokeContext, instruction_addr: u64, account_infos_addr: u64, @@ -475,14 +493,36 @@ impl SyscallInvokeSigned for SyscallInvokeSignedRust { check_instruction_size(ix.accounts.len(), ix.data.len(), invoke_context)?; - let accounts = translate_slice::( + let account_metas = translate_slice::( memory_mapping, ix.accounts.as_ptr() as u64, ix.accounts.len() as u64, invoke_context.get_check_aligned(), invoke_context.get_check_size(), - )? - .to_vec(); + )?; + let accounts = if invoke_context + .feature_set + .is_active(&feature_set::disable_cpi_setting_executable_and_rent_epoch::id()) + { + let mut accounts = Vec::with_capacity(ix.accounts.len()); + #[allow(clippy::needless_range_loop)] + for account_index in 0..ix.accounts.len() { + #[allow(clippy::indexing_slicing)] + let account_meta = &account_metas[account_index]; + if unsafe { + std::ptr::read_volatile(&account_meta.is_signer as *const _ as *const u8) > 1 + || std::ptr::read_volatile( + &account_meta.is_writable as *const _ as *const u8, + ) > 1 + } { + return Err(Box::new(InstructionError::InvalidArgument)); + } + accounts.push(account_meta.clone()); + } + accounts + } else { + account_metas.to_vec() + }; let ix_data_len = ix.data.len() as u64; if invoke_context @@ -649,10 +689,10 @@ struct SolSignerSeedsC { len: u64, } -declare_syscall!( +declare_builtin_function!( /// Cross-program invocation called from C SyscallInvokeSignedC, - fn inner_call( + fn rust( invoke_context: &mut InvokeContext, instruction_addr: u64, account_infos_addr: u64, @@ -695,7 +735,7 @@ impl SyscallInvokeSigned for SyscallInvokeSignedC { ix_c.program_id_addr, invoke_context.get_check_aligned(), )?; - let meta_cs = translate_slice::( + let account_metas = translate_slice::( memory_mapping, ix_c.accounts_addr, ix_c.accounts_len, @@ -724,21 +764,53 @@ impl SyscallInvokeSigned for SyscallInvokeSignedC { invoke_context.get_check_size(), )? .to_vec(); - let accounts = meta_cs - .iter() - .map(|meta_c| { + + let accounts = if invoke_context + .feature_set + .is_active(&feature_set::disable_cpi_setting_executable_and_rent_epoch::id()) + { + let mut accounts = Vec::with_capacity(ix_c.accounts_len as usize); + #[allow(clippy::needless_range_loop)] + for account_index in 0..ix_c.accounts_len as usize { + #[allow(clippy::indexing_slicing)] + let account_meta = &account_metas[account_index]; + if unsafe { + std::ptr::read_volatile(&account_meta.is_signer as *const _ as *const u8) > 1 + || std::ptr::read_volatile( + &account_meta.is_writable as *const _ as *const u8, + ) > 1 + } { + return Err(Box::new(InstructionError::InvalidArgument)); + } let pubkey = translate_type::( memory_mapping, - meta_c.pubkey_addr, + account_meta.pubkey_addr, invoke_context.get_check_aligned(), )?; - Ok(AccountMeta { + accounts.push(AccountMeta { pubkey: *pubkey, - is_signer: meta_c.is_signer, - is_writable: meta_c.is_writable, + is_signer: account_meta.is_signer, + is_writable: account_meta.is_writable, + }); + } + accounts + } else { + account_metas + .iter() + .map(|account_meta| { + let pubkey = translate_type::( + memory_mapping, + account_meta.pubkey_addr, + invoke_context.get_check_aligned(), + )?; + Ok(AccountMeta { + pubkey: *pubkey, + is_signer: account_meta.is_signer, + is_writable: account_meta.is_writable, + }) }) - }) - .collect::, Error>>()?; + .collect::, Error>>()? + }; Ok(StableInstruction { accounts: accounts.into(), @@ -848,17 +920,34 @@ where invoke_context.get_check_size(), )?; check_account_infos(account_infos.len(), invoke_context)?; - let account_info_keys = account_infos - .iter() - .map(|account_info| { - translate_type::( + let account_info_keys = if invoke_context + .feature_set + .is_active(&feature_set::disable_cpi_setting_executable_and_rent_epoch::id()) + { + let mut account_info_keys = Vec::with_capacity(account_infos_len as usize); + #[allow(clippy::needless_range_loop)] + for account_index in 0..account_infos_len as usize { + #[allow(clippy::indexing_slicing)] + let account_info = &account_infos[account_index]; + account_info_keys.push(translate_type::( memory_mapping, key_addr(account_info), invoke_context.get_check_aligned(), - ) - }) - .collect::, Error>>()?; - + )?); + } + account_info_keys + } else { + account_infos + .iter() + .map(|account_info| { + translate_type::( + memory_mapping, + key_addr(account_info), + invoke_context.get_check_aligned(), + ) + }) + .collect::, Error>>()? + }; Ok((account_infos, account_info_keys)) } @@ -879,6 +968,7 @@ where F: Fn( &InvokeContext, &'b MemoryMapping<'a>, + bool, u64, &T, &SerializedAccountMetadata, @@ -887,6 +977,9 @@ where let transaction_context = &invoke_context.transaction_context; let instruction_context = transaction_context.get_current_instruction_context()?; let mut accounts = Vec::with_capacity(instruction_accounts.len().saturating_add(1)); + let is_disable_cpi_setting_executable_and_rent_epoch_active = invoke_context + .feature_set + .is_active(&disable_cpi_setting_executable_and_rent_epoch::id()); let program_account_index = program_indices .last() @@ -943,16 +1036,19 @@ where })?; // build the CallerAccount corresponding to this account. + if caller_account_index >= account_infos.len() { + return Err(Box::new(SyscallError::InvalidLength)); + } + #[allow(clippy::indexing_slicing)] let caller_account = do_translate( invoke_context, memory_mapping, + is_disable_cpi_setting_executable_and_rent_epoch_active, account_infos_addr.saturating_add( caller_account_index.saturating_mul(mem::size_of::()) as u64, ), - account_infos - .get(caller_account_index) - .ok_or(SyscallError::InvalidLength)?, + &account_infos[caller_account_index], serialized_metadata, )?; @@ -1367,21 +1463,28 @@ fn update_caller_account( caller_account.vm_data_addr, caller_account.original_data_len, )? { - // Since each instruction account is directly mapped in a memory region - // with a *fixed* length, upon returning from CPI we must ensure that the - // current capacity is at least the original length (what is mapped in - // memory), so that the account's memory region never points to an - // invalid address. + // Since each instruction account is directly mapped in a memory region with a *fixed* + // length, upon returning from CPI we must ensure that the current capacity is at least + // the original length (what is mapped in memory), so that the account's memory region + // never points to an invalid address. + // + // Note that the capacity can be smaller than the original length only if the account is + // reallocated using the AccountSharedData API directly (deprecated). BorrowedAccount + // and CoW don't trigger this, see BorrowedAccount::make_data_mut. let min_capacity = caller_account.original_data_len; if callee_account.capacity() < min_capacity { - callee_account.reserve(min_capacity.saturating_sub(callee_account.capacity()))?; + callee_account + .reserve(min_capacity.saturating_sub(callee_account.get_data().len()))?; zero_all_mapped_spare_capacity = true; } - // If an account's data pointer has changed - because of CoW, reserve() as called above - // or because of using AccountSharedData directly (deprecated) - we must update the - // corresponding MemoryRegion in the caller's address space. Address spaces are fixed so - // we don't need to update the MemoryRegion's length. + // If an account's data pointer has changed we must update the corresponding + // MemoryRegion in the caller's address space. Address spaces are fixed so we don't need + // to update the MemoryRegion's length. + // + // An account's data pointer can change if the account is reallocated because of CoW, + // because of BorrowedAccount::make_data_mut or by a program that uses the + // AccountSharedData API directly (deprecated). let callee_ptr = callee_account.get_data().as_ptr() as u64; if region.host_addr.get() != callee_ptr { region.host_addr.set(callee_ptr); @@ -1392,7 +1495,6 @@ fn update_caller_account( let prev_len = *caller_account.ref_to_len_in_vm.get()? as usize; let post_len = callee_account.get_data().len(); - let realloc_bytes_used = post_len.saturating_sub(caller_account.original_data_len); if prev_len != post_len { let max_increase = if direct_mapping && !invoke_context.get_check_aligned() { 0 @@ -1416,37 +1518,8 @@ fn update_caller_account( if post_len < prev_len { if direct_mapping { // We have two separate regions to zero out: the account data - // and the realloc region. - // - // Here we zero the account data region. - let spare_len = if zero_all_mapped_spare_capacity { - // In the unlikely case where the account data vector has - // changed - which can happen during CoW - we zero the whole - // extra capacity up to the original data length. - // - // The extra capacity up to original data length is - // accessible from the vm and since it's uninitialized - // memory, it could be a source of non determinism. - caller_account.original_data_len - } else { - // If the allocation has not changed, we only zero the - // difference between the previous and current lengths. The - // rest of the memory contains whatever it contained before, - // which is deterministic. - prev_len - } - .saturating_sub(post_len); - if spare_len > 0 { - let dst = callee_account - .spare_data_capacity_mut()? - .get_mut(..spare_len) - .ok_or_else(|| Box::new(InstructionError::AccountDataTooSmall))? - .as_mut_ptr(); - // Safety: we check bounds above - unsafe { ptr::write_bytes(dst, 0, spare_len) }; - } - - // Here we zero the realloc region. + // and the realloc region. Here we zero the realloc region, the + // data region is zeroed further down below. // // This is done for compatibility but really only necessary for // the fringe case of a program calling itself, see @@ -1539,51 +1612,92 @@ fn update_caller_account( } } } - if !direct_mapping { - let to_slice = &mut caller_account.serialized_data; - let from_slice = callee_account - .get_data() - .get(0..post_len) - .ok_or(SyscallError::InvalidLength)?; - if to_slice.len() != from_slice.len() { - return Err(Box::new(InstructionError::AccountDataTooSmall)); - } - to_slice.copy_from_slice(from_slice); - } else if realloc_bytes_used > 0 { - // In the is_loader_deprecated case, we must have failed with - // InvalidRealloc by now. - debug_assert!(!is_loader_deprecated); - - let to_slice = { - // If a callee reallocs an account, we write into the caller's - // realloc region regardless of whether the caller has write - // permissions to the account or not. If the callee has been able to - // make changes, it means they had permissions to do so, and here - // we're just going to reflect those changes to the caller's frame. + + if direct_mapping { + // Here we zero the account data region. + // + // If zero_all_mapped_spare_capacity=true, we need to zero regardless of whether the account + // size changed, because the underlying vector holding the account might have been + // reallocated and contain uninitialized memory in the spare capacity. + // + // See TEST_CPI_CHANGE_ACCOUNT_DATA_MEMORY_ALLOCATION for an example of + // this case. + let spare_len = if zero_all_mapped_spare_capacity { + // In the unlikely case where the account data vector has + // changed - which can happen during CoW - we zero the whole + // extra capacity up to the original data length. // - // Therefore we temporarily configure the realloc region as writable - // then set it back to whatever state it had. - let realloc_region = caller_account - .realloc_region(memory_mapping, is_loader_deprecated)? - .unwrap(); // unwrapping here is fine, we asserted !is_loader_deprecated - let original_state = realloc_region.state.replace(MemoryState::Writable); - defer! { - realloc_region.state.set(original_state); - }; + // The extra capacity up to original data length is + // accessible from the vm and since it's uninitialized + // memory, it could be a source of non determinism. + caller_account.original_data_len + } else { + // If the allocation has not changed, we only zero the + // difference between the previous and current lengths. The + // rest of the memory contains whatever it contained before, + // which is deterministic. + prev_len + } + .saturating_sub(post_len); + + if spare_len > 0 { + let dst = callee_account + .spare_data_capacity_mut()? + .get_mut(..spare_len) + .ok_or_else(|| Box::new(InstructionError::AccountDataTooSmall))? + .as_mut_ptr(); + // Safety: we check bounds above + unsafe { ptr::write_bytes(dst, 0, spare_len) }; + } - translate_slice_mut::( - memory_mapping, - caller_account - .vm_data_addr - .saturating_add(caller_account.original_data_len as u64), - realloc_bytes_used as u64, - invoke_context.get_check_aligned(), - invoke_context.get_check_size(), - )? - }; + // Propagate changes to the realloc region in the callee up to the caller. + let realloc_bytes_used = post_len.saturating_sub(caller_account.original_data_len); + if realloc_bytes_used > 0 { + // In the is_loader_deprecated case, we must have failed with + // InvalidRealloc by now. + debug_assert!(!is_loader_deprecated); + + let to_slice = { + // If a callee reallocs an account, we write into the caller's + // realloc region regardless of whether the caller has write + // permissions to the account or not. If the callee has been able to + // make changes, it means they had permissions to do so, and here + // we're just going to reflect those changes to the caller's frame. + // + // Therefore we temporarily configure the realloc region as writable + // then set it back to whatever state it had. + let realloc_region = caller_account + .realloc_region(memory_mapping, is_loader_deprecated)? + .unwrap(); // unwrapping here is fine, we asserted !is_loader_deprecated + let original_state = realloc_region.state.replace(MemoryState::Writable); + defer! { + realloc_region.state.set(original_state); + }; + + translate_slice_mut::( + memory_mapping, + caller_account + .vm_data_addr + .saturating_add(caller_account.original_data_len as u64), + realloc_bytes_used as u64, + invoke_context.get_check_aligned(), + invoke_context.get_check_size(), + )? + }; + let from_slice = callee_account + .get_data() + .get(caller_account.original_data_len..post_len) + .ok_or(SyscallError::InvalidLength)?; + if to_slice.len() != from_slice.len() { + return Err(Box::new(InstructionError::AccountDataTooSmall)); + } + to_slice.copy_from_slice(from_slice); + } + } else { + let to_slice = &mut caller_account.serialized_data; let from_slice = callee_account .get_data() - .get(caller_account.original_data_len..post_len) + .get(0..post_len) .ok_or(SyscallError::InvalidLength)?; if to_slice.len() != from_slice.len() { return Err(Box::new(InstructionError::AccountDataTooSmall)); @@ -1643,7 +1757,7 @@ mod tests { invoke_context::SerializedAccountMetadata, with_mock_invoke_context, }, solana_rbpf::{ - ebpf::MM_INPUT_START, elf::SBPFVersion, memory_region::MemoryRegion, vm::Config, + ebpf::MM_INPUT_START, memory_region::MemoryRegion, program::SBPFVersion, vm::Config, }, solana_sdk::{ account::{Account, AccountSharedData}, @@ -1822,6 +1936,7 @@ mod tests { let caller_account = CallerAccount::from_account_info( &invoke_context, &memory_mapping, + false, vm_addr, account_info, &account_metadata, diff --git a/programs/bpf_loader/src/syscalls/logging.rs b/programs/bpf_loader/src/syscalls/logging.rs index f6d69153d2b..c5faf0a1057 100644 --- a/programs/bpf_loader/src/syscalls/logging.rs +++ b/programs/bpf_loader/src/syscalls/logging.rs @@ -1,9 +1,9 @@ -use {super::*, crate::declare_syscall, solana_rbpf::vm::ContextObject}; +use {super::*, solana_rbpf::vm::ContextObject}; -declare_syscall!( +declare_builtin_function!( /// Log a user's info message SyscallLog, - fn inner_call( + fn rust( invoke_context: &mut InvokeContext, addr: u64, len: u64, @@ -36,10 +36,10 @@ declare_syscall!( } ); -declare_syscall!( +declare_builtin_function!( /// Log 5 64-bit values SyscallLogU64, - fn inner_call( + fn rust( invoke_context: &mut InvokeContext, arg1: u64, arg2: u64, @@ -59,10 +59,10 @@ declare_syscall!( } ); -declare_syscall!( +declare_builtin_function!( /// Log current compute consumption SyscallLogBpfComputeUnits, - fn inner_call( + fn rust( invoke_context: &mut InvokeContext, _arg1: u64, _arg2: u64, @@ -83,10 +83,10 @@ declare_syscall!( } ); -declare_syscall!( +declare_builtin_function!( /// Log 5 64-bit values SyscallLogPubkey, - fn inner_call( + fn rust( invoke_context: &mut InvokeContext, pubkey_addr: u64, _arg2: u64, @@ -108,10 +108,10 @@ declare_syscall!( } ); -declare_syscall!( +declare_builtin_function!( /// Log data handling SyscallLogData, - fn inner_call( + fn rust( invoke_context: &mut InvokeContext, addr: u64, len: u64, diff --git a/programs/bpf_loader/src/syscalls/mem_ops.rs b/programs/bpf_loader/src/syscalls/mem_ops.rs index 93d5b69cecd..848151dca91 100644 --- a/programs/bpf_loader/src/syscalls/mem_ops.rs +++ b/programs/bpf_loader/src/syscalls/mem_ops.rs @@ -1,6 +1,5 @@ use { super::*, - crate::declare_syscall, solana_rbpf::{error::EbpfError, memory_region::MemoryRegion}, std::slice, }; @@ -14,10 +13,10 @@ fn mem_op_consume(invoke_context: &mut InvokeContext, n: u64) -> Result<(), Erro consume_compute_meter(invoke_context, cost) } -declare_syscall!( +declare_builtin_function!( /// memcpy SyscallMemcpy, - fn inner_call( + fn rust( invoke_context: &mut InvokeContext, dst_addr: u64, src_addr: u64, @@ -37,10 +36,10 @@ declare_syscall!( } ); -declare_syscall!( +declare_builtin_function!( /// memmove SyscallMemmove, - fn inner_call( + fn rust( invoke_context: &mut InvokeContext, dst_addr: u64, src_addr: u64, @@ -55,10 +54,10 @@ declare_syscall!( } ); -declare_syscall!( +declare_builtin_function!( /// memcmp SyscallMemcmp, - fn inner_call( + fn rust( invoke_context: &mut InvokeContext, s1_addr: u64, s2_addr: u64, @@ -113,10 +112,10 @@ declare_syscall!( } ); -declare_syscall!( +declare_builtin_function!( /// memset SyscallMemset, - fn inner_call( + fn rust( invoke_context: &mut InvokeContext, dst_addr: u64, c: u64, @@ -295,8 +294,8 @@ fn iter_memory_pair_chunks( src_access: AccessType, src_addr: u64, dst_access: AccessType, - mut dst_addr: u64, - n: u64, + dst_addr: u64, + n_bytes: u64, memory_mapping: &MemoryMapping, reverse: bool, mut fun: F, @@ -305,52 +304,90 @@ where T: Default, F: FnMut(*const u8, *const u8, usize) -> Result, { - let mut src_chunk_iter = MemoryChunkIterator::new(memory_mapping, src_access, src_addr, n) - .map_err(EbpfError::from)?; - loop { - // iterate source chunks - let (src_region, src_vm_addr, mut src_len) = match if reverse { - src_chunk_iter.next_back() - } else { - src_chunk_iter.next() - } { - Some(item) => item?, - None => break, - }; - - let mut src_host_addr = Result::from(src_region.vm_to_host(src_vm_addr, src_len as u64))?; - let mut dst_chunk_iter = MemoryChunkIterator::new(memory_mapping, dst_access, dst_addr, n) + let mut src_chunk_iter = + MemoryChunkIterator::new(memory_mapping, src_access, src_addr, n_bytes) .map_err(EbpfError::from)?; - // iterate over destination chunks until this source chunk has been completely copied - while src_len > 0 { - loop { - let (dst_region, dst_vm_addr, dst_len) = match if reverse { - dst_chunk_iter.next_back() + let mut dst_chunk_iter = + MemoryChunkIterator::new(memory_mapping, dst_access, dst_addr, n_bytes) + .map_err(EbpfError::from)?; + + let mut src_chunk = None; + let mut dst_chunk = None; + + macro_rules! memory_chunk { + ($chunk_iter:ident, $chunk:ident) => { + if let Some($chunk) = &mut $chunk { + // Keep processing the current chunk + $chunk + } else { + // This is either the first call or we've processed all the bytes in the current + // chunk. Move to the next one. + let chunk = match if reverse { + $chunk_iter.next_back() } else { - dst_chunk_iter.next() + $chunk_iter.next() } { Some(item) => item?, None => break, }; - let dst_host_addr = - Result::from(dst_region.vm_to_host(dst_vm_addr, dst_len as u64))?; - let chunk_len = src_len.min(dst_len); - fun( - src_host_addr as *const u8, - dst_host_addr as *const u8, - chunk_len, - )?; - src_len = src_len.saturating_sub(chunk_len); - if reverse { - dst_addr = dst_addr.saturating_sub(chunk_len as u64); - } else { - dst_addr = dst_addr.saturating_add(chunk_len as u64); - } - if src_len == 0 { - break; - } - src_host_addr = src_host_addr.saturating_add(chunk_len as u64); + $chunk.insert(chunk) } + }; + } + + loop { + let (src_region, src_chunk_addr, src_remaining) = memory_chunk!(src_chunk_iter, src_chunk); + let (dst_region, dst_chunk_addr, dst_remaining) = memory_chunk!(dst_chunk_iter, dst_chunk); + + // We always process same-length pairs + let chunk_len = *src_remaining.min(dst_remaining); + + let (src_host_addr, dst_host_addr) = { + let (src_addr, dst_addr) = if reverse { + // When scanning backwards not only we want to scan regions from the end, + // we want to process the memory within regions backwards as well. + ( + src_chunk_addr + .saturating_add(*src_remaining as u64) + .saturating_sub(chunk_len as u64), + dst_chunk_addr + .saturating_add(*dst_remaining as u64) + .saturating_sub(chunk_len as u64), + ) + } else { + (*src_chunk_addr, *dst_chunk_addr) + }; + + ( + Result::from(src_region.vm_to_host(src_addr, chunk_len as u64))?, + Result::from(dst_region.vm_to_host(dst_addr, chunk_len as u64))?, + ) + }; + + fun( + src_host_addr as *const u8, + dst_host_addr as *const u8, + chunk_len, + )?; + + // Update how many bytes we have left to scan in each chunk + *src_remaining = src_remaining.saturating_sub(chunk_len); + *dst_remaining = dst_remaining.saturating_sub(chunk_len); + + if !reverse { + // We've scanned `chunk_len` bytes so we move the vm address forward. In reverse + // mode we don't do this since we make progress by decreasing src_len and + // dst_len. + *src_chunk_addr = src_chunk_addr.saturating_add(chunk_len as u64); + *dst_chunk_addr = dst_chunk_addr.saturating_add(chunk_len as u64); + } + + if *src_remaining == 0 { + src_chunk = None; + } + + if *dst_remaining == 0 { + dst_chunk = None; } } @@ -375,7 +412,6 @@ impl<'a> MemoryChunkIterator<'a> { len: u64, ) -> Result, EbpfError> { let vm_addr_end = vm_addr.checked_add(len).ok_or(EbpfError::AccessViolation( - 0, access_type, vm_addr, len, @@ -394,26 +430,19 @@ impl<'a> MemoryChunkIterator<'a> { fn region(&mut self, vm_addr: u64) -> Result<&'a MemoryRegion, Error> { match self.memory_mapping.region(self.access_type, vm_addr) { Ok(region) => Ok(region), - Err(error) => match error.downcast_ref() { - Some(EbpfError::AccessViolation(pc, access_type, _vm_addr, _len, name)) => { - Err(Box::new(EbpfError::AccessViolation( - *pc, - *access_type, - self.initial_vm_addr, - self.len, - name, - ))) - } - Some(EbpfError::StackAccessViolation(pc, access_type, _vm_addr, _len, frame)) => { + Err(error) => match error { + EbpfError::AccessViolation(access_type, _vm_addr, _len, name) => Err(Box::new( + EbpfError::AccessViolation(access_type, self.initial_vm_addr, self.len, name), + )), + EbpfError::StackAccessViolation(access_type, _vm_addr, _len, frame) => { Err(Box::new(EbpfError::StackAccessViolation( - *pc, - *access_type, + access_type, self.initial_vm_addr, self.len, - *frame, + frame, ))) } - _ => Err(error), + _ => Err(error.into()), }, } } @@ -485,11 +514,13 @@ impl<'a> DoubleEndedIterator for MemoryChunkIterator<'a> { #[cfg(test)] #[allow(clippy::indexing_slicing)] +#[allow(clippy::arithmetic_side_effects)] mod tests { use { super::*, assert_matches::assert_matches, - solana_rbpf::{ebpf::MM_PROGRAM_START, elf::SBPFVersion}, + solana_rbpf::{ebpf::MM_PROGRAM_START, program::SBPFVersion}, + test_case::test_case, }; fn to_chunk_vec<'a>( @@ -547,7 +578,7 @@ mod tests { .unwrap(); assert_matches!( src_chunk_iter.next().unwrap().unwrap_err().downcast_ref().unwrap(), - EbpfError::AccessViolation(0, AccessType::Load, addr, 42, "unknown") if *addr == MM_PROGRAM_START - 1 + EbpfError::AccessViolation(AccessType::Load, addr, 42, "unknown") if *addr == MM_PROGRAM_START - 1 ); // check oob at the upper bound. Since the memory mapping isn't empty, @@ -558,7 +589,7 @@ mod tests { assert!(src_chunk_iter.next().unwrap().is_ok()); assert_matches!( src_chunk_iter.next().unwrap().unwrap_err().downcast_ref().unwrap(), - EbpfError::AccessViolation(0, AccessType::Load, addr, 43, "program") if *addr == MM_PROGRAM_START + EbpfError::AccessViolation(AccessType::Load, addr, 43, "program") if *addr == MM_PROGRAM_START ); // check oob at the upper bound on the first next_back() @@ -568,7 +599,7 @@ mod tests { .rev(); assert_matches!( src_chunk_iter.next().unwrap().unwrap_err().downcast_ref().unwrap(), - EbpfError::AccessViolation(0, AccessType::Load, addr, 43, "program") if *addr == MM_PROGRAM_START + EbpfError::AccessViolation(AccessType::Load, addr, 43, "program") if *addr == MM_PROGRAM_START ); // check oob at the upper bound on the 2nd next_back() @@ -579,7 +610,7 @@ mod tests { assert!(src_chunk_iter.next().unwrap().is_ok()); assert_matches!( src_chunk_iter.next().unwrap().unwrap_err().downcast_ref().unwrap(), - EbpfError::AccessViolation(0, AccessType::Load, addr, 43, "unknown") if *addr == MM_PROGRAM_START - 1 + EbpfError::AccessViolation(AccessType::Load, addr, 43, "unknown") if *addr == MM_PROGRAM_START - 1 ); } @@ -707,7 +738,7 @@ mod tests { false, |_src, _dst, _len| Ok::<_, Error>(0), ).unwrap_err().downcast_ref().unwrap(), - EbpfError::AccessViolation(0, AccessType::Load, addr, 8, "program") if *addr == MM_PROGRAM_START + 8 + EbpfError::AccessViolation(AccessType::Load, addr, 8, "program") if *addr == MM_PROGRAM_START + 8 ); // src is shorter than dst @@ -722,12 +753,12 @@ mod tests { false, |_src, _dst, _len| Ok::<_, Error>(0), ).unwrap_err().downcast_ref().unwrap(), - EbpfError::AccessViolation(0, AccessType::Load, addr, 3, "program") if *addr == MM_PROGRAM_START + 10 + EbpfError::AccessViolation(AccessType::Load, addr, 3, "program") if *addr == MM_PROGRAM_START + 10 ); } #[test] - #[should_panic(expected = "AccessViolation(0, Store, 4294967296, 4")] + #[should_panic(expected = "AccessViolation(Store, 4294967296, 4")] fn test_memmove_non_contiguous_readonly() { let config = Config { aligned_memory_mapping: false, @@ -748,76 +779,63 @@ mod tests { memmove_non_contiguous(MM_PROGRAM_START, MM_PROGRAM_START + 8, 4, &memory_mapping).unwrap(); } - #[test] - fn test_overlapping_memmove_non_contiguous_right() { + #[test_case(&[], (0, 0, 0); "no regions")] + #[test_case(&[10], (1, 10, 0); "single region 0 len")] + #[test_case(&[10], (0, 5, 5); "single region no overlap")] + #[test_case(&[10], (0, 0, 10) ; "single region complete overlap")] + #[test_case(&[10], (2, 0, 5); "single region partial overlap start")] + #[test_case(&[10], (0, 1, 6); "single region partial overlap middle")] + #[test_case(&[10], (2, 5, 5); "single region partial overlap end")] + #[test_case(&[3, 5], (0, 5, 2) ; "two regions no overlap, single source region")] + #[test_case(&[4, 7], (0, 5, 5) ; "two regions no overlap, multiple source regions")] + #[test_case(&[3, 8], (0, 0, 11) ; "two regions complete overlap")] + #[test_case(&[2, 9], (3, 0, 5) ; "two regions partial overlap start")] + #[test_case(&[3, 9], (1, 2, 5) ; "two regions partial overlap middle")] + #[test_case(&[7, 3], (2, 6, 4) ; "two regions partial overlap end")] + #[test_case(&[2, 6, 3, 4], (0, 10, 2) ; "many regions no overlap, single source region")] + #[test_case(&[2, 1, 2, 5, 6], (2, 10, 4) ; "many regions no overlap, multiple source regions")] + #[test_case(&[8, 1, 3, 6], (0, 0, 18) ; "many regions complete overlap")] + #[test_case(&[7, 3, 1, 4, 5], (5, 0, 8) ; "many regions overlap start")] + #[test_case(&[1, 5, 2, 9, 3], (5, 4, 8) ; "many regions overlap middle")] + #[test_case(&[3, 9, 1, 1, 2, 1], (2, 9, 8) ; "many regions overlap end")] + fn test_memmove_non_contiguous( + regions: &[usize], + (src_offset, dst_offset, len): (usize, usize, usize), + ) { let config = Config { aligned_memory_mapping: false, ..Config::default() }; - let mem1 = vec![0x11; 1]; - let mut mem2 = vec![0x22; 2]; - let mut mem3 = vec![0x33; 3]; - let mut mem4 = vec![0x44; 4]; - let memory_mapping = MemoryMapping::new( - vec![ - MemoryRegion::new_readonly(&mem1, MM_PROGRAM_START), - MemoryRegion::new_writable(&mut mem2, MM_PROGRAM_START + 1), - MemoryRegion::new_writable(&mut mem3, MM_PROGRAM_START + 3), - MemoryRegion::new_writable(&mut mem4, MM_PROGRAM_START + 6), - ], - &config, - &SBPFVersion::V2, - ) - .unwrap(); - - // overlapping memmove right - the implementation will copy backwards - assert_eq!( - memmove_non_contiguous(MM_PROGRAM_START + 1, MM_PROGRAM_START, 7, &memory_mapping) - .unwrap(), - 0 - ); - assert_eq!(&mem1, &[0x11]); - assert_eq!(&mem2, &[0x11, 0x22]); - assert_eq!(&mem3, &[0x22, 0x33, 0x33]); - assert_eq!(&mem4, &[0x33, 0x44, 0x44, 0x44]); - } - - #[test] - fn test_overlapping_memmove_non_contiguous_left() { - let config = Config { - aligned_memory_mapping: false, - ..Config::default() + let (mem, memory_mapping) = build_memory_mapping(regions, &config); + + // flatten the memory so we can memmove it with ptr::copy + let mut expected_memory = flatten_memory(&mem); + unsafe { + std::ptr::copy( + expected_memory.as_ptr().add(src_offset), + expected_memory.as_mut_ptr().add(dst_offset), + len, + ) }; - let mut mem1 = vec![0x11; 1]; - let mut mem2 = vec![0x22; 2]; - let mut mem3 = vec![0x33; 3]; - let mut mem4 = vec![0x44; 4]; - let memory_mapping = MemoryMapping::new( - vec![ - MemoryRegion::new_writable(&mut mem1, MM_PROGRAM_START), - MemoryRegion::new_writable(&mut mem2, MM_PROGRAM_START + 1), - MemoryRegion::new_writable(&mut mem3, MM_PROGRAM_START + 3), - MemoryRegion::new_writable(&mut mem4, MM_PROGRAM_START + 6), - ], - &config, - &SBPFVersion::V2, + + // do our memmove + memmove_non_contiguous( + MM_PROGRAM_START + dst_offset as u64, + MM_PROGRAM_START + src_offset as u64, + len as u64, + &memory_mapping, ) .unwrap(); - // overlapping memmove left - the implementation will copy forward - assert_eq!( - memmove_non_contiguous(MM_PROGRAM_START, MM_PROGRAM_START + 1, 7, &memory_mapping) - .unwrap(), - 0 - ); - assert_eq!(&mem1, &[0x22]); - assert_eq!(&mem2, &[0x22, 0x33]); - assert_eq!(&mem3, &[0x33, 0x33, 0x44]); - assert_eq!(&mem4, &[0x44, 0x44, 0x44, 0x44]); + // flatten memory post our memmove + let memory = flatten_memory(&mem); + + // compare libc's memmove with ours + assert_eq!(expected_memory, memory); } #[test] - #[should_panic(expected = "AccessViolation(0, Store, 4294967296, 9")] + #[should_panic(expected = "AccessViolation(Store, 4294967296, 9")] fn test_memset_non_contiguous_readonly() { let config = Config { aligned_memory_mapping: false, @@ -924,4 +942,33 @@ mod tests { unsafe { memcmp(b"oobar", b"obarb", 5) } ); } + + fn build_memory_mapping<'a>( + regions: &[usize], + config: &'a Config, + ) -> (Vec>, MemoryMapping<'a>) { + let mut regs = vec![]; + let mut mem = Vec::new(); + let mut offset = 0; + for (i, region_len) in regions.iter().enumerate() { + mem.push( + (0..*region_len) + .map(|x| (i * 10 + x) as u8) + .collect::>(), + ); + regs.push(MemoryRegion::new_writable( + &mut mem[i], + MM_PROGRAM_START + offset as u64, + )); + offset += *region_len; + } + + let memory_mapping = MemoryMapping::new(regs, config, &SBPFVersion::V2).unwrap(); + + (mem, memory_mapping) + } + + fn flatten_memory(mem: &[Vec]) -> Vec { + mem.iter().flatten().copied().collect() + } } diff --git a/programs/bpf_loader/src/syscalls/mod.rs b/programs/bpf_loader/src/syscalls/mod.rs index c4a7fe1e6db..6346b4919b9 100644 --- a/programs/bpf_loader/src/syscalls/mod.rs +++ b/programs/bpf_loader/src/syscalls/mod.rs @@ -16,9 +16,10 @@ use { stable_log, timings::ExecuteTimings, }, solana_rbpf::{ - elf::FunctionRegistry, + declare_builtin_function, memory_region::{AccessType, MemoryMapping}, - vm::{BuiltinFunction, BuiltinProgram, Config, ProgramResult}, + program::{BuiltinFunction, BuiltinProgram, FunctionRegistry}, + vm::Config, }, solana_sdk::{ account::{ReadableAccount, WritableAccount}, @@ -44,7 +45,7 @@ use { remaining_compute_units_syscall_enabled, stop_sibling_instruction_search_at_parent, stop_truncating_strings_in_syscalls, switch_to_new_elf_parser, }, - hash::{Hasher, HASH_BYTES}, + hash::{Hash, Hasher}, instruction::{ AccountMeta, InstructionError, ProcessedSiblingInstruction, TRANSACTION_LEVEL_STACK_HEIGHT, @@ -133,6 +134,103 @@ pub enum SyscallError { type Error = Box; +pub trait HasherImpl { + const NAME: &'static str; + type Output: AsRef<[u8]>; + + fn create_hasher() -> Self; + fn hash(&mut self, val: &[u8]); + fn result(self) -> Self::Output; + fn get_base_cost(compute_budget: &ComputeBudget) -> u64; + fn get_byte_cost(compute_budget: &ComputeBudget) -> u64; + fn get_max_slices(compute_budget: &ComputeBudget) -> u64; +} + +pub struct Sha256Hasher(Hasher); +pub struct Blake3Hasher(blake3::Hasher); +pub struct Keccak256Hasher(keccak::Hasher); + +impl HasherImpl for Sha256Hasher { + const NAME: &'static str = "Sha256"; + type Output = Hash; + + fn create_hasher() -> Self { + Sha256Hasher(Hasher::default()) + } + + fn hash(&mut self, val: &[u8]) { + self.0.hash(val); + } + + fn result(self) -> Self::Output { + self.0.result() + } + + fn get_base_cost(compute_budget: &ComputeBudget) -> u64 { + compute_budget.sha256_base_cost + } + fn get_byte_cost(compute_budget: &ComputeBudget) -> u64 { + compute_budget.sha256_byte_cost + } + fn get_max_slices(compute_budget: &ComputeBudget) -> u64 { + compute_budget.sha256_max_slices + } +} + +impl HasherImpl for Blake3Hasher { + const NAME: &'static str = "Blake3"; + type Output = blake3::Hash; + + fn create_hasher() -> Self { + Blake3Hasher(blake3::Hasher::default()) + } + + fn hash(&mut self, val: &[u8]) { + self.0.hash(val); + } + + fn result(self) -> Self::Output { + self.0.result() + } + + fn get_base_cost(compute_budget: &ComputeBudget) -> u64 { + compute_budget.sha256_base_cost + } + fn get_byte_cost(compute_budget: &ComputeBudget) -> u64 { + compute_budget.sha256_byte_cost + } + fn get_max_slices(compute_budget: &ComputeBudget) -> u64 { + compute_budget.sha256_max_slices + } +} + +impl HasherImpl for Keccak256Hasher { + const NAME: &'static str = "Keccak256"; + type Output = keccak::Hash; + + fn create_hasher() -> Self { + Keccak256Hasher(keccak::Hasher::default()) + } + + fn hash(&mut self, val: &[u8]) { + self.0.hash(val); + } + + fn result(self) -> Self::Output { + self.0.result() + } + + fn get_base_cost(compute_budget: &ComputeBudget) -> u64 { + compute_budget.sha256_base_cost + } + fn get_byte_cost(compute_budget: &ComputeBudget) -> u64 { + compute_budget.sha256_byte_cost + } + fn get_max_slices(compute_budget: &ComputeBudget) -> u64 { + compute_budget.sha256_max_slices + } +} + fn consume_compute_meter(invoke_context: &InvokeContext, amount: u64) -> Result<(), Error> { invoke_context.consume_checked(amount)?; Ok(()) @@ -177,7 +275,7 @@ pub fn create_program_runtime_environment_v1<'a>( max_call_depth: compute_budget.max_call_depth, stack_frame_size: compute_budget.stack_frame_size, enable_address_translation: true, - enable_stack_frame_gaps: true, + enable_stack_frame_gaps: !feature_set.is_active(&bpf_account_data_direct_mapping::id()), instruction_meter_checkpoint_distance: 10000, enable_instruction_meter: true, enable_instruction_tracing: debugging_features, @@ -185,7 +283,6 @@ pub fn create_program_runtime_environment_v1<'a>( reject_broken_elfs: reject_deployment_of_broken_elfs, noop_instruction_rate: 256, sanitize_user_provided_values: true, - encrypt_runtime_environment: true, external_internal_function_hash_collision: feature_set .is_active(&error_on_syscall_bpf_function_hash_collisions::id()), reject_callx_r10: feature_set.is_active(&reject_callx_r10::id()), @@ -199,42 +296,42 @@ pub fn create_program_runtime_environment_v1<'a>( let mut result = FunctionRegistry::>::default(); // Abort - result.register_function_hashed(*b"abort", SyscallAbort::call)?; + result.register_function_hashed(*b"abort", SyscallAbort::vm)?; // Panic - result.register_function_hashed(*b"sol_panic_", SyscallPanic::call)?; + result.register_function_hashed(*b"sol_panic_", SyscallPanic::vm)?; // Logging - result.register_function_hashed(*b"sol_log_", SyscallLog::call)?; - result.register_function_hashed(*b"sol_log_64_", SyscallLogU64::call)?; - result.register_function_hashed(*b"sol_log_compute_units_", SyscallLogBpfComputeUnits::call)?; - result.register_function_hashed(*b"sol_log_pubkey", SyscallLogPubkey::call)?; + result.register_function_hashed(*b"sol_log_", SyscallLog::vm)?; + result.register_function_hashed(*b"sol_log_64_", SyscallLogU64::vm)?; + result.register_function_hashed(*b"sol_log_compute_units_", SyscallLogBpfComputeUnits::vm)?; + result.register_function_hashed(*b"sol_log_pubkey", SyscallLogPubkey::vm)?; // Program defined addresses (PDA) result.register_function_hashed( *b"sol_create_program_address", - SyscallCreateProgramAddress::call, + SyscallCreateProgramAddress::vm, )?; result.register_function_hashed( *b"sol_try_find_program_address", - SyscallTryFindProgramAddress::call, + SyscallTryFindProgramAddress::vm, )?; // Sha256 - result.register_function_hashed(*b"sol_sha256", SyscallSha256::call)?; + result.register_function_hashed(*b"sol_sha256", SyscallHash::vm::)?; // Keccak256 - result.register_function_hashed(*b"sol_keccak256", SyscallKeccak256::call)?; + result.register_function_hashed(*b"sol_keccak256", SyscallHash::vm::)?; // Secp256k1 Recover - result.register_function_hashed(*b"sol_secp256k1_recover", SyscallSecp256k1Recover::call)?; + result.register_function_hashed(*b"sol_secp256k1_recover", SyscallSecp256k1Recover::vm)?; // Blake3 register_feature_gated_function!( result, blake3_syscall_enabled, *b"sol_blake3", - SyscallBlake3::call, + SyscallHash::vm::, )?; // Elliptic Curve Operations @@ -242,78 +339,78 @@ pub fn create_program_runtime_environment_v1<'a>( result, curve25519_syscall_enabled, *b"sol_curve_validate_point", - SyscallCurvePointValidation::call, + SyscallCurvePointValidation::vm, )?; register_feature_gated_function!( result, curve25519_syscall_enabled, *b"sol_curve_group_op", - SyscallCurveGroupOps::call, + SyscallCurveGroupOps::vm, )?; register_feature_gated_function!( result, curve25519_syscall_enabled, *b"sol_curve_multiscalar_mul", - SyscallCurveMultiscalarMultiplication::call, + SyscallCurveMultiscalarMultiplication::vm, )?; // Sysvars - result.register_function_hashed(*b"sol_get_clock_sysvar", SyscallGetClockSysvar::call)?; + result.register_function_hashed(*b"sol_get_clock_sysvar", SyscallGetClockSysvar::vm)?; result.register_function_hashed( *b"sol_get_epoch_schedule_sysvar", - SyscallGetEpochScheduleSysvar::call, + SyscallGetEpochScheduleSysvar::vm, )?; register_feature_gated_function!( result, !disable_fees_sysvar, *b"sol_get_fees_sysvar", - SyscallGetFeesSysvar::call, + SyscallGetFeesSysvar::vm, )?; - result.register_function_hashed(*b"sol_get_rent_sysvar", SyscallGetRentSysvar::call)?; + result.register_function_hashed(*b"sol_get_rent_sysvar", SyscallGetRentSysvar::vm)?; register_feature_gated_function!( result, last_restart_slot_syscall_enabled, *b"sol_get_last_restart_slot", - SyscallGetLastRestartSlotSysvar::call, + SyscallGetLastRestartSlotSysvar::vm, )?; register_feature_gated_function!( result, epoch_rewards_syscall_enabled, *b"sol_get_epoch_rewards_sysvar", - SyscallGetEpochRewardsSysvar::call, + SyscallGetEpochRewardsSysvar::vm, )?; // Memory ops - result.register_function_hashed(*b"sol_memcpy_", SyscallMemcpy::call)?; - result.register_function_hashed(*b"sol_memmove_", SyscallMemmove::call)?; - result.register_function_hashed(*b"sol_memcmp_", SyscallMemcmp::call)?; - result.register_function_hashed(*b"sol_memset_", SyscallMemset::call)?; + result.register_function_hashed(*b"sol_memcpy_", SyscallMemcpy::vm)?; + result.register_function_hashed(*b"sol_memmove_", SyscallMemmove::vm)?; + result.register_function_hashed(*b"sol_memcmp_", SyscallMemcmp::vm)?; + result.register_function_hashed(*b"sol_memset_", SyscallMemset::vm)?; // Processed sibling instructions result.register_function_hashed( *b"sol_get_processed_sibling_instruction", - SyscallGetProcessedSiblingInstruction::call, + SyscallGetProcessedSiblingInstruction::vm, )?; // Stack height - result.register_function_hashed(*b"sol_get_stack_height", SyscallGetStackHeight::call)?; + result.register_function_hashed(*b"sol_get_stack_height", SyscallGetStackHeight::vm)?; // Return data - result.register_function_hashed(*b"sol_set_return_data", SyscallSetReturnData::call)?; - result.register_function_hashed(*b"sol_get_return_data", SyscallGetReturnData::call)?; + result.register_function_hashed(*b"sol_set_return_data", SyscallSetReturnData::vm)?; + result.register_function_hashed(*b"sol_get_return_data", SyscallGetReturnData::vm)?; // Cross-program invocation - result.register_function_hashed(*b"sol_invoke_signed_c", SyscallInvokeSignedC::call)?; - result.register_function_hashed(*b"sol_invoke_signed_rust", SyscallInvokeSignedRust::call)?; + result.register_function_hashed(*b"sol_invoke_signed_c", SyscallInvokeSignedC::vm)?; + result.register_function_hashed(*b"sol_invoke_signed_rust", SyscallInvokeSignedRust::vm)?; // Memory allocator register_feature_gated_function!( result, !disable_deploy_of_alloc_free_syscall, *b"sol_alloc_free_", - SyscallAllocFree::call, + SyscallAllocFree::vm, )?; // Alt_bn128 @@ -321,7 +418,7 @@ pub fn create_program_runtime_environment_v1<'a>( result, enable_alt_bn128_syscall, *b"sol_alt_bn128_group_op", - SyscallAltBn128::call, + SyscallAltBn128::vm, )?; // Big_mod_exp @@ -329,7 +426,7 @@ pub fn create_program_runtime_environment_v1<'a>( result, enable_big_mod_exp_syscall, *b"sol_big_mod_exp", - SyscallBigModExp::call, + SyscallBigModExp::vm, )?; // Poseidon @@ -337,7 +434,7 @@ pub fn create_program_runtime_environment_v1<'a>( result, enable_poseidon_syscall, *b"sol_poseidon", - SyscallPoseidon::call, + SyscallPoseidon::vm, )?; // Accessing remaining compute units @@ -345,7 +442,7 @@ pub fn create_program_runtime_environment_v1<'a>( result, remaining_compute_units_syscall_enabled, *b"sol_remaining_compute_units", - SyscallRemainingComputeUnits::call + SyscallRemainingComputeUnits::vm )?; // Alt_bn128_compression @@ -353,11 +450,11 @@ pub fn create_program_runtime_environment_v1<'a>( result, enable_alt_bn128_compression_syscall, *b"sol_alt_bn128_compression", - SyscallAltBn128Compression::call, + SyscallAltBn128Compression::vm, )?; // Log data - result.register_function_hashed(*b"sol_log_data", SyscallLogData::call)?; + result.register_function_hashed(*b"sol_log_data", SyscallLogData::vm)?; Ok(BuiltinProgram::new_loader(config, result)) } @@ -375,7 +472,10 @@ fn translate( vm_addr: u64, len: u64, ) -> Result { - memory_mapping.map(access_type, vm_addr, len, 0).into() + memory_mapping + .map(access_type, vm_addr, len) + .map_err(|err| err.into()) + .into() } fn translate_type_inner<'a, T>( @@ -494,39 +594,13 @@ fn translate_string_and_do( } } -#[macro_export] -macro_rules! declare_syscall { - ($(#[$attr:meta])* $name:ident, $inner_call:item) => { - $(#[$attr])* - pub struct $name {} - impl $name { - $inner_call - pub fn call( - invoke_context: &mut InvokeContext, - arg_a: u64, - arg_b: u64, - arg_c: u64, - arg_d: u64, - arg_e: u64, - memory_mapping: &mut MemoryMapping, - result: &mut ProgramResult, - ) { - let converted_result: ProgramResult = Self::inner_call( - invoke_context, arg_a, arg_b, arg_c, arg_d, arg_e, memory_mapping, - ).into(); - *result = converted_result; - } - } - }; -} - -declare_syscall!( +declare_builtin_function!( /// Abort syscall functions, called when the SBF program calls `abort()` /// LLVM will insert calls to `abort()` if it detects an untenable situation, /// `abort()` is not intended to be called explicitly by the program. /// Causes the SBF program to be halted immediately SyscallAbort, - fn inner_call( + fn rust( _invoke_context: &mut InvokeContext, _arg1: u64, _arg2: u64, @@ -539,11 +613,11 @@ declare_syscall!( } ); -declare_syscall!( +declare_builtin_function!( /// Panic syscall function, called when the SBF program calls 'sol_panic_()` /// Causes the SBF program to be halted immediately SyscallPanic, - fn inner_call( + fn rust( invoke_context: &mut InvokeContext, file: u64, len: u64, @@ -568,7 +642,7 @@ declare_syscall!( } ); -declare_syscall!( +declare_builtin_function!( /// Dynamic memory allocation syscall called when the SBF program calls /// `sol_alloc_free_()`. The allocator is expected to allocate/free /// from/to a given chunk of memory and enforce size restrictions. The @@ -576,7 +650,7 @@ declare_syscall!( /// information about that memory (start address and size) is passed /// to the VM to use for enforcement. SyscallAllocFree, - fn inner_call( + fn rust( invoke_context: &mut InvokeContext, size: u64, free_addr: u64, @@ -643,10 +717,10 @@ fn translate_and_check_program_address_inputs<'a>( Ok((seeds, program_id)) } -declare_syscall!( +declare_builtin_function!( /// Create a program address SyscallCreateProgramAddress, - fn inner_call( + fn rust( invoke_context: &mut InvokeContext, seeds_addr: u64, seeds_len: u64, @@ -684,10 +758,10 @@ declare_syscall!( } ); -declare_syscall!( +declare_builtin_function!( /// Create a program address SyscallTryFindProgramAddress, - fn inner_call( + fn rust( invoke_context: &mut InvokeContext, seeds_addr: u64, seeds_len: u64, @@ -751,140 +825,10 @@ declare_syscall!( } ); -declare_syscall!( - /// SHA256 - SyscallSha256, - fn inner_call( - invoke_context: &mut InvokeContext, - vals_addr: u64, - vals_len: u64, - result_addr: u64, - _arg4: u64, - _arg5: u64, - memory_mapping: &mut MemoryMapping, - ) -> Result { - let compute_budget = invoke_context.get_compute_budget(); - if compute_budget.sha256_max_slices < vals_len { - ic_msg!( - invoke_context, - "Sha256 hashing {} sequences in one syscall is over the limit {}", - vals_len, - compute_budget.sha256_max_slices, - ); - return Err(SyscallError::TooManySlices.into()); - } - - consume_compute_meter(invoke_context, compute_budget.sha256_base_cost)?; - - let hash_result = translate_slice_mut::( - memory_mapping, - result_addr, - HASH_BYTES as u64, - invoke_context.get_check_aligned(), - invoke_context.get_check_size(), - )?; - let mut hasher = Hasher::default(); - if vals_len > 0 { - let vals = translate_slice::<&[u8]>( - memory_mapping, - vals_addr, - vals_len, - invoke_context.get_check_aligned(), - invoke_context.get_check_size(), - )?; - for val in vals.iter() { - let bytes = translate_slice::( - memory_mapping, - val.as_ptr() as u64, - val.len() as u64, - invoke_context.get_check_aligned(), - invoke_context.get_check_size(), - )?; - let cost = compute_budget.mem_op_base_cost.max( - compute_budget.sha256_byte_cost.saturating_mul( - (val.len() as u64) - .checked_div(2) - .expect("div by non-zero literal"), - ), - ); - consume_compute_meter(invoke_context, cost)?; - hasher.hash(bytes); - } - } - hash_result.copy_from_slice(&hasher.result().to_bytes()); - Ok(0) - } -); - -declare_syscall!( - // Keccak256 - SyscallKeccak256, - fn inner_call( - invoke_context: &mut InvokeContext, - vals_addr: u64, - vals_len: u64, - result_addr: u64, - _arg4: u64, - _arg5: u64, - memory_mapping: &mut MemoryMapping, - ) -> Result { - let compute_budget = invoke_context.get_compute_budget(); - if compute_budget.sha256_max_slices < vals_len { - ic_msg!( - invoke_context, - "Keccak256 hashing {} sequences in one syscall is over the limit {}", - vals_len, - compute_budget.sha256_max_slices, - ); - return Err(SyscallError::TooManySlices.into()); - } - - consume_compute_meter(invoke_context, compute_budget.sha256_base_cost)?; - - let hash_result = translate_slice_mut::( - memory_mapping, - result_addr, - keccak::HASH_BYTES as u64, - invoke_context.get_check_aligned(), - invoke_context.get_check_size(), - )?; - let mut hasher = keccak::Hasher::default(); - if vals_len > 0 { - let vals = translate_slice::<&[u8]>( - memory_mapping, - vals_addr, - vals_len, - invoke_context.get_check_aligned(), - invoke_context.get_check_size(), - )?; - for val in vals.iter() { - let bytes = translate_slice::( - memory_mapping, - val.as_ptr() as u64, - val.len() as u64, - invoke_context.get_check_aligned(), - invoke_context.get_check_size(), - )?; - let cost = compute_budget.mem_op_base_cost.max( - compute_budget.sha256_byte_cost.saturating_mul( - (val.len() as u64) - .checked_div(2) - .expect("div by non-zero literal"), - ), - ); - consume_compute_meter(invoke_context, cost)?; - hasher.hash(bytes); - } - } - hash_result.copy_from_slice(&hasher.result().to_bytes()); - Ok(0) - } -); - -declare_syscall!( +declare_builtin_function!( /// secp256k1_recover SyscallSecp256k1Recover, - fn inner_call( + fn rust( invoke_context: &mut InvokeContext, hash_addr: u64, recovery_id_val: u64, @@ -952,12 +896,12 @@ declare_syscall!( } ); -declare_syscall!( +declare_builtin_function!( // Elliptic Curve Point Validation // // Currently, only curve25519 Edwards and Ristretto representations are supported SyscallCurvePointValidation, - fn inner_call( + fn rust( invoke_context: &mut InvokeContext, curve_id: u64, point_addr: u64, @@ -1009,12 +953,12 @@ declare_syscall!( } ); -declare_syscall!( +declare_builtin_function!( // Elliptic Curve Group Operations // // Currently, only curve25519 Edwards and Ristretto representations are supported SyscallCurveGroupOps, - fn inner_call( + fn rust( invoke_context: &mut InvokeContext, curve_id: u64, group_op: u64, @@ -1210,12 +1154,12 @@ declare_syscall!( } ); -declare_syscall!( +declare_builtin_function!( // Elliptic Curve Multiscalar Multiplication // // Currently, only curve25519 Edwards and Ristretto representations are supported SyscallCurveMultiscalarMultiplication, - fn inner_call( + fn rust( invoke_context: &mut InvokeContext, curve_id: u64, scalars_addr: u64, @@ -1227,6 +1171,17 @@ declare_syscall!( use solana_zk_token_sdk::curve25519::{ curve_syscall_traits::*, edwards, ristretto, scalar, }; + + let restrict_msm_length = invoke_context + .feature_set + .is_active(&feature_set::curve25519_restrict_msm_length::id()); + #[allow(clippy::collapsible_if)] + if restrict_msm_length { + if points_len > 512 { + return Err(Box::new(SyscallError::InvalidLength)); + } + } + match curve_id { CURVE25519_EDWARDS => { let cost = invoke_context @@ -1315,75 +1270,10 @@ declare_syscall!( } ); -declare_syscall!( - // Blake3 - SyscallBlake3, - fn inner_call( - invoke_context: &mut InvokeContext, - vals_addr: u64, - vals_len: u64, - result_addr: u64, - _arg4: u64, - _arg5: u64, - memory_mapping: &mut MemoryMapping, - ) -> Result { - let compute_budget = invoke_context.get_compute_budget(); - if compute_budget.sha256_max_slices < vals_len { - ic_msg!( - invoke_context, - "Blake3 hashing {} sequences in one syscall is over the limit {}", - vals_len, - compute_budget.sha256_max_slices, - ); - return Err(SyscallError::TooManySlices.into()); - } - - consume_compute_meter(invoke_context, compute_budget.sha256_base_cost)?; - - let hash_result = translate_slice_mut::( - memory_mapping, - result_addr, - blake3::HASH_BYTES as u64, - invoke_context.get_check_aligned(), - invoke_context.get_check_size(), - )?; - let mut hasher = blake3::Hasher::default(); - if vals_len > 0 { - let vals = translate_slice::<&[u8]>( - memory_mapping, - vals_addr, - vals_len, - invoke_context.get_check_aligned(), - invoke_context.get_check_size(), - )?; - for val in vals.iter() { - let bytes = translate_slice::( - memory_mapping, - val.as_ptr() as u64, - val.len() as u64, - invoke_context.get_check_aligned(), - invoke_context.get_check_size(), - )?; - let cost = compute_budget.mem_op_base_cost.max( - compute_budget.sha256_byte_cost.saturating_mul( - (val.len() as u64) - .checked_div(2) - .expect("div by non-zero literal"), - ), - ); - consume_compute_meter(invoke_context, cost)?; - hasher.hash(bytes); - } - } - hash_result.copy_from_slice(&hasher.result().to_bytes()); - Ok(0) - } -); - -declare_syscall!( +declare_builtin_function!( /// Set return data SyscallSetReturnData, - fn inner_call( + fn rust( invoke_context: &mut InvokeContext, addr: u64, len: u64, @@ -1429,13 +1319,13 @@ declare_syscall!( } ); -declare_syscall!( +declare_builtin_function!( /// Get return data SyscallGetReturnData, - fn inner_call( + fn rust( invoke_context: &mut InvokeContext, return_data_addr: u64, - mut length: u64, + length: u64, program_id_addr: u64, _arg4: u64, _arg5: u64, @@ -1446,7 +1336,7 @@ declare_syscall!( consume_compute_meter(invoke_context, budget.syscall_base_cost)?; let (program_id, return_data) = invoke_context.transaction_context.get_return_data(); - length = length.min(return_data.len() as u64); + let length = length.min(return_data.len() as u64); if length != 0 { let cost = length .saturating_add(size_of::() as u64) @@ -1494,10 +1384,10 @@ declare_syscall!( } ); -declare_syscall!( +declare_builtin_function!( /// Get a processed sigling instruction SyscallGetProcessedSiblingInstruction, - fn inner_call( + fn rust( invoke_context: &mut InvokeContext, index: u64, meta_addr: u64, @@ -1640,10 +1530,10 @@ declare_syscall!( } ); -declare_syscall!( +declare_builtin_function!( /// Get current call stack height SyscallGetStackHeight, - fn inner_call( + fn rust( invoke_context: &mut InvokeContext, _arg1: u64, _arg2: u64, @@ -1660,10 +1550,10 @@ declare_syscall!( } ); -declare_syscall!( +declare_builtin_function!( /// alt_bn128 group operations SyscallAltBn128, - fn inner_call( + fn rust( invoke_context: &mut InvokeContext, group_op: u64, input_addr: u64, @@ -1747,10 +1637,10 @@ declare_syscall!( } ); -declare_syscall!( +declare_builtin_function!( /// Big integer modular exponentiation SyscallBigModExp, - fn inner_call( + fn rust( invoke_context: &mut InvokeContext, params: u64, return_value: u64, @@ -1826,10 +1716,10 @@ declare_syscall!( } ); -declare_syscall!( +declare_builtin_function!( // Poseidon SyscallPoseidon, - fn inner_call( + fn rust( invoke_context: &mut InvokeContext, parameters: u64, endianness: u64, @@ -1898,10 +1788,10 @@ declare_syscall!( } ); -declare_syscall!( +declare_builtin_function!( /// Read remaining compute units SyscallRemainingComputeUnits, - fn inner_call( + fn rust( invoke_context: &mut InvokeContext, _arg1: u64, _arg2: u64, @@ -1918,10 +1808,10 @@ declare_syscall!( } ); -declare_syscall!( +declare_builtin_function!( /// alt_bn128 g1 and g2 compression and decompression SyscallAltBn128Compression, - fn inner_call( + fn rust( invoke_context: &mut InvokeContext, op: u64, input_addr: u64, @@ -2021,6 +1911,75 @@ declare_syscall!( } ); +declare_builtin_function!( + // Generic Hashing Syscall + SyscallHash, + fn rust( + invoke_context: &mut InvokeContext, + vals_addr: u64, + vals_len: u64, + result_addr: u64, + _arg4: u64, + _arg5: u64, + memory_mapping: &mut MemoryMapping, + ) -> Result { + let compute_budget = invoke_context.get_compute_budget(); + let hash_base_cost = H::get_base_cost(compute_budget); + let hash_byte_cost = H::get_byte_cost(compute_budget); + let hash_max_slices = H::get_max_slices(compute_budget); + if hash_max_slices < vals_len { + ic_msg!( + invoke_context, + "{} Hashing {} sequences in one syscall is over the limit {}", + H::NAME, + vals_len, + hash_max_slices, + ); + return Err(SyscallError::TooManySlices.into()); + } + + consume_compute_meter(invoke_context, hash_base_cost)?; + + let hash_result = translate_slice_mut::( + memory_mapping, + result_addr, + std::mem::size_of::() as u64, + invoke_context.get_check_aligned(), + invoke_context.get_check_size(), + )?; + let mut hasher = H::create_hasher(); + if vals_len > 0 { + let vals = translate_slice::<&[u8]>( + memory_mapping, + vals_addr, + vals_len, + invoke_context.get_check_aligned(), + invoke_context.get_check_size(), + )?; + for val in vals.iter() { + let bytes = translate_slice::( + memory_mapping, + val.as_ptr() as u64, + val.len() as u64, + invoke_context.get_check_aligned(), + invoke_context.get_check_size(), + )?; + let cost = compute_budget.mem_op_base_cost.max( + hash_byte_cost.saturating_mul( + (val.len() as u64) + .checked_div(2) + .expect("div by non-zero literal"), + ), + ); + consume_compute_meter(invoke_context, cost)?; + hasher.hash(bytes); + } + } + hash_result.copy_from_slice(hasher.result().as_ref()); + Ok(0) + } +); + #[cfg(test)] #[allow(clippy::arithmetic_side_effects)] #[allow(clippy::indexing_slicing)] @@ -2034,16 +1993,13 @@ mod tests { core::slice, solana_program_runtime::{invoke_context::InvokeContext, with_mock_invoke_context}, solana_rbpf::{ - elf::SBPFVersion, - error::EbpfError, - memory_region::MemoryRegion, - vm::{BuiltinFunction, Config}, + error::EbpfError, memory_region::MemoryRegion, program::SBPFVersion, vm::Config, }, solana_sdk::{ account::{create_account_shared_data_for_test, AccountSharedData}, bpf_loader, fee_calculator::FeeCalculator, - hash::hashv, + hash::{hashv, HASH_BYTES}, instruction::Instruction, program::check_type_assumptions, stable_layout::stable_instruction::StableInstruction, @@ -2057,9 +2013,8 @@ mod tests { macro_rules! assert_access_violation { ($result:expr, $va:expr, $len:expr) => { match $result.unwrap_err().downcast_ref::().unwrap() { - EbpfError::AccessViolation(_, _, va, len, _) if $va == *va && $len == *len => {} - EbpfError::StackAccessViolation(_, _, va, len, _) if $va == *va && $len == *len => { - } + EbpfError::AccessViolation(_, va, len, _) if $va == *va && $len == *len => {} + EbpfError::StackAccessViolation(_, va, len, _) if $va == *va && $len == *len => {} _ => panic!(), } }; @@ -2295,17 +2250,7 @@ mod tests { prepare_mockup!(invoke_context, program_id, bpf_loader::id()); let config = Config::default(); let mut memory_mapping = MemoryMapping::new(vec![], &config, &SBPFVersion::V2).unwrap(); - let mut result = ProgramResult::Ok(0); - SyscallAbort::call( - &mut invoke_context, - 0, - 0, - 0, - 0, - 0, - &mut memory_mapping, - &mut result, - ); + let result = SyscallAbort::rust(&mut invoke_context, 0, 0, 0, 0, 0, &mut memory_mapping); result.unwrap(); } @@ -2324,8 +2269,7 @@ mod tests { .unwrap(); invoke_context.mock_set_remaining(string.len() as u64 - 1); - let mut result = ProgramResult::Ok(0); - SyscallPanic::call( + let result = SyscallPanic::rust( &mut invoke_context, 0x100000000, string.len() as u64, @@ -2333,16 +2277,14 @@ mod tests { 84, 0, &mut memory_mapping, - &mut result, ); assert_matches!( result, - ProgramResult::Err(error) if error.downcast_ref::().unwrap() == &InstructionError::ComputationalBudgetExceeded + Result::Err(error) if error.downcast_ref::().unwrap() == &InstructionError::ComputationalBudgetExceeded ); invoke_context.mock_set_remaining(string.len() as u64); - let mut result = ProgramResult::Ok(0); - SyscallPanic::call( + let result = SyscallPanic::rust( &mut invoke_context, 0x100000000, string.len() as u64, @@ -2350,7 +2292,6 @@ mod tests { 84, 0, &mut memory_mapping, - &mut result, ); result.unwrap(); } @@ -2369,8 +2310,7 @@ mod tests { .unwrap(); invoke_context.mock_set_remaining(400 - 1); - let mut result = ProgramResult::Ok(0); - SyscallLog::call( + let result = SyscallLog::rust( &mut invoke_context, 0x100000001, // AccessViolation string.len() as u64, @@ -2378,11 +2318,9 @@ mod tests { 0, 0, &mut memory_mapping, - &mut result, ); assert_access_violation!(result, 0x100000001, string.len() as u64); - let mut result = ProgramResult::Ok(0); - SyscallLog::call( + let result = SyscallLog::rust( &mut invoke_context, 0x100000000, string.len() as u64 * 2, // AccessViolation @@ -2390,12 +2328,10 @@ mod tests { 0, 0, &mut memory_mapping, - &mut result, ); assert_access_violation!(result, 0x100000000, string.len() as u64 * 2); - let mut result = ProgramResult::Ok(0); - SyscallLog::call( + let result = SyscallLog::rust( &mut invoke_context, 0x100000000, string.len() as u64, @@ -2403,11 +2339,9 @@ mod tests { 0, 0, &mut memory_mapping, - &mut result, ); result.unwrap(); - let mut result = ProgramResult::Ok(0); - SyscallLog::call( + let result = SyscallLog::rust( &mut invoke_context, 0x100000000, string.len() as u64, @@ -2415,11 +2349,10 @@ mod tests { 0, 0, &mut memory_mapping, - &mut result, ); assert_matches!( result, - ProgramResult::Err(error) if error.downcast_ref::().unwrap() == &InstructionError::ComputationalBudgetExceeded + Result::Err(error) if error.downcast_ref::().unwrap() == &InstructionError::ComputationalBudgetExceeded ); assert_eq!( @@ -2440,17 +2373,7 @@ mod tests { invoke_context.mock_set_remaining(cost); let config = Config::default(); let mut memory_mapping = MemoryMapping::new(vec![], &config, &SBPFVersion::V2).unwrap(); - let mut result = ProgramResult::Ok(0); - SyscallLogU64::call( - &mut invoke_context, - 1, - 2, - 3, - 4, - 5, - &mut memory_mapping, - &mut result, - ); + let result = SyscallLogU64::rust(&mut invoke_context, 1, 2, 3, 4, 5, &mut memory_mapping); result.unwrap(); assert_eq!( @@ -2477,8 +2400,7 @@ mod tests { ) .unwrap(); - let mut result = ProgramResult::Ok(0); - SyscallLogPubkey::call( + let result = SyscallLogPubkey::rust( &mut invoke_context, 0x100000001, // AccessViolation 32, @@ -2486,30 +2408,19 @@ mod tests { 0, 0, &mut memory_mapping, - &mut result, ); assert_access_violation!(result, 0x100000001, 32); invoke_context.mock_set_remaining(1); - let mut result = ProgramResult::Ok(0); - SyscallLogPubkey::call( - &mut invoke_context, - 100, - 32, - 0, - 0, - 0, - &mut memory_mapping, - &mut result, - ); + let result = + SyscallLogPubkey::rust(&mut invoke_context, 100, 32, 0, 0, 0, &mut memory_mapping); assert_matches!( result, - ProgramResult::Err(error) if error.downcast_ref::().unwrap() == &InstructionError::ComputationalBudgetExceeded + Result::Err(error) if error.downcast_ref::().unwrap() == &InstructionError::ComputationalBudgetExceeded ); invoke_context.mock_set_remaining(cost); - let mut result = ProgramResult::Ok(0); - SyscallLogPubkey::call( + let result = SyscallLogPubkey::rust( &mut invoke_context, 0x100000000, 0, @@ -2517,7 +2428,6 @@ mod tests { 0, 0, &mut memory_mapping, - &mut result, ); result.unwrap(); @@ -2540,8 +2450,7 @@ mod tests { let mut vm = vm.unwrap(); let invoke_context = &mut vm.context_object_pointer; let memory_mapping = &mut vm.memory_mapping; - let mut result = ProgramResult::Ok(0); - SyscallAllocFree::call( + let result = SyscallAllocFree::rust( invoke_context, solana_sdk::entrypoint::HEAP_LENGTH as u64, 0, @@ -2549,11 +2458,9 @@ mod tests { 0, 0, memory_mapping, - &mut result, ); assert_ne!(result.unwrap(), 0); - let mut result = ProgramResult::Ok(0); - SyscallAllocFree::call( + let result = SyscallAllocFree::rust( invoke_context, solana_sdk::entrypoint::HEAP_LENGTH as u64, 0, @@ -2561,20 +2468,10 @@ mod tests { 0, 0, memory_mapping, - &mut result, ); assert_eq!(result.unwrap(), 0); - let mut result = ProgramResult::Ok(0); - SyscallAllocFree::call( - invoke_context, - u64::MAX, - 0, - 0, - 0, - 0, - memory_mapping, - &mut result, - ); + let result = + SyscallAllocFree::rust(invoke_context, u64::MAX, 0, 0, 0, 0, memory_mapping); assert_eq!(result.unwrap(), 0); } @@ -2587,12 +2484,10 @@ mod tests { let invoke_context = &mut vm.context_object_pointer; let memory_mapping = &mut vm.memory_mapping; for _ in 0..100 { - let mut result = ProgramResult::Ok(0); - SyscallAllocFree::call(invoke_context, 1, 0, 0, 0, 0, memory_mapping, &mut result); + let result = SyscallAllocFree::rust(invoke_context, 1, 0, 0, 0, 0, memory_mapping); assert_ne!(result.unwrap(), 0); } - let mut result = ProgramResult::Ok(0); - SyscallAllocFree::call( + let result = SyscallAllocFree::rust( invoke_context, solana_sdk::entrypoint::HEAP_LENGTH as u64, 0, @@ -2600,7 +2495,6 @@ mod tests { 0, 0, memory_mapping, - &mut result, ); assert_eq!(result.unwrap(), 0); } @@ -2613,12 +2507,10 @@ mod tests { let invoke_context = &mut vm.context_object_pointer; let memory_mapping = &mut vm.memory_mapping; for _ in 0..12 { - let mut result = ProgramResult::Ok(0); - SyscallAllocFree::call(invoke_context, 1, 0, 0, 0, 0, memory_mapping, &mut result); + let result = SyscallAllocFree::rust(invoke_context, 1, 0, 0, 0, 0, memory_mapping); assert_ne!(result.unwrap(), 0); } - let mut result = ProgramResult::Ok(0); - SyscallAllocFree::call( + let result = SyscallAllocFree::rust( invoke_context, solana_sdk::entrypoint::HEAP_LENGTH as u64, 0, @@ -2626,7 +2518,6 @@ mod tests { 0, 0, memory_mapping, - &mut result, ); assert_eq!(result.unwrap(), 0); } @@ -2639,8 +2530,7 @@ mod tests { let mut vm = vm.unwrap(); let invoke_context = &mut vm.context_object_pointer; let memory_mapping = &mut vm.memory_mapping; - let mut result = ProgramResult::Ok(0); - SyscallAllocFree::call( + let result = SyscallAllocFree::rust( invoke_context, size_of::() as u64, 0, @@ -2648,7 +2538,6 @@ mod tests { 0, 0, memory_mapping, - &mut result, ); let address = result.unwrap(); assert_ne!(address, 0); @@ -2705,8 +2594,7 @@ mod tests { * 4, ); - let mut result = ProgramResult::Ok(0); - SyscallSha256::call( + let result = SyscallHash::rust::( &mut invoke_context, ro_va, ro_len, @@ -2714,14 +2602,12 @@ mod tests { 0, 0, &mut memory_mapping, - &mut result, ); result.unwrap(); let hash_local = hashv(&[bytes1.as_ref(), bytes2.as_ref()]).to_bytes(); assert_eq!(hash_result, hash_local); - let mut result = ProgramResult::Ok(0); - SyscallSha256::call( + let result = SyscallHash::rust::( &mut invoke_context, ro_va - 1, // AccessViolation ro_len, @@ -2729,11 +2615,9 @@ mod tests { 0, 0, &mut memory_mapping, - &mut result, ); assert_access_violation!(result, ro_va - 1, 32); - let mut result = ProgramResult::Ok(0); - SyscallSha256::call( + let result = SyscallHash::rust::( &mut invoke_context, ro_va, ro_len + 1, // AccessViolation @@ -2741,11 +2625,9 @@ mod tests { 0, 0, &mut memory_mapping, - &mut result, ); assert_access_violation!(result, ro_va, 48); - let mut result = ProgramResult::Ok(0); - SyscallSha256::call( + let result = SyscallHash::rust::( &mut invoke_context, ro_va, ro_len, @@ -2753,11 +2635,9 @@ mod tests { 0, 0, &mut memory_mapping, - &mut result, ); assert_access_violation!(result, rw_va - 1, HASH_BYTES as u64); - let mut result = ProgramResult::Ok(0); - SyscallSha256::call( + let result = SyscallHash::rust::( &mut invoke_context, ro_va, ro_len, @@ -2765,11 +2645,10 @@ mod tests { 0, 0, &mut memory_mapping, - &mut result, ); assert_matches!( result, - ProgramResult::Err(error) if error.downcast_ref::().unwrap() == &InstructionError::ComputationalBudgetExceeded + Result::Err(error) if error.downcast_ref::().unwrap() == &InstructionError::ComputationalBudgetExceeded ); } @@ -2809,8 +2688,7 @@ mod tests { * 2, ); - let mut result = ProgramResult::Ok(0); - SyscallCurvePointValidation::call( + let result = SyscallCurvePointValidation::rust( &mut invoke_context, CURVE25519_EDWARDS, valid_bytes_va, @@ -2818,12 +2696,10 @@ mod tests { 0, 0, &mut memory_mapping, - &mut result, ); assert_eq!(0, result.unwrap()); - let mut result = ProgramResult::Ok(0); - SyscallCurvePointValidation::call( + let result = SyscallCurvePointValidation::rust( &mut invoke_context, CURVE25519_EDWARDS, invalid_bytes_va, @@ -2831,12 +2707,10 @@ mod tests { 0, 0, &mut memory_mapping, - &mut result, ); assert_eq!(1, result.unwrap()); - let mut result = ProgramResult::Ok(0); - SyscallCurvePointValidation::call( + let result = SyscallCurvePointValidation::rust( &mut invoke_context, CURVE25519_EDWARDS, valid_bytes_va, @@ -2844,11 +2718,10 @@ mod tests { 0, 0, &mut memory_mapping, - &mut result, ); assert_matches!( result, - ProgramResult::Err(error) if error.downcast_ref::().unwrap() == &InstructionError::ComputationalBudgetExceeded + Result::Err(error) if error.downcast_ref::().unwrap() == &InstructionError::ComputationalBudgetExceeded ); } @@ -2888,8 +2761,7 @@ mod tests { * 2, ); - let mut result = ProgramResult::Ok(0); - SyscallCurvePointValidation::call( + let result = SyscallCurvePointValidation::rust( &mut invoke_context, CURVE25519_RISTRETTO, valid_bytes_va, @@ -2897,12 +2769,10 @@ mod tests { 0, 0, &mut memory_mapping, - &mut result, ); assert_eq!(0, result.unwrap()); - let mut result = ProgramResult::Ok(0); - SyscallCurvePointValidation::call( + let result = SyscallCurvePointValidation::rust( &mut invoke_context, CURVE25519_RISTRETTO, invalid_bytes_va, @@ -2910,12 +2780,10 @@ mod tests { 0, 0, &mut memory_mapping, - &mut result, ); assert_eq!(1, result.unwrap()); - let mut result = ProgramResult::Ok(0); - SyscallCurvePointValidation::call( + let result = SyscallCurvePointValidation::rust( &mut invoke_context, CURVE25519_RISTRETTO, valid_bytes_va, @@ -2923,11 +2791,10 @@ mod tests { 0, 0, &mut memory_mapping, - &mut result, ); assert_matches!( result, - ProgramResult::Err(error) if error.downcast_ref::().unwrap() == &InstructionError::ComputationalBudgetExceeded + Result::Err(error) if error.downcast_ref::().unwrap() == &InstructionError::ComputationalBudgetExceeded ); } @@ -2989,8 +2856,7 @@ mod tests { * 2, ); - let mut result = ProgramResult::Ok(0); - SyscallCurveGroupOps::call( + let result = SyscallCurveGroupOps::rust( &mut invoke_context, CURVE25519_EDWARDS, ADD, @@ -2998,7 +2864,6 @@ mod tests { right_point_va, result_point_va, &mut memory_mapping, - &mut result, ); assert_eq!(0, result.unwrap()); @@ -3008,8 +2873,7 @@ mod tests { ]; assert_eq!(expected_sum, result_point); - let mut result = ProgramResult::Ok(0); - SyscallCurveGroupOps::call( + let result = SyscallCurveGroupOps::rust( &mut invoke_context, CURVE25519_EDWARDS, ADD, @@ -3017,12 +2881,10 @@ mod tests { right_point_va, result_point_va, &mut memory_mapping, - &mut result, ); assert_eq!(1, result.unwrap()); - let mut result = ProgramResult::Ok(0); - SyscallCurveGroupOps::call( + let result = SyscallCurveGroupOps::rust( &mut invoke_context, CURVE25519_EDWARDS, SUB, @@ -3030,7 +2892,6 @@ mod tests { right_point_va, result_point_va, &mut memory_mapping, - &mut result, ); assert_eq!(0, result.unwrap()); @@ -3040,8 +2901,7 @@ mod tests { ]; assert_eq!(expected_difference, result_point); - let mut result = ProgramResult::Ok(0); - SyscallCurveGroupOps::call( + let result = SyscallCurveGroupOps::rust( &mut invoke_context, CURVE25519_EDWARDS, SUB, @@ -3049,12 +2909,10 @@ mod tests { right_point_va, result_point_va, &mut memory_mapping, - &mut result, ); assert_eq!(1, result.unwrap()); - let mut result = ProgramResult::Ok(0); - SyscallCurveGroupOps::call( + let result = SyscallCurveGroupOps::rust( &mut invoke_context, CURVE25519_EDWARDS, MUL, @@ -3062,7 +2920,6 @@ mod tests { right_point_va, result_point_va, &mut memory_mapping, - &mut result, ); result.unwrap(); @@ -3072,8 +2929,7 @@ mod tests { ]; assert_eq!(expected_product, result_point); - let mut result = ProgramResult::Ok(0); - SyscallCurveGroupOps::call( + let result = SyscallCurveGroupOps::rust( &mut invoke_context, CURVE25519_EDWARDS, MUL, @@ -3081,12 +2937,10 @@ mod tests { invalid_point_va, result_point_va, &mut memory_mapping, - &mut result, ); assert_eq!(1, result.unwrap()); - let mut result = ProgramResult::Ok(0); - SyscallCurveGroupOps::call( + let result = SyscallCurveGroupOps::rust( &mut invoke_context, CURVE25519_EDWARDS, MUL, @@ -3094,11 +2948,10 @@ mod tests { invalid_point_va, result_point_va, &mut memory_mapping, - &mut result, ); assert_matches!( result, - ProgramResult::Err(error) if error.downcast_ref::().unwrap() == &InstructionError::ComputationalBudgetExceeded + Result::Err(error) if error.downcast_ref::().unwrap() == &InstructionError::ComputationalBudgetExceeded ); } @@ -3160,8 +3013,7 @@ mod tests { * 2, ); - let mut result = ProgramResult::Ok(0); - SyscallCurveGroupOps::call( + let result = SyscallCurveGroupOps::rust( &mut invoke_context, CURVE25519_RISTRETTO, ADD, @@ -3169,7 +3021,6 @@ mod tests { right_point_va, result_point_va, &mut memory_mapping, - &mut result, ); assert_eq!(0, result.unwrap()); @@ -3179,8 +3030,7 @@ mod tests { ]; assert_eq!(expected_sum, result_point); - let mut result = ProgramResult::Ok(0); - SyscallCurveGroupOps::call( + let result = SyscallCurveGroupOps::rust( &mut invoke_context, CURVE25519_RISTRETTO, ADD, @@ -3188,12 +3038,10 @@ mod tests { right_point_va, result_point_va, &mut memory_mapping, - &mut result, ); assert_eq!(1, result.unwrap()); - let mut result = ProgramResult::Ok(0); - SyscallCurveGroupOps::call( + let result = SyscallCurveGroupOps::rust( &mut invoke_context, CURVE25519_RISTRETTO, SUB, @@ -3201,7 +3049,6 @@ mod tests { right_point_va, result_point_va, &mut memory_mapping, - &mut result, ); assert_eq!(0, result.unwrap()); @@ -3211,8 +3058,7 @@ mod tests { ]; assert_eq!(expected_difference, result_point); - let mut result = ProgramResult::Ok(0); - SyscallCurveGroupOps::call( + let result = SyscallCurveGroupOps::rust( &mut invoke_context, CURVE25519_RISTRETTO, SUB, @@ -3220,13 +3066,11 @@ mod tests { right_point_va, result_point_va, &mut memory_mapping, - &mut result, ); assert_eq!(1, result.unwrap()); - let mut result = ProgramResult::Ok(0); - SyscallCurveGroupOps::call( + let result = SyscallCurveGroupOps::rust( &mut invoke_context, CURVE25519_RISTRETTO, MUL, @@ -3234,7 +3078,6 @@ mod tests { right_point_va, result_point_va, &mut memory_mapping, - &mut result, ); result.unwrap(); @@ -3244,8 +3087,7 @@ mod tests { ]; assert_eq!(expected_product, result_point); - let mut result = ProgramResult::Ok(0); - SyscallCurveGroupOps::call( + let result = SyscallCurveGroupOps::rust( &mut invoke_context, CURVE25519_RISTRETTO, MUL, @@ -3253,13 +3095,11 @@ mod tests { invalid_point_va, result_point_va, &mut memory_mapping, - &mut result, ); assert_eq!(1, result.unwrap()); - let mut result = ProgramResult::Ok(0); - SyscallCurveGroupOps::call( + let result = SyscallCurveGroupOps::rust( &mut invoke_context, CURVE25519_RISTRETTO, MUL, @@ -3267,11 +3107,10 @@ mod tests { invalid_point_va, result_point_va, &mut memory_mapping, - &mut result, ); assert_matches!( result, - ProgramResult::Err(error) if error.downcast_ref::().unwrap() == &InstructionError::ComputationalBudgetExceeded + Result::Err(error) if error.downcast_ref::().unwrap() == &InstructionError::ComputationalBudgetExceeded ); } @@ -3348,8 +3187,7 @@ mod tests { .curve25519_ristretto_msm_incremental_cost, ); - let mut result = ProgramResult::Ok(0); - SyscallCurveMultiscalarMultiplication::call( + let result = SyscallCurveMultiscalarMultiplication::rust( &mut invoke_context, CURVE25519_EDWARDS, scalars_va, @@ -3357,7 +3195,6 @@ mod tests { 2, result_point_va, &mut memory_mapping, - &mut result, ); assert_eq!(0, result.unwrap()); @@ -3367,8 +3204,7 @@ mod tests { ]; assert_eq!(expected_product, result_point); - let mut result = ProgramResult::Ok(0); - SyscallCurveMultiscalarMultiplication::call( + let result = SyscallCurveMultiscalarMultiplication::rust( &mut invoke_context, CURVE25519_RISTRETTO, scalars_va, @@ -3376,7 +3212,6 @@ mod tests { 2, result_point_va, &mut memory_mapping, - &mut result, ); assert_eq!(0, result.unwrap()); @@ -3387,6 +3222,122 @@ mod tests { assert_eq!(expected_product, result_point); } + #[test] + fn test_syscall_multiscalar_multiplication_maximum_length_exceeded() { + use solana_zk_token_sdk::curve25519::curve_syscall_traits::{ + CURVE25519_EDWARDS, CURVE25519_RISTRETTO, + }; + + let config = Config::default(); + prepare_mockup!(invoke_context, program_id, bpf_loader::id()); + + let scalar: [u8; 32] = [ + 254, 198, 23, 138, 67, 243, 184, 110, 236, 115, 236, 205, 205, 215, 79, 114, 45, 250, + 78, 137, 3, 107, 136, 237, 49, 126, 117, 223, 37, 191, 88, 6, + ]; + let scalars = [scalar; 513]; + let scalars_va = 0x100000000; + + let edwards_point: [u8; 32] = [ + 252, 31, 230, 46, 173, 95, 144, 148, 158, 157, 63, 10, 8, 68, 58, 176, 142, 192, 168, + 53, 61, 105, 194, 166, 43, 56, 246, 236, 28, 146, 114, 133, + ]; + let edwards_points = [edwards_point; 513]; + let edwards_points_va = 0x200000000; + + let ristretto_point: [u8; 32] = [ + 130, 35, 97, 25, 18, 199, 33, 239, 85, 143, 119, 111, 49, 51, 224, 40, 167, 185, 240, + 179, 25, 194, 213, 41, 14, 155, 104, 18, 181, 197, 15, 112, + ]; + let ristretto_points = [ristretto_point; 513]; + let ristretto_points_va = 0x300000000; + + let mut result_point: [u8; 32] = [0; 32]; + let result_point_va = 0x400000000; + + let mut memory_mapping = MemoryMapping::new( + vec![ + MemoryRegion::new_readonly(bytes_of_slice(&scalars), scalars_va), + MemoryRegion::new_readonly(bytes_of_slice(&edwards_points), edwards_points_va), + MemoryRegion::new_readonly(bytes_of_slice(&ristretto_points), ristretto_points_va), + MemoryRegion::new_writable(bytes_of_slice_mut(&mut result_point), result_point_va), + ], + &config, + &SBPFVersion::V2, + ) + .unwrap(); + + // test Edwards + invoke_context.mock_set_remaining(500_000); + let result = SyscallCurveMultiscalarMultiplication::rust( + &mut invoke_context, + CURVE25519_EDWARDS, + scalars_va, + edwards_points_va, + 512, // below maximum vector length + result_point_va, + &mut memory_mapping, + ); + + assert_eq!(0, result.unwrap()); + let expected_product = [ + 20, 146, 226, 37, 22, 61, 86, 249, 208, 40, 38, 11, 126, 101, 10, 82, 81, 77, 88, 209, + 15, 76, 82, 251, 180, 133, 84, 243, 162, 0, 11, 145, + ]; + assert_eq!(expected_product, result_point); + + invoke_context.mock_set_remaining(500_000); + let result = SyscallCurveMultiscalarMultiplication::rust( + &mut invoke_context, + CURVE25519_EDWARDS, + scalars_va, + edwards_points_va, + 513, // above maximum vector length + result_point_va, + &mut memory_mapping, + ) + .unwrap_err() + .downcast::() + .unwrap(); + + assert_eq!(*result, SyscallError::InvalidLength); + + // test Ristretto + invoke_context.mock_set_remaining(500_000); + let result = SyscallCurveMultiscalarMultiplication::rust( + &mut invoke_context, + CURVE25519_RISTRETTO, + scalars_va, + ristretto_points_va, + 512, // below maximum vector length + result_point_va, + &mut memory_mapping, + ); + + assert_eq!(0, result.unwrap()); + let expected_product = [ + 146, 224, 127, 193, 252, 64, 196, 181, 246, 104, 27, 116, 183, 52, 200, 239, 2, 108, + 21, 27, 97, 44, 95, 65, 26, 218, 223, 39, 197, 132, 51, 49, + ]; + assert_eq!(expected_product, result_point); + + invoke_context.mock_set_remaining(500_000); + let result = SyscallCurveMultiscalarMultiplication::rust( + &mut invoke_context, + CURVE25519_RISTRETTO, + scalars_va, + ristretto_points_va, + 513, // above maximum vector length + result_point_va, + &mut memory_mapping, + ) + .unwrap_err() + .downcast::() + .unwrap(); + + assert_eq!(*result, SyscallError::InvalidLength); + } + fn create_filled_type(zero_init: bool) -> T { let mut val = T::default(); let p = &mut val as *mut _ as *mut u8; @@ -3488,8 +3439,7 @@ mod tests { ) .unwrap(); - let mut result = ProgramResult::Ok(0); - SyscallGetClockSysvar::call( + let result = SyscallGetClockSysvar::rust( &mut invoke_context, got_clock_va, 0, @@ -3497,7 +3447,6 @@ mod tests { 0, 0, &mut memory_mapping, - &mut result, ); result.unwrap(); assert_eq!(got_clock, src_clock); @@ -3526,8 +3475,7 @@ mod tests { ) .unwrap(); - let mut result = ProgramResult::Ok(0); - SyscallGetEpochScheduleSysvar::call( + let result = SyscallGetEpochScheduleSysvar::rust( &mut invoke_context, got_epochschedule_va, 0, @@ -3535,7 +3483,6 @@ mod tests { 0, 0, &mut memory_mapping, - &mut result, ); result.unwrap(); assert_eq!(got_epochschedule, src_epochschedule); @@ -3565,8 +3512,7 @@ mod tests { ) .unwrap(); - let mut result = ProgramResult::Ok(0); - SyscallGetFeesSysvar::call( + let result = SyscallGetFeesSysvar::rust( &mut invoke_context, got_fees_va, 0, @@ -3574,7 +3520,6 @@ mod tests { 0, 0, &mut memory_mapping, - &mut result, ); result.unwrap(); assert_eq!(got_fees, src_fees); @@ -3599,8 +3544,7 @@ mod tests { ) .unwrap(); - let mut result = ProgramResult::Ok(0); - SyscallGetRentSysvar::call( + let result = SyscallGetRentSysvar::rust( &mut invoke_context, got_rent_va, 0, @@ -3608,7 +3552,6 @@ mod tests { 0, 0, &mut memory_mapping, - &mut result, ); result.unwrap(); assert_eq!(got_rent, src_rent); @@ -3635,8 +3578,7 @@ mod tests { ) .unwrap(); - let mut result = ProgramResult::Ok(0); - SyscallGetEpochRewardsSysvar::call( + let result = SyscallGetEpochRewardsSysvar::rust( &mut invoke_context, got_rewards_va, 0, @@ -3644,7 +3586,6 @@ mod tests { 0, 0, &mut memory_mapping, - &mut result, ); result.unwrap(); assert_eq!(got_rewards, src_rewards); @@ -3658,12 +3599,22 @@ mod tests { } } + type BuiltinFunctionRustInterface<'a> = fn( + &mut InvokeContext<'a>, + u64, + u64, + u64, + u64, + u64, + &mut MemoryMapping, + ) -> Result>; + fn call_program_address_common<'a, 'b: 'a>( invoke_context: &'a mut InvokeContext<'b>, seeds: &[&[u8]], program_id: &Pubkey, overlap_outputs: bool, - syscall: BuiltinFunction>, + syscall: BuiltinFunctionRustInterface<'b>, ) -> Result<(Pubkey, u8), Error> { const SEEDS_VA: u64 = 0x100000000; const PROGRAM_ID_VA: u64 = 0x200000000; @@ -3696,8 +3647,7 @@ mod tests { )); let mut memory_mapping = MemoryMapping::new(regions, &config, &SBPFVersion::V2).unwrap(); - let mut result = ProgramResult::Ok(0); - syscall( + let result = syscall( invoke_context, SEEDS_VA, seeds.len() as u64, @@ -3709,9 +3659,8 @@ mod tests { BUMP_SEED_VA }, &mut memory_mapping, - &mut result, ); - Result::::from(result).map(|_| (address, bump_seed)) + result.map(|_| (address, bump_seed)) } fn create_program_address( @@ -3724,7 +3673,7 @@ mod tests { seeds, address, false, - SyscallCreateProgramAddress::call, + SyscallCreateProgramAddress::rust, )?; Ok(address) } @@ -3739,7 +3688,7 @@ mod tests { seeds, address, false, - SyscallTryFindProgramAddress::call, + SyscallTryFindProgramAddress::rust, ) } @@ -3766,8 +3715,7 @@ mod tests { prepare_mockup!(invoke_context, program_id, bpf_loader::id()); - let mut result = ProgramResult::Ok(0); - SyscallSetReturnData::call( + let result = SyscallSetReturnData::rust( &mut invoke_context, SRC_VA, data.len() as u64, @@ -3775,12 +3723,10 @@ mod tests { 0, 0, &mut memory_mapping, - &mut result, ); assert_eq!(result.unwrap(), 0); - let mut result = ProgramResult::Ok(0); - SyscallGetReturnData::call( + let result = SyscallGetReturnData::rust( &mut invoke_context, DST_VA, data_buffer.len() as u64, @@ -3788,14 +3734,12 @@ mod tests { 0, 0, &mut memory_mapping, - &mut result, ); assert_eq!(result.unwrap() as usize, data.len()); assert_eq!(data.get(0..data_buffer.len()).unwrap(), data_buffer); assert_eq!(id_buffer, program_id.to_bytes()); - let mut result = ProgramResult::Ok(0); - SyscallGetReturnData::call( + let result = SyscallGetReturnData::rust( &mut invoke_context, PROGRAM_ID_VA, data_buffer.len() as u64, @@ -3803,11 +3747,10 @@ mod tests { 0, 0, &mut memory_mapping, - &mut result, ); assert_matches!( result, - ProgramResult::Err(error) if error.downcast_ref::().unwrap() == &SyscallError::CopyOverlapping + Result::Err(error) if error.downcast_ref::().unwrap() == &SyscallError::CopyOverlapping ); } @@ -3901,8 +3844,7 @@ mod tests { .unwrap(); invoke_context.mock_set_remaining(syscall_base_cost); - let mut result = ProgramResult::Ok(0); - SyscallGetProcessedSiblingInstruction::call( + let result = SyscallGetProcessedSiblingInstruction::rust( &mut invoke_context, 0, VM_BASE_ADDRESS.saturating_add(META_OFFSET as u64), @@ -3910,7 +3852,6 @@ mod tests { VM_BASE_ADDRESS.saturating_add(DATA_OFFSET as u64), VM_BASE_ADDRESS.saturating_add(ACCOUNTS_OFFSET as u64), &mut memory_mapping, - &mut result, ); assert_eq!(result.unwrap(), 1); { @@ -3933,8 +3874,7 @@ mod tests { } invoke_context.mock_set_remaining(syscall_base_cost); - let mut result = ProgramResult::Ok(0); - SyscallGetProcessedSiblingInstruction::call( + let result = SyscallGetProcessedSiblingInstruction::rust( &mut invoke_context, 1, VM_BASE_ADDRESS.saturating_add(META_OFFSET as u64), @@ -3942,13 +3882,11 @@ mod tests { VM_BASE_ADDRESS.saturating_add(DATA_OFFSET as u64), VM_BASE_ADDRESS.saturating_add(ACCOUNTS_OFFSET as u64), &mut memory_mapping, - &mut result, ); assert_eq!(result.unwrap(), 0); invoke_context.mock_set_remaining(syscall_base_cost); - let mut result = ProgramResult::Ok(0); - SyscallGetProcessedSiblingInstruction::call( + let result = SyscallGetProcessedSiblingInstruction::rust( &mut invoke_context, 0, VM_BASE_ADDRESS.saturating_add(META_OFFSET as u64), @@ -3956,11 +3894,10 @@ mod tests { VM_BASE_ADDRESS.saturating_add(META_OFFSET as u64), VM_BASE_ADDRESS.saturating_add(META_OFFSET as u64), &mut memory_mapping, - &mut result, ); assert_matches!( result, - ProgramResult::Err(error) if error.downcast_ref::().unwrap() == &SyscallError::CopyOverlapping + Result::Err(error) if error.downcast_ref::().unwrap() == &SyscallError::CopyOverlapping ); } @@ -4142,7 +4079,7 @@ mod tests { seeds, &address, true, - SyscallTryFindProgramAddress::call, + SyscallTryFindProgramAddress::rust, ), Result::Err(error) if error.downcast_ref::().unwrap() == &SyscallError::CopyOverlapping ); @@ -4190,8 +4127,7 @@ mod tests { + (MAX_LEN * MAX_LEN) / budget.big_modular_exponentiation_cost, ); - let mut result = ProgramResult::Ok(0); - SyscallBigModExp::call( + let result = SyscallBigModExp::rust( &mut invoke_context, VADDR_PARAMS, VADDR_OUT, @@ -4199,7 +4135,6 @@ mod tests { 0, 0, &mut memory_mapping, - &mut result, ); assert_eq!(result.unwrap(), 0); @@ -4233,8 +4168,7 @@ mod tests { + (INV_LEN * INV_LEN) / budget.big_modular_exponentiation_cost, ); - let mut result = ProgramResult::Ok(0); - SyscallBigModExp::call( + let result = SyscallBigModExp::rust( &mut invoke_context, VADDR_PARAMS, VADDR_OUT, @@ -4242,12 +4176,11 @@ mod tests { 0, 0, &mut memory_mapping, - &mut result, ); assert_matches!( result, - ProgramResult::Err(error) if error.downcast_ref::().unwrap() == &SyscallError::InvalidLength + Result::Err(error) if error.downcast_ref::().unwrap() == &SyscallError::InvalidLength ); } } diff --git a/programs/bpf_loader/src/syscalls/sysvar.rs b/programs/bpf_loader/src/syscalls/sysvar.rs index d86402b3407..e8777569cef 100644 --- a/programs/bpf_loader/src/syscalls/sysvar.rs +++ b/programs/bpf_loader/src/syscalls/sysvar.rs @@ -1,4 +1,4 @@ -use {super::*, crate::declare_syscall}; +use super::*; fn get_sysvar( sysvar: Result, InstructionError>, @@ -22,10 +22,10 @@ fn get_sysvar( Ok(SUCCESS) } -declare_syscall!( +declare_builtin_function!( /// Get a Clock sysvar SyscallGetClockSysvar, - fn inner_call( + fn rust( invoke_context: &mut InvokeContext, var_addr: u64, _arg2: u64, @@ -44,10 +44,10 @@ declare_syscall!( } ); -declare_syscall!( +declare_builtin_function!( /// Get a EpochSchedule sysvar SyscallGetEpochScheduleSysvar, - fn inner_call( + fn rust( invoke_context: &mut InvokeContext, var_addr: u64, _arg2: u64, @@ -66,10 +66,10 @@ declare_syscall!( } ); -declare_syscall!( +declare_builtin_function!( /// Get a EpochRewards sysvar SyscallGetEpochRewardsSysvar, - fn inner_call( + fn rust( invoke_context: &mut InvokeContext, var_addr: u64, _arg2: u64, @@ -88,10 +88,10 @@ declare_syscall!( } ); -declare_syscall!( +declare_builtin_function!( /// Get a Fees sysvar SyscallGetFeesSysvar, - fn inner_call( + fn rust( invoke_context: &mut InvokeContext, var_addr: u64, _arg2: u64, @@ -113,10 +113,10 @@ declare_syscall!( } ); -declare_syscall!( +declare_builtin_function!( /// Get a Rent sysvar SyscallGetRentSysvar, - fn inner_call( + fn rust( invoke_context: &mut InvokeContext, var_addr: u64, _arg2: u64, @@ -135,10 +135,10 @@ declare_syscall!( } ); -declare_syscall!( +declare_builtin_function!( /// Get a Last Restart Slot sysvar SyscallGetLastRestartSlotSysvar, - fn inner_call( + fn rust( invoke_context: &mut InvokeContext, var_addr: u64, _arg2: u64, diff --git a/programs/compute-budget/src/lib.rs b/programs/compute-budget/src/lib.rs index e296ca3a2f8..01bbd7a8b4f 100644 --- a/programs/compute-budget/src/lib.rs +++ b/programs/compute-budget/src/lib.rs @@ -2,11 +2,7 @@ use solana_program_runtime::declare_process_instruction; pub const DEFAULT_COMPUTE_UNITS: u64 = 150; -declare_process_instruction!( - process_instruction, - DEFAULT_COMPUTE_UNITS, - |_invoke_context| { - // Do nothing, compute budget instructions handled by the runtime - Ok(()) - } -); +declare_process_instruction!(Entrypoint, DEFAULT_COMPUTE_UNITS, |_invoke_context| { + // Do nothing, compute budget instructions handled by the runtime + Ok(()) +}); diff --git a/programs/config/src/config_processor.rs b/programs/config/src/config_processor.rs index 628e77cb93a..d0534056984 100644 --- a/programs/config/src/config_processor.rs +++ b/programs/config/src/config_processor.rs @@ -13,131 +13,127 @@ use { pub const DEFAULT_COMPUTE_UNITS: u64 = 450; -declare_process_instruction!( - process_instruction, - DEFAULT_COMPUTE_UNITS, - |invoke_context| { - let transaction_context = &invoke_context.transaction_context; - let instruction_context = transaction_context.get_current_instruction_context()?; - let data = instruction_context.get_instruction_data(); - - let key_list: ConfigKeys = limited_deserialize(data)?; - let config_account_key = transaction_context.get_key_of_account_at_index( - instruction_context.get_index_of_instruction_account_in_transaction(0)?, - )?; - let config_account = - instruction_context.try_borrow_instruction_account(transaction_context, 0)?; - let is_config_account_signer = config_account.is_signer(); - let current_data: ConfigKeys = { - if config_account.get_owner() != &crate::id() { - return Err(InstructionError::InvalidAccountOwner); - } +declare_process_instruction!(Entrypoint, DEFAULT_COMPUTE_UNITS, |invoke_context| { + let transaction_context = &invoke_context.transaction_context; + let instruction_context = transaction_context.get_current_instruction_context()?; + let data = instruction_context.get_instruction_data(); + + let key_list: ConfigKeys = limited_deserialize(data)?; + let config_account_key = transaction_context.get_key_of_account_at_index( + instruction_context.get_index_of_instruction_account_in_transaction(0)?, + )?; + let config_account = + instruction_context.try_borrow_instruction_account(transaction_context, 0)?; + let is_config_account_signer = config_account.is_signer(); + let current_data: ConfigKeys = { + if config_account.get_owner() != &crate::id() { + return Err(InstructionError::InvalidAccountOwner); + } - deserialize(config_account.get_data()).map_err(|err| { - ic_msg!( - invoke_context, - "Unable to deserialize config account: {}", - err - ); - InstructionError::InvalidAccountData - })? - }; - drop(config_account); - - let current_signer_keys: Vec = current_data - .keys - .iter() - .filter(|(_, is_signer)| *is_signer) - .map(|(pubkey, _)| *pubkey) - .collect(); - if current_signer_keys.is_empty() { - // Config account keypair must be a signer on account initialization, - // or when no signers specified in Config data - if !is_config_account_signer { - return Err(InstructionError::MissingRequiredSignature); - } + deserialize(config_account.get_data()).map_err(|err| { + ic_msg!( + invoke_context, + "Unable to deserialize config account: {}", + err + ); + InstructionError::InvalidAccountData + })? + }; + drop(config_account); + + let current_signer_keys: Vec = current_data + .keys + .iter() + .filter(|(_, is_signer)| *is_signer) + .map(|(pubkey, _)| *pubkey) + .collect(); + if current_signer_keys.is_empty() { + // Config account keypair must be a signer on account initialization, + // or when no signers specified in Config data + if !is_config_account_signer { + return Err(InstructionError::MissingRequiredSignature); } + } - let mut counter = 0; - for (signer, _) in key_list.keys.iter().filter(|(_, is_signer)| *is_signer) { - counter += 1; - if signer != config_account_key { - let signer_account = instruction_context - .try_borrow_instruction_account(transaction_context, counter as IndexOfAccount) - .map_err(|_| { - ic_msg!( - invoke_context, - "account {:?} is not in account list", - signer, - ); - InstructionError::MissingRequiredSignature - })?; - if !signer_account.is_signer() { - ic_msg!( - invoke_context, - "account {:?} signer_key().is_none()", - signer - ); - return Err(InstructionError::MissingRequiredSignature); - } - if signer_account.get_key() != signer { + let mut counter = 0; + for (signer, _) in key_list.keys.iter().filter(|(_, is_signer)| *is_signer) { + counter += 1; + if signer != config_account_key { + let signer_account = instruction_context + .try_borrow_instruction_account(transaction_context, counter as IndexOfAccount) + .map_err(|_| { ic_msg!( invoke_context, - "account[{:?}].signer_key() does not match Config data)", - counter + 1 + "account {:?} is not in account list", + signer, ); - return Err(InstructionError::MissingRequiredSignature); - } - // If Config account is already initialized, update signatures must match Config data - if !current_data.keys.is_empty() - && !current_signer_keys.iter().any(|pubkey| pubkey == signer) - { - ic_msg!( - invoke_context, - "account {:?} is not in stored signer list", - signer - ); - return Err(InstructionError::MissingRequiredSignature); - } - } else if !is_config_account_signer { - ic_msg!(invoke_context, "account[0].signer_key().is_none()"); + InstructionError::MissingRequiredSignature + })?; + if !signer_account.is_signer() { + ic_msg!( + invoke_context, + "account {:?} signer_key().is_none()", + signer + ); return Err(InstructionError::MissingRequiredSignature); } - } - - if invoke_context - .feature_set - .is_active(&feature_set::dedupe_config_program_signers::id()) - { - let total_new_keys = key_list.keys.len(); - let unique_new_keys = key_list.keys.into_iter().collect::>(); - if unique_new_keys.len() != total_new_keys { - ic_msg!(invoke_context, "new config contains duplicate keys"); - return Err(InstructionError::InvalidArgument); + if signer_account.get_key() != signer { + ic_msg!( + invoke_context, + "account[{:?}].signer_key() does not match Config data)", + counter + 1 + ); + return Err(InstructionError::MissingRequiredSignature); } - } - - // Check for Config data signers not present in incoming account update - if current_signer_keys.len() > counter { - ic_msg!( - invoke_context, - "too few signers: {:?}; expected: {:?}", - counter, - current_signer_keys.len() - ); + // If Config account is already initialized, update signatures must match Config data + if !current_data.keys.is_empty() + && !current_signer_keys.iter().any(|pubkey| pubkey == signer) + { + ic_msg!( + invoke_context, + "account {:?} is not in stored signer list", + signer + ); + return Err(InstructionError::MissingRequiredSignature); + } + } else if !is_config_account_signer { + ic_msg!(invoke_context, "account[0].signer_key().is_none()"); return Err(InstructionError::MissingRequiredSignature); } + } - let mut config_account = - instruction_context.try_borrow_instruction_account(transaction_context, 0)?; - if config_account.get_data().len() < data.len() { - ic_msg!(invoke_context, "instruction data too large"); - return Err(InstructionError::InvalidInstructionData); + if invoke_context + .feature_set + .is_active(&feature_set::dedupe_config_program_signers::id()) + { + let total_new_keys = key_list.keys.len(); + let unique_new_keys = key_list.keys.into_iter().collect::>(); + if unique_new_keys.len() != total_new_keys { + ic_msg!(invoke_context, "new config contains duplicate keys"); + return Err(InstructionError::InvalidArgument); } - config_account.get_data_mut()?[..data.len()].copy_from_slice(data); - Ok(()) } -); + + // Check for Config data signers not present in incoming account update + if current_signer_keys.len() > counter { + ic_msg!( + invoke_context, + "too few signers: {:?}; expected: {:?}", + counter, + current_signer_keys.len() + ); + return Err(InstructionError::MissingRequiredSignature); + } + + let mut config_account = + instruction_context.try_borrow_instruction_account(transaction_context, 0)?; + if config_account.get_data().len() < data.len() { + ic_msg!(invoke_context, "instruction data too large"); + return Err(InstructionError::InvalidInstructionData); + } + config_account.get_data_mut()?[..data.len()].copy_from_slice(data); + Ok(()) +}); #[cfg(test)] mod tests { @@ -169,7 +165,7 @@ mod tests { transaction_accounts, instruction_accounts, expected_result, - super::process_instruction, + Entrypoint::vm, |_invoke_context| {}, |_invoke_context| {}, ) diff --git a/programs/loader-v4/src/lib.rs b/programs/loader-v4/src/lib.rs index 4645b33c26a..4047de8b092 100644 --- a/programs/loader-v4/src/lib.rs +++ b/programs/loader-v4/src/lib.rs @@ -12,10 +12,12 @@ use { }, solana_rbpf::{ aligned_memory::AlignedMemory, - ebpf, - elf::{Executable, FunctionRegistry}, + declare_builtin_function, ebpf, + elf::Executable, + error::ProgramResult, memory_region::{MemoryMapping, MemoryRegion}, - vm::{BuiltinProgram, Config, ContextObject, EbpfVm, ProgramResult}, + program::{BuiltinProgram, FunctionRegistry}, + vm::{Config, ContextObject, EbpfVm}, }, solana_sdk::{ entrypoint::SUCCESS, @@ -81,7 +83,6 @@ pub fn create_program_runtime_environment_v2<'a>( reject_broken_elfs: true, noop_instruction_rate: 256, sanitize_user_provided_values: true, - encrypt_runtime_environment: true, external_internal_function_hash_collision: true, reject_callx_r10: true, enable_sbpf_v1: false, @@ -131,7 +132,7 @@ pub fn create_vm<'a, 'b>( Box::new(InstructionError::ProgramEnvironmentSetupFailure) })?; Ok(EbpfVm::new( - config, + program.get_loader().clone(), sbpf_version, invoke_context, memory_mapping, @@ -182,9 +183,9 @@ fn execute<'a, 'b: 'a>( match result { ProgramResult::Ok(status) if status != SUCCESS => { let error: InstructionError = status.into(); - Err(Box::new(error) as Box) + Err(error.into()) } - ProgramResult::Err(error) => Err(error), + ProgramResult::Err(error) => Err(error.into()), _ => Ok(()), } } @@ -523,18 +524,20 @@ pub fn process_instruction_transfer_authority( Ok(()) } -pub fn process_instruction( - invoke_context: &mut InvokeContext, - _arg0: u64, - _arg1: u64, - _arg2: u64, - _arg3: u64, - _arg4: u64, - _memory_mapping: &mut MemoryMapping, - result: &mut ProgramResult, -) { - *result = process_instruction_inner(invoke_context).into(); -} +declare_builtin_function!( + Entrypoint, + fn rust( + invoke_context: &mut InvokeContext, + _arg0: u64, + _arg1: u64, + _arg2: u64, + _arg3: u64, + _arg4: u64, + _memory_mapping: &mut MemoryMapping, + ) -> Result> { + process_instruction_inner(invoke_context) + } +); pub fn process_instruction_inner( invoke_context: &mut InvokeContext, @@ -696,7 +699,7 @@ mod tests { transaction_accounts, instruction_accounts, expected_result, - super::process_instruction, + Entrypoint::vm, |invoke_context| { invoke_context .programs_modified_by_tx diff --git a/programs/sbf/Cargo.lock b/programs/sbf/Cargo.lock index 3a113d5336a..68ce913f2bf 100644 --- a/programs/sbf/Cargo.lock +++ b/programs/sbf/Cargo.lock @@ -63,6 +63,78 @@ dependencies = [ "zeroize", ] +[[package]] +name = "agave-geyser-plugin-interface" +version = "1.17.30" +dependencies = [ + "log", + "solana-sdk", + "solana-transaction-status", + "thiserror", +] + +[[package]] +name = "agave-validator" +version = "1.17.30" +dependencies = [ + "agave-geyser-plugin-interface", + "chrono", + "clap 2.33.3", + "console", + "core_affinity", + "crossbeam-channel", + "fd-lock", + "indicatif", + "itertools", + "jsonrpc-core", + "jsonrpc-core-client", + "jsonrpc-derive", + "jsonrpc-ipc-server", + "jsonrpc-server-utils", + "lazy_static", + "libc", + "libloading", + "log", + "num_cpus", + "rand 0.8.5", + "rayon", + "serde", + "serde_json", + "serde_yaml", + "signal-hook", + "solana-accounts-db", + "solana-clap-utils", + "solana-cli-config", + "solana-core", + "solana-download-utils", + "solana-entry", + "solana-faucet", + "solana-genesis-utils", + "solana-geyser-plugin-manager", + "solana-gossip", + "solana-ledger", + "solana-logger", + "solana-metrics", + "solana-net-utils", + "solana-perf", + "solana-poh", + "solana-rpc", + "solana-rpc-client", + "solana-rpc-client-api", + "solana-runtime", + "solana-sdk", + "solana-send-transaction-service", + "solana-storage-bigtable", + "solana-streamer", + "solana-test-validator", + "solana-tpu-client", + "solana-version", + "solana-vote-program", + "symlink", + "thiserror", + "tikv-jemallocator", +] + [[package]] name = "ahash" version = "0.7.6" @@ -76,14 +148,15 @@ dependencies = [ [[package]] name = "ahash" -version = "0.8.3" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" +checksum = "cd7d5a2cecb58716e47d67d5703a249964b14c7be1ec3cad3affc295b2d1c35d" dependencies = [ "cfg-if 1.0.0", "getrandom 0.2.10", "once_cell", "version_check", + "zerocopy", ] [[package]] @@ -668,7 +741,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4114279215a005bc675e386011e594e1d9b800918cea18fcadadcce864a2046b" dependencies = [ "borsh-derive 0.10.3", - "hashbrown 0.13.2", + "hashbrown 0.12.3", ] [[package]] @@ -1890,9 +1963,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.18" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17f8a914c2987b688368b5138aa05321db91f4090cf26118185672ad588bce21" +checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" dependencies = [ "bytes", "fnv", @@ -1900,7 +1973,7 @@ dependencies = [ "futures-sink", "futures-util", "http", - "indexmap 1.9.3", + "indexmap 2.0.1", "slab", "tokio", "tokio-util 0.7.1", @@ -1940,7 +2013,7 @@ version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" dependencies = [ - "ahash 0.8.3", + "ahash 0.8.5", ] [[package]] @@ -2604,12 +2677,13 @@ dependencies = [ [[package]] name = "light-poseidon" -version = "0.1.1" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "949bdd22e4ed93481d45e9a6badb34b99132bcad0c8a8d4f05c42f7dcc7b90bc" +checksum = "3c9a85a9752c549ceb7578064b4ed891179d20acd85f27318573b64d2d7ee7ee" dependencies = [ "ark-bn254", "ark-ff", + "num-bigint 0.4.4", "thiserror", ] @@ -2992,11 +3066,11 @@ dependencies = [ [[package]] name = "num_enum" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70bf6736f74634d299d00086f02986875b3c2d924781a6a2cb6c201e73da0ceb" +checksum = "683751d591e6d81200c39fb0d1032608b77724f34114db54f571ff1317b337c0" dependencies = [ - "num_enum_derive 0.7.0", + "num_enum_derive 0.7.1", ] [[package]] @@ -3013,9 +3087,9 @@ dependencies = [ [[package]] name = "num_enum_derive" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56ea360eafe1022f7cc56cd7b869ed57330fb2453d0c7831d99b74c65d2f5597" +checksum = "6c11e44798ad209ccdd91fc192f0526a369a01234f7373e1b141c96d7cee4f0e" dependencies = [ "proc-macro-crate 1.1.3", "proc-macro2", @@ -3376,9 +3450,9 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.2.7" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d31d11c69a6b52a174b42bdc0c30e5e11670f90788b2c471c31c1d17d449443" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" [[package]] name = "pin-utils" @@ -4330,9 +4404,9 @@ checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" [[package]] name = "shlex" -version = "1.1.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook" @@ -4446,7 +4520,7 @@ dependencies = [ [[package]] name = "solana-account-decoder" -version = "1.17.0" +version = "1.17.30" dependencies = [ "Inflector", "base64 0.21.4", @@ -4461,6 +4535,7 @@ dependencies = [ "solana-sdk", "spl-token", "spl-token-2022", + "spl-token-group-interface", "spl-token-metadata-interface", "thiserror", "zstd", @@ -4468,7 +4543,7 @@ dependencies = [ [[package]] name = "solana-accounts-db" -version = "1.17.0" +version = "1.17.30" dependencies = [ "arrayref", "bincode", @@ -4525,7 +4600,7 @@ dependencies = [ [[package]] name = "solana-address-lookup-table-program" -version = "1.17.0" +version = "1.17.30" dependencies = [ "bincode", "bytemuck", @@ -4544,7 +4619,7 @@ dependencies = [ [[package]] name = "solana-banks-client" -version = "1.17.0" +version = "1.17.30" dependencies = [ "borsh 0.10.3", "futures 0.3.28", @@ -4559,7 +4634,7 @@ dependencies = [ [[package]] name = "solana-banks-interface" -version = "1.17.0" +version = "1.17.30" dependencies = [ "serde", "solana-sdk", @@ -4568,7 +4643,7 @@ dependencies = [ [[package]] name = "solana-banks-server" -version = "1.17.0" +version = "1.17.30" dependencies = [ "bincode", "crossbeam-channel", @@ -4586,7 +4661,7 @@ dependencies = [ [[package]] name = "solana-bloom" -version = "1.17.0" +version = "1.17.30" dependencies = [ "bv", "fnv", @@ -4603,7 +4678,7 @@ dependencies = [ [[package]] name = "solana-bpf-loader-program" -version = "1.17.0" +version = "1.17.30" dependencies = [ "bincode", "byteorder 1.4.3", @@ -4620,7 +4695,7 @@ dependencies = [ [[package]] name = "solana-bpf-rust-big-mod-exp" -version = "1.17.0" +version = "1.17.30" dependencies = [ "array-bytes", "serde", @@ -4630,7 +4705,7 @@ dependencies = [ [[package]] name = "solana-bucket-map" -version = "1.17.0" +version = "1.17.30" dependencies = [ "bv", "bytemuck", @@ -4646,7 +4721,7 @@ dependencies = [ [[package]] name = "solana-clap-utils" -version = "1.17.0" +version = "1.17.30" dependencies = [ "chrono", "clap 2.33.3", @@ -4661,7 +4736,7 @@ dependencies = [ [[package]] name = "solana-cli-config" -version = "1.17.0" +version = "1.17.30" dependencies = [ "dirs-next", "lazy_static", @@ -4675,7 +4750,7 @@ dependencies = [ [[package]] name = "solana-cli-output" -version = "1.17.0" +version = "1.17.30" dependencies = [ "Inflector", "base64 0.21.4", @@ -4700,7 +4775,7 @@ dependencies = [ [[package]] name = "solana-client" -version = "1.17.0" +version = "1.17.30" dependencies = [ "async-trait", "bincode", @@ -4731,7 +4806,7 @@ dependencies = [ [[package]] name = "solana-compute-budget-program" -version = "1.17.0" +version = "1.17.30" dependencies = [ "solana-program-runtime", "solana-sdk", @@ -4739,7 +4814,7 @@ dependencies = [ [[package]] name = "solana-config-program" -version = "1.17.0" +version = "1.17.30" dependencies = [ "bincode", "chrono", @@ -4751,7 +4826,7 @@ dependencies = [ [[package]] name = "solana-connection-cache" -version = "1.17.0" +version = "1.17.30" dependencies = [ "async-trait", "bincode", @@ -4771,7 +4846,7 @@ dependencies = [ [[package]] name = "solana-core" -version = "1.17.0" +version = "1.17.30" dependencies = [ "base64 0.21.4", "bincode", @@ -4843,7 +4918,7 @@ dependencies = [ [[package]] name = "solana-cost-model" -version = "1.17.0" +version = "1.17.30" dependencies = [ "lazy_static", "log", @@ -4865,7 +4940,7 @@ dependencies = [ [[package]] name = "solana-download-utils" -version = "1.17.0" +version = "1.17.30" dependencies = [ "console", "indicatif", @@ -4877,7 +4952,7 @@ dependencies = [ [[package]] name = "solana-entry" -version = "1.17.0" +version = "1.17.30" dependencies = [ "bincode", "crossbeam-channel", @@ -4897,7 +4972,7 @@ dependencies = [ [[package]] name = "solana-faucet" -version = "1.17.0" +version = "1.17.30" dependencies = [ "bincode", "byteorder 1.4.3", @@ -4919,9 +4994,9 @@ dependencies = [ [[package]] name = "solana-frozen-abi" -version = "1.17.0" +version = "1.17.30" dependencies = [ - "ahash 0.8.3", + "ahash 0.8.5", "blake3", "block-buffer 0.10.4", "bs58", @@ -4947,7 +5022,7 @@ dependencies = [ [[package]] name = "solana-frozen-abi-macro" -version = "1.17.0" +version = "1.17.30" dependencies = [ "proc-macro2", "quote", @@ -4957,7 +5032,7 @@ dependencies = [ [[package]] name = "solana-genesis-utils" -version = "1.17.0" +version = "1.17.30" dependencies = [ "log", "solana-accounts-db", @@ -4966,20 +5041,11 @@ dependencies = [ "solana-sdk", ] -[[package]] -name = "solana-geyser-plugin-interface" -version = "1.17.0" -dependencies = [ - "log", - "solana-sdk", - "solana-transaction-status", - "thiserror", -] - [[package]] name = "solana-geyser-plugin-manager" -version = "1.17.0" +version = "1.17.30" dependencies = [ + "agave-geyser-plugin-interface", "bs58", "crossbeam-channel", "json5", @@ -4990,7 +5056,6 @@ dependencies = [ "serde_json", "solana-accounts-db", "solana-entry", - "solana-geyser-plugin-interface", "solana-ledger", "solana-measure", "solana-metrics", @@ -5003,7 +5068,7 @@ dependencies = [ [[package]] name = "solana-gossip" -version = "1.17.0" +version = "1.17.30" dependencies = [ "assert_matches", "bincode", @@ -5051,7 +5116,7 @@ dependencies = [ [[package]] name = "solana-ledger" -version = "1.17.0" +version = "1.17.30" dependencies = [ "assert_matches", "bincode", @@ -5115,7 +5180,7 @@ dependencies = [ [[package]] name = "solana-loader-v4-program" -version = "1.17.0" +version = "1.17.30" dependencies = [ "log", "solana-measure", @@ -5126,7 +5191,7 @@ dependencies = [ [[package]] name = "solana-logger" -version = "1.17.0" +version = "1.17.30" dependencies = [ "env_logger", "lazy_static", @@ -5135,7 +5200,7 @@ dependencies = [ [[package]] name = "solana-measure" -version = "1.17.0" +version = "1.17.30" dependencies = [ "log", "solana-sdk", @@ -5143,7 +5208,7 @@ dependencies = [ [[package]] name = "solana-merkle-tree" -version = "1.17.0" +version = "1.17.30" dependencies = [ "fast-math", "solana-program", @@ -5151,7 +5216,7 @@ dependencies = [ [[package]] name = "solana-metrics" -version = "1.17.0" +version = "1.17.30" dependencies = [ "crossbeam-channel", "gethostname", @@ -5164,7 +5229,7 @@ dependencies = [ [[package]] name = "solana-net-utils" -version = "1.17.0" +version = "1.17.30" dependencies = [ "bincode", "clap 3.1.6", @@ -5184,9 +5249,9 @@ dependencies = [ [[package]] name = "solana-perf" -version = "1.17.0" +version = "1.17.30" dependencies = [ - "ahash 0.8.3", + "ahash 0.8.5", "bincode", "bv", "caps", @@ -5199,7 +5264,10 @@ dependencies = [ "nix", "rand 0.8.5", "rayon", + "rustc_version", "serde", + "solana-frozen-abi", + "solana-frozen-abi-macro", "solana-metrics", "solana-rayon-threadlimit", "solana-sdk", @@ -5208,7 +5276,7 @@ dependencies = [ [[package]] name = "solana-poh" -version = "1.17.0" +version = "1.17.30" dependencies = [ "core_affinity", "crossbeam-channel", @@ -5224,7 +5292,7 @@ dependencies = [ [[package]] name = "solana-program" -version = "1.17.0" +version = "1.17.30" dependencies = [ "ark-bn254", "ark-ec", @@ -5276,7 +5344,7 @@ dependencies = [ [[package]] name = "solana-program-runtime" -version = "1.17.0" +version = "1.17.30" dependencies = [ "base64 0.21.4", "bincode", @@ -5302,7 +5370,7 @@ dependencies = [ [[package]] name = "solana-program-test" -version = "1.17.0" +version = "1.17.30" dependencies = [ "assert_matches", "async-trait", @@ -5322,6 +5390,7 @@ dependencies = [ "solana-runtime", "solana-sdk", "solana-vote-program", + "solana_rbpf", "test-case", "thiserror", "tokio", @@ -5329,7 +5398,7 @@ dependencies = [ [[package]] name = "solana-pubsub-client" -version = "1.17.0" +version = "1.17.30" dependencies = [ "crossbeam-channel", "futures-util", @@ -5352,7 +5421,7 @@ dependencies = [ [[package]] name = "solana-quic-client" -version = "1.17.0" +version = "1.17.30" dependencies = [ "async-mutex", "async-trait", @@ -5377,7 +5446,7 @@ dependencies = [ [[package]] name = "solana-rayon-threadlimit" -version = "1.17.0" +version = "1.17.30" dependencies = [ "lazy_static", "num_cpus", @@ -5385,7 +5454,7 @@ dependencies = [ [[package]] name = "solana-remote-wallet" -version = "1.17.0" +version = "1.17.30" dependencies = [ "console", "dialoguer", @@ -5402,7 +5471,7 @@ dependencies = [ [[package]] name = "solana-rpc" -version = "1.17.0" +version = "1.17.30" dependencies = [ "base64 0.21.4", "bincode", @@ -5457,7 +5526,7 @@ dependencies = [ [[package]] name = "solana-rpc-client" -version = "1.17.0" +version = "1.17.30" dependencies = [ "async-trait", "base64 0.21.4", @@ -5481,7 +5550,7 @@ dependencies = [ [[package]] name = "solana-rpc-client-api" -version = "1.17.0" +version = "1.17.30" dependencies = [ "base64 0.21.4", "bs58", @@ -5501,7 +5570,7 @@ dependencies = [ [[package]] name = "solana-rpc-client-nonce-utils" -version = "1.17.0" +version = "1.17.30" dependencies = [ "clap 2.33.3", "solana-clap-utils", @@ -5512,7 +5581,7 @@ dependencies = [ [[package]] name = "solana-runtime" -version = "1.17.0" +version = "1.17.30" dependencies = [ "arrayref", "base64 0.21.4", @@ -5587,7 +5656,7 @@ dependencies = [ [[package]] name = "solana-sbf-programs" -version = "1.17.0" +version = "1.17.30" dependencies = [ "bincode", "byteorder 1.4.3", @@ -5616,7 +5685,7 @@ dependencies = [ [[package]] name = "solana-sbf-rust-128bit" -version = "1.17.0" +version = "1.17.30" dependencies = [ "solana-program", "solana-sbf-rust-128bit-dep", @@ -5624,21 +5693,21 @@ dependencies = [ [[package]] name = "solana-sbf-rust-128bit-dep" -version = "1.17.0" +version = "1.17.30" dependencies = [ "solana-program", ] [[package]] name = "solana-sbf-rust-alloc" -version = "1.17.0" +version = "1.17.30" dependencies = [ "solana-program", ] [[package]] name = "solana-sbf-rust-alt-bn128" -version = "1.17.0" +version = "1.17.30" dependencies = [ "array-bytes", "solana-program", @@ -5646,7 +5715,7 @@ dependencies = [ [[package]] name = "solana-sbf-rust-alt-bn128-compression" -version = "1.17.0" +version = "1.17.30" dependencies = [ "array-bytes", "solana-program", @@ -5654,21 +5723,21 @@ dependencies = [ [[package]] name = "solana-sbf-rust-call-depth" -version = "1.17.0" +version = "1.17.30" dependencies = [ "solana-program", ] [[package]] name = "solana-sbf-rust-caller-access" -version = "1.17.0" +version = "1.17.30" dependencies = [ "solana-program", ] [[package]] name = "solana-sbf-rust-curve25519" -version = "1.17.0" +version = "1.17.30" dependencies = [ "solana-program", "solana-zk-token-sdk", @@ -5676,14 +5745,14 @@ dependencies = [ [[package]] name = "solana-sbf-rust-custom-heap" -version = "1.17.0" +version = "1.17.30" dependencies = [ "solana-program", ] [[package]] name = "solana-sbf-rust-dep-crate" -version = "1.17.0" +version = "1.17.30" dependencies = [ "byteorder 1.4.3", "solana-program", @@ -5691,21 +5760,21 @@ dependencies = [ [[package]] name = "solana-sbf-rust-deprecated-loader" -version = "1.17.0" +version = "1.17.30" dependencies = [ "solana-program", ] [[package]] name = "solana-sbf-rust-dup-accounts" -version = "1.17.0" +version = "1.17.30" dependencies = [ "solana-program", ] [[package]] name = "solana-sbf-rust-error-handling" -version = "1.17.0" +version = "1.17.30" dependencies = [ "num-derive 0.3.0", "num-traits", @@ -5715,42 +5784,42 @@ dependencies = [ [[package]] name = "solana-sbf-rust-external-spend" -version = "1.17.0" +version = "1.17.30" dependencies = [ "solana-program", ] [[package]] name = "solana-sbf-rust-finalize" -version = "1.17.0" +version = "1.17.30" dependencies = [ "solana-program", ] [[package]] name = "solana-sbf-rust-get-minimum-delegation" -version = "1.17.0" +version = "1.17.30" dependencies = [ "solana-program", ] [[package]] name = "solana-sbf-rust-inner_instruction_alignment_check" -version = "1.17.0" +version = "1.17.30" dependencies = [ "solana-program", ] [[package]] name = "solana-sbf-rust-instruction-introspection" -version = "1.17.0" +version = "1.17.30" dependencies = [ "solana-program", ] [[package]] name = "solana-sbf-rust-invoke" -version = "1.17.0" +version = "1.17.30" dependencies = [ "rustversion", "solana-program", @@ -5760,49 +5829,49 @@ dependencies = [ [[package]] name = "solana-sbf-rust-invoke-and-error" -version = "1.17.0" +version = "1.17.30" dependencies = [ "solana-program", ] [[package]] name = "solana-sbf-rust-invoke-and-ok" -version = "1.17.0" +version = "1.17.30" dependencies = [ "solana-program", ] [[package]] name = "solana-sbf-rust-invoke-and-return" -version = "1.17.0" +version = "1.17.30" dependencies = [ "solana-program", ] [[package]] name = "solana-sbf-rust-invoked" -version = "1.17.0" +version = "1.17.30" dependencies = [ "solana-program", ] [[package]] name = "solana-sbf-rust-iter" -version = "1.17.0" +version = "1.17.30" dependencies = [ "solana-program", ] [[package]] name = "solana-sbf-rust-log-data" -version = "1.17.0" +version = "1.17.30" dependencies = [ "solana-program", ] [[package]] name = "solana-sbf-rust-many-args" -version = "1.17.0" +version = "1.17.30" dependencies = [ "solana-program", "solana-sbf-rust-many-args-dep", @@ -5810,14 +5879,14 @@ dependencies = [ [[package]] name = "solana-sbf-rust-many-args-dep" -version = "1.17.0" +version = "1.17.30" dependencies = [ "solana-program", ] [[package]] name = "solana-sbf-rust-mem" -version = "1.17.0" +version = "1.17.30" dependencies = [ "solana-program", "solana-program-runtime", @@ -5827,7 +5896,7 @@ dependencies = [ [[package]] name = "solana-sbf-rust-membuiltins" -version = "1.17.0" +version = "1.17.30" dependencies = [ "solana-program", "solana-sbf-rust-mem", @@ -5835,21 +5904,21 @@ dependencies = [ [[package]] name = "solana-sbf-rust-noop" -version = "1.17.0" +version = "1.17.30" dependencies = [ "solana-program", ] [[package]] name = "solana-sbf-rust-panic" -version = "1.17.0" +version = "1.17.30" dependencies = [ "solana-program", ] [[package]] name = "solana-sbf-rust-param-passing" -version = "1.17.0" +version = "1.17.30" dependencies = [ "solana-program", "solana-sbf-rust-param-passing-dep", @@ -5857,14 +5926,14 @@ dependencies = [ [[package]] name = "solana-sbf-rust-param-passing-dep" -version = "1.17.0" +version = "1.17.30" dependencies = [ "solana-program", ] [[package]] name = "solana-sbf-rust-poseidon" -version = "1.17.0" +version = "1.17.30" dependencies = [ "array-bytes", "solana-program", @@ -5872,7 +5941,7 @@ dependencies = [ [[package]] name = "solana-sbf-rust-rand" -version = "1.17.0" +version = "1.17.30" dependencies = [ "getrandom 0.2.10", "rand 0.8.5", @@ -5881,14 +5950,14 @@ dependencies = [ [[package]] name = "solana-sbf-rust-realloc" -version = "1.17.0" +version = "1.17.30" dependencies = [ "solana-program", ] [[package]] name = "solana-sbf-rust-realloc-invoke" -version = "1.17.0" +version = "1.17.30" dependencies = [ "solana-program", "solana-sbf-rust-realloc", @@ -5896,7 +5965,7 @@ dependencies = [ [[package]] name = "solana-sbf-rust-remaining-compute-units" -version = "1.17.0" +version = "1.17.30" dependencies = [ "solana-program", "solana-program-runtime", @@ -5906,21 +5975,21 @@ dependencies = [ [[package]] name = "solana-sbf-rust-ro-account_modify" -version = "1.17.0" +version = "1.17.30" dependencies = [ "solana-program", ] [[package]] name = "solana-sbf-rust-ro-modify" -version = "1.17.0" +version = "1.17.30" dependencies = [ "solana-program", ] [[package]] name = "solana-sbf-rust-sanity" -version = "1.17.0" +version = "1.17.30" dependencies = [ "solana-program", "solana-program-runtime", @@ -5930,7 +5999,7 @@ dependencies = [ [[package]] name = "solana-sbf-rust-secp256k1-recover" -version = "1.17.0" +version = "1.17.30" dependencies = [ "libsecp256k1 0.7.0", "solana-program", @@ -5938,7 +6007,7 @@ dependencies = [ [[package]] name = "solana-sbf-rust-sha" -version = "1.17.0" +version = "1.17.30" dependencies = [ "blake3", "solana-program", @@ -5946,46 +6015,46 @@ dependencies = [ [[package]] name = "solana-sbf-rust-sibling-instructions" -version = "1.17.0" +version = "1.17.30" dependencies = [ "solana-program", ] [[package]] name = "solana-sbf-rust-sibling_inner-instructions" -version = "1.17.0" +version = "1.17.30" dependencies = [ "solana-program", ] [[package]] name = "solana-sbf-rust-simulation" -version = "1.17.0" +version = "1.17.30" dependencies = [ + "agave-validator", "solana-logger", "solana-program", "solana-program-test", "solana-sdk", - "solana-validator", ] [[package]] name = "solana-sbf-rust-spoof1" -version = "1.17.0" +version = "1.17.30" dependencies = [ "solana-program", ] [[package]] name = "solana-sbf-rust-spoof1-system" -version = "1.17.0" +version = "1.17.30" dependencies = [ "solana-program", ] [[package]] name = "solana-sbf-rust-sysvar" -version = "1.17.0" +version = "1.17.30" dependencies = [ "solana-program", "solana-program-runtime", @@ -5995,21 +6064,21 @@ dependencies = [ [[package]] name = "solana-sbf-rust-upgradeable" -version = "1.17.0" +version = "1.17.30" dependencies = [ "solana-program", ] [[package]] name = "solana-sbf-rust-upgraded" -version = "1.17.0" +version = "1.17.30" dependencies = [ "solana-program", ] [[package]] name = "solana-sdk" -version = "1.17.0" +version = "1.17.30" dependencies = [ "assert_matches", "base64 0.21.4", @@ -6061,7 +6130,7 @@ dependencies = [ [[package]] name = "solana-sdk-macro" -version = "1.17.0" +version = "1.17.30" dependencies = [ "bs58", "proc-macro2", @@ -6070,9 +6139,15 @@ dependencies = [ "syn 2.0.37", ] +[[package]] +name = "solana-security-txt" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "468aa43b7edb1f9b7b7b686d5c3aeb6630dc1708e86e31343499dd5c4d775183" + [[package]] name = "solana-send-transaction-service" -version = "1.17.0" +version = "1.17.30" dependencies = [ "crossbeam-channel", "log", @@ -6086,7 +6161,7 @@ dependencies = [ [[package]] name = "solana-stake-program" -version = "1.17.0" +version = "1.17.30" dependencies = [ "bincode", "log", @@ -6099,7 +6174,7 @@ dependencies = [ [[package]] name = "solana-storage-bigtable" -version = "1.17.0" +version = "1.17.30" dependencies = [ "backoff", "bincode", @@ -6131,7 +6206,7 @@ dependencies = [ [[package]] name = "solana-storage-proto" -version = "1.17.0" +version = "1.17.30" dependencies = [ "bincode", "bs58", @@ -6146,11 +6221,12 @@ dependencies = [ [[package]] name = "solana-streamer" -version = "1.17.0" +version = "1.17.30" dependencies = [ "async-channel", "bytes", "crossbeam-channel", + "futures 0.3.28", "futures-util", "histogram", "indexmap 2.0.1", @@ -6176,7 +6252,7 @@ dependencies = [ [[package]] name = "solana-system-program" -version = "1.17.0" +version = "1.17.30" dependencies = [ "bincode", "log", @@ -6188,7 +6264,7 @@ dependencies = [ [[package]] name = "solana-test-validator" -version = "1.17.0" +version = "1.17.30" dependencies = [ "base64 0.21.4", "bincode", @@ -6218,7 +6294,7 @@ dependencies = [ [[package]] name = "solana-thin-client" -version = "1.17.0" +version = "1.17.30" dependencies = [ "bincode", "log", @@ -6231,7 +6307,7 @@ dependencies = [ [[package]] name = "solana-tpu-client" -version = "1.17.0" +version = "1.17.30" dependencies = [ "async-trait", "bincode", @@ -6253,7 +6329,7 @@ dependencies = [ [[package]] name = "solana-transaction-status" -version = "1.17.0" +version = "1.17.30" dependencies = [ "Inflector", "base64 0.21.4", @@ -6276,7 +6352,7 @@ dependencies = [ [[package]] name = "solana-turbine" -version = "1.17.0" +version = "1.17.30" dependencies = [ "bincode", "bytes", @@ -6311,7 +6387,7 @@ dependencies = [ [[package]] name = "solana-udp-client" -version = "1.17.0" +version = "1.17.30" dependencies = [ "async-trait", "solana-connection-cache", @@ -6322,71 +6398,9 @@ dependencies = [ "tokio", ] -[[package]] -name = "solana-validator" -version = "1.17.0" -dependencies = [ - "chrono", - "clap 2.33.3", - "console", - "core_affinity", - "crossbeam-channel", - "fd-lock", - "indicatif", - "itertools", - "jsonrpc-core", - "jsonrpc-core-client", - "jsonrpc-derive", - "jsonrpc-ipc-server", - "jsonrpc-server-utils", - "lazy_static", - "libc", - "libloading", - "log", - "num_cpus", - "rand 0.8.5", - "rayon", - "serde", - "serde_json", - "serde_yaml", - "signal-hook", - "solana-accounts-db", - "solana-clap-utils", - "solana-cli-config", - "solana-core", - "solana-download-utils", - "solana-entry", - "solana-faucet", - "solana-genesis-utils", - "solana-geyser-plugin-interface", - "solana-geyser-plugin-manager", - "solana-gossip", - "solana-ledger", - "solana-logger", - "solana-metrics", - "solana-net-utils", - "solana-perf", - "solana-poh", - "solana-rpc", - "solana-rpc-client", - "solana-rpc-client-api", - "solana-runtime", - "solana-sdk", - "solana-send-transaction-service", - "solana-storage-bigtable", - "solana-streamer", - "solana-test-validator", - "solana-tpu-client", - "solana-version", - "solana-vote-program", - "symlink", - "thiserror", - "tikv-jemallocator", -] - [[package]] name = "solana-version" -version = "1.17.0" +version = "1.17.30" dependencies = [ "log", "rustc_version", @@ -6400,7 +6414,7 @@ dependencies = [ [[package]] name = "solana-vote" -version = "1.17.0" +version = "1.17.30" dependencies = [ "crossbeam-channel", "itertools", @@ -6417,7 +6431,7 @@ dependencies = [ [[package]] name = "solana-vote-program" -version = "1.17.0" +version = "1.17.30" dependencies = [ "bincode", "log", @@ -6437,7 +6451,7 @@ dependencies = [ [[package]] name = "solana-zk-token-proof-program" -version = "1.17.0" +version = "1.17.30" dependencies = [ "bytemuck", "num-derive 0.3.0", @@ -6449,7 +6463,7 @@ dependencies = [ [[package]] name = "solana-zk-token-sdk" -version = "1.17.0" +version = "1.17.30" dependencies = [ "aes-gcm-siv", "base64 0.21.4", @@ -6476,9 +6490,9 @@ dependencies = [ [[package]] name = "solana_rbpf" -version = "0.7.2" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "103318aa365ff7caa8cf534f2246b5eb7e5b34668736d52b1266b143f7a21196" +checksum = "3d457cc2ba742c120492a64b7fa60e22c575e891f6b55039f4d736568fb112a3" dependencies = [ "byteorder 1.4.3", "combine", @@ -6517,9 +6531,9 @@ dependencies = [ [[package]] name = "spl-associated-token-account" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "385e31c29981488f2820b2022d8e731aae3b02e6e18e2fd854e4c9a94dc44fc3" +checksum = "992d9c64c2564cc8f63a4b508bf3ebcdf2254b0429b13cd1d31adb6162432a5f" dependencies = [ "assert_matches", "borsh 0.10.3", @@ -6615,9 +6629,9 @@ dependencies = [ [[package]] name = "spl-tlv-account-resolution" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "062e148d3eab7b165582757453632ffeef490c02c86a48bfdb4988f63eefb3b9" +checksum = "3f7020347c07892c08560d230fbb8a980316c9e198e22b198b7b9d951ff96047" dependencies = [ "bytemuck", "solana-program", @@ -6644,26 +6658,41 @@ dependencies = [ [[package]] name = "spl-token-2022" -version = "0.9.0" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4abf34a65ba420584a0c35f3903f8d727d1f13ababbdc3f714c6b065a686e86" +checksum = "d697fac19fd74ff472dfcc13f0b442dd71403178ce1de7b5d16f83a33561c059" dependencies = [ "arrayref", "bytemuck", "num-derive 0.4.0", "num-traits", - "num_enum 0.7.0", + "num_enum 0.7.1", "solana-program", + "solana-security-txt", "solana-zk-token-sdk", "spl-memo", "spl-pod", "spl-token", + "spl-token-group-interface", "spl-token-metadata-interface", "spl-transfer-hook-interface", "spl-type-length-value", "thiserror", ] +[[package]] +name = "spl-token-group-interface" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b889509d49fa74a4a033ca5dae6c2307e9e918122d97e58562f5c4ffa795c75d" +dependencies = [ + "bytemuck", + "solana-program", + "spl-discriminator", + "spl-pod", + "spl-program-error", +] + [[package]] name = "spl-token-metadata-interface" version = "0.2.0" @@ -6680,9 +6709,9 @@ dependencies = [ [[package]] name = "spl-transfer-hook-interface" -version = "0.3.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "051d31803f873cabe71aec3c1b849f35248beae5d19a347d93a5c9cccc5d5a9b" +checksum = "7aabdb7c471566f6ddcee724beb8618449ea24b399e58d464d6b5bc7db550259" dependencies = [ "arrayref", "bytemuck", @@ -7273,11 +7302,10 @@ checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" [[package]] name = "tracing" -version = "0.1.29" +version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "375a639232caf30edfc78e8d89b2d4c375515393e7af7e16f01cd96917fb2105" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ - "cfg-if 1.0.0", "log", "pin-project-lite", "tracing-attributes", @@ -7286,22 +7314,22 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.18" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4f480b8f81512e825f337ad51e94c1eb5d3bbdf2b363dcd01e2b19a9ffe3f8e" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.37", ] [[package]] name = "tracing-core" -version = "0.1.26" +version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f54c8ca710e81886d498c2fd3331b56c93aa248d49de2222ad2742247c60072f" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" dependencies = [ - "lazy_static", + "once_cell", "valuable", ] @@ -7913,6 +7941,26 @@ dependencies = [ "time", ] +[[package]] +name = "zerocopy" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.37", +] + [[package]] name = "zeroize" version = "1.3.0" diff --git a/programs/sbf/Cargo.toml b/programs/sbf/Cargo.toml index 5e2370576d4..c8f7ffb76ce 100644 --- a/programs/sbf/Cargo.toml +++ b/programs/sbf/Cargo.toml @@ -1,5 +1,5 @@ [workspace.package] -version = "1.17.0" +version = "1.17.30" description = "Solana SBF test program written in Rust" authors = ["Solana Labs Maintainers "] repository = "https://github.com/solana-labs/solana" @@ -25,30 +25,30 @@ rand = "0.8" rustversion = "1.0.14" serde = "1.0.112" serde_json = "1.0.56" -solana_rbpf = "=0.7.2" -solana-account-decoder = { path = "../../account-decoder", version = "=1.17.0" } -solana-accounts-db = { path = "../../accounts-db", version = "=1.17.0" } -solana-bpf-loader-program = { path = "../bpf_loader", version = "=1.17.0" } -solana-cli-output = { path = "../../cli-output", version = "=1.17.0" } -solana-ledger = { path = "../../ledger", version = "=1.17.0" } -solana-logger = { path = "../../logger", version = "=1.17.0" } -solana-measure = { path = "../../measure", version = "=1.17.0" } -solana-program = { path = "../../sdk/program", version = "=1.17.0" } -solana-program-runtime = { path = "../../program-runtime", version = "=1.17.0" } -solana-program-test = { path = "../../program-test", version = "=1.17.0" } -solana-runtime = { path = "../../runtime", version = "=1.17.0" } -solana-sbf-rust-128bit-dep = { path = "rust/128bit_dep", version = "=1.17.0" } -solana-sbf-rust-invoke = { path = "rust/invoke", version = "=1.17.0" } -solana-sbf-rust-invoked = { path = "rust/invoked", version = "=1.17.0", default-features = false } -solana-sbf-rust-many-args-dep = { path = "rust/many_args_dep", version = "=1.17.0" } -solana-sbf-rust-mem = { path = "rust/mem", version = "=1.17.0" } -solana-sbf-rust-param-passing-dep = { path = "rust/param_passing_dep", version = "=1.17.0" } -solana-sbf-rust-realloc = { path = "rust/realloc", version = "=1.17.0", default-features = false } -solana-sbf-rust-realloc-invoke = { path = "rust/realloc_invoke", version = "=1.17.0" } -solana-sdk = { path = "../../sdk", version = "=1.17.0" } -solana-transaction-status = { path = "../../transaction-status", version = "=1.17.0" } -solana-validator = { path = "../../validator", version = "=1.17.0" } -solana-zk-token-sdk = { path = "../../zk-token-sdk", version = "=1.17.0" } +solana_rbpf = "=0.8.0" +solana-account-decoder = { path = "../../account-decoder", version = "=1.17.30" } +solana-accounts-db = { path = "../../accounts-db", version = "=1.17.30" } +solana-bpf-loader-program = { path = "../bpf_loader", version = "=1.17.30" } +solana-cli-output = { path = "../../cli-output", version = "=1.17.30" } +solana-ledger = { path = "../../ledger", version = "=1.17.30" } +solana-logger = { path = "../../logger", version = "=1.17.30" } +solana-measure = { path = "../../measure", version = "=1.17.30" } +solana-program = { path = "../../sdk/program", version = "=1.17.30" } +solana-program-runtime = { path = "../../program-runtime", version = "=1.17.30" } +solana-program-test = { path = "../../program-test", version = "=1.17.30" } +solana-runtime = { path = "../../runtime", version = "=1.17.30" } +solana-sbf-rust-128bit-dep = { path = "rust/128bit_dep", version = "=1.17.30" } +solana-sbf-rust-invoke = { path = "rust/invoke", version = "=1.17.30" } +solana-sbf-rust-invoked = { path = "rust/invoked", version = "=1.17.30", default-features = false } +solana-sbf-rust-many-args-dep = { path = "rust/many_args_dep", version = "=1.17.30" } +solana-sbf-rust-mem = { path = "rust/mem", version = "=1.17.30" } +solana-sbf-rust-param-passing-dep = { path = "rust/param_passing_dep", version = "=1.17.30" } +solana-sbf-rust-realloc = { path = "rust/realloc", version = "=1.17.30", default-features = false } +solana-sbf-rust-realloc-invoke = { path = "rust/realloc_invoke", version = "=1.17.30" } +solana-sdk = { path = "../../sdk", version = "=1.17.30" } +solana-transaction-status = { path = "../../transaction-status", version = "=1.17.30" } +agave-validator = { path = "../../validator", version = "=1.17.30" } +solana-zk-token-sdk = { path = "../../zk-token-sdk", version = "=1.17.30" } static_assertions = "1.1.0" thiserror = "1.0" diff --git a/programs/sbf/benches/bpf_loader.rs b/programs/sbf/benches/bpf_loader.rs index cfb20868b81..bec2106708a 100644 --- a/programs/sbf/benches/bpf_loader.rs +++ b/programs/sbf/benches/bpf_loader.rs @@ -33,7 +33,7 @@ use { bpf_loader, client::SyncClient, entrypoint::SUCCESS, - feature_set::FeatureSet, + feature_set::{self, FeatureSet}, instruction::{AccountMeta, Instruction}, message::Message, native_loader, @@ -101,6 +101,7 @@ fn bench_program_create_executable(bencher: &mut Bencher) { } #[bench] +#[ignore] fn bench_program_alu(bencher: &mut Bencher) { let ns_per_s = 1000000000; let one_million = 1000000; @@ -118,13 +119,18 @@ fn bench_program_alu(bencher: &mut Bencher) { true, false, ); + #[allow(unused_mut)] let mut executable = Executable::::from_elf(&elf, Arc::new(program_runtime_environment.unwrap())) .unwrap(); executable.verify::().unwrap(); - executable.jit_compile().unwrap(); + #[cfg(all(not(target_os = "windows"), target_arch = "x86_64"))] + { + executable.jit_compile().unwrap(); + } + create_vm!( vm, &executable, @@ -181,12 +187,20 @@ fn bench_program_alu(bencher: &mut Bencher) { } #[bench] +#[ignore] fn bench_program_execute_noop(bencher: &mut Bencher) { let GenesisConfigInfo { - genesis_config, + mut genesis_config, mint_keypair, .. } = create_genesis_config(50); + + // deactivate `disable_bpf_loader_instructions` feature so that the program + // can be loaded, finalized and benched. + genesis_config + .accounts + .remove(&feature_set::disable_bpf_loader_instructions::id()); + let bank = Bank::new_for_benches(&genesis_config); let bank = Arc::new(bank); let mut bank_client = BankClient::new_shared(bank.clone()); diff --git a/programs/sbf/c/src/invoked/invoked.c b/programs/sbf/c/src/invoked/invoked.c index f57896c47bf..37671cfb9fe 100644 --- a/programs/sbf/c/src/invoked/invoked.c +++ b/programs/sbf/c/src/invoked/invoked.c @@ -31,10 +31,11 @@ extern uint64_t entrypoint(const uint8_t *input) { static const int INVOKED_PROGRAM_DUP_INDEX = 3; sol_assert(sol_deserialize(input, ¶ms, 4)); - SolPubkey sbf_loader_id = - (SolPubkey){.x = {2, 168, 246, 145, 78, 136, 161, 110, 57, 90, 225, - 40, 148, 143, 250, 105, 86, 147, 55, 104, 24, 221, - 71, 67, 82, 33, 243, 198, 0, 0, 0, 0}}; + SolPubkey sbf_loader_upgradeable_id = + (SolPubkey){.x = { + 2, 168, 246, 145, 78, 136, 161, 176, 226, 16, 21, 62, + 247, 99, 174, 43, 0, 194, 185, 61, 22, 193, 36, 210, 192, + 83, 122, 16, 4, 128, 0, 0}}; for (int i = 0; i < params.data_len; i++) { sol_assert(params.data[i] == i); @@ -63,7 +64,7 @@ extern uint64_t entrypoint(const uint8_t *input) { sol_assert( SolPubkey_same(accounts[INVOKED_PROGRAM_INDEX].key, params.program_id)) sol_assert(SolPubkey_same(accounts[INVOKED_PROGRAM_INDEX].owner, - &sbf_loader_id)); + &sbf_loader_upgradeable_id)); sol_assert(!accounts[INVOKED_PROGRAM_INDEX].is_signer); sol_assert(!accounts[INVOKED_PROGRAM_INDEX].is_writable); sol_assert(accounts[INVOKED_PROGRAM_INDEX].rent_epoch == UINT64_MAX); diff --git a/programs/sbf/c/src/read_program/read_program.c b/programs/sbf/c/src/read_program/read_program.c index 47e5a22d4c1..28d242efa7f 100644 --- a/programs/sbf/c/src/read_program/read_program.c +++ b/programs/sbf/c/src/read_program/read_program.c @@ -10,7 +10,7 @@ extern uint64_t entrypoint(const uint8_t *input) { return ERROR_INVALID_ARGUMENT; } - char ka_data[] = {0x7F, 0x45, 0x4C, 0x46}; + char ka_data[] = {0x02, 0x00, 0x00, 0x00}; sol_assert(params.ka_num == 1); sol_assert(!sol_memcmp(params.ka[0].data, ka_data, 4)); diff --git a/programs/sbf/rust/invoke/src/processor.rs b/programs/sbf/rust/invoke/src/processor.rs index 7c689cfcf86..2e58c3298a5 100644 --- a/programs/sbf/rust/invoke/src/processor.rs +++ b/programs/sbf/rust/invoke/src/processor.rs @@ -1286,6 +1286,61 @@ fn process_instruction( }, &vec![0; original_data_len - new_len] ); + + // Realloc to [0xFC; 2] + invoke( + &create_instruction( + *callee_program_id, + &[ + (accounts[ARGUMENT_INDEX].key, true, false), + (callee_program_id, false, false), + ], + vec![0xFC; 2], + ), + accounts, + ) + .unwrap(); + + // Check that [2..20] is zeroed + let new_len = account.data_len(); + assert_eq!(&*account.data.borrow(), &[0xFC; 2]); + assert_eq!( + unsafe { + slice::from_raw_parts( + account.data.borrow().as_ptr().add(new_len), + original_data_len - new_len, + ) + }, + &vec![0; original_data_len - new_len] + ); + + // Realloc to [0xFC; 2]. Here we keep the same length, but realloc the underlying + // vector. CPI must zero even if the length is unchanged. + invoke( + &create_instruction( + *callee_program_id, + &[ + (accounts[ARGUMENT_INDEX].key, true, false), + (callee_program_id, false, false), + ], + vec![0xFC; 2], + ), + accounts, + ) + .unwrap(); + + // Check that [2..20] is zeroed + let new_len = account.data_len(); + assert_eq!(&*account.data.borrow(), &[0xFC; 2]); + assert_eq!( + unsafe { + slice::from_raw_parts( + account.data.borrow().as_ptr().add(new_len), + original_data_len - new_len, + ) + }, + &vec![0; original_data_len - new_len] + ); } TEST_WRITE_ACCOUNT => { msg!("TEST_WRITE_ACCOUNT"); diff --git a/programs/sbf/rust/invoked/src/processor.rs b/programs/sbf/rust/invoked/src/processor.rs index 52d02dc99a6..8f60a90c15c 100644 --- a/programs/sbf/rust/invoked/src/processor.rs +++ b/programs/sbf/rust/invoked/src/processor.rs @@ -7,7 +7,7 @@ use { crate::instructions::*, solana_program::{ account_info::AccountInfo, - bpf_loader, + bpf_loader_upgradeable, entrypoint::{ProgramResult, MAX_PERMITTED_DATA_INCREASE}, log::sol_log_64, msg, @@ -70,7 +70,10 @@ fn process_instruction( assert!(!accounts[INVOKED_ARGUMENT_INDEX].executable); assert_eq!(accounts[INVOKED_PROGRAM_INDEX].key, program_id); - assert_eq!(accounts[INVOKED_PROGRAM_INDEX].owner, &bpf_loader::id()); + assert_eq!( + accounts[INVOKED_PROGRAM_INDEX].owner, + &bpf_loader_upgradeable::id() + ); assert!(!accounts[INVOKED_PROGRAM_INDEX].is_signer); assert!(!accounts[INVOKED_PROGRAM_INDEX].is_writable); assert_eq!(accounts[INVOKED_PROGRAM_INDEX].rent_epoch, u64::MAX); diff --git a/programs/sbf/rust/simulation/Cargo.toml b/programs/sbf/rust/simulation/Cargo.toml index 7091ef9d5ad..e9728e5916b 100644 --- a/programs/sbf/rust/simulation/Cargo.toml +++ b/programs/sbf/rust/simulation/Cargo.toml @@ -16,10 +16,10 @@ test-bpf = [] solana-program = { workspace = true } [dev-dependencies] +agave-validator = { workspace = true } solana-logger = { workspace = true } solana-program-test = { workspace = true } solana-sdk = { workspace = true } -solana-validator = { workspace = true } [lib] crate-type = ["cdylib", "lib"] diff --git a/programs/sbf/rust/simulation/tests/validator.rs b/programs/sbf/rust/simulation/tests/validator.rs index 3044ad9a642..17de51e665e 100644 --- a/programs/sbf/rust/simulation/tests/validator.rs +++ b/programs/sbf/rust/simulation/tests/validator.rs @@ -1,13 +1,13 @@ #![cfg(feature = "test-bpf")] use { + agave_validator::test_validator::*, solana_program::{ instruction::{AccountMeta, Instruction}, pubkey::Pubkey, sysvar, }, solana_sdk::{signature::Signer, transaction::Transaction}, - solana_validator::test_validator::*, }; #[test] diff --git a/programs/sbf/tests/programs.rs b/programs/sbf/tests/programs.rs index f6b680b1054..8da4cdc18f4 100644 --- a/programs/sbf/tests/programs.rs +++ b/programs/sbf/tests/programs.rs @@ -25,9 +25,8 @@ use { solana_runtime::{ bank::TransactionBalancesSet, loader_utils::{ - create_program, load_and_finalize_program, load_program, load_program_from_file, - load_upgradeable_buffer, load_upgradeable_program, set_upgrade_authority, - upgrade_program, + create_program, load_program_from_file, load_upgradeable_buffer, + load_upgradeable_program, set_upgrade_authority, upgrade_program, }, }, solana_sbf_rust_invoke::instructions::*, @@ -42,12 +41,11 @@ use { entrypoint::MAX_PERMITTED_DATA_INCREASE, feature_set::{self, remove_deprecated_request_unit_ix, FeatureSet}, fee::FeeStructure, - loader_instruction, message::{v0::LoadedAddresses, SanitizedMessage}, signature::keypair_from_seed, stake, system_instruction::{self, MAX_PERMITTED_DATA_LENGTH}, - sysvar::{self, clock, rent}, + sysvar::{self, clock}, transaction::VersionedTransaction, }, solana_transaction_status::{ @@ -253,19 +251,45 @@ fn execute_transactions( .collect() } -fn load_program_and_advance_slot( +fn load_upgradeable_program_wrapper( + bank_client: &BankClient, + mint_keypair: &Keypair, + authority_keypair: &Keypair, + name: &str, +) -> Pubkey { + let buffer_keypair = Keypair::new(); + let program_keypair = Keypair::new(); + load_upgradeable_program( + bank_client, + mint_keypair, + &buffer_keypair, + &program_keypair, + authority_keypair, + name, + ); + program_keypair.pubkey() +} + +fn load_upgradeable_program_and_advance_slot( bank_client: &mut BankClient, - loader_id: &Pubkey, - payer_keypair: &Keypair, + mint_keypair: &Keypair, + authority_keypair: &Keypair, name: &str, ) -> (Arc, Pubkey) { - let pubkey = load_program(bank_client, loader_id, payer_keypair, name); - ( - bank_client - .advance_slot(1, &Pubkey::default()) - .expect("Failed to advance the slot"), - pubkey, - ) + let program_id = + load_upgradeable_program_wrapper(bank_client, mint_keypair, authority_keypair, name); + + // load_upgradeable_program sets clock sysvar to 1, which causes the program to be effective + // after 2 slots. They need to be called individually to create the correct fork graph in between. + bank_client + .advance_slot(1, &Pubkey::default()) + .expect("Failed to advance the slot"); + + let bank = bank_client + .advance_slot(1, &Pubkey::default()) + .expect("Failed to advance the slot"); + + (bank, program_id) } #[test] @@ -334,12 +358,13 @@ fn test_program_sbf_sanity() { let bank = Bank::new_for_tests(&genesis_config); let mut bank_client = BankClient::new(bank); + let authority_keypair = Keypair::new(); // Call user program - let (_, program_id) = load_program_and_advance_slot( + let (_, program_id) = load_upgradeable_program_and_advance_slot( &mut bank_client, - &bpf_loader::id(), &mint_keypair, + &authority_keypair, program.0, ); let account_metas = vec![ @@ -399,85 +424,42 @@ fn test_program_sbf_loader_deprecated() { #[test] #[cfg(feature = "sbf_rust")] -fn test_sol_alloc_free_no_longer_deployable() { +#[should_panic( + expected = "called `Result::unwrap()` on an `Err` value: TransactionError(InstructionError(1, InvalidAccountData))" +)] +fn test_sol_alloc_free_no_longer_deployable_with_upgradeable_loader() { solana_logger::setup(); - let program_keypair = Keypair::new(); - let program_address = program_keypair.pubkey(); - let GenesisConfigInfo { genesis_config, mint_keypair, .. } = create_genesis_config(50); - let mut bank = Bank::new_for_tests(&genesis_config); - - // Populate loader account with elf that depends on _sol_alloc_free syscall - let elf = load_program_from_file("solana_sbf_rust_deprecated_loader"); - let mut program_account = AccountSharedData::new(1, elf.len(), &bpf_loader::id()); - program_account - .data_as_mut_slice() - .get_mut(..) - .unwrap() - .copy_from_slice(&elf); - bank.store_account(&program_address, &program_account); - - let finalize_tx = Transaction::new( - &[&mint_keypair, &program_keypair], - Message::new( - &[loader_instruction::finalize( - &program_keypair.pubkey(), - &bpf_loader::id(), - )], - Some(&mint_keypair.pubkey()), - ), - bank.last_blockhash(), - ); - let invoke_tx = Transaction::new( - &[&mint_keypair], - Message::new( - &[Instruction::new_with_bytes( - program_address, - &[255], - vec![AccountMeta::new(mint_keypair.pubkey(), true)], - )], - Some(&mint_keypair.pubkey()), - ), - bank.last_blockhash(), - ); + let bank = Bank::new_for_tests(&genesis_config); + let bank = Arc::new(bank); + let mut bank_client = BankClient::new_shared(bank.clone()); + let authority_keypair = Keypair::new(); - // Try and deploy a program that depends on _sol_alloc_free - assert_eq!( - bank.process_transaction(&finalize_tx).unwrap_err(), - TransactionError::InstructionError(0, InstructionError::InvalidAccountData) + // Populate loader account with `solana_sbf_rust_deprecated_loader` elf, which + // depends on `sol_alloc_free_` syscall. This can be verified with + // $ elfdump solana_sbf_rust_deprecated_loader.so + // : 0000000000001ab8 000000070000000a R_BPF_64_32 0000000000000000 sol_alloc_free_ + // In the symbol table, there is `sol_alloc_free_`. + // In fact, `sol_alloc_free_` is called from sbf allocator, which is originated from + // AccountInfo::realloc() in the program code. + + // Expect that deployment to fail. B/C during deployment, there is an elf + // verification step, which uses the runtime to look up relocatable symbols + // in elf inside syscall table. In this case, `sol_alloc_free_` can't be + // found in syscall table. Hence, the verification fails and the deployment + // fails. + let (_bank, _program_id) = load_upgradeable_program_and_advance_slot( + &mut bank_client, + &mint_keypair, + &authority_keypair, + "solana_sbf_rust_deprecated_loader", ); - - // Enable _sol_alloc_free syscall - bank.deactivate_feature(&solana_sdk::feature_set::disable_deploy_of_alloc_free_syscall::id()); - bank.clear_signatures(); - bank.clear_program_cache(); - - // Try and finalize the program now that sol_alloc_free is re-enabled - assert!(bank.process_transaction(&finalize_tx).is_ok()); - let new_slot = bank.slot() + 1; - let mut bank = Bank::new_from_parent(Arc::new(bank), &Pubkey::default(), new_slot); - - // invoke the program - assert!(bank.process_transaction(&invoke_tx).is_ok()); - - // disable _sol_alloc_free - bank.activate_feature(&solana_sdk::feature_set::disable_deploy_of_alloc_free_syscall::id()); - bank.clear_signatures(); - - // invoke should still succeed because cached - assert!(bank.process_transaction(&invoke_tx).is_ok()); - - bank.clear_signatures(); - bank.clear_program_cache(); - - // invoke should still succeed on execute because the program is already deployed - assert!(bank.process_transaction(&invoke_tx).is_ok()); } #[test] @@ -503,13 +485,15 @@ fn test_program_sbf_duplicate_accounts() { mint_keypair, .. } = create_genesis_config(50); + let bank = Bank::new_for_tests(&genesis_config); let bank = Arc::new(bank); let mut bank_client = BankClient::new_shared(bank.clone()); - let (bank, program_id) = load_program_and_advance_slot( + let authority_keypair = Keypair::new(); + let (bank, program_id) = load_upgradeable_program_and_advance_slot( &mut bank_client, - &bpf_loader::id(), &mint_keypair, + &authority_keypair, program, ); let payee_account = AccountSharedData::new(10, 1, &program_id); @@ -607,12 +591,14 @@ fn test_program_sbf_error_handling() { mint_keypair, .. } = create_genesis_config(50); + let bank = Bank::new_for_tests(&genesis_config); let mut bank_client = BankClient::new(bank); - let (_, program_id) = load_program_and_advance_slot( + let authority_keypair = Keypair::new(); + let (_bank, program_id) = load_upgradeable_program_and_advance_slot( &mut bank_client, - &bpf_loader::id(), &mint_keypair, + &authority_keypair, program, ); let account_metas = vec![AccountMeta::new(mint_keypair.pubkey(), true)]; @@ -712,14 +698,16 @@ fn test_return_data_and_log_data_syscall() { mint_keypair, .. } = create_genesis_config(50); + let bank = Bank::new_for_tests(&genesis_config); let bank = Arc::new(bank); let mut bank_client = BankClient::new_shared(bank.clone()); + let authority_keypair = Keypair::new(); - let (bank, program_id) = load_program_and_advance_slot( + let (bank, program_id) = load_upgradeable_program_and_advance_slot( &mut bank_client, - &bpf_loader::id(), &mint_keypair, + &authority_keypair, program, ); @@ -780,18 +768,30 @@ fn test_program_sbf_invoke_sanity() { mint_keypair, .. } = create_genesis_config(50); + let bank = Bank::new_for_tests(&genesis_config); let bank = Arc::new(bank); let mut bank_client = BankClient::new_shared(bank.clone()); + let authority_keypair = Keypair::new(); + + let invoke_program_id = load_upgradeable_program_wrapper( + &bank_client, + &mint_keypair, + &authority_keypair, + program.1, + ); - let invoke_program_id = - load_program(&bank_client, &bpf_loader::id(), &mint_keypair, program.1); - let invoked_program_id = - load_program(&bank_client, &bpf_loader::id(), &mint_keypair, program.2); - let (bank, noop_program_id) = load_program_and_advance_slot( + let invoked_program_id = load_upgradeable_program_wrapper( + &bank_client, + &mint_keypair, + &authority_keypair, + program.2, + ); + + let (bank, noop_program_id) = load_upgradeable_program_and_advance_slot( &mut bank_client, - &bpf_loader::id(), &mint_keypair, + &authority_keypair, program.3, ); @@ -1177,20 +1177,22 @@ fn test_program_sbf_program_id_spoofing() { mint_keypair, .. } = create_genesis_config(50); + let bank = Bank::new_for_tests(&genesis_config); let bank = Arc::new(bank); let mut bank_client = BankClient::new_shared(bank.clone()); + let authority_keypair = Keypair::new(); - let malicious_swap_pubkey = load_program( + let malicious_swap_pubkey = load_upgradeable_program_wrapper( &bank_client, - &bpf_loader::id(), &mint_keypair, + &authority_keypair, "solana_sbf_rust_spoof1", ); - let (bank, malicious_system_pubkey) = load_program_and_advance_slot( + let (bank, malicious_system_pubkey) = load_upgradeable_program_and_advance_slot( &mut bank_client, - &bpf_loader::id(), &mint_keypair, + &authority_keypair, "solana_sbf_rust_spoof1_system", ); @@ -1228,20 +1230,22 @@ fn test_program_sbf_caller_has_access_to_cpi_program() { mint_keypair, .. } = create_genesis_config(50); + let bank = Bank::new_for_tests(&genesis_config); let bank = Arc::new(bank); let mut bank_client = BankClient::new_shared(bank.clone()); + let authority_keypair = Keypair::new(); - let caller_pubkey = load_program( + let caller_pubkey = load_upgradeable_program_wrapper( &bank_client, - &bpf_loader::id(), &mint_keypair, + &authority_keypair, "solana_sbf_rust_caller_access", ); - let (_, caller2_pubkey) = load_program_and_advance_slot( + let (_bank, caller2_pubkey) = load_upgradeable_program_and_advance_slot( &mut bank_client, - &bpf_loader::id(), &mint_keypair, + &authority_keypair, "solana_sbf_rust_caller_access", ); let account_metas = vec![ @@ -1266,14 +1270,16 @@ fn test_program_sbf_ro_modify() { mint_keypair, .. } = create_genesis_config(50); + let bank = Bank::new_for_tests(&genesis_config); let bank = Arc::new(bank); let mut bank_client = BankClient::new_shared(bank.clone()); + let authority_keypair = Keypair::new(); - let (bank, program_pubkey) = load_program_and_advance_slot( + let (bank, program_pubkey) = load_upgradeable_program_and_advance_slot( &mut bank_client, - &bpf_loader::id(), &mint_keypair, + &authority_keypair, "solana_sbf_rust_ro_modify", ); @@ -1321,12 +1327,14 @@ fn test_program_sbf_call_depth() { mint_keypair, .. } = create_genesis_config(50); + let bank = Bank::new_for_tests(&genesis_config); let mut bank_client = BankClient::new(bank); - let (_, program_id) = load_program_and_advance_slot( + let authority_keypair = Keypair::new(); + let (_, program_id) = load_upgradeable_program_and_advance_slot( &mut bank_client, - &bpf_loader::id(), &mint_keypair, + &authority_keypair, "solana_sbf_rust_call_depth", ); @@ -1354,12 +1362,14 @@ fn test_program_sbf_compute_budget() { mint_keypair, .. } = create_genesis_config(50); + let bank = Bank::new_for_tests(&genesis_config); let mut bank_client = BankClient::new(bank); - let (_, program_id) = load_program_and_advance_slot( + let authority_keypair = Keypair::new(); + let (_, program_id) = load_upgradeable_program_and_advance_slot( &mut bank_client, - &bpf_loader::id(), &mint_keypair, + &authority_keypair, "solana_sbf_rust_noop", ); let message = Message::new( @@ -1449,7 +1459,7 @@ fn assert_instruction_count() { transaction_accounts, instruction_accounts, Ok(()), - solana_bpf_loader_program::process_instruction, + solana_bpf_loader_program::Entrypoint::vm, |invoke_context| { *prev_compute_meter.borrow_mut() = invoke_context.get_remaining(); solana_bpf_loader_program::test_utils::load_all_invoked_programs(invoke_context); @@ -1481,14 +1491,16 @@ fn test_program_sbf_instruction_introspection() { mint_keypair, .. } = create_genesis_config(50_000); + let bank = Bank::new_for_tests(&genesis_config); let bank = Arc::new(bank); let mut bank_client = BankClient::new_shared(bank.clone()); + let authority_keypair = Keypair::new(); - let (bank, program_id) = load_program_and_advance_slot( + let (bank, program_id) = load_upgradeable_program_and_advance_slot( &mut bank_client, - &bpf_loader::id(), &mint_keypair, + &authority_keypair, "solana_sbf_rust_instruction_introspection", ); @@ -1529,83 +1541,6 @@ fn test_program_sbf_instruction_introspection() { assert!(bank.get_account(&sysvar::instructions::id()).is_none()); } -#[test] -#[cfg(feature = "sbf_rust")] -fn test_program_sbf_test_use_latest_executor() { - solana_logger::setup(); - - let GenesisConfigInfo { - genesis_config, - mint_keypair, - .. - } = create_genesis_config(50); - let bank = Bank::new_for_tests(&genesis_config); - let mut bank_client = BankClient::new(bank); - let panic_id = load_program( - &bank_client, - &bpf_loader::id(), - &mint_keypair, - "solana_sbf_rust_panic", - ); - - // Write the panic program into the program account - let (program_keypair, instruction) = load_and_finalize_program( - &bank_client, - &bpf_loader::id(), - None, - &mint_keypair, - "solana_sbf_rust_panic", - ); - - // Finalize the panic program, but fail the tx - let message = Message::new( - &[ - instruction, - Instruction::new_with_bytes(panic_id, &[0], vec![]), - ], - Some(&mint_keypair.pubkey()), - ); - - bank_client - .advance_slot(1, &Pubkey::default()) - .expect("Failed to advance the slot"); - assert!(bank_client - .send_and_confirm_message(&[&mint_keypair, &program_keypair], message) - .is_err()); - - // Write the noop program into the same program account - let (program_keypair, instruction) = load_and_finalize_program( - &bank_client, - &bpf_loader::id(), - Some(program_keypair), - &mint_keypair, - "solana_sbf_rust_noop", - ); - bank_client - .advance_slot(1, &Pubkey::default()) - .expect("Failed to advance the slot"); - let message = Message::new(&[instruction], Some(&mint_keypair.pubkey())); - bank_client - .send_and_confirm_message(&[&mint_keypair, &program_keypair], message) - .unwrap(); - - // Call the noop program, should get noop not panic - let message = Message::new( - &[Instruction::new_with_bytes( - program_keypair.pubkey(), - &[0], - vec![], - )], - Some(&mint_keypair.pubkey()), - ); - bank_client - .advance_slot(1, &Pubkey::default()) - .expect("Failed to advance the slot"); - assert!(bank_client - .send_and_confirm_message(&[&mint_keypair], message) - .is_ok()); -} - #[test] #[cfg(feature = "sbf_rust")] fn test_program_sbf_upgrade() { @@ -2079,10 +2014,9 @@ fn test_program_sbf_invoke_in_same_tx_as_redeployment() { ); // load_upgradeable_program sets clock sysvar to 1, which causes the program to be effective - // after 2 slots. So we need to advance the bank client by 2 slots here. - let bank = bank_client - .advance_slot(2, &Pubkey::default()) - .expect("Failed to advance slot"); + // after 2 slots. They need to be called individually to create the correct fork graph in between. + bank_client.advance_slot(1, &Pubkey::default()).unwrap(); + let bank = bank_client.advance_slot(1, &Pubkey::default()).unwrap(); // Prepare redeployment let buffer_keypair = Keypair::new(); @@ -2176,10 +2110,9 @@ fn test_program_sbf_invoke_in_same_tx_as_undeployment() { ); // load_upgradeable_program sets clock sysvar to 1, which causes the program to be effective - // after 2 slots. So we need to advance the bank client by 2 slots here. - let bank = bank_client - .advance_slot(2, &Pubkey::default()) - .expect("Failed to advance slot"); + // after 2 slots. They need to be called individually to create the correct fork graph in between. + bank_client.advance_slot(1, &Pubkey::default()).unwrap(); + let bank = bank_client.advance_slot(1, &Pubkey::default()).unwrap(); // Prepare undeployment let (programdata_address, _) = Pubkey::find_program_address( @@ -2228,28 +2161,28 @@ fn test_program_sbf_invoke_upgradeable_via_cpi() { mint_keypair, .. } = create_genesis_config(50); + let bank = Bank::new_for_tests(&genesis_config); let mut bank_client = BankClient::new(bank); - let invoke_and_return = load_program( - &bank_client, - &bpf_loader::id(), + let authority_keypair = Keypair::new(); + let (_bank, invoke_and_return) = load_upgradeable_program_and_advance_slot( + &mut bank_client, &mint_keypair, + &authority_keypair, "solana_sbf_rust_invoke_and_return", ); // Deploy upgradeable program - let buffer_keypair = Keypair::new(); - let program_keypair = Keypair::new(); - let program_id = program_keypair.pubkey(); - let authority_keypair = Keypair::new(); - load_upgradeable_program( + let program_id = load_upgradeable_program_wrapper( &bank_client, &mint_keypair, - &buffer_keypair, - &program_keypair, &authority_keypair, "solana_sbf_rust_upgradeable", ); + bank_client.set_sysvar_for_tests(&clock::Clock { + slot: 2, + ..clock::Clock::default() + }); bank_client .advance_slot(1, &Pubkey::default()) @@ -2283,7 +2216,7 @@ fn test_program_sbf_invoke_upgradeable_via_cpi() { "solana_sbf_rust_upgraded", ); bank_client.set_sysvar_for_tests(&clock::Clock { - slot: 2, + slot: 3, ..clock::Clock::default() }); bank_client @@ -2363,9 +2296,15 @@ fn test_program_sbf_disguised_as_sbf_loader() { bank.deactivate_feature( &solana_sdk::feature_set::remove_bpf_loader_incorrect_program_id::id(), ); - let bank_client = BankClient::new(bank); + let mut bank_client = BankClient::new(bank); + let authority_keypair = Keypair::new(); - let program_id = load_program(&bank_client, &bpf_loader::id(), &mint_keypair, program); + let (_bank, program_id) = load_upgradeable_program_and_advance_slot( + &mut bank_client, + &mint_keypair, + &authority_keypair, + program, + ); let account_metas = vec![AccountMeta::new_readonly(program_id, false)]; let instruction = Instruction::new_with_bytes(bpf_loader::id(), &[1], account_metas); let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction); @@ -2386,13 +2325,15 @@ fn test_program_reads_from_program_account() { mint_keypair, .. } = create_genesis_config(50); + let bank = Bank::new_for_tests(&genesis_config); let mut bank_client = BankClient::new(bank); + let authority_keypair = Keypair::new(); - let (_, program_id) = load_program_and_advance_slot( + let (_bank, program_id) = load_upgradeable_program_and_advance_slot( &mut bank_client, - &bpf_loader::id(), &mint_keypair, + &authority_keypair, "read_program", ); let account_metas = vec![AccountMeta::new_readonly(program_id, false)]; @@ -2412,6 +2353,7 @@ fn test_program_sbf_c_dup() { mint_keypair, .. } = create_genesis_config(50); + let bank = Bank::new_for_tests(&genesis_config); let account_address = Pubkey::new_unique(); @@ -2419,9 +2361,13 @@ fn test_program_sbf_c_dup() { bank.store_account(&account_address, &account); let mut bank_client = BankClient::new(bank); - - let (_, program_id) = - load_program_and_advance_slot(&mut bank_client, &bpf_loader::id(), &mint_keypair, "ser"); + let authority_keypair = Keypair::new(); + let (_, program_id) = load_upgradeable_program_and_advance_slot( + &mut bank_client, + &mint_keypair, + &authority_keypair, + "ser", + ); let account_metas = vec![ AccountMeta::new_readonly(account_address, false), AccountMeta::new_readonly(account_address, false), @@ -2442,12 +2388,14 @@ fn test_program_sbf_upgrade_via_cpi() { mint_keypair, .. } = create_genesis_config(50); + let bank = Bank::new_for_tests(&genesis_config); let mut bank_client = BankClient::new(bank); - let invoke_and_return = load_program( - &bank_client, - &bpf_loader::id(), + let authority_keypair = Keypair::new(); + let (_bank, invoke_and_return) = load_upgradeable_program_and_advance_slot( + &mut bank_client, &mint_keypair, + &authority_keypair, "solana_sbf_rust_invoke_and_return", ); @@ -2552,27 +2500,23 @@ fn test_program_sbf_set_upgrade_authority_via_cpi() { mint_keypair, .. } = create_genesis_config(50); + let bank = Bank::new_for_tests(&genesis_config); let mut bank_client = BankClient::new(bank); + let authority_keypair = Keypair::new(); // Deploy CPI invoker program - let invoke_and_return = load_program( + let invoke_and_return = load_upgradeable_program_wrapper( &bank_client, - &bpf_loader::id(), &mint_keypair, + &authority_keypair, "solana_sbf_rust_invoke_and_return", ); // Deploy upgradeable program - let buffer_keypair = Keypair::new(); - let program_keypair = Keypair::new(); - let program_id = program_keypair.pubkey(); - let authority_keypair = Keypair::new(); - load_upgradeable_program( + let program_id = load_upgradeable_program_wrapper( &bank_client, &mint_keypair, - &buffer_keypair, - &program_keypair, &authority_keypair, "solana_sbf_rust_upgradeable", ); @@ -2758,54 +2702,6 @@ fn test_program_upgradeable_locks() { assert_eq!(results2[1], Err(TransactionError::AccountInUse)); } -#[test] -#[cfg(feature = "sbf_rust")] -fn test_program_sbf_finalize() { - solana_logger::setup(); - - let GenesisConfigInfo { - genesis_config, - mint_keypair, - .. - } = create_genesis_config(50); - let bank = Bank::new_for_tests(&genesis_config); - let bank = Arc::new(bank); - let mut bank_client = BankClient::new_shared(bank.clone()); - - let (_, program_pubkey) = load_program_and_advance_slot( - &mut bank_client, - &bpf_loader::id(), - &mint_keypair, - "solana_sbf_rust_finalize", - ); - - // Write the noop program into the same program account - let (program_keypair, _instruction) = load_and_finalize_program( - &bank_client, - &bpf_loader::id(), - None, - &mint_keypair, - "solana_sbf_rust_noop", - ); - - bank_client - .advance_slot(1, &Pubkey::default()) - .expect("Failed to advance the slot"); - - let account_metas = vec![ - AccountMeta::new(program_keypair.pubkey(), true), - AccountMeta::new_readonly(bpf_loader::id(), false), - AccountMeta::new(rent::id(), false), - ]; - let instruction = Instruction::new_with_bytes(program_pubkey, &[], account_metas.clone()); - let message = Message::new(&[instruction], Some(&mint_keypair.pubkey())); - let result = bank_client.send_and_confirm_message(&[&mint_keypair, &program_keypair], message); - assert_eq!( - result.unwrap_err().unwrap(), - TransactionError::InstructionError(0, InstructionError::ProgramFailedToComplete) - ); -} - #[test] #[cfg(feature = "sbf_rust")] fn test_program_sbf_ro_account_modify() { @@ -2816,14 +2712,16 @@ fn test_program_sbf_ro_account_modify() { mint_keypair, .. } = create_genesis_config(50); + let bank = Bank::new_for_tests(&genesis_config); let bank = Arc::new(bank); let mut bank_client = BankClient::new_shared(bank.clone()); + let authority_keypair = Keypair::new(); - let (bank, program_id) = load_program_and_advance_slot( + let (bank, program_id) = load_upgradeable_program_and_advance_slot( &mut bank_client, - &bpf_loader::id(), &mint_keypair, + &authority_keypair, "solana_sbf_rust_ro_account_modify", ); @@ -2878,6 +2776,7 @@ fn test_program_sbf_realloc() { mint_keypair, .. } = create_genesis_config(1_000_000_000_000); + let mint_pubkey = mint_keypair.pubkey(); let signer = &[&mint_keypair]; for direct_mapping in [false, true] { @@ -2890,11 +2789,12 @@ fn test_program_sbf_realloc() { } let bank = Arc::new(bank); let mut bank_client = BankClient::new_shared(bank.clone()); + let authority_keypair = Keypair::new(); - let (bank, program_id) = load_program_and_advance_slot( + let (bank, program_id) = load_upgradeable_program_and_advance_slot( &mut bank_client, - &bpf_loader::id(), &mint_keypair, + &authority_keypair, "solana_sbf_rust_realloc", ); @@ -3212,24 +3112,26 @@ fn test_program_sbf_realloc_invoke() { .. } = create_genesis_config(1_000_000_000_000); genesis_config.rent = Rent::default(); + let mint_pubkey = mint_keypair.pubkey(); let signer = &[&mint_keypair]; let bank = Bank::new_for_tests(&genesis_config); let bank = Arc::new(bank); let mut bank_client = BankClient::new_shared(bank.clone()); + let authority_keypair = Keypair::new(); - let realloc_program_id = load_program( + let realloc_program_id = load_upgradeable_program_wrapper( &bank_client, - &bpf_loader::id(), &mint_keypair, + &authority_keypair, "solana_sbf_rust_realloc", ); - let (bank, realloc_invoke_program_id) = load_program_and_advance_slot( + let (bank, realloc_invoke_program_id) = load_upgradeable_program_and_advance_slot( &mut bank_client, - &bpf_loader::id(), &mint_keypair, + &authority_keypair, "solana_sbf_rust_realloc_invoke", ); @@ -3684,7 +3586,7 @@ fn test_program_sbf_realloc_invoke() { TransactionError::InstructionError(0, InstructionError::InvalidRealloc) ); - // Realloc rescursively and fill data + // Realloc recursively and fill data let invoke_keypair = Keypair::new(); let invoke_pubkey = invoke_keypair.pubkey().clone(); let invoke_account = AccountSharedData::new(START_BALANCE, 0, &realloc_invoke_program_id); @@ -3731,32 +3633,34 @@ fn test_program_sbf_processed_inner_instruction() { mint_keypair, .. } = create_genesis_config(50); + let bank = Bank::new_for_tests(&genesis_config); let bank = Arc::new(bank); let mut bank_client = BankClient::new_shared(bank.clone()); + let authority_keypair = Keypair::new(); - let sibling_program_id = load_program( + let sibling_program_id = load_upgradeable_program_wrapper( &bank_client, - &bpf_loader::id(), &mint_keypair, + &authority_keypair, "solana_sbf_rust_sibling_instructions", ); - let sibling_inner_program_id = load_program( + let sibling_inner_program_id = load_upgradeable_program_wrapper( &bank_client, - &bpf_loader::id(), &mint_keypair, + &authority_keypair, "solana_sbf_rust_sibling_inner_instructions", ); - let noop_program_id = load_program( + let noop_program_id = load_upgradeable_program_wrapper( &bank_client, - &bpf_loader::id(), &mint_keypair, + &authority_keypair, "solana_sbf_rust_noop", ); - let (_, invoke_and_return_program_id) = load_program_and_advance_slot( + let (_, invoke_and_return_program_id) = load_upgradeable_program_and_advance_slot( &mut bank_client, - &bpf_loader::id(), &mint_keypair, + &authority_keypair, "solana_sbf_rust_invoke_and_return", ); @@ -3807,18 +3711,19 @@ fn test_program_fees() { mint_keypair, .. } = create_genesis_config(500_000_000); + genesis_config.fee_rate_governor = FeeRateGovernor::new(congestion_multiplier, 0); let mut bank = Bank::new_for_tests(&genesis_config); let fee_structure = FeeStructure::new(0.000005, 0.0, vec![(200, 0.0000005), (1400000, 0.000005)]); bank.fee_structure = fee_structure.clone(); - bank.feature_set = Arc::new(FeatureSet::all_enabled()); let mut bank_client = BankClient::new(bank); + let authority_keypair = Keypair::new(); - let (_, program_id) = load_program_and_advance_slot( + let (_bank, program_id) = load_upgradeable_program_and_advance_slot( &mut bank_client, - &bpf_loader::id(), &mint_keypair, + &authority_keypair, "solana_sbf_rust_noop", ); @@ -3886,15 +3791,16 @@ fn test_get_minimum_delegation() { mint_keypair, .. } = create_genesis_config(100_123_456_789); - let mut bank = Bank::new_for_tests(&genesis_config); - bank.feature_set = Arc::new(FeatureSet::all_enabled()); + + let bank = Bank::new_for_tests(&genesis_config); let bank = Arc::new(bank); let mut bank_client = BankClient::new_shared(bank.clone()); + let authority_keypair = Keypair::new(); - let (_, program_id) = load_program_and_advance_slot( + let (_bank, program_id) = load_upgradeable_program_and_advance_slot( &mut bank_client, - &bpf_loader::id(), &mint_keypair, + &authority_keypair, "solana_sbf_rust_get_minimum_delegation", ); @@ -3961,25 +3867,26 @@ fn test_cpi_account_ownership_writability() { bank.feature_set = Arc::new(feature_set); let bank = Arc::new(bank); let mut bank_client = BankClient::new_shared(bank); + let authority_keypair = Keypair::new(); - let invoke_program_id = load_program( + let invoke_program_id = load_upgradeable_program_wrapper( &bank_client, - &bpf_loader::id(), &mint_keypair, + &authority_keypair, "solana_sbf_rust_invoke", ); - let invoked_program_id = load_program( + let invoked_program_id = load_upgradeable_program_wrapper( &bank_client, - &bpf_loader::id(), &mint_keypair, + &authority_keypair, "solana_sbf_rust_invoked", ); - let (bank, realloc_program_id) = load_program_and_advance_slot( + let (bank, realloc_program_id) = load_upgradeable_program_and_advance_slot( &mut bank_client, - &bpf_loader::id(), &mint_keypair, + &authority_keypair, "solana_sbf_rust_realloc", ); @@ -4139,21 +4046,23 @@ fn test_cpi_account_data_updates() { if !direct_mapping { feature_set.deactivate(&feature_set::bpf_account_data_direct_mapping::id()); } + bank.feature_set = Arc::new(feature_set); let bank = Arc::new(bank); let mut bank_client = BankClient::new_shared(bank); + let authority_keypair = Keypair::new(); - let invoke_program_id = load_program( + let invoke_program_id = load_upgradeable_program_wrapper( &bank_client, - &bpf_loader::id(), &mint_keypair, + &authority_keypair, "solana_sbf_rust_invoke", ); - let (bank, realloc_program_id) = load_program_and_advance_slot( + let (bank, realloc_program_id) = load_upgradeable_program_and_advance_slot( &mut bank_client, - &bpf_loader::id(), &mint_keypair, + &authority_keypair, "solana_sbf_rust_realloc", ); @@ -4286,6 +4195,7 @@ fn test_cpi_deprecated_loader_realloc() { if !direct_mapping { feature_set.deactivate(&feature_set::bpf_account_data_direct_mapping::id()); } + bank.feature_set = Arc::new(feature_set); let bank = Arc::new(bank); @@ -4296,11 +4206,12 @@ fn test_cpi_deprecated_loader_realloc() { ); let mut bank_client = BankClient::new_shared(bank); + let authority_keypair = Keypair::new(); - let (bank, invoke_program_id) = load_program_and_advance_slot( + let (bank, invoke_program_id) = load_upgradeable_program_and_advance_slot( &mut bank_client, - &bpf_loader::id(), &mint_keypair, + &authority_keypair, "solana_sbf_rust_invoke", ); @@ -4393,11 +4304,10 @@ fn test_cpi_change_account_data_memory_allocation() { mint_keypair, .. } = create_genesis_config(100_123_456_789); + let mut bank = Bank::new_for_tests(&genesis_config); - let feature_set = FeatureSet::all_enabled(); - bank.feature_set = Arc::new(feature_set); - declare_process_instruction!(process_instruction, 42, |invoke_context| { + declare_process_instruction!(MockBuiltin, 42, |invoke_context| { let transaction_context = &invoke_context.transaction_context; let instruction_context = transaction_context.get_current_instruction_context()?; let instruction_data = instruction_context.get_instruction_data(); @@ -4413,12 +4323,18 @@ fn test_cpi_change_account_data_memory_allocation() { // Test changing the account data both in place and by changing the // underlying vector. CPI will have to detect the vector change and - // update the corresponding memory region. In both cases CPI will have + // update the corresponding memory region. In all cases CPI will have // to zero the spare bytes correctly. - if instruction_data[0] == 0xFE { - account.set_data(instruction_data.to_vec()); - } else { - account.set_data_from_slice(instruction_data); + match instruction_data[0] { + 0xFE => account.set_data(instruction_data.to_vec()), + 0xFD => account.set_data_from_slice(instruction_data), + 0xFC => { + // Exercise the update_caller_account capacity check where account len != capacity. + let mut data = instruction_data.to_vec(); + data.reserve_exact(1); + account.set_data(data) + } + _ => panic!(), } Ok(()) @@ -4428,16 +4344,17 @@ fn test_cpi_change_account_data_memory_allocation() { bank.add_builtin( builtin_program_id, "test_cpi_change_account_data_memory_allocation_builtin".to_string(), - LoadedProgram::new_builtin(0, 42, process_instruction), + LoadedProgram::new_builtin(0, 42, MockBuiltin::vm), ); let bank = Arc::new(bank); let mut bank_client = BankClient::new_shared(bank); + let authority_keypair = Keypair::new(); - let (bank, invoke_program_id) = load_program_and_advance_slot( + let (bank, invoke_program_id) = load_upgradeable_program_and_advance_slot( &mut bank_client, - &bpf_loader::id(), &mint_keypair, + &authority_keypair, "solana_sbf_rust_invoke", ); @@ -4472,19 +4389,19 @@ fn test_cpi_invalid_account_info_pointers() { mint_keypair, .. } = create_genesis_config(100_123_456_789); - let mut bank = Bank::new_for_tests(&genesis_config); - let feature_set = FeatureSet::all_enabled(); - bank.feature_set = Arc::new(feature_set); + + let bank = Bank::new_for_tests(&genesis_config); let bank = Arc::new(bank); let mut bank_client = BankClient::new_shared(bank); + let authority_keypair = Keypair::new(); let c_invoke_program_id = - load_program(&bank_client, &bpf_loader::id(), &mint_keypair, "invoke"); + load_upgradeable_program_wrapper(&bank_client, &mint_keypair, &authority_keypair, "invoke"); - let (bank, invoke_program_id) = load_program_and_advance_slot( + let (bank, invoke_program_id) = load_upgradeable_program_and_advance_slot( &mut bank_client, - &bpf_loader::id(), &mint_keypair, + &authority_keypair, "solana_sbf_rust_invoke", ); @@ -4545,11 +4462,12 @@ fn test_deny_executable_write() { } let bank = Arc::new(bank); let mut bank_client = BankClient::new_shared(bank); + let authority_keypair = Keypair::new(); - let (_bank, invoke_program_id) = load_program_and_advance_slot( + let (_bank, invoke_program_id) = load_upgradeable_program_and_advance_slot( &mut bank_client, - &bpf_loader::id(), &mint_keypair, + &authority_keypair, "solana_sbf_rust_invoke", ); diff --git a/programs/stake/src/stake_instruction.rs b/programs/stake/src/stake_instruction.rs index 20b6c9e0ebe..cd0b59e8253 100644 --- a/programs/stake/src/stake_instruction.rs +++ b/programs/stake/src/stake_instruction.rs @@ -54,411 +54,391 @@ fn get_optional_pubkey<'a>( pub const DEFAULT_COMPUTE_UNITS: u64 = 750; -declare_process_instruction!( - process_instruction, - DEFAULT_COMPUTE_UNITS, - |invoke_context| { - let transaction_context = &invoke_context.transaction_context; - let instruction_context = transaction_context.get_current_instruction_context()?; - let data = instruction_context.get_instruction_data(); - - trace!("process_instruction: {:?}", data); - - let get_stake_account = || { - let me = instruction_context.try_borrow_instruction_account(transaction_context, 0)?; - if *me.get_owner() != id() { - return Err(InstructionError::InvalidAccountOwner); - } - Ok(me) - }; +declare_process_instruction!(Entrypoint, DEFAULT_COMPUTE_UNITS, |invoke_context| { + let transaction_context = &invoke_context.transaction_context; + let instruction_context = transaction_context.get_current_instruction_context()?; + let data = instruction_context.get_instruction_data(); - let signers = instruction_context.get_signers(transaction_context)?; - match limited_deserialize(data) { - Ok(StakeInstruction::Initialize(authorized, lockup)) => { - let mut me = get_stake_account()?; - let rent = - get_sysvar_with_account_check::rent(invoke_context, instruction_context, 1)?; - initialize(&mut me, &authorized, &lockup, &rent) - } - Ok(StakeInstruction::Authorize(authorized_pubkey, stake_authorize)) => { - let mut me = get_stake_account()?; - let require_custodian_for_locked_stake_authorize = invoke_context - .feature_set - .is_active(&feature_set::require_custodian_for_locked_stake_authorize::id()); - - if require_custodian_for_locked_stake_authorize { - let clock = get_sysvar_with_account_check::clock( - invoke_context, - instruction_context, - 1, - )?; - instruction_context.check_number_of_instruction_accounts(3)?; - let custodian_pubkey = - get_optional_pubkey(transaction_context, instruction_context, 3, false)?; - - authorize( - &mut me, - &signers, - &authorized_pubkey, - stake_authorize, - require_custodian_for_locked_stake_authorize, - &clock, - custodian_pubkey, - ) - } else { - authorize( - &mut me, - &signers, - &authorized_pubkey, - stake_authorize, - require_custodian_for_locked_stake_authorize, - &Clock::default(), - None, - ) - } - } - Ok(StakeInstruction::AuthorizeWithSeed(args)) => { - let mut me = get_stake_account()?; - instruction_context.check_number_of_instruction_accounts(2)?; - let require_custodian_for_locked_stake_authorize = invoke_context - .feature_set - .is_active(&feature_set::require_custodian_for_locked_stake_authorize::id()); - if require_custodian_for_locked_stake_authorize { - let clock = get_sysvar_with_account_check::clock( - invoke_context, - instruction_context, - 2, - )?; - let custodian_pubkey = - get_optional_pubkey(transaction_context, instruction_context, 3, false)?; - - authorize_with_seed( - transaction_context, - instruction_context, - &mut me, - 1, - &args.authority_seed, - &args.authority_owner, - &args.new_authorized_pubkey, - args.stake_authorize, - require_custodian_for_locked_stake_authorize, - &clock, - custodian_pubkey, - ) - } else { - authorize_with_seed( - transaction_context, - instruction_context, - &mut me, - 1, - &args.authority_seed, - &args.authority_owner, - &args.new_authorized_pubkey, - args.stake_authorize, - require_custodian_for_locked_stake_authorize, - &Clock::default(), - None, - ) - } + trace!("process_instruction: {:?}", data); + + let get_stake_account = || { + let me = instruction_context.try_borrow_instruction_account(transaction_context, 0)?; + if *me.get_owner() != id() { + return Err(InstructionError::InvalidAccountOwner); + } + Ok(me) + }; + + let signers = instruction_context.get_signers(transaction_context)?; + match limited_deserialize(data) { + Ok(StakeInstruction::Initialize(authorized, lockup)) => { + let mut me = get_stake_account()?; + let rent = get_sysvar_with_account_check::rent(invoke_context, instruction_context, 1)?; + initialize(&mut me, &authorized, &lockup, &rent) + } + Ok(StakeInstruction::Authorize(authorized_pubkey, stake_authorize)) => { + let mut me = get_stake_account()?; + let require_custodian_for_locked_stake_authorize = invoke_context + .feature_set + .is_active(&feature_set::require_custodian_for_locked_stake_authorize::id()); + + if require_custodian_for_locked_stake_authorize { + let clock = + get_sysvar_with_account_check::clock(invoke_context, instruction_context, 1)?; + instruction_context.check_number_of_instruction_accounts(3)?; + let custodian_pubkey = + get_optional_pubkey(transaction_context, instruction_context, 3, false)?; + + authorize( + &mut me, + &signers, + &authorized_pubkey, + stake_authorize, + require_custodian_for_locked_stake_authorize, + &clock, + custodian_pubkey, + ) + } else { + authorize( + &mut me, + &signers, + &authorized_pubkey, + stake_authorize, + require_custodian_for_locked_stake_authorize, + &Clock::default(), + None, + ) } - Ok(StakeInstruction::DelegateStake) => { - let me = get_stake_account()?; - instruction_context.check_number_of_instruction_accounts(2)?; + } + Ok(StakeInstruction::AuthorizeWithSeed(args)) => { + let mut me = get_stake_account()?; + instruction_context.check_number_of_instruction_accounts(2)?; + let require_custodian_for_locked_stake_authorize = invoke_context + .feature_set + .is_active(&feature_set::require_custodian_for_locked_stake_authorize::id()); + if require_custodian_for_locked_stake_authorize { let clock = get_sysvar_with_account_check::clock(invoke_context, instruction_context, 2)?; - let stake_history = get_sysvar_with_account_check::stake_history( - invoke_context, - instruction_context, - 3, - )?; - instruction_context.check_number_of_instruction_accounts(5)?; - drop(me); - if !invoke_context - .feature_set - .is_active(&feature_set::reduce_stake_warmup_cooldown::id()) - { - // Post feature activation, remove both the feature gate code and the config completely in the interface - let config_account = instruction_context - .try_borrow_instruction_account(transaction_context, 4)?; - #[allow(deprecated)] - if !config::check_id(config_account.get_key()) { - return Err(InstructionError::InvalidArgument); - } - config::from(&config_account).ok_or(InstructionError::InvalidArgument)?; - } - delegate( - invoke_context, + let custodian_pubkey = + get_optional_pubkey(transaction_context, instruction_context, 3, false)?; + + authorize_with_seed( transaction_context, instruction_context, - 0, + &mut me, 1, + &args.authority_seed, + &args.authority_owner, + &args.new_authorized_pubkey, + args.stake_authorize, + require_custodian_for_locked_stake_authorize, &clock, - &stake_history, - &signers, - &invoke_context.feature_set, + custodian_pubkey, ) - } - Ok(StakeInstruction::Split(lamports)) => { - let me = get_stake_account()?; - instruction_context.check_number_of_instruction_accounts(2)?; - drop(me); - split( - invoke_context, + } else { + authorize_with_seed( transaction_context, instruction_context, - 0, - lamports, + &mut me, 1, - &signers, + &args.authority_seed, + &args.authority_owner, + &args.new_authorized_pubkey, + args.stake_authorize, + require_custodian_for_locked_stake_authorize, + &Clock::default(), + None, ) } - Ok(StakeInstruction::Merge) => { - let me = get_stake_account()?; - instruction_context.check_number_of_instruction_accounts(2)?; + } + Ok(StakeInstruction::DelegateStake) => { + let me = get_stake_account()?; + instruction_context.check_number_of_instruction_accounts(2)?; + let clock = + get_sysvar_with_account_check::clock(invoke_context, instruction_context, 2)?; + let stake_history = get_sysvar_with_account_check::stake_history( + invoke_context, + instruction_context, + 3, + )?; + instruction_context.check_number_of_instruction_accounts(5)?; + drop(me); + if !invoke_context + .feature_set + .is_active(&feature_set::reduce_stake_warmup_cooldown::id()) + { + // Post feature activation, remove both the feature gate code and the config completely in the interface + let config_account = + instruction_context.try_borrow_instruction_account(transaction_context, 4)?; + #[allow(deprecated)] + if !config::check_id(config_account.get_key()) { + return Err(InstructionError::InvalidArgument); + } + config::from(&config_account).ok_or(InstructionError::InvalidArgument)?; + } + delegate( + invoke_context, + transaction_context, + instruction_context, + 0, + 1, + &clock, + &stake_history, + &signers, + &invoke_context.feature_set, + ) + } + Ok(StakeInstruction::Split(lamports)) => { + let me = get_stake_account()?; + instruction_context.check_number_of_instruction_accounts(2)?; + drop(me); + split( + invoke_context, + transaction_context, + instruction_context, + 0, + lamports, + 1, + &signers, + ) + } + Ok(StakeInstruction::Merge) => { + let me = get_stake_account()?; + instruction_context.check_number_of_instruction_accounts(2)?; + let clock = + get_sysvar_with_account_check::clock(invoke_context, instruction_context, 2)?; + let stake_history = get_sysvar_with_account_check::stake_history( + invoke_context, + instruction_context, + 3, + )?; + drop(me); + merge( + invoke_context, + transaction_context, + instruction_context, + 0, + 1, + &clock, + &stake_history, + &signers, + ) + } + Ok(StakeInstruction::Withdraw(lamports)) => { + let me = get_stake_account()?; + instruction_context.check_number_of_instruction_accounts(2)?; + let clock = + get_sysvar_with_account_check::clock(invoke_context, instruction_context, 2)?; + let stake_history = get_sysvar_with_account_check::stake_history( + invoke_context, + instruction_context, + 3, + )?; + instruction_context.check_number_of_instruction_accounts(5)?; + drop(me); + withdraw( + transaction_context, + instruction_context, + 0, + lamports, + 1, + &clock, + &stake_history, + 4, + if instruction_context.get_number_of_instruction_accounts() >= 6 { + Some(5) + } else { + None + }, + new_warmup_cooldown_rate_epoch(invoke_context), + ) + } + Ok(StakeInstruction::Deactivate) => { + let mut me = get_stake_account()?; + let clock = + get_sysvar_with_account_check::clock(invoke_context, instruction_context, 1)?; + deactivate(invoke_context, &mut me, &clock, &signers) + } + Ok(StakeInstruction::SetLockup(lockup)) => { + let mut me = get_stake_account()?; + let clock = invoke_context.get_sysvar_cache().get_clock()?; + set_lockup(&mut me, &lockup, &signers, &clock) + } + Ok(StakeInstruction::InitializeChecked) => { + let mut me = get_stake_account()?; + if invoke_context + .feature_set + .is_active(&feature_set::vote_stake_checked_instructions::id()) + { + instruction_context.check_number_of_instruction_accounts(4)?; + let staker_pubkey = transaction_context.get_key_of_account_at_index( + instruction_context.get_index_of_instruction_account_in_transaction(2)?, + )?; + let withdrawer_pubkey = transaction_context.get_key_of_account_at_index( + instruction_context.get_index_of_instruction_account_in_transaction(3)?, + )?; + if !instruction_context.is_instruction_account_signer(3)? { + return Err(InstructionError::MissingRequiredSignature); + } + + let authorized = Authorized { + staker: *staker_pubkey, + withdrawer: *withdrawer_pubkey, + }; + + let rent = + get_sysvar_with_account_check::rent(invoke_context, instruction_context, 1)?; + initialize(&mut me, &authorized, &Lockup::default(), &rent) + } else { + Err(InstructionError::InvalidInstructionData) + } + } + Ok(StakeInstruction::AuthorizeChecked(stake_authorize)) => { + let mut me = get_stake_account()?; + if invoke_context + .feature_set + .is_active(&feature_set::vote_stake_checked_instructions::id()) + { let clock = - get_sysvar_with_account_check::clock(invoke_context, instruction_context, 2)?; - let stake_history = get_sysvar_with_account_check::stake_history( - invoke_context, - instruction_context, - 3, + get_sysvar_with_account_check::clock(invoke_context, instruction_context, 1)?; + instruction_context.check_number_of_instruction_accounts(4)?; + let authorized_pubkey = transaction_context.get_key_of_account_at_index( + instruction_context.get_index_of_instruction_account_in_transaction(3)?, )?; - drop(me); - merge( - invoke_context, - transaction_context, - instruction_context, - 0, - 1, - &clock, - &stake_history, + if !instruction_context.is_instruction_account_signer(3)? { + return Err(InstructionError::MissingRequiredSignature); + } + let custodian_pubkey = + get_optional_pubkey(transaction_context, instruction_context, 4, false)?; + + authorize( + &mut me, &signers, + authorized_pubkey, + stake_authorize, + true, + &clock, + custodian_pubkey, ) + } else { + Err(InstructionError::InvalidInstructionData) } - Ok(StakeInstruction::Withdraw(lamports)) => { - let me = get_stake_account()?; + } + Ok(StakeInstruction::AuthorizeCheckedWithSeed(args)) => { + let mut me = get_stake_account()?; + if invoke_context + .feature_set + .is_active(&feature_set::vote_stake_checked_instructions::id()) + { instruction_context.check_number_of_instruction_accounts(2)?; let clock = get_sysvar_with_account_check::clock(invoke_context, instruction_context, 2)?; - let stake_history = get_sysvar_with_account_check::stake_history( - invoke_context, - instruction_context, - 3, + instruction_context.check_number_of_instruction_accounts(4)?; + let authorized_pubkey = transaction_context.get_key_of_account_at_index( + instruction_context.get_index_of_instruction_account_in_transaction(3)?, )?; - instruction_context.check_number_of_instruction_accounts(5)?; - drop(me); - withdraw( + if !instruction_context.is_instruction_account_signer(3)? { + return Err(InstructionError::MissingRequiredSignature); + } + let custodian_pubkey = + get_optional_pubkey(transaction_context, instruction_context, 4, false)?; + + authorize_with_seed( transaction_context, instruction_context, - 0, - lamports, + &mut me, 1, + &args.authority_seed, + &args.authority_owner, + authorized_pubkey, + args.stake_authorize, + true, &clock, - &stake_history, - 4, - if instruction_context.get_number_of_instruction_accounts() >= 6 { - Some(5) - } else { - None - }, - new_warmup_cooldown_rate_epoch(invoke_context), + custodian_pubkey, ) + } else { + Err(InstructionError::InvalidInstructionData) } - Ok(StakeInstruction::Deactivate) => { - let mut me = get_stake_account()?; - let clock = - get_sysvar_with_account_check::clock(invoke_context, instruction_context, 1)?; - deactivate(invoke_context, &mut me, &clock, &signers) - } - Ok(StakeInstruction::SetLockup(lockup)) => { - let mut me = get_stake_account()?; + } + Ok(StakeInstruction::SetLockupChecked(lockup_checked)) => { + let mut me = get_stake_account()?; + if invoke_context + .feature_set + .is_active(&feature_set::vote_stake_checked_instructions::id()) + { + let custodian_pubkey = + get_optional_pubkey(transaction_context, instruction_context, 2, true)?; + + let lockup = LockupArgs { + unix_timestamp: lockup_checked.unix_timestamp, + epoch: lockup_checked.epoch, + custodian: custodian_pubkey.cloned(), + }; let clock = invoke_context.get_sysvar_cache().get_clock()?; set_lockup(&mut me, &lockup, &signers, &clock) + } else { + Err(InstructionError::InvalidInstructionData) } - Ok(StakeInstruction::InitializeChecked) => { - let mut me = get_stake_account()?; - if invoke_context - .feature_set - .is_active(&feature_set::vote_stake_checked_instructions::id()) - { - instruction_context.check_number_of_instruction_accounts(4)?; - let staker_pubkey = transaction_context.get_key_of_account_at_index( - instruction_context.get_index_of_instruction_account_in_transaction(2)?, - )?; - let withdrawer_pubkey = transaction_context.get_key_of_account_at_index( - instruction_context.get_index_of_instruction_account_in_transaction(3)?, - )?; - if !instruction_context.is_instruction_account_signer(3)? { - return Err(InstructionError::MissingRequiredSignature); - } - - let authorized = Authorized { - staker: *staker_pubkey, - withdrawer: *withdrawer_pubkey, - }; - - let rent = get_sysvar_with_account_check::rent( - invoke_context, - instruction_context, - 1, - )?; - initialize(&mut me, &authorized, &Lockup::default(), &rent) - } else { - Err(InstructionError::InvalidInstructionData) - } - } - Ok(StakeInstruction::AuthorizeChecked(stake_authorize)) => { - let mut me = get_stake_account()?; - if invoke_context - .feature_set - .is_active(&feature_set::vote_stake_checked_instructions::id()) - { - let clock = get_sysvar_with_account_check::clock( - invoke_context, - instruction_context, - 1, - )?; - instruction_context.check_number_of_instruction_accounts(4)?; - let authorized_pubkey = transaction_context.get_key_of_account_at_index( - instruction_context.get_index_of_instruction_account_in_transaction(3)?, - )?; - if !instruction_context.is_instruction_account_signer(3)? { - return Err(InstructionError::MissingRequiredSignature); - } - let custodian_pubkey = - get_optional_pubkey(transaction_context, instruction_context, 4, false)?; - - authorize( - &mut me, - &signers, - authorized_pubkey, - stake_authorize, - true, - &clock, - custodian_pubkey, - ) - } else { - Err(InstructionError::InvalidInstructionData) - } - } - Ok(StakeInstruction::AuthorizeCheckedWithSeed(args)) => { - let mut me = get_stake_account()?; - if invoke_context + } + Ok(StakeInstruction::GetMinimumDelegation) => { + let feature_set = invoke_context.feature_set.as_ref(); + let minimum_delegation = crate::get_minimum_delegation(feature_set); + let minimum_delegation = Vec::from(minimum_delegation.to_le_bytes()); + invoke_context + .transaction_context + .set_return_data(id(), minimum_delegation) + } + Ok(StakeInstruction::DeactivateDelinquent) => { + let mut me = get_stake_account()?; + instruction_context.check_number_of_instruction_accounts(3)?; + + let clock = invoke_context.get_sysvar_cache().get_clock()?; + deactivate_delinquent( + invoke_context, + transaction_context, + instruction_context, + &mut me, + 1, + 2, + clock.epoch, + ) + } + Ok(StakeInstruction::Redelegate) => { + let mut me = get_stake_account()?; + if invoke_context + .feature_set + .is_active(&feature_set::stake_redelegate_instruction::id()) + { + instruction_context.check_number_of_instruction_accounts(3)?; + if !invoke_context .feature_set - .is_active(&feature_set::vote_stake_checked_instructions::id()) + .is_active(&feature_set::reduce_stake_warmup_cooldown::id()) { - instruction_context.check_number_of_instruction_accounts(2)?; - let clock = get_sysvar_with_account_check::clock( - invoke_context, - instruction_context, - 2, - )?; - instruction_context.check_number_of_instruction_accounts(4)?; - let authorized_pubkey = transaction_context.get_key_of_account_at_index( - instruction_context.get_index_of_instruction_account_in_transaction(3)?, - )?; - if !instruction_context.is_instruction_account_signer(3)? { - return Err(InstructionError::MissingRequiredSignature); + // Post feature activation, remove both the feature gate code and the config completely in the interface + let config_account = instruction_context + .try_borrow_instruction_account(transaction_context, 3)?; + #[allow(deprecated)] + if !config::check_id(config_account.get_key()) { + return Err(InstructionError::InvalidArgument); } - let custodian_pubkey = - get_optional_pubkey(transaction_context, instruction_context, 4, false)?; - - authorize_with_seed( - transaction_context, - instruction_context, - &mut me, - 1, - &args.authority_seed, - &args.authority_owner, - authorized_pubkey, - args.stake_authorize, - true, - &clock, - custodian_pubkey, - ) - } else { - Err(InstructionError::InvalidInstructionData) - } - } - Ok(StakeInstruction::SetLockupChecked(lockup_checked)) => { - let mut me = get_stake_account()?; - if invoke_context - .feature_set - .is_active(&feature_set::vote_stake_checked_instructions::id()) - { - let custodian_pubkey = - get_optional_pubkey(transaction_context, instruction_context, 2, true)?; - - let lockup = LockupArgs { - unix_timestamp: lockup_checked.unix_timestamp, - epoch: lockup_checked.epoch, - custodian: custodian_pubkey.cloned(), - }; - let clock = invoke_context.get_sysvar_cache().get_clock()?; - set_lockup(&mut me, &lockup, &signers, &clock) - } else { - Err(InstructionError::InvalidInstructionData) + config::from(&config_account).ok_or(InstructionError::InvalidArgument)?; } - } - Ok(StakeInstruction::GetMinimumDelegation) => { - let feature_set = invoke_context.feature_set.as_ref(); - let minimum_delegation = crate::get_minimum_delegation(feature_set); - let minimum_delegation = Vec::from(minimum_delegation.to_le_bytes()); - invoke_context - .transaction_context - .set_return_data(id(), minimum_delegation) - } - Ok(StakeInstruction::DeactivateDelinquent) => { - let mut me = get_stake_account()?; - instruction_context.check_number_of_instruction_accounts(3)?; - - let clock = invoke_context.get_sysvar_cache().get_clock()?; - deactivate_delinquent( + redelegate( invoke_context, transaction_context, instruction_context, &mut me, 1, 2, - clock.epoch, + &signers, ) + } else { + Err(InstructionError::InvalidInstructionData) } - Ok(StakeInstruction::Redelegate) => { - let mut me = get_stake_account()?; - if invoke_context - .feature_set - .is_active(&feature_set::stake_redelegate_instruction::id()) - { - instruction_context.check_number_of_instruction_accounts(3)?; - if !invoke_context - .feature_set - .is_active(&feature_set::reduce_stake_warmup_cooldown::id()) - { - // Post feature activation, remove both the feature gate code and the config completely in the interface - let config_account = instruction_context - .try_borrow_instruction_account(transaction_context, 3)?; - #[allow(deprecated)] - if !config::check_id(config_account.get_key()) { - return Err(InstructionError::InvalidArgument); - } - config::from(&config_account).ok_or(InstructionError::InvalidArgument)?; - } - redelegate( - invoke_context, - transaction_context, - instruction_context, - &mut me, - 1, - 2, - &signers, - ) - } else { - Err(InstructionError::InvalidInstructionData) - } - } - Err(err) => Err(err), } + Err(err) => Err(err), } -); +}); #[cfg(test)] mod tests { @@ -572,7 +552,7 @@ mod tests { transaction_accounts, instruction_accounts, expected_result, - super::process_instruction, + Entrypoint::vm, |invoke_context| { invoke_context.feature_set = Arc::clone(&feature_set); }, @@ -7046,7 +7026,7 @@ mod tests { transaction_accounts, instruction_accounts, Ok(()), - super::process_instruction, + Entrypoint::vm, |invoke_context| { invoke_context.feature_set = Arc::clone(&feature_set); }, diff --git a/programs/system/src/system_processor.rs b/programs/system/src/system_processor.rs index dc6c1e3dbe9..b224997dc62 100644 --- a/programs/system/src/system_processor.rs +++ b/programs/system/src/system_processor.rs @@ -314,252 +314,246 @@ fn transfer_with_seed( pub const DEFAULT_COMPUTE_UNITS: u64 = 150; -declare_process_instruction!( - process_instruction, - DEFAULT_COMPUTE_UNITS, - |invoke_context| { - let transaction_context = &invoke_context.transaction_context; - let instruction_context = transaction_context.get_current_instruction_context()?; - let instruction_data = instruction_context.get_instruction_data(); - let instruction = limited_deserialize(instruction_data)?; - - trace!("process_instruction: {:?}", instruction); - - let signers = instruction_context.get_signers(transaction_context)?; - match instruction { - SystemInstruction::CreateAccount { +declare_process_instruction!(Entrypoint, DEFAULT_COMPUTE_UNITS, |invoke_context| { + let transaction_context = &invoke_context.transaction_context; + let instruction_context = transaction_context.get_current_instruction_context()?; + let instruction_data = instruction_context.get_instruction_data(); + let instruction = limited_deserialize(instruction_data)?; + + trace!("process_instruction: {:?}", instruction); + + let signers = instruction_context.get_signers(transaction_context)?; + match instruction { + SystemInstruction::CreateAccount { + lamports, + space, + owner, + } => { + instruction_context.check_number_of_instruction_accounts(2)?; + let to_address = Address::create( + transaction_context.get_key_of_account_at_index( + instruction_context.get_index_of_instruction_account_in_transaction(1)?, + )?, + None, + invoke_context, + )?; + create_account( + 0, + 1, + &to_address, lamports, space, - owner, - } => { - instruction_context.check_number_of_instruction_accounts(2)?; - let to_address = Address::create( - transaction_context.get_key_of_account_at_index( - instruction_context.get_index_of_instruction_account_in_transaction(1)?, - )?, - None, - invoke_context, - )?; - create_account( - 0, - 1, - &to_address, - lamports, - space, - &owner, - &signers, - invoke_context, - transaction_context, - instruction_context, - ) - } - SystemInstruction::CreateAccountWithSeed { - base, - seed, + &owner, + &signers, + invoke_context, + transaction_context, + instruction_context, + ) + } + SystemInstruction::CreateAccountWithSeed { + base, + seed, + lamports, + space, + owner, + } => { + instruction_context.check_number_of_instruction_accounts(2)?; + let to_address = Address::create( + transaction_context.get_key_of_account_at_index( + instruction_context.get_index_of_instruction_account_in_transaction(1)?, + )?, + Some((&base, &seed, &owner)), + invoke_context, + )?; + create_account( + 0, + 1, + &to_address, lamports, space, - owner, - } => { - instruction_context.check_number_of_instruction_accounts(2)?; - let to_address = Address::create( - transaction_context.get_key_of_account_at_index( - instruction_context.get_index_of_instruction_account_in_transaction(1)?, - )?, - Some((&base, &seed, &owner)), - invoke_context, - )?; - create_account( - 0, - 1, - &to_address, - lamports, - space, - &owner, - &signers, - invoke_context, - transaction_context, - instruction_context, - ) - } - SystemInstruction::Assign { owner } => { - instruction_context.check_number_of_instruction_accounts(1)?; - let mut account = - instruction_context.try_borrow_instruction_account(transaction_context, 0)?; - let address = Address::create( - transaction_context.get_key_of_account_at_index( - instruction_context.get_index_of_instruction_account_in_transaction(0)?, - )?, - None, - invoke_context, - )?; - assign(&mut account, &address, &owner, &signers, invoke_context) - } - SystemInstruction::Transfer { lamports } => { - instruction_context.check_number_of_instruction_accounts(2)?; - transfer( - 0, - 1, - lamports, - invoke_context, - transaction_context, - instruction_context, - ) - } - SystemInstruction::TransferWithSeed { + &owner, + &signers, + invoke_context, + transaction_context, + instruction_context, + ) + } + SystemInstruction::Assign { owner } => { + instruction_context.check_number_of_instruction_accounts(1)?; + let mut account = + instruction_context.try_borrow_instruction_account(transaction_context, 0)?; + let address = Address::create( + transaction_context.get_key_of_account_at_index( + instruction_context.get_index_of_instruction_account_in_transaction(0)?, + )?, + None, + invoke_context, + )?; + assign(&mut account, &address, &owner, &signers, invoke_context) + } + SystemInstruction::Transfer { lamports } => { + instruction_context.check_number_of_instruction_accounts(2)?; + transfer( + 0, + 1, lamports, - from_seed, - from_owner, - } => { - instruction_context.check_number_of_instruction_accounts(3)?; - transfer_with_seed( - 0, - 1, - &from_seed, - &from_owner, - 2, - lamports, - invoke_context, - transaction_context, - instruction_context, - ) - } - SystemInstruction::AdvanceNonceAccount => { - instruction_context.check_number_of_instruction_accounts(1)?; - let mut me = - instruction_context.try_borrow_instruction_account(transaction_context, 0)?; - #[allow(deprecated)] - let recent_blockhashes = get_sysvar_with_account_check::recent_blockhashes( - invoke_context, - instruction_context, - 1, - )?; - if recent_blockhashes.is_empty() { - ic_msg!( - invoke_context, - "Advance nonce account: recent blockhash list is empty", - ); - return Err(SystemError::NonceNoRecentBlockhashes.into()); - } - advance_nonce_account(&mut me, &signers, invoke_context) - } - SystemInstruction::WithdrawNonceAccount(lamports) => { - instruction_context.check_number_of_instruction_accounts(2)?; - #[allow(deprecated)] - let _recent_blockhashes = get_sysvar_with_account_check::recent_blockhashes( - invoke_context, - instruction_context, - 2, - )?; - let rent = - get_sysvar_with_account_check::rent(invoke_context, instruction_context, 3)?; - withdraw_nonce_account( - 0, - lamports, - 1, - &rent, - &signers, + invoke_context, + transaction_context, + instruction_context, + ) + } + SystemInstruction::TransferWithSeed { + lamports, + from_seed, + from_owner, + } => { + instruction_context.check_number_of_instruction_accounts(3)?; + transfer_with_seed( + 0, + 1, + &from_seed, + &from_owner, + 2, + lamports, + invoke_context, + transaction_context, + instruction_context, + ) + } + SystemInstruction::AdvanceNonceAccount => { + instruction_context.check_number_of_instruction_accounts(1)?; + let mut me = + instruction_context.try_borrow_instruction_account(transaction_context, 0)?; + #[allow(deprecated)] + let recent_blockhashes = get_sysvar_with_account_check::recent_blockhashes( + invoke_context, + instruction_context, + 1, + )?; + if recent_blockhashes.is_empty() { + ic_msg!( invoke_context, - transaction_context, - instruction_context, - ) + "Advance nonce account: recent blockhash list is empty", + ); + return Err(SystemError::NonceNoRecentBlockhashes.into()); } - SystemInstruction::InitializeNonceAccount(authorized) => { - instruction_context.check_number_of_instruction_accounts(1)?; - let mut me = - instruction_context.try_borrow_instruction_account(transaction_context, 0)?; - #[allow(deprecated)] - let recent_blockhashes = get_sysvar_with_account_check::recent_blockhashes( + advance_nonce_account(&mut me, &signers, invoke_context) + } + SystemInstruction::WithdrawNonceAccount(lamports) => { + instruction_context.check_number_of_instruction_accounts(2)?; + #[allow(deprecated)] + let _recent_blockhashes = get_sysvar_with_account_check::recent_blockhashes( + invoke_context, + instruction_context, + 2, + )?; + let rent = get_sysvar_with_account_check::rent(invoke_context, instruction_context, 3)?; + withdraw_nonce_account( + 0, + lamports, + 1, + &rent, + &signers, + invoke_context, + transaction_context, + instruction_context, + ) + } + SystemInstruction::InitializeNonceAccount(authorized) => { + instruction_context.check_number_of_instruction_accounts(1)?; + let mut me = + instruction_context.try_borrow_instruction_account(transaction_context, 0)?; + #[allow(deprecated)] + let recent_blockhashes = get_sysvar_with_account_check::recent_blockhashes( + invoke_context, + instruction_context, + 1, + )?; + if recent_blockhashes.is_empty() { + ic_msg!( invoke_context, - instruction_context, - 1, - )?; - if recent_blockhashes.is_empty() { - ic_msg!( - invoke_context, - "Initialize nonce account: recent blockhash list is empty", - ); - return Err(SystemError::NonceNoRecentBlockhashes.into()); - } - let rent = - get_sysvar_with_account_check::rent(invoke_context, instruction_context, 2)?; - initialize_nonce_account(&mut me, &authorized, &rent, invoke_context) + "Initialize nonce account: recent blockhash list is empty", + ); + return Err(SystemError::NonceNoRecentBlockhashes.into()); } - SystemInstruction::AuthorizeNonceAccount(nonce_authority) => { - instruction_context.check_number_of_instruction_accounts(1)?; - let mut me = - instruction_context.try_borrow_instruction_account(transaction_context, 0)?; - authorize_nonce_account(&mut me, &nonce_authority, &signers, invoke_context) + let rent = get_sysvar_with_account_check::rent(invoke_context, instruction_context, 2)?; + initialize_nonce_account(&mut me, &authorized, &rent, invoke_context) + } + SystemInstruction::AuthorizeNonceAccount(nonce_authority) => { + instruction_context.check_number_of_instruction_accounts(1)?; + let mut me = + instruction_context.try_borrow_instruction_account(transaction_context, 0)?; + authorize_nonce_account(&mut me, &nonce_authority, &signers, invoke_context) + } + SystemInstruction::UpgradeNonceAccount => { + instruction_context.check_number_of_instruction_accounts(1)?; + let mut nonce_account = + instruction_context.try_borrow_instruction_account(transaction_context, 0)?; + if !system_program::check_id(nonce_account.get_owner()) { + return Err(InstructionError::InvalidAccountOwner); } - SystemInstruction::UpgradeNonceAccount => { - instruction_context.check_number_of_instruction_accounts(1)?; - let mut nonce_account = - instruction_context.try_borrow_instruction_account(transaction_context, 0)?; - if !system_program::check_id(nonce_account.get_owner()) { - return Err(InstructionError::InvalidAccountOwner); - } - if !nonce_account.is_writable() { - return Err(InstructionError::InvalidArgument); - } - let nonce_versions: nonce::state::Versions = nonce_account.get_state()?; - match nonce_versions.upgrade() { - None => Err(InstructionError::InvalidArgument), - Some(nonce_versions) => nonce_account.set_state(&nonce_versions), - } + if !nonce_account.is_writable() { + return Err(InstructionError::InvalidArgument); } - SystemInstruction::Allocate { space } => { - instruction_context.check_number_of_instruction_accounts(1)?; - let mut account = - instruction_context.try_borrow_instruction_account(transaction_context, 0)?; - let address = Address::create( - transaction_context.get_key_of_account_at_index( - instruction_context.get_index_of_instruction_account_in_transaction(0)?, - )?, - None, - invoke_context, - )?; - allocate(&mut account, &address, space, &signers, invoke_context) + let nonce_versions: nonce::state::Versions = nonce_account.get_state()?; + match nonce_versions.upgrade() { + None => Err(InstructionError::InvalidArgument), + Some(nonce_versions) => nonce_account.set_state(&nonce_versions), } - SystemInstruction::AllocateWithSeed { - base, - seed, + } + SystemInstruction::Allocate { space } => { + instruction_context.check_number_of_instruction_accounts(1)?; + let mut account = + instruction_context.try_borrow_instruction_account(transaction_context, 0)?; + let address = Address::create( + transaction_context.get_key_of_account_at_index( + instruction_context.get_index_of_instruction_account_in_transaction(0)?, + )?, + None, + invoke_context, + )?; + allocate(&mut account, &address, space, &signers, invoke_context) + } + SystemInstruction::AllocateWithSeed { + base, + seed, + space, + owner, + } => { + instruction_context.check_number_of_instruction_accounts(1)?; + let mut account = + instruction_context.try_borrow_instruction_account(transaction_context, 0)?; + let address = Address::create( + transaction_context.get_key_of_account_at_index( + instruction_context.get_index_of_instruction_account_in_transaction(0)?, + )?, + Some((&base, &seed, &owner)), + invoke_context, + )?; + allocate_and_assign( + &mut account, + &address, space, - owner, - } => { - instruction_context.check_number_of_instruction_accounts(1)?; - let mut account = - instruction_context.try_borrow_instruction_account(transaction_context, 0)?; - let address = Address::create( - transaction_context.get_key_of_account_at_index( - instruction_context.get_index_of_instruction_account_in_transaction(0)?, - )?, - Some((&base, &seed, &owner)), - invoke_context, - )?; - allocate_and_assign( - &mut account, - &address, - space, - &owner, - &signers, - invoke_context, - ) - } - SystemInstruction::AssignWithSeed { base, seed, owner } => { - instruction_context.check_number_of_instruction_accounts(1)?; - let mut account = - instruction_context.try_borrow_instruction_account(transaction_context, 0)?; - let address = Address::create( - transaction_context.get_key_of_account_at_index( - instruction_context.get_index_of_instruction_account_in_transaction(0)?, - )?, - Some((&base, &seed, &owner)), - invoke_context, - )?; - assign(&mut account, &address, &owner, &signers, invoke_context) - } + &owner, + &signers, + invoke_context, + ) + } + SystemInstruction::AssignWithSeed { base, seed, owner } => { + instruction_context.check_number_of_instruction_accounts(1)?; + let mut account = + instruction_context.try_borrow_instruction_account(transaction_context, 0)?; + let address = Address::create( + transaction_context.get_key_of_account_at_index( + instruction_context.get_index_of_instruction_account_in_transaction(0)?, + )?, + Some((&base, &seed, &owner)), + invoke_context, + )?; + assign(&mut account, &address, &owner, &signers, invoke_context) } } -); +}); #[cfg(test)] mod tests { @@ -609,7 +603,7 @@ mod tests { transaction_accounts, instruction_accounts, expected_result, - super::process_instruction, + Entrypoint::vm, |_invoke_context| {}, |_invoke_context| {}, ) @@ -1599,7 +1593,7 @@ mod tests { }, ], Ok(()), - super::process_instruction, + Entrypoint::vm, |invoke_context: &mut InvokeContext| { invoke_context.blockhash = hash(&serialize(&0).unwrap()); }, @@ -1946,7 +1940,7 @@ mod tests { }, ], Err(SystemError::NonceNoRecentBlockhashes.into()), - super::process_instruction, + Entrypoint::vm, |invoke_context: &mut InvokeContext| { invoke_context.blockhash = hash(&serialize(&0).unwrap()); }, diff --git a/programs/vote/benches/process_vote.rs b/programs/vote/benches/process_vote.rs index 6c9cb979c90..a092056c935 100644 --- a/programs/vote/benches/process_vote.rs +++ b/programs/vote/benches/process_vote.rs @@ -48,7 +48,7 @@ fn create_accounts() -> (Slot, SlotHashes, Vec, Vec = vec![0; VoteState::size_of()]; let versioned = VoteStateVersions::new_current(vote_state); @@ -108,7 +108,7 @@ fn bench_process_vote_instruction( transaction_accounts.clone(), instruction_account_metas.clone(), Ok(()), - solana_vote_program::vote_processor::process_instruction, + solana_vote_program::vote_processor::Entrypoint::vm, |_invoke_context| {}, |_invoke_context| {}, ); diff --git a/programs/vote/src/vote_processor.rs b/programs/vote/src/vote_processor.rs index 423193f5d33..d09309ddc81 100644 --- a/programs/vote/src/vote_processor.rs +++ b/programs/vote/src/vote_processor.rs @@ -54,209 +54,198 @@ fn process_authorize_with_seed_instruction( // units; can consume based on instructions in the future like `bpf_loader` does. pub const DEFAULT_COMPUTE_UNITS: u64 = 2_100; -declare_process_instruction!( - process_instruction, - DEFAULT_COMPUTE_UNITS, - |invoke_context| { - let transaction_context = &invoke_context.transaction_context; - let instruction_context = transaction_context.get_current_instruction_context()?; - let data = instruction_context.get_instruction_data(); - - trace!("process_instruction: {:?}", data); - - let mut me = instruction_context.try_borrow_instruction_account(transaction_context, 0)?; - if *me.get_owner() != id() { - return Err(InstructionError::InvalidAccountOwner); - } +declare_process_instruction!(Entrypoint, DEFAULT_COMPUTE_UNITS, |invoke_context| { + let transaction_context = &invoke_context.transaction_context; + let instruction_context = transaction_context.get_current_instruction_context()?; + let data = instruction_context.get_instruction_data(); - let signers = instruction_context.get_signers(transaction_context)?; - match limited_deserialize(data)? { - VoteInstruction::InitializeAccount(vote_init) => { - let rent = - get_sysvar_with_account_check::rent(invoke_context, instruction_context, 1)?; - if !rent.is_exempt(me.get_lamports(), me.get_data().len()) { - return Err(InstructionError::InsufficientFunds); - } - let clock = - get_sysvar_with_account_check::clock(invoke_context, instruction_context, 2)?; - vote_state::initialize_account( - &mut me, - &vote_init, - &signers, - &clock, - &invoke_context.feature_set, - ) + trace!("process_instruction: {:?}", data); + + let mut me = instruction_context.try_borrow_instruction_account(transaction_context, 0)?; + if *me.get_owner() != id() { + return Err(InstructionError::InvalidAccountOwner); + } + + let signers = instruction_context.get_signers(transaction_context)?; + match limited_deserialize(data)? { + VoteInstruction::InitializeAccount(vote_init) => { + let rent = get_sysvar_with_account_check::rent(invoke_context, instruction_context, 1)?; + if !rent.is_exempt(me.get_lamports(), me.get_data().len()) { + return Err(InstructionError::InsufficientFunds); } - VoteInstruction::Authorize(voter_pubkey, vote_authorize) => { - let clock = - get_sysvar_with_account_check::clock(invoke_context, instruction_context, 1)?; - vote_state::authorize( - &mut me, - &voter_pubkey, - vote_authorize, - &signers, - &clock, - &invoke_context.feature_set, - ) + let clock = + get_sysvar_with_account_check::clock(invoke_context, instruction_context, 2)?; + vote_state::initialize_account( + &mut me, + &vote_init, + &signers, + &clock, + &invoke_context.feature_set, + ) + } + VoteInstruction::Authorize(voter_pubkey, vote_authorize) => { + let clock = + get_sysvar_with_account_check::clock(invoke_context, instruction_context, 1)?; + vote_state::authorize( + &mut me, + &voter_pubkey, + vote_authorize, + &signers, + &clock, + &invoke_context.feature_set, + ) + } + VoteInstruction::AuthorizeWithSeed(args) => { + instruction_context.check_number_of_instruction_accounts(3)?; + process_authorize_with_seed_instruction( + invoke_context, + instruction_context, + transaction_context, + &mut me, + &args.new_authority, + args.authorization_type, + &args.current_authority_derived_key_owner, + args.current_authority_derived_key_seed.as_str(), + ) + } + VoteInstruction::AuthorizeCheckedWithSeed(args) => { + instruction_context.check_number_of_instruction_accounts(4)?; + let new_authority = transaction_context.get_key_of_account_at_index( + instruction_context.get_index_of_instruction_account_in_transaction(3)?, + )?; + if !instruction_context.is_instruction_account_signer(3)? { + return Err(InstructionError::MissingRequiredSignature); } - VoteInstruction::AuthorizeWithSeed(args) => { - instruction_context.check_number_of_instruction_accounts(3)?; - process_authorize_with_seed_instruction( - invoke_context, - instruction_context, - transaction_context, - &mut me, - &args.new_authority, - args.authorization_type, - &args.current_authority_derived_key_owner, - args.current_authority_derived_key_seed.as_str(), - ) + process_authorize_with_seed_instruction( + invoke_context, + instruction_context, + transaction_context, + &mut me, + new_authority, + args.authorization_type, + &args.current_authority_derived_key_owner, + args.current_authority_derived_key_seed.as_str(), + ) + } + VoteInstruction::UpdateValidatorIdentity => { + instruction_context.check_number_of_instruction_accounts(2)?; + let node_pubkey = transaction_context.get_key_of_account_at_index( + instruction_context.get_index_of_instruction_account_in_transaction(1)?, + )?; + vote_state::update_validator_identity( + &mut me, + node_pubkey, + &signers, + &invoke_context.feature_set, + ) + } + VoteInstruction::UpdateCommission(commission) => { + if invoke_context.feature_set.is_active( + &feature_set::commission_updates_only_allowed_in_first_half_of_epoch::id(), + ) { + let sysvar_cache = invoke_context.get_sysvar_cache(); + let epoch_schedule = sysvar_cache.get_epoch_schedule()?; + let clock = sysvar_cache.get_clock()?; + if !vote_state::is_commission_update_allowed(clock.slot, &epoch_schedule) { + return Err(VoteError::CommissionUpdateTooLate.into()); + } } - VoteInstruction::AuthorizeCheckedWithSeed(args) => { + vote_state::update_commission( + &mut me, + commission, + &signers, + &invoke_context.feature_set, + ) + } + VoteInstruction::Vote(vote) | VoteInstruction::VoteSwitch(vote, _) => { + let slot_hashes = + get_sysvar_with_account_check::slot_hashes(invoke_context, instruction_context, 1)?; + let clock = + get_sysvar_with_account_check::clock(invoke_context, instruction_context, 2)?; + vote_state::process_vote_with_account( + &mut me, + &slot_hashes, + &clock, + &vote, + &signers, + &invoke_context.feature_set, + ) + } + VoteInstruction::UpdateVoteState(vote_state_update) + | VoteInstruction::UpdateVoteStateSwitch(vote_state_update, _) => { + let sysvar_cache = invoke_context.get_sysvar_cache(); + let slot_hashes = sysvar_cache.get_slot_hashes()?; + let clock = sysvar_cache.get_clock()?; + vote_state::process_vote_state_update( + &mut me, + slot_hashes.slot_hashes(), + &clock, + vote_state_update, + &signers, + &invoke_context.feature_set, + ) + } + VoteInstruction::CompactUpdateVoteState(vote_state_update) + | VoteInstruction::CompactUpdateVoteStateSwitch(vote_state_update, _) => { + let sysvar_cache = invoke_context.get_sysvar_cache(); + let slot_hashes = sysvar_cache.get_slot_hashes()?; + let clock = sysvar_cache.get_clock()?; + vote_state::process_vote_state_update( + &mut me, + slot_hashes.slot_hashes(), + &clock, + vote_state_update, + &signers, + &invoke_context.feature_set, + ) + } + + VoteInstruction::Withdraw(lamports) => { + instruction_context.check_number_of_instruction_accounts(2)?; + let rent_sysvar = invoke_context.get_sysvar_cache().get_rent()?; + let clock_sysvar = invoke_context.get_sysvar_cache().get_clock()?; + + drop(me); + vote_state::withdraw( + transaction_context, + instruction_context, + 0, + lamports, + 1, + &signers, + &rent_sysvar, + &clock_sysvar, + &invoke_context.feature_set, + ) + } + VoteInstruction::AuthorizeChecked(vote_authorize) => { + if invoke_context + .feature_set + .is_active(&feature_set::vote_stake_checked_instructions::id()) + { instruction_context.check_number_of_instruction_accounts(4)?; - let new_authority = transaction_context.get_key_of_account_at_index( + let voter_pubkey = transaction_context.get_key_of_account_at_index( instruction_context.get_index_of_instruction_account_in_transaction(3)?, )?; if !instruction_context.is_instruction_account_signer(3)? { return Err(InstructionError::MissingRequiredSignature); } - process_authorize_with_seed_instruction( - invoke_context, - instruction_context, - transaction_context, - &mut me, - new_authority, - args.authorization_type, - &args.current_authority_derived_key_owner, - args.current_authority_derived_key_seed.as_str(), - ) - } - VoteInstruction::UpdateValidatorIdentity => { - instruction_context.check_number_of_instruction_accounts(2)?; - let node_pubkey = transaction_context.get_key_of_account_at_index( - instruction_context.get_index_of_instruction_account_in_transaction(1)?, - )?; - vote_state::update_validator_identity( - &mut me, - node_pubkey, - &signers, - &invoke_context.feature_set, - ) - } - VoteInstruction::UpdateCommission(commission) => { - if invoke_context.feature_set.is_active( - &feature_set::commission_updates_only_allowed_in_first_half_of_epoch::id(), - ) { - let sysvar_cache = invoke_context.get_sysvar_cache(); - let epoch_schedule = sysvar_cache.get_epoch_schedule()?; - let clock = sysvar_cache.get_clock()?; - if !vote_state::is_commission_update_allowed(clock.slot, &epoch_schedule) { - return Err(VoteError::CommissionUpdateTooLate.into()); - } - } - vote_state::update_commission( - &mut me, - commission, - &signers, - &invoke_context.feature_set, - ) - } - VoteInstruction::Vote(vote) | VoteInstruction::VoteSwitch(vote, _) => { - let slot_hashes = get_sysvar_with_account_check::slot_hashes( - invoke_context, - instruction_context, - 1, - )?; let clock = - get_sysvar_with_account_check::clock(invoke_context, instruction_context, 2)?; - vote_state::process_vote_with_account( - &mut me, - &slot_hashes, - &clock, - &vote, - &signers, - &invoke_context.feature_set, - ) - } - VoteInstruction::UpdateVoteState(vote_state_update) - | VoteInstruction::UpdateVoteStateSwitch(vote_state_update, _) => { - let sysvar_cache = invoke_context.get_sysvar_cache(); - let slot_hashes = sysvar_cache.get_slot_hashes()?; - let clock = sysvar_cache.get_clock()?; - vote_state::process_vote_state_update( + get_sysvar_with_account_check::clock(invoke_context, instruction_context, 1)?; + vote_state::authorize( &mut me, - slot_hashes.slot_hashes(), - &clock, - vote_state_update, + voter_pubkey, + vote_authorize, &signers, - &invoke_context.feature_set, - ) - } - VoteInstruction::CompactUpdateVoteState(vote_state_update) - | VoteInstruction::CompactUpdateVoteStateSwitch(vote_state_update, _) => { - let sysvar_cache = invoke_context.get_sysvar_cache(); - let slot_hashes = sysvar_cache.get_slot_hashes()?; - let clock = sysvar_cache.get_clock()?; - vote_state::process_vote_state_update( - &mut me, - slot_hashes.slot_hashes(), &clock, - vote_state_update, - &signers, - &invoke_context.feature_set, - ) - } - - VoteInstruction::Withdraw(lamports) => { - instruction_context.check_number_of_instruction_accounts(2)?; - let rent_sysvar = invoke_context.get_sysvar_cache().get_rent()?; - let clock_sysvar = invoke_context.get_sysvar_cache().get_clock()?; - - drop(me); - vote_state::withdraw( - transaction_context, - instruction_context, - 0, - lamports, - 1, - &signers, - &rent_sysvar, - &clock_sysvar, &invoke_context.feature_set, ) - } - VoteInstruction::AuthorizeChecked(vote_authorize) => { - if invoke_context - .feature_set - .is_active(&feature_set::vote_stake_checked_instructions::id()) - { - instruction_context.check_number_of_instruction_accounts(4)?; - let voter_pubkey = transaction_context.get_key_of_account_at_index( - instruction_context.get_index_of_instruction_account_in_transaction(3)?, - )?; - if !instruction_context.is_instruction_account_signer(3)? { - return Err(InstructionError::MissingRequiredSignature); - } - let clock = get_sysvar_with_account_check::clock( - invoke_context, - instruction_context, - 1, - )?; - vote_state::authorize( - &mut me, - voter_pubkey, - vote_authorize, - &signers, - &clock, - &invoke_context.feature_set, - ) - } else { - Err(InstructionError::InvalidInstructionData) - } + } else { + Err(InstructionError::InvalidInstructionData) } } } -); +}); #[cfg(test)] mod tests { @@ -320,7 +309,7 @@ mod tests { transaction_accounts, instruction_accounts, expected_result, - super::process_instruction, + Entrypoint::vm, |_invoke_context| {}, |_invoke_context| {}, ) @@ -339,7 +328,7 @@ mod tests { transaction_accounts, instruction_accounts, expected_result, - super::process_instruction, + Entrypoint::vm, |invoke_context| { invoke_context.feature_set = std::sync::Arc::new(FeatureSet::default()); }, diff --git a/programs/vote/src/vote_state/mod.rs b/programs/vote/src/vote_state/mod.rs index e83171d06e0..b68a0ee04ac 100644 --- a/programs/vote/src/vote_state/mod.rs +++ b/programs/vote/src/vote_state/mod.rs @@ -616,6 +616,9 @@ pub fn process_new_vote_state( let timely_vote_credits = feature_set.map_or(false, |f| { f.is_active(&feature_set::timely_vote_credits::id()) }); + let deprecate_unused_legacy_vote_plumbing = feature_set.map_or(false, |f| { + f.is_active(&feature_set::deprecate_unused_legacy_vote_plumbing::id()) + }); let mut earned_credits = if timely_vote_credits { 0_u64 } else { 1_u64 }; if let Some(new_root) = new_root { @@ -625,7 +628,11 @@ pub fn process_new_vote_state( if current_vote.slot() <= new_root { if timely_vote_credits || (current_vote.slot() != new_root) { earned_credits = earned_credits - .checked_add(vote_state.credits_for_vote_at_index(current_vote_state_index)) + .checked_add(vote_state.credits_for_vote_at_index( + current_vote_state_index, + timely_vote_credits, + deprecate_unused_legacy_vote_plumbing, + )) .expect("`earned_credits` does not overflow"); } current_vote_state_index = current_vote_state_index @@ -738,11 +745,19 @@ pub fn process_vote_unfiltered( slot_hashes: &[SlotHash], epoch: Epoch, current_slot: Slot, + timely_vote_credits: bool, + deprecate_unused_legacy_vote_plumbing: bool, ) -> Result<(), VoteError> { check_slots_are_valid(vote_state, vote_slots, &vote.hash, slot_hashes)?; - vote_slots - .iter() - .for_each(|s| vote_state.process_next_vote_slot(*s, epoch, current_slot)); + vote_slots.iter().for_each(|s| { + vote_state.process_next_vote_slot( + *s, + epoch, + current_slot, + timely_vote_credits, + deprecate_unused_legacy_vote_plumbing, + ) + }); Ok(()) } @@ -752,6 +767,8 @@ pub fn process_vote( slot_hashes: &[SlotHash], epoch: Epoch, current_slot: Slot, + timely_vote_credits: bool, + deprecate_unused_legacy_vote_plumbing: bool, ) -> Result<(), VoteError> { if vote.slots.is_empty() { return Err(VoteError::EmptySlots); @@ -773,6 +790,8 @@ pub fn process_vote( slot_hashes, epoch, current_slot, + timely_vote_credits, + deprecate_unused_legacy_vote_plumbing, ) } @@ -789,6 +808,8 @@ pub fn process_vote_unchecked(vote_state: &mut VoteState, vote: Vote) -> Result< &slot_hashes, vote_state.current_epoch(), 0, + true, + true, ) } @@ -1036,7 +1057,18 @@ pub fn process_vote_with_account( ) -> Result<(), InstructionError> { let mut vote_state = verify_and_get_vote_state(vote_account, clock, signers)?; - process_vote(&mut vote_state, vote, slot_hashes, clock.epoch, clock.slot)?; + let timely_vote_credits = feature_set.is_active(&feature_set::timely_vote_credits::id()); + let deprecate_unused_legacy_vote_plumbing = + feature_set.is_active(&feature_set::deprecate_unused_legacy_vote_plumbing::id()); + process_vote( + &mut vote_state, + vote, + slot_hashes, + clock.epoch, + clock.slot, + timely_vote_credits, + deprecate_unused_legacy_vote_plumbing, + )?; if let Some(timestamp) = vote.timestamp { vote.slots .iter() @@ -1095,8 +1127,6 @@ pub fn do_process_vote_state_update( // a. In many tests. // b. In the genesis tool that initializes a cluster to create the bootstrap validator. // c. In the ledger tool when creating bootstrap vote accounts. -// In all cases, initializing with the 1_14_11 version of VoteState is safest, as this version will in-place upgrade -// the first time it is altered by a vote transaction. pub fn create_account_with_authorized( node_pubkey: &Pubkey, authorized_voter: &Pubkey, @@ -1104,7 +1134,7 @@ pub fn create_account_with_authorized( commission: u8, lamports: u64, ) -> AccountSharedData { - let mut vote_account = AccountSharedData::new(lamports, VoteState1_14_11::size_of(), &id()); + let mut vote_account = AccountSharedData::new(lamports, VoteState::size_of(), &id()); let vote_state = VoteState::new( &VoteInit { @@ -1116,8 +1146,11 @@ pub fn create_account_with_authorized( &Clock::default(), ); - let version1_14_11 = VoteStateVersions::V1_14_11(Box::new(VoteState1_14_11::from(vote_state))); - VoteState::serialize(&version1_14_11, vote_account.data_as_mut_slice()).unwrap(); + VoteState::serialize( + &VoteStateVersions::Current(Box::new(vote_state)), + vote_account.data_as_mut_slice(), + ) + .unwrap(); vote_account } @@ -1218,7 +1251,7 @@ mod tests { 134, 135, ] .into_iter() - .for_each(|v| vote_state.process_next_vote_slot(v, 4, 0)); + .for_each(|v| vote_state.process_next_vote_slot(v, 4, 0, false, true)); let version1_14_11_serialized = bincode::serialize(&VoteStateVersions::V1_14_11(Box::new( VoteState1_14_11::from(vote_state.clone()), @@ -1510,11 +1543,11 @@ mod tests { let slot_hashes: Vec<_> = vote.slots.iter().rev().map(|x| (*x, vote.hash)).collect(); assert_eq!( - process_vote(&mut vote_state_a, &vote, &slot_hashes, 0, 0), + process_vote(&mut vote_state_a, &vote, &slot_hashes, 0, 0, true, true), Ok(()) ); assert_eq!( - process_vote(&mut vote_state_b, &vote, &slot_hashes, 0, 0), + process_vote(&mut vote_state_b, &vote, &slot_hashes, 0, 0, true, true), Ok(()) ); assert_eq!(recent_votes(&vote_state_a), recent_votes(&vote_state_b)); @@ -1527,12 +1560,12 @@ mod tests { let vote = Vote::new(vec![0], Hash::default()); let slot_hashes: Vec<_> = vec![(0, vote.hash)]; assert_eq!( - process_vote(&mut vote_state, &vote, &slot_hashes, 0, 0), + process_vote(&mut vote_state, &vote, &slot_hashes, 0, 0, true, true), Ok(()) ); let recent = recent_votes(&vote_state); assert_eq!( - process_vote(&mut vote_state, &vote, &slot_hashes, 0, 0), + process_vote(&mut vote_state, &vote, &slot_hashes, 0, 0, true, true), Err(VoteError::VoteTooOld) ); assert_eq!(recent, recent_votes(&vote_state)); @@ -1592,7 +1625,7 @@ mod tests { let vote = Vote::new(vec![0], Hash::default()); let slot_hashes: Vec<_> = vec![(*vote.slots.last().unwrap(), vote.hash)]; assert_eq!( - process_vote(&mut vote_state, &vote, &slot_hashes, 0, 0), + process_vote(&mut vote_state, &vote, &slot_hashes, 0, 0, true, true), Ok(()) ); assert_eq!( @@ -1608,7 +1641,7 @@ mod tests { let vote = Vote::new(vec![0], Hash::default()); let slot_hashes: Vec<_> = vec![(*vote.slots.last().unwrap(), vote.hash)]; assert_eq!( - process_vote(&mut vote_state, &vote, &slot_hashes, 0, 0), + process_vote(&mut vote_state, &vote, &slot_hashes, 0, 0, true, true), Ok(()) ); @@ -1627,7 +1660,7 @@ mod tests { let vote = Vote::new(vec![0], Hash::default()); let slot_hashes: Vec<_> = vec![(*vote.slots.last().unwrap(), vote.hash)]; assert_eq!( - process_vote(&mut vote_state, &vote, &slot_hashes, 0, 0), + process_vote(&mut vote_state, &vote, &slot_hashes, 0, 0, true, true), Ok(()) ); @@ -1644,7 +1677,7 @@ mod tests { let vote = Vote::new(vec![], Hash::default()); assert_eq!( - process_vote(&mut vote_state, &vote, &[], 0, 0), + process_vote(&mut vote_state, &vote, &[], 0, 0, true, true), Err(VoteError::EmptySlots) ); } @@ -1900,7 +1933,9 @@ mod tests { &vote, &slot_hashes, 0, - vote_group.1 // vote_group.1 is the slot in which the vote was cast + vote_group.1, // vote_group.1 is the slot in which the vote was cast + true, + true ), Ok(()) ); @@ -2792,7 +2827,7 @@ mod tests { // error with `VotesTooOldAllFiltered` let slot_hashes = vec![(3, Hash::new_unique()), (2, Hash::new_unique())]; assert_eq!( - process_vote(&mut vote_state, &vote, &slot_hashes, 0, 0), + process_vote(&mut vote_state, &vote, &slot_hashes, 0, 0, true, true), Err(VoteError::VotesTooOldAllFiltered) ); @@ -2806,7 +2841,7 @@ mod tests { .1; let vote = Vote::new(vec![old_vote_slot, vote_slot], vote_slot_hash); - process_vote(&mut vote_state, &vote, &slot_hashes, 0, 0).unwrap(); + process_vote(&mut vote_state, &vote, &slot_hashes, 0, 0, true, true).unwrap(); assert_eq!( vote_state .votes @@ -2835,8 +2870,17 @@ mod tests { .unwrap() .1; let vote = Vote::new(vote_slots, vote_hash); - process_vote_unfiltered(&mut vote_state, &vote.slots, &vote, slot_hashes, 0, 0) - .unwrap(); + process_vote_unfiltered( + &mut vote_state, + &vote.slots, + &vote, + slot_hashes, + 0, + 0, + true, + true, + ) + .unwrap(); } vote_state diff --git a/programs/zk-token-proof/src/lib.rs b/programs/zk-token-proof/src/lib.rs index 6ed1fb1f33e..e2629502011 100644 --- a/programs/zk-token-proof/src/lib.rs +++ b/programs/zk-token-proof/src/lib.rs @@ -17,20 +17,20 @@ use { }; pub const CLOSE_CONTEXT_STATE_COMPUTE_UNITS: u64 = 3_300; -pub const VERIFY_ZERO_BALANCE_COMPUTE_UNITS: u64 = 6012; -pub const VERIFY_WITHDRAW_COMPUTE_UNITS: u64 = 112_454; -pub const VERIFY_CIPHERTEXT_CIPHERTEXT_EQUALITY_COMPUTE_UNITS: u64 = 7_943; -pub const VERIFY_TRANSFER_COMPUTE_UNITS: u64 = 219_290; -pub const VERIFY_TRANSFER_WITH_FEE_COMPUTE_UNITS: u64 = 407_121; -pub const VERIFY_PUBKEY_VALIDITY_COMPUTE_UNITS: u64 = 2_619; -pub const VERIFY_RANGE_PROOF_U64_COMPUTE_UNITS: u64 = 105_066; -pub const VERIFY_BATCHED_RANGE_PROOF_U64_COMPUTE_UNITS: u64 = 111_478; -pub const VERIFY_BATCHED_RANGE_PROOF_U128_COMPUTE_UNITS: u64 = 204_512; +pub const VERIFY_ZERO_BALANCE_COMPUTE_UNITS: u64 = 6_000; +pub const VERIFY_WITHDRAW_COMPUTE_UNITS: u64 = 110_000; +pub const VERIFY_CIPHERTEXT_CIPHERTEXT_EQUALITY_COMPUTE_UNITS: u64 = 8_000; +pub const VERIFY_TRANSFER_COMPUTE_UNITS: u64 = 219_000; +pub const VERIFY_TRANSFER_WITH_FEE_COMPUTE_UNITS: u64 = 407_000; +pub const VERIFY_PUBKEY_VALIDITY_COMPUTE_UNITS: u64 = 2_600; +pub const VERIFY_RANGE_PROOF_U64_COMPUTE_UNITS: u64 = 105_000; +pub const VERIFY_BATCHED_RANGE_PROOF_U64_COMPUTE_UNITS: u64 = 111_000; +pub const VERIFY_BATCHED_RANGE_PROOF_U128_COMPUTE_UNITS: u64 = 200_000; pub const VERIFY_BATCHED_RANGE_PROOF_U256_COMPUTE_UNITS: u64 = 368_000; -pub const VERIFY_CIPHERTEXT_COMMITMENT_EQUALITY_COMPUTE_UNITS: u64 = 6_424; -pub const VERIFY_GROUPED_CIPHERTEXT_2_HANDLES_VALIDITY_COMPUTE_UNITS: u64 = 6_440; -pub const VERIFY_BATCHED_GROUPED_CIPHERTEXT_2_HANDLES_VALIDITY_COMPUTE_UNITS: u64 = 12_575; -pub const VERIFY_FEE_SIGMA_COMPUTE_UNITS: u64 = 6_547; +pub const VERIFY_CIPHERTEXT_COMMITMENT_EQUALITY_COMPUTE_UNITS: u64 = 6_400; +pub const VERIFY_GROUPED_CIPHERTEXT_2_HANDLES_VALIDITY_COMPUTE_UNITS: u64 = 6_400; +pub const VERIFY_BATCHED_GROUPED_CIPHERTEXT_2_HANDLES_VALIDITY_COMPUTE_UNITS: u64 = 13_000; +pub const VERIFY_FEE_SIGMA_COMPUTE_UNITS: u64 = 6_500; fn process_verify_proof(invoke_context: &mut InvokeContext) -> Result<(), InstructionError> where @@ -130,11 +130,16 @@ fn process_close_proof_context(invoke_context: &mut InvokeContext) -> Result<(), Ok(()) } -declare_process_instruction!(process_instruction, 0, |invoke_context| { +declare_process_instruction!(Entrypoint, 0, |invoke_context| { // Consume compute units if feature `native_programs_consume_cu` is activated let native_programs_consume_cu = invoke_context .feature_set .is_active(&feature_set::native_programs_consume_cu::id()); + + let enable_zk_transfer_with_fee = invoke_context + .feature_set + .is_active(&feature_set::enable_zk_transfer_with_fee::id()); + let transaction_context = &invoke_context.transaction_context; let instruction_context = transaction_context.get_current_instruction_context()?; let instruction_data = instruction_context.get_instruction_data(); @@ -198,6 +203,11 @@ declare_process_instruction!(process_instruction, 0, |invoke_context| { process_verify_proof::(invoke_context) } ProofInstruction::VerifyTransferWithFee => { + // transfer with fee related proofs are not enabled + if !enable_zk_transfer_with_fee { + return Err(InstructionError::InvalidInstructionData); + } + if native_programs_consume_cu { invoke_context .consume_checked(VERIFY_TRANSFER_WITH_FEE_COMPUTE_UNITS) @@ -247,6 +257,11 @@ declare_process_instruction!(process_instruction, 0, |invoke_context| { ) } ProofInstruction::VerifyBatchedRangeProofU256 => { + // transfer with fee related proofs are not enabled + if !enable_zk_transfer_with_fee { + return Err(InstructionError::InvalidInstructionData); + } + if native_programs_consume_cu { invoke_context .consume_checked(VERIFY_BATCHED_RANGE_PROOF_U256_COMPUTE_UNITS) @@ -291,6 +306,11 @@ declare_process_instruction!(process_instruction, 0, |invoke_context| { >(invoke_context) } ProofInstruction::VerifyFeeSigma => { + // transfer with fee related proofs are not enabled + if !enable_zk_transfer_with_fee { + return Err(InstructionError::InvalidInstructionData); + } + invoke_context .consume_checked(VERIFY_FEE_SIGMA_COMPUTE_UNITS) .map_err(|_| InstructionError::ComputationalBudgetExceeded)?; diff --git a/pubsub-client/src/nonblocking/pubsub_client.rs b/pubsub-client/src/nonblocking/pubsub_client.rs index fe0540ebed4..c14b6ee1b08 100644 --- a/pubsub-client/src/nonblocking/pubsub_client.rs +++ b/pubsub-client/src/nonblocking/pubsub_client.rs @@ -33,7 +33,7 @@ //! By default the [`block_subscribe`] and [`vote_subscribe`] events are //! disabled on RPC nodes. They can be enabled by passing //! `--rpc-pubsub-enable-block-subscription` and -//! `--rpc-pubsub-enable-vote-subscription` to `solana-validator`. When these +//! `--rpc-pubsub-enable-vote-subscription` to `agave-validator`. When these //! methods are disabled, the RPC server will return a "Method not found" error //! message. //! @@ -381,7 +381,7 @@ impl PubsubClient { /// Receives messages of type [`RpcBlockUpdate`] when a block is confirmed or finalized. /// /// This method is disabled by default. It can be enabled by passing - /// `--rpc-pubsub-enable-block-subscription` to `solana-validator`. + /// `--rpc-pubsub-enable-block-subscription` to `agave-validator`. /// /// # RPC Reference /// @@ -452,7 +452,7 @@ impl PubsubClient { /// votes are observed prior to confirmation and may never be confirmed. /// /// This method is disabled by default. It can be enabled by passing - /// `--rpc-pubsub-enable-vote-subscription` to `solana-validator`. + /// `--rpc-pubsub-enable-vote-subscription` to `agave-validator`. /// /// # RPC Reference /// diff --git a/pubsub-client/src/pubsub_client.rs b/pubsub-client/src/pubsub_client.rs index 612df285d8e..49139983521 100644 --- a/pubsub-client/src/pubsub_client.rs +++ b/pubsub-client/src/pubsub_client.rs @@ -32,7 +32,7 @@ //! By default the [`block_subscribe`] and [`vote_subscribe`] events are //! disabled on RPC nodes. They can be enabled by passing //! `--rpc-pubsub-enable-block-subscription` and -//! `--rpc-pubsub-enable-vote-subscription` to `solana-validator`. When these +//! `--rpc-pubsub-enable-vote-subscription` to `agave-validator`. When these //! methods are disabled, the RPC server will return a "Method not found" error //! message. //! @@ -416,7 +416,7 @@ impl PubsubClient { /// Receives messages of type [`RpcBlockUpdate`] when a block is confirmed or finalized. /// /// This method is disabled by default. It can be enabled by passing - /// `--rpc-pubsub-enable-block-subscription` to `solana-validator`. + /// `--rpc-pubsub-enable-block-subscription` to `agave-validator`. /// /// # RPC Reference /// @@ -578,7 +578,7 @@ impl PubsubClient { /// votes are observed prior to confirmation and may never be confirmed. /// /// This method is disabled by default. It can be enabled by passing - /// `--rpc-pubsub-enable-vote-subscription` to `solana-validator`. + /// `--rpc-pubsub-enable-vote-subscription` to `agave-validator`. /// /// # RPC Reference /// diff --git a/quic-client/tests/quic_client.rs b/quic-client/tests/quic_client.rs index 7608e2b7b26..a9b9e636476 100644 --- a/quic-client/tests/quic_client.rs +++ b/quic-client/tests/quic_client.rs @@ -46,7 +46,7 @@ mod tests { assert_eq!(p.meta().size, num_bytes); } } - assert_eq!(total_packets, num_expected_packets); + assert!(total_packets > 0); } fn server_args() -> (UdpSocket, Arc, Keypair, IpAddr) { @@ -135,7 +135,7 @@ mod tests { assert_eq!(p.meta().size, num_bytes); } } - assert_eq!(total_packets, num_expected_packets); + assert!(total_packets > 0); } #[tokio::test] @@ -178,7 +178,9 @@ mod tests { let num_bytes = PACKET_DATA_SIZE; let num_expected_packets: usize = 3000; let packets = vec![vec![0u8; PACKET_DATA_SIZE]; num_expected_packets]; - assert!(client.send_data_batch(&packets).await.is_ok()); + for packet in packets { + let _ = client.send_data(&packet).await; + } nonblocking_check_packets(receiver, num_bytes, num_expected_packets).await; exit.store(true, Ordering::Relaxed); @@ -204,7 +206,7 @@ mod tests { let (sender, receiver) = unbounded(); let staked_nodes = Arc::new(RwLock::new(StakedNodes::default())); let (request_recv_socket, request_recv_exit, keypair, request_recv_ip) = server_args(); - let (request_recv_endpoint, request_recv_thread) = solana_streamer::quic::spawn_server( + let (request_recv_endpoints, request_recv_thread) = solana_streamer::quic::spawn_server( "quic_streamer_test", request_recv_socket.try_clone().unwrap(), &keypair, @@ -220,7 +222,7 @@ mod tests { ) .unwrap(); - drop(request_recv_endpoint); + drop(request_recv_endpoints); // Response Receiver: let (response_recv_socket, response_recv_exit, keypair2, response_recv_ip) = server_args(); let (sender2, receiver2) = unbounded(); diff --git a/rbpf-cli/src/main.rs b/rbpf-cli/src/main.rs index e7db982026f..9e243f0836a 100644 --- a/rbpf-cli/src/main.rs +++ b/rbpf-cli/src/main.rs @@ -1,6 +1,6 @@ fn main() { println!( - r##"rbpf-cli is replaced by solana-ledger-tool program run subcommand. -Please, use 'solana-ledger-tool program run --help' for more information."## + r##"rbpf-cli is replaced by agave-ledger-tool program run subcommand. +Please, use 'agave-ledger-tool program run --help' for more information."## ); } diff --git a/rpc-client/src/http_sender.rs b/rpc-client/src/http_sender.rs index 902f86ce631..6ef22cc42c8 100644 --- a/rpc-client/src/http_sender.rs +++ b/rpc-client/src/http_sender.rs @@ -47,31 +47,41 @@ impl HttpSender { /// /// The URL is an HTTP URL, usually for port 8899. pub fn new_with_timeout(url: U, timeout: Duration) -> Self { - let mut default_headers = header::HeaderMap::new(); - default_headers.append( - header::HeaderName::from_static("solana-client"), - header::HeaderValue::from_str( - format!("rust/{}", solana_version::Version::default()).as_str(), - ) - .unwrap(), - ); - - let client = Arc::new( + Self::new_with_client( + url, reqwest::Client::builder() - .default_headers(default_headers) + .default_headers(Self::default_headers()) .timeout(timeout) .pool_idle_timeout(timeout) .build() .expect("build rpc client"), - ); + ) + } + /// Create an HTTP RPC sender. + /// + /// Most flexible way to create a sender. Pass a created `reqwest::Client`. + pub fn new_with_client(url: U, client: reqwest::Client) -> Self { Self { - client, + client: Arc::new(client), url: url.to_string(), request_id: AtomicU64::new(0), stats: RwLock::new(RpcTransportStats::default()), } } + + /// Create default headers used by HTTP Sender. + pub fn default_headers() -> header::HeaderMap { + let mut default_headers = header::HeaderMap::new(); + default_headers.append( + header::HeaderName::from_static("solana-client"), + header::HeaderValue::from_str( + format!("rust/{}", solana_version::Version::default()).as_str(), + ) + .unwrap(), + ); + default_headers + } } struct StatsUpdater<'a> { diff --git a/rpc/src/optimistically_confirmed_bank_tracker.rs b/rpc/src/optimistically_confirmed_bank_tracker.rs index 0cd37fb8a3f..6a9e0e2a8a5 100644 --- a/rpc/src/optimistically_confirmed_bank_tracker.rs +++ b/rpc/src/optimistically_confirmed_bank_tracker.rs @@ -424,7 +424,7 @@ mod tests { let exit = Arc::new(AtomicBool::new(false)); let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(100); let bank = Bank::new_for_tests(&genesis_config); - let bank_forks = Arc::new(RwLock::new(BankForks::new(bank))); + let bank_forks = BankForks::new_rw_arc(bank); let bank0 = bank_forks.read().unwrap().get(0).unwrap(); let bank1 = Bank::new_from_parent(bank0, &Pubkey::default(), 1); bank_forks.write().unwrap().insert(bank1); diff --git a/rpc/src/parsed_token_accounts.rs b/rpc/src/parsed_token_accounts.rs index 597ffb249de..d93cda521e6 100644 --- a/rpc/src/parsed_token_accounts.rs +++ b/rpc/src/parsed_token_accounts.rs @@ -1,4 +1,5 @@ use { + crate::rpc::account_resolver, jsonrpc_core::{Error, Result}, solana_account_decoder::{ parse_account_data::AccountAdditionalData, parse_token::get_token_account_mint, UiAccount, @@ -18,11 +19,19 @@ pub fn get_parsed_token_account( bank: &Bank, pubkey: &Pubkey, account: AccountSharedData, + // only used for simulation results + overwrite_accounts: Option<&HashMap>, ) -> UiAccount { let additional_data = get_token_account_mint(account.data()) - .and_then(|mint_pubkey| get_mint_owner_and_decimals(bank, &mint_pubkey).ok()) - .map(|(_, decimals)| AccountAdditionalData { - spl_token_decimals: Some(decimals), + .and_then(|mint_pubkey| { + account_resolver::get_account_from_overwrites_or_bank( + &mint_pubkey, + bank, + overwrite_accounts, + ) + }) + .map(|mint_account| AccountAdditionalData { + spl_token_decimals: get_mint_decimals(mint_account.data()).ok(), }); UiAccount::encode( diff --git a/rpc/src/rpc.rs b/rpc/src/rpc.rs index 7ff2ffa42b5..a10f3cd14a7 100644 --- a/rpc/src/rpc.rs +++ b/rpc/src/rpc.rs @@ -112,6 +112,8 @@ use { }, }; +pub mod account_resolver; + type RpcCustomResult = std::result::Result; pub const MAX_REQUEST_BODY_SIZE: usize = 50 * (1 << 10); // 50kB @@ -149,12 +151,15 @@ pub struct JsonRpcConfig { pub obsolete_v1_7_api: bool, pub rpc_scan_and_fix_roots: bool, pub max_request_body_size: Option, + /// Disable the health check, used for tests and TestValidator + pub disable_health_check: bool, } impl JsonRpcConfig { pub fn default_for_test() -> Self { Self { full_api: true, + disable_health_check: true, ..Self::default() } } @@ -166,6 +171,7 @@ pub struct RpcBigtableConfig { pub bigtable_instance_name: String, pub bigtable_app_profile_id: String, pub timeout: Option, + pub max_message_size: usize, } impl Default for RpcBigtableConfig { @@ -177,6 +183,7 @@ impl Default for RpcBigtableConfig { bigtable_instance_name, bigtable_app_profile_id, timeout: None, + max_message_size: solana_storage_bigtable::DEFAULT_MAX_MESSAGE_SIZE, } } } @@ -343,10 +350,7 @@ impl JsonRpcRequestProcessor { connection_cache: Arc, ) -> Self { let genesis_hash = bank.hash(); - let bank_forks = Arc::new(RwLock::new(BankForks::new_from_banks( - &[bank.clone()], - bank.slot(), - ))); + let bank_forks = BankForks::new_from_banks(&[bank.clone()], bank.slot()); let blockstore = Arc::new(Blockstore::open(&get_tmp_ledger_path!()).unwrap()); let exit = Arc::new(AtomicBool::new(false)); let cluster_info = Arc::new({ @@ -374,6 +378,10 @@ impl JsonRpcRequestProcessor { ); let leader_schedule_cache = Arc::new(LeaderScheduleCache::new_from_bank(&bank)); + let startup_verification_complete = Arc::clone(bank.get_startup_verification_complete()); + let slot = bank.slot(); + let optimistically_confirmed_bank = + Arc::new(RwLock::new(OptimisticallyConfirmedBank { bank })); Self { config: JsonRpcConfig::default(), snapshot_config: None, @@ -381,24 +389,22 @@ impl JsonRpcRequestProcessor { block_commitment_cache: Arc::new(RwLock::new(BlockCommitmentCache::new( HashMap::new(), 0, - CommitmentSlots::new_from_slot(bank.slot()), + CommitmentSlots::new_from_slot(slot), ))), - blockstore, + blockstore: Arc::clone(&blockstore), validator_exit: create_validator_exit(exit.clone()), health: Arc::new(RpcHealth::new( - cluster_info.clone(), - None, + Arc::clone(&optimistically_confirmed_bank), + blockstore, 0, exit, - Arc::clone(bank.get_startup_verification_complete()), + startup_verification_complete, )), cluster_info, genesis_hash, transaction_sender: Arc::new(Mutex::new(sender)), bigtable_ledger_storage: None, - optimistically_confirmed_bank: Arc::new(RwLock::new(OptimisticallyConfirmedBank { - bank, - })), + optimistically_confirmed_bank, largest_accounts_cache: Arc::new(RwLock::new(LargestAccountsCache::new(30))), max_slots: Arc::new(MaxSlots::default()), leader_schedule_cache, @@ -425,7 +431,7 @@ impl JsonRpcRequestProcessor { })?; let encoding = encoding.unwrap_or(UiAccountEncoding::Binary); - let response = get_encoded_account(&bank, pubkey, encoding, data_slice)?; + let response = get_encoded_account(&bank, pubkey, encoding, data_slice, None)?; Ok(new_response(&bank, response)) } @@ -448,7 +454,7 @@ impl JsonRpcRequestProcessor { let accounts = pubkeys .into_iter() - .map(|pubkey| get_encoded_account(&bank, &pubkey, encoding, data_slice)) + .map(|pubkey| get_encoded_account(&bank, &pubkey, encoding, data_slice, None)) .collect::>>()?; Ok(new_response(&bank, accounts)) } @@ -2281,13 +2287,15 @@ fn get_encoded_account( pubkey: &Pubkey, encoding: UiAccountEncoding, data_slice: Option, + // only used for simulation results + overwrite_accounts: Option<&HashMap>, ) -> Result> { - match bank.get_account(pubkey) { + match account_resolver::get_account_from_overwrites_or_bank(pubkey, bank, overwrite_accounts) { Some(account) => { let response = if is_known_spl_token_id(account.owner()) && encoding == UiAccountEncoding::JsonParsed { - get_parsed_token_account(bank, pubkey, account) + get_parsed_token_account(bank, pubkey, account, overwrite_accounts) } else { encode_account(&account, pubkey, encoding, data_slice)? }; @@ -2542,7 +2550,7 @@ pub mod rpc_minimal { #[rpc(meta, name = "getVersion")] fn get_version(&self, meta: Self::Metadata) -> Result; - // TODO: Refactor `solana-validator wait-for-restart-window` to not require this method, so + // TODO: Refactor `agave-validator wait-for-restart-window` to not require this method, so // it can be removed from rpc_minimal #[rpc(meta, name = "getVoteAccounts")] fn get_vote_accounts( @@ -2551,7 +2559,7 @@ pub mod rpc_minimal { config: Option, ) -> Result; - // TODO: Refactor `solana-validator wait-for-restart-window` to not require this method, so + // TODO: Refactor `agave-validator wait-for-restart-window` to not require this method, so // it can be removed from rpc_minimal #[rpc(meta, name = "getLeaderSchedule")] fn get_leader_schedule( @@ -2677,7 +2685,7 @@ pub mod rpc_minimal { }) } - // TODO: Refactor `solana-validator wait-for-restart-window` to not require this method, so + // TODO: Refactor `agave-validator wait-for-restart-window` to not require this method, so // it can be removed from rpc_minimal fn get_vote_accounts( &self, @@ -2688,7 +2696,7 @@ pub mod rpc_minimal { meta.get_vote_accounts(config) } - // TODO: Refactor `solana-validator wait-for-restart-window` to not require this method, so + // TODO: Refactor `agave-validator wait-for-restart-window` to not require this method, so // it can be removed from rpc_minimal fn get_leader_schedule( &self, @@ -3779,19 +3787,24 @@ pub mod rpc_full { if result.is_err() { Some(vec![None; config_accounts.addresses.len()]) } else { + let mut post_simulation_accounts_map = HashMap::new(); + for (pubkey, data) in post_simulation_accounts { + post_simulation_accounts_map.insert(pubkey, data); + } + Some( config_accounts .addresses .iter() .map(|address_str| { - let address = verify_pubkey(address_str)?; - post_simulation_accounts - .iter() - .find(|(key, _account)| key == &address) - .map(|(pubkey, account)| { - encode_account(account, pubkey, accounts_encoding, None) - }) - .transpose() + let pubkey = verify_pubkey(address_str)?; + get_encoded_account( + bank, + &pubkey, + accounts_encoding, + None, + Some(&post_simulation_accounts_map), + ) }) .collect::>>()?, ) @@ -4787,6 +4800,8 @@ pub mod tests { // note that this means that slot 0 will always be considered complete let max_complete_transaction_status_slot = Arc::new(AtomicU64::new(0)); let max_complete_rewards_slot = Arc::new(AtomicU64::new(0)); + let optimistically_confirmed_bank = + OptimisticallyConfirmedBank::locked_from_bank_forks_root(&bank_forks); let meta = JsonRpcRequestProcessor::new( config, @@ -4795,11 +4810,11 @@ pub mod tests { block_commitment_cache.clone(), blockstore.clone(), validator_exit, - RpcHealth::stub(), + RpcHealth::stub(optimistically_confirmed_bank.clone(), blockstore.clone()), cluster_info, Hash::default(), None, - OptimisticallyConfirmedBank::locked_from_bank_forks_root(&bank_forks), + optimistically_confirmed_bank, Arc::new(RwLock::new(LargestAccountsCache::new(30))), max_slots.clone(), Arc::new(LeaderScheduleCache::new_from_bank(&bank)), @@ -6106,6 +6121,148 @@ pub mod tests { assert_eq!(result, expected); } + #[test] + fn test_rpc_simulate_transaction_with_parsing_token_accounts() { + let rpc = RpcHandler::start(); + let bank = rpc.working_bank(); + let RpcHandler { + ref meta, ref io, .. + } = rpc; + + // init mint + let mint_rent_exempt_amount = + bank.get_minimum_balance_for_rent_exemption(spl_token::state::Mint::LEN); + let mint_pubkey = Pubkey::from_str("mint111111111111111111111111111111111111111").unwrap(); + let mut mint_data = [0u8; spl_token::state::Mint::LEN]; + Pack::pack_into_slice( + &spl_token::state::Mint { + mint_authority: COption::None, + supply: 0, + decimals: 8, + is_initialized: true, + freeze_authority: COption::None, + }, + &mut mint_data, + ); + let account = AccountSharedData::create( + mint_rent_exempt_amount, + mint_data.into(), + spl_token::id(), + false, + 0, + ); + bank.store_account(&mint_pubkey, &account); + + // init token account + let token_account_rent_exempt_amount = + bank.get_minimum_balance_for_rent_exemption(spl_token::state::Account::LEN); + let token_account_pubkey = Pubkey::new_unique(); + let owner_pubkey = Pubkey::from_str("owner11111111111111111111111111111111111111").unwrap(); + let mut token_account_data = [0u8; spl_token::state::Account::LEN]; + Pack::pack_into_slice( + &spl_token::state::Account { + mint: mint_pubkey, + owner: owner_pubkey, + amount: 1, + delegate: COption::None, + state: spl_token::state::AccountState::Initialized, + is_native: COption::None, + delegated_amount: 0, + close_authority: COption::None, + }, + &mut token_account_data, + ); + let account = AccountSharedData::create( + token_account_rent_exempt_amount, + token_account_data.into(), + spl_token::id(), + false, + 0, + ); + bank.store_account(&token_account_pubkey, &account); + + // prepare tx + let fee_payer = rpc.mint_keypair; + let recent_blockhash = bank.confirmed_last_blockhash(); + let tx = + system_transaction::transfer(&fee_payer, &token_account_pubkey, 1, recent_blockhash); + let tx_serialized_encoded = bs58::encode(serialize(&tx).unwrap()).into_string(); + + // Simulation bank must be frozen + bank.freeze(); + + let req = format!( + r#"{{"jsonrpc":"2.0", + "id":1, + "method":"simulateTransaction", + "params":[ + "{}", + {{ + "sigVerify": true, + "accounts": {{ + "encoding": "jsonParsed", + "addresses": ["{}", "{}"] + }} + }} + ] + }}"#, + tx_serialized_encoded, + solana_sdk::pubkey::new_rand(), + token_account_pubkey, + ); + let res = io.handle_request_sync(&req, meta.clone()); + let expected = json!({ + "jsonrpc": "2.0", + "result": { + "context": {"slot": 0, "apiVersion": RpcApiVersion::default()}, + "value":{ + "accounts": [ + null, + { + "data": { + "parsed": { + "info": { + "isNative": false, + "mint": "mint111111111111111111111111111111111111111", + "owner": "owner11111111111111111111111111111111111111", + "state": "initialized", + "tokenAmount": { + "amount": "1", + "decimals": 8, + "uiAmount": 0.00000001, + "uiAmountString": "0.00000001" + } + }, + "type": "account" + }, + "program": "spl-token", + "space": 165 + }, + "executable": false, + "lamports": (token_account_rent_exempt_amount + 1), + "owner": bs58::encode(spl_token::id()).into_string(), + "rentEpoch": u64::MAX, + "space": spl_token::state::Account::LEN + }, + ], + "err": null, + "logs":[ + "Program 11111111111111111111111111111111 invoke [1]", + "Program 11111111111111111111111111111111 success" + ], + "returnData": null, + "unitsConsumed": 150, + } + }, + "id": 1, + }); + let expected: Response = + serde_json::from_value(expected).expect("expected response deserialization"); + let result: Response = serde_json::from_str(&res.expect("actual response")) + .expect("actual response deserialization"); + assert_eq!(result, expected); + } + #[test] #[should_panic(expected = "simulation bank must be frozen")] fn test_rpc_simulate_transaction_panic_on_unfrozen_bank() { @@ -6398,7 +6555,11 @@ pub mod tests { let blockstore = Arc::new(Blockstore::open(&ledger_path).unwrap()); let block_commitment_cache = Arc::new(RwLock::new(BlockCommitmentCache::default())); let (bank_forks, mint_keypair, ..) = new_bank_forks(); - let health = RpcHealth::stub(); + let optimistically_confirmed_bank = + OptimisticallyConfirmedBank::locked_from_bank_forks_root(&bank_forks); + let health = RpcHealth::stub(optimistically_confirmed_bank.clone(), blockstore.clone()); + // Mark the node as healthy to start + health.stub_set_health_status(Some(RpcHealthStatus::Ok)); // Freeze bank 0 to prevent a panic in `run_transaction_simulation()` bank_forks.write().unwrap().get(0).unwrap().freeze(); @@ -6429,7 +6590,7 @@ pub mod tests { cluster_info, Hash::default(), None, - OptimisticallyConfirmedBank::locked_from_bank_forks_root(&bank_forks), + optimistically_confirmed_bank, Arc::new(RwLock::new(LargestAccountsCache::new(30))), Arc::new(MaxSlots::default()), Arc::new(LeaderScheduleCache::default()), @@ -6616,7 +6777,7 @@ pub mod tests { let bank = Bank::new_for_tests_with_config(&genesis_config, config); ( - Arc::new(RwLock::new(BankForks::new(bank))), + BankForks::new_rw_arc(bank), mint_keypair, Arc::new(voting_keypair), ) @@ -6690,18 +6851,20 @@ pub mod tests { .my_contact_info() .tpu(connection_cache.protocol()) .unwrap(); + let optimistically_confirmed_bank = + OptimisticallyConfirmedBank::locked_from_bank_forks_root(&bank_forks); let (request_processor, receiver) = JsonRpcRequestProcessor::new( JsonRpcConfig::default(), None, bank_forks.clone(), block_commitment_cache, - blockstore, + blockstore.clone(), validator_exit, - RpcHealth::stub(), + RpcHealth::stub(optimistically_confirmed_bank.clone(), blockstore), cluster_info, Hash::default(), None, - OptimisticallyConfirmedBank::locked_from_bank_forks_root(&bank_forks), + optimistically_confirmed_bank, Arc::new(RwLock::new(LargestAccountsCache::new(30))), Arc::new(MaxSlots::default()), Arc::new(LeaderScheduleCache::default()), @@ -8297,7 +8460,7 @@ pub mod tests { let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(100); let bank = Bank::new_for_tests(&genesis_config); - let bank_forks = Arc::new(RwLock::new(BankForks::new(bank))); + let bank_forks = BankForks::new_rw_arc(bank); let bank0 = bank_forks.read().unwrap().get(0).unwrap(); let bank1 = Bank::new_from_parent(bank0, &Pubkey::default(), 1); bank_forks.write().unwrap().insert(bank1); @@ -8327,9 +8490,9 @@ pub mod tests { None, bank_forks.clone(), block_commitment_cache, - blockstore, + blockstore.clone(), validator_exit, - RpcHealth::stub(), + RpcHealth::stub(optimistically_confirmed_bank.clone(), blockstore.clone()), cluster_info, Hash::default(), None, diff --git a/rpc/src/rpc/account_resolver.rs b/rpc/src/rpc/account_resolver.rs new file mode 100644 index 00000000000..44d232a24b1 --- /dev/null +++ b/rpc/src/rpc/account_resolver.rs @@ -0,0 +1,15 @@ +use { + solana_runtime::bank::Bank, + solana_sdk::{account::AccountSharedData, pubkey::Pubkey}, + std::collections::HashMap, +}; + +pub(crate) fn get_account_from_overwrites_or_bank( + pubkey: &Pubkey, + bank: &Bank, + overwrite_accounts: Option<&HashMap>, +) -> Option { + overwrite_accounts + .and_then(|accounts| accounts.get(pubkey).cloned()) + .or_else(|| bank.get_account(pubkey)) +} diff --git a/rpc/src/rpc_health.rs b/rpc/src/rpc_health.rs index 022b2e03d1e..aff4fa48f03 100644 --- a/rpc/src/rpc_health.rs +++ b/rpc/src/rpc_health.rs @@ -1,12 +1,10 @@ use { - solana_gossip::cluster_info::ClusterInfo, - solana_sdk::{clock::Slot, pubkey::Pubkey}, - std::{ - collections::HashSet, - sync::{ - atomic::{AtomicBool, Ordering}, - Arc, - }, + crate::optimistically_confirmed_bank_tracker::OptimisticallyConfirmedBank, + solana_ledger::blockstore::Blockstore, + solana_sdk::clock::Slot, + std::sync::{ + atomic::{AtomicBool, Ordering}, + Arc, RwLock, }, }; @@ -18,8 +16,8 @@ pub enum RpcHealthStatus { } pub struct RpcHealth { - cluster_info: Arc, - known_validators: Option>, + optimistically_confirmed_bank: Arc>, + blockstore: Arc, health_check_slot_distance: u64, override_health_check: Arc, startup_verification_complete: Arc, @@ -29,15 +27,15 @@ pub struct RpcHealth { impl RpcHealth { pub fn new( - cluster_info: Arc, - known_validators: Option>, + optimistically_confirmed_bank: Arc>, + blockstore: Arc, health_check_slot_distance: u64, override_health_check: Arc, startup_verification_complete: Arc, ) -> Self { Self { - cluster_info, - known_validators, + optimistically_confirmed_bank, + blockstore, health_check_slot_distance, override_health_check, startup_verification_complete, @@ -54,84 +52,74 @@ impl RpcHealth { } } + if self.override_health_check.load(Ordering::Relaxed) { + return RpcHealthStatus::Ok; + } if !self.startup_verification_complete.load(Ordering::Acquire) { return RpcHealthStatus::Unknown; } - if self.override_health_check.load(Ordering::Relaxed) { - RpcHealthStatus::Ok - } else if let Some(known_validators) = &self.known_validators { - match ( - self.cluster_info - .get_accounts_hash_for_node(&self.cluster_info.id(), |hashes| { - hashes - .iter() - .max_by(|a, b| a.0.cmp(&b.0)) - .map(|slot_hash| slot_hash.0) - }) - .flatten(), - known_validators - .iter() - .filter_map(|known_validator| { - self.cluster_info - .get_accounts_hash_for_node(known_validator, |hashes| { - hashes - .iter() - .max_by(|a, b| a.0.cmp(&b.0)) - .map(|slot_hash| slot_hash.0) - }) - .flatten() - }) - .max(), - ) { - ( - Some(latest_account_hash_slot), - Some(latest_known_validator_account_hash_slot), - ) => { - // The validator is considered healthy if its latest account hash slot is within - // `health_check_slot_distance` of the latest known validator's account hash slot - if latest_account_hash_slot - > latest_known_validator_account_hash_slot - .saturating_sub(self.health_check_slot_distance) - { - RpcHealthStatus::Ok - } else { - let num_slots = latest_known_validator_account_hash_slot - .saturating_sub(latest_account_hash_slot); - warn!( - "health check: behind by {} slots: me={}, latest known_validator={}", - num_slots, - latest_account_hash_slot, - latest_known_validator_account_hash_slot - ); - RpcHealthStatus::Behind { num_slots } - } - } - (latest_account_hash_slot, latest_known_validator_account_hash_slot) => { - if latest_account_hash_slot.is_none() { - warn!("health check: latest_account_hash_slot not available"); - } - if latest_known_validator_account_hash_slot.is_none() { - warn!( - "health check: latest_known_validator_account_hash_slot not available" - ); - } - RpcHealthStatus::Unknown - } + // A node can observe votes by both replaying blocks and observing gossip. + // + // ClusterInfoVoteListener receives votes from both of these sources and then records + // optimistically confirmed slots in the Blockstore via OptimisticConfirmationVerifier. + // Thus, it is possible for a node to record an optimistically confirmed slot before the + // node has replayed and validated the slot for itself. + // + // OptimisticallyConfirmedBank holds a bank for the latest optimistically confirmed slot + // that the node has replayed. It is true that the node will have replayed that slot by + // virtue of having a bank available. Observing that the cluster has optimistically + // confirmed a slot through gossip is not enough to reconstruct the bank. + // + // So, comparing the latest optimistic slot from the Blockstore vs. the slot from the + // OptimisticallyConfirmedBank bank allows a node to see where it stands in relation to the + // tip of the cluster. + let my_latest_optimistically_confirmed_slot = self + .optimistically_confirmed_bank + .read() + .unwrap() + .bank + .slot(); + + let mut optimistic_slot_infos = match self.blockstore.get_latest_optimistic_slots(1) { + Ok(infos) => infos, + Err(err) => { + warn!("health check: blockstore error: {err}"); + return RpcHealthStatus::Unknown; } - } else { - // No known validator point of reference available, so this validator is healthy - // because it's running + }; + let Some((cluster_latest_optimistically_confirmed_slot, _, _)) = + optimistic_slot_infos.pop() + else { + warn!("health check: blockstore does not contain any optimistically confirmed slots"); + return RpcHealthStatus::Unknown; + }; + + if my_latest_optimistically_confirmed_slot + >= cluster_latest_optimistically_confirmed_slot + .saturating_sub(self.health_check_slot_distance) + { RpcHealthStatus::Ok + } else { + let num_slots = cluster_latest_optimistically_confirmed_slot + .saturating_sub(my_latest_optimistically_confirmed_slot); + warn!( + "health check: behind by {num_slots} \ + slots: me={my_latest_optimistically_confirmed_slot}, \ + latest cluster={cluster_latest_optimistically_confirmed_slot}", + ); + RpcHealthStatus::Behind { num_slots } } } #[cfg(test)] - pub(crate) fn stub() -> Arc { - use crate::rpc::tests::new_test_cluster_info; + pub(crate) fn stub( + optimistically_confirmed_bank: Arc>, + blockstore: Arc, + ) -> Arc { Arc::new(Self::new( - Arc::new(new_test_cluster_info()), - None, + optimistically_confirmed_bank, + blockstore, 42, Arc::new(AtomicBool::new(false)), Arc::new(AtomicBool::new(true)), @@ -143,3 +131,84 @@ impl RpcHealth { *self.stub_health_status.write().unwrap() = stub_health_status; } } + +#[cfg(test)] +pub mod tests { + use { + super::*, + solana_ledger::{ + genesis_utils::{create_genesis_config, GenesisConfigInfo}, + get_tmp_ledger_path_auto_delete, + }, + solana_runtime::{bank::Bank, bank_forks::BankForks}, + solana_sdk::{clock::UnixTimestamp, hash::Hash, pubkey::Pubkey}, + }; + + #[test] + fn test_get_health() { + let ledger_path = get_tmp_ledger_path_auto_delete!(); + let blockstore = Arc::new(Blockstore::open(ledger_path.path()).unwrap()); + let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(100); + let bank = Bank::new_for_tests(&genesis_config); + let bank_forks = BankForks::new_rw_arc(bank); + let optimistically_confirmed_bank = + OptimisticallyConfirmedBank::locked_from_bank_forks_root(&bank_forks); + let bank0 = bank_forks.read().unwrap().root_bank(); + assert!(bank0.slot() == 0); + + let health_check_slot_distance = 10; + let override_health_check = Arc::new(AtomicBool::new(true)); + let startup_verification_complete = Arc::clone(bank0.get_startup_verification_complete()); + let health = RpcHealth::new( + optimistically_confirmed_bank.clone(), + blockstore.clone(), + health_check_slot_distance, + override_health_check.clone(), + startup_verification_complete, + ); + + // Override health check set to true - status is ok + assert_eq!(health.check(), RpcHealthStatus::Ok); + + // Remove the override - status now unknown with incomplete startup verification + override_health_check.store(false, Ordering::Relaxed); + assert_eq!(health.check(), RpcHealthStatus::Unknown); + + // Mark startup verification complete - status still unknown as no slots have been + // optimistically confirmed yet + bank0.set_startup_verification_complete(); + assert_eq!(health.check(), RpcHealthStatus::Unknown); + + // Mark slot 15 as being optimistically confirmed in the Blockstore, this could + // happen if the cluster confirmed the slot and this node became aware through gossip, + // but this node has not yet replayed slot 15. The local view of the latest optimistic + // slot is still slot 0 so status will be behind + blockstore + .insert_optimistic_slot(15, &Hash::default(), UnixTimestamp::default()) + .unwrap(); + assert_eq!(health.check(), RpcHealthStatus::Behind { num_slots: 15 }); + + // Simulate this node observing slot 4 as optimistically confirmed - status still behind + let bank4 = Arc::new(Bank::new_from_parent(bank0, &Pubkey::default(), 4)); + optimistically_confirmed_bank.write().unwrap().bank = bank4.clone(); + assert_eq!(health.check(), RpcHealthStatus::Behind { num_slots: 11 }); + + // Simulate this node observing slot 5 as optimistically confirmed - status now ok + // as distance is <= health_check_slot_distance + let bank5 = Arc::new(Bank::new_from_parent(bank4, &Pubkey::default(), 5)); + optimistically_confirmed_bank.write().unwrap().bank = bank5.clone(); + assert_eq!(health.check(), RpcHealthStatus::Ok); + + // Node now up with tip of cluster + let bank15 = Arc::new(Bank::new_from_parent(bank5, &Pubkey::default(), 15)); + optimistically_confirmed_bank.write().unwrap().bank = bank15.clone(); + assert_eq!(health.check(), RpcHealthStatus::Ok); + + // Node "beyond" tip of cluster - this technically isn't possible but could be + // observed locally due to a race between updates to Blockstore and + // OptimisticallyConfirmedBank. Either way, not a problem and status is ok. + let bank16 = Arc::new(Bank::new_from_parent(bank15, &Pubkey::default(), 16)); + optimistically_confirmed_bank.write().unwrap().bank = bank16.clone(); + assert_eq!(health.check(), RpcHealthStatus::Ok); + } +} diff --git a/rpc/src/rpc_pubsub.rs b/rpc/src/rpc_pubsub.rs index e45a5f8af68..19244415bd6 100644 --- a/rpc/src/rpc_pubsub.rs +++ b/rpc/src/rpc_pubsub.rs @@ -659,7 +659,7 @@ mod tests { current_slot: Slot, ) -> transaction::Result<()> { bank_forks - .write() + .read() .unwrap() .get(current_slot) .unwrap() @@ -684,7 +684,7 @@ mod tests { let bob_pubkey = bob.pubkey(); let bank = Bank::new_for_tests(&genesis_config); let blockhash = bank.last_blockhash(); - let bank_forks = Arc::new(RwLock::new(BankForks::new(bank))); + let bank_forks = BankForks::new_rw_arc(bank); let max_complete_transaction_status_slot = Arc::new(AtomicU64::default()); let max_complete_rewards_slot = Arc::new(AtomicU64::default()); let rpc_subscriptions = Arc::new(RpcSubscriptions::new_for_tests( @@ -813,7 +813,7 @@ mod tests { let bob_pubkey = solana_sdk::pubkey::new_rand(); let bank = Bank::new_for_tests(&genesis_config); let blockhash = bank.last_blockhash(); - let bank_forks = Arc::new(RwLock::new(BankForks::new(bank))); + let bank_forks = BankForks::new_rw_arc(bank); let mut io = IoHandler::<()>::default(); let max_complete_transaction_status_slot = Arc::new(AtomicU64::default()); @@ -871,7 +871,7 @@ mod tests { let stake_program_id = stake::program::id(); let bank = Bank::new_for_tests(&genesis_config); let blockhash = bank.last_blockhash(); - let bank_forks = Arc::new(RwLock::new(BankForks::new(bank))); + let bank_forks = BankForks::new_rw_arc(bank); let bank0 = bank_forks.read().unwrap().get(0).unwrap(); let bank1 = Bank::new_from_parent(bank0, &Pubkey::default(), 1); bank_forks.write().unwrap().insert(bank1); @@ -999,7 +999,7 @@ mod tests { let nonce_account = Keypair::new(); let bank = Bank::new_for_tests(&genesis_config); let blockhash = bank.last_blockhash(); - let bank_forks = Arc::new(RwLock::new(BankForks::new(bank))); + let bank_forks = BankForks::new_rw_arc(bank); let bank0 = bank_forks.read().unwrap().get(0).unwrap(); let bank1 = Bank::new_from_parent(bank0, &Pubkey::default(), 1); bank_forks.write().unwrap().insert(bank1); @@ -1088,9 +1088,7 @@ mod tests { let bob_pubkey = solana_sdk::pubkey::new_rand(); let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(10_000); - let bank_forks = Arc::new(RwLock::new(BankForks::new(Bank::new_for_tests( - &genesis_config, - )))); + let bank_forks = BankForks::new_rw_arc(Bank::new_for_tests(&genesis_config)); let mut io = IoHandler::<()>::default(); let max_complete_transaction_status_slot = Arc::new(AtomicU64::default()); @@ -1138,7 +1136,7 @@ mod tests { } = create_genesis_config(10_000); let bank = Bank::new_for_tests(&genesis_config); let blockhash = bank.last_blockhash(); - let bank_forks = Arc::new(RwLock::new(BankForks::new(bank))); + let bank_forks = BankForks::new_rw_arc(bank); let bob = Keypair::new(); let exit = Arc::new(AtomicBool::new(false)); @@ -1168,7 +1166,7 @@ mod tests { let tx = system_transaction::transfer(&alice, &bob.pubkey(), 100, blockhash); bank_forks - .write() + .read() .unwrap() .get(1) .unwrap() @@ -1190,7 +1188,7 @@ mod tests { } = create_genesis_config(10_000); let bank = Bank::new_for_tests(&genesis_config); let blockhash = bank.last_blockhash(); - let bank_forks = Arc::new(RwLock::new(BankForks::new(bank))); + let bank_forks = BankForks::new_rw_arc(bank); let bank0 = bank_forks.read().unwrap().get(0).unwrap(); let bank1 = Bank::new_from_parent(bank0, &Pubkey::default(), 1); bank_forks.write().unwrap().insert(bank1); @@ -1223,7 +1221,7 @@ mod tests { let tx = system_transaction::transfer(&alice, &bob.pubkey(), 100, blockhash); bank_forks - .write() + .read() .unwrap() .get(1) .unwrap() @@ -1272,7 +1270,7 @@ mod tests { fn test_slot_subscribe() { let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(10_000); let bank = Bank::new_for_tests(&genesis_config); - let bank_forks = Arc::new(RwLock::new(BankForks::new(bank))); + let bank_forks = BankForks::new_rw_arc(bank); let max_complete_transaction_status_slot = Arc::new(AtomicU64::default()); let max_complete_rewards_slot = Arc::new(AtomicU64::default()); let rpc_subscriptions = Arc::new(RpcSubscriptions::default_with_bank_forks( @@ -1305,7 +1303,7 @@ mod tests { fn test_slot_unsubscribe() { let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(10_000); let bank = Bank::new_for_tests(&genesis_config); - let bank_forks = Arc::new(RwLock::new(BankForks::new(bank))); + let bank_forks = BankForks::new_rw_arc(bank); let max_complete_transaction_status_slot = Arc::new(AtomicU64::default()); let max_complete_rewards_slot = Arc::new(AtomicU64::default()); let rpc_subscriptions = Arc::new(RpcSubscriptions::default_with_bank_forks( @@ -1348,7 +1346,7 @@ mod tests { ); let exit = Arc::new(AtomicBool::new(false)); let bank = Bank::new_for_tests(&genesis_config); - let bank_forks = Arc::new(RwLock::new(BankForks::new(bank))); + let bank_forks = BankForks::new_rw_arc(bank); // Setup Subscriptions let optimistically_confirmed_bank = @@ -1390,7 +1388,7 @@ mod tests { fn test_vote_unsubscribe() { let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(10_000); let bank = Bank::new_for_tests(&genesis_config); - let bank_forks = Arc::new(RwLock::new(BankForks::new(bank))); + let bank_forks = BankForks::new_rw_arc(bank); let max_complete_transaction_status_slot = Arc::new(AtomicU64::default()); let max_complete_rewards_slot = Arc::new(AtomicU64::default()); let rpc_subscriptions = Arc::new(RpcSubscriptions::default_with_bank_forks( @@ -1409,7 +1407,7 @@ mod tests { fn test_get_version() { let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(10_000); let bank = Bank::new_for_tests(&genesis_config); - let bank_forks = Arc::new(RwLock::new(BankForks::new(bank))); + let bank_forks = BankForks::new_rw_arc(bank); let max_complete_transaction_status_slot = Arc::new(AtomicU64::default()); let max_complete_rewards_slot = Arc::new(AtomicU64::default()); let rpc_subscriptions = Arc::new(RpcSubscriptions::default_with_bank_forks( diff --git a/rpc/src/rpc_pubsub_service.rs b/rpc/src/rpc_pubsub_service.rs index 99eab0a2353..85b753815ab 100644 --- a/rpc/src/rpc_pubsub_service.rs +++ b/rpc/src/rpc_pubsub_service.rs @@ -485,7 +485,7 @@ mod tests { let max_complete_rewards_slot = Arc::new(AtomicU64::default()); let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(10_000); let bank = Bank::new_for_tests(&genesis_config); - let bank_forks = Arc::new(RwLock::new(BankForks::new(bank))); + let bank_forks = BankForks::new_rw_arc(bank); let optimistically_confirmed_bank = OptimisticallyConfirmedBank::locked_from_bank_forks_root(&bank_forks); let subscriptions = Arc::new(RpcSubscriptions::new_for_tests( diff --git a/rpc/src/rpc_service.rs b/rpc/src/rpc_service.rs index 4fdde57c31a..329de948e24 100644 --- a/rpc/src/rpc_service.rs +++ b/rpc/src/rpc_service.rs @@ -37,12 +37,11 @@ use { }, solana_sdk::{ exit::Exit, genesis_config::DEFAULT_GENESIS_DOWNLOAD_PATH, hash::Hash, - native_token::lamports_to_sol, pubkey::Pubkey, + native_token::lamports_to_sol, }, solana_send_transaction_service::send_transaction_service::{self, SendTransactionService}, solana_storage_bigtable::CredentialType, std::{ - collections::HashSet, net::SocketAddr, path::{Path, PathBuf}, sync::{ @@ -350,7 +349,6 @@ impl JsonRpcService { ledger_path: &Path, validator_exit: Arc>, exit: Arc, - known_validators: Option>, override_health_check: Arc, startup_verification_complete: Arc, optimistically_confirmed_bank: Arc>, @@ -368,8 +366,8 @@ impl JsonRpcService { let rpc_niceness_adj = config.rpc_niceness_adj; let health = Arc::new(RpcHealth::new( - cluster_info.clone(), - known_validators, + Arc::clone(&optimistically_confirmed_bank), + Arc::clone(&blockstore), config.health_check_slot_distance, override_health_check, startup_verification_complete, @@ -408,6 +406,7 @@ impl JsonRpcService { ref bigtable_instance_name, ref bigtable_app_profile_id, timeout, + max_message_size, }) = config.rpc_bigtable_config { let bigtable_config = solana_storage_bigtable::LedgerStorageConfig { @@ -416,6 +415,7 @@ impl JsonRpcService { credential_type: CredentialType::Filepath(None), instance_name: bigtable_instance_name.clone(), app_profile_id: bigtable_app_profile_id.clone(), + max_message_size, }; runtime .block_on(solana_storage_bigtable::LedgerStorage::new_with_config( @@ -586,10 +586,6 @@ mod tests { use { super::*, crate::rpc::{create_validator_exit, tests::new_test_cluster_info}, - solana_gossip::{ - crds::GossipRoute, - crds_value::{AccountsHashes, CrdsData, CrdsValue}, - }, solana_ledger::{ genesis_utils::{create_genesis_config, GenesisConfigInfo}, get_tmp_ledger_path, @@ -623,7 +619,7 @@ mod tests { ip_addr, solana_net_utils::find_available_port_in_range(ip_addr, (10000, 65535)).unwrap(), ); - let bank_forks = Arc::new(RwLock::new(BankForks::new(bank))); + let bank_forks = BankForks::new_rw_arc(bank); let ledger_path = get_tmp_ledger_path!(); let blockstore = Arc::new(Blockstore::open(&ledger_path).unwrap()); let block_commitment_cache = Arc::new(RwLock::new(BlockCommitmentCache::default())); @@ -643,7 +639,6 @@ mod tests { &PathBuf::from("farf"), validator_exit, exit, - None, Arc::new(AtomicBool::new(false)), Arc::new(AtomicBool::new(true)), optimistically_confirmed_bank, @@ -681,7 +676,7 @@ mod tests { } = create_genesis_config(10_000); genesis_config.cluster_type = ClusterType::MainnetBeta; let bank = Bank::new_for_tests(&genesis_config); - Arc::new(RwLock::new(BankForks::new(bank))) + BankForks::new_rw_arc(bank) } #[test] @@ -726,18 +721,25 @@ mod tests { #[test] fn test_is_file_get_path() { + let ledger_path = get_tmp_ledger_path!(); + let blockstore = Arc::new(Blockstore::open(&ledger_path).unwrap()); + let bank_forks = create_bank_forks(); + let optimistically_confirmed_bank = + OptimisticallyConfirmedBank::locked_from_bank_forks_root(&bank_forks); + let health = RpcHealth::stub(optimistically_confirmed_bank, blockstore); + let bank_forks = create_bank_forks(); let rrm = RpcRequestMiddleware::new( - PathBuf::from("/"), + ledger_path.clone(), None, bank_forks.clone(), - RpcHealth::stub(), + health.clone(), ); let rrm_with_snapshot_config = RpcRequestMiddleware::new( - PathBuf::from("/"), + ledger_path.clone(), Some(SnapshotConfig::default()), bank_forks, - RpcHealth::stub(), + health, ); assert!(rrm.is_file_get_path(DEFAULT_GENESIS_DOWNLOAD_PATH)); @@ -828,14 +830,17 @@ mod tests { let runtime = Runtime::new().unwrap(); let ledger_path = get_tmp_ledger_path!(); - std::fs::create_dir(&ledger_path).unwrap(); - + let blockstore = Arc::new(Blockstore::open(&ledger_path).unwrap()); let genesis_path = ledger_path.join(DEFAULT_GENESIS_ARCHIVE); + let bank_forks = create_bank_forks(); + let optimistically_confirmed_bank = + OptimisticallyConfirmedBank::locked_from_bank_forks_root(&bank_forks); + let rrm = RpcRequestMiddleware::new( ledger_path.clone(), None, - create_bank_forks(), - RpcHealth::stub(), + bank_forks, + RpcHealth::stub(optimistically_confirmed_bank, blockstore), ); // File does not exist => request should fail. @@ -883,106 +888,4 @@ mod tests { } } } - - #[test] - fn test_health_check_with_no_known_validators() { - let rm = RpcRequestMiddleware::new( - PathBuf::from("/"), - None, - create_bank_forks(), - RpcHealth::stub(), - ); - assert_eq!(rm.health_check(), "ok"); - } - - #[test] - fn test_health_check_with_known_validators() { - let cluster_info = Arc::new(new_test_cluster_info()); - let health_check_slot_distance = 123; - let override_health_check = Arc::new(AtomicBool::new(false)); - let startup_verification_complete = Arc::new(AtomicBool::new(true)); - let known_validators = vec![ - solana_sdk::pubkey::new_rand(), - solana_sdk::pubkey::new_rand(), - solana_sdk::pubkey::new_rand(), - ]; - - let health = Arc::new(RpcHealth::new( - cluster_info.clone(), - Some(known_validators.clone().into_iter().collect()), - health_check_slot_distance, - override_health_check.clone(), - startup_verification_complete, - )); - - let rm = RpcRequestMiddleware::new(PathBuf::from("/"), None, create_bank_forks(), health); - - // No account hashes for this node or any known validators - assert_eq!(rm.health_check(), "unknown"); - - // No account hashes for any known validators - cluster_info.push_accounts_hashes(vec![(1000, Hash::default()), (900, Hash::default())]); - cluster_info.flush_push_queue(); - assert_eq!(rm.health_check(), "unknown"); - - // Override health check - override_health_check.store(true, Ordering::Relaxed); - assert_eq!(rm.health_check(), "ok"); - override_health_check.store(false, Ordering::Relaxed); - - // This node is ahead of the known validators - cluster_info - .gossip - .crds - .write() - .unwrap() - .insert( - CrdsValue::new_unsigned(CrdsData::AccountsHashes(AccountsHashes::new( - known_validators[0], - vec![ - (1, Hash::default()), - (1001, Hash::default()), - (2, Hash::default()), - ], - ))), - 1, - GossipRoute::LocalMessage, - ) - .unwrap(); - assert_eq!(rm.health_check(), "ok"); - - // Node is slightly behind the known validators - cluster_info - .gossip - .crds - .write() - .unwrap() - .insert( - CrdsValue::new_unsigned(CrdsData::AccountsHashes(AccountsHashes::new( - known_validators[1], - vec![(1000 + health_check_slot_distance - 1, Hash::default())], - ))), - 1, - GossipRoute::LocalMessage, - ) - .unwrap(); - assert_eq!(rm.health_check(), "ok"); - - // Node is far behind the known validators - cluster_info - .gossip - .crds - .write() - .unwrap() - .insert( - CrdsValue::new_unsigned(CrdsData::AccountsHashes(AccountsHashes::new( - known_validators[2], - vec![(1000 + health_check_slot_distance, Hash::default())], - ))), - 1, - GossipRoute::LocalMessage, - ) - .unwrap(); - assert_eq!(rm.health_check(), "behind"); - } } diff --git a/rpc/src/rpc_subscription_tracker.rs b/rpc/src/rpc_subscription_tracker.rs index 41a2f3506cb..97ceb576636 100644 --- a/rpc/src/rpc_subscription_tracker.rs +++ b/rpc/src/rpc_subscription_tracker.rs @@ -700,7 +700,7 @@ mod tests { fn subscription_info() { let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(10_000); let bank = Bank::new_for_tests(&genesis_config); - let bank_forks = Arc::new(RwLock::new(BankForks::new(bank))); + let bank_forks = BankForks::new_rw_arc(bank); let mut tracker = SubscriptionsTracker::new(bank_forks); tracker.subscribe(SubscriptionParams::Slot, 0.into(), || 0); @@ -746,7 +746,7 @@ mod tests { let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(10_000); let bank = Bank::new_for_tests(&genesis_config); - let bank_forks = Arc::new(RwLock::new(BankForks::new(bank))); + let bank_forks = BankForks::new_rw_arc(bank); let mut tracker = SubscriptionsTracker::new(bank_forks); tracker.subscribe(SubscriptionParams::Slot, 0.into(), || 0); diff --git a/rpc/src/rpc_subscriptions.rs b/rpc/src/rpc_subscriptions.rs index 6fca7d45035..7910e539fad 100644 --- a/rpc/src/rpc_subscriptions.rs +++ b/rpc/src/rpc_subscriptions.rs @@ -383,7 +383,7 @@ fn filter_account_result( if is_known_spl_token_id(account.owner()) && params.encoding == UiAccountEncoding::JsonParsed { - get_parsed_token_account(&bank, ¶ms.pubkey, account) + get_parsed_token_account(&bank, ¶ms.pubkey, account, None) } else { UiAccount::encode(¶ms.pubkey, &account, params.encoding, None, None) } @@ -1324,7 +1324,7 @@ pub(crate) mod tests { } = create_genesis_config(100); let bank = Bank::new_for_tests(&genesis_config); let blockhash = bank.last_blockhash(); - let bank_forks = Arc::new(RwLock::new(BankForks::new(bank))); + let bank_forks = BankForks::new_rw_arc(bank); let bank0 = bank_forks.read().unwrap().get(0).unwrap(); let bank1 = Bank::new_from_parent(bank0, &Pubkey::default(), 1); bank_forks.write().unwrap().insert(bank1); @@ -1470,7 +1470,7 @@ pub(crate) mod tests { } = create_genesis_config(10_000); let bank = Bank::new_for_tests(&genesis_config); let rent_exempt_amount = bank.get_minimum_balance_for_rent_exemption(0); - let bank_forks = Arc::new(RwLock::new(BankForks::new(bank))); + let bank_forks = BankForks::new_rw_arc(bank); let optimistically_confirmed_bank = OptimisticallyConfirmedBank::locked_from_bank_forks_root(&bank_forks); let ledger_path = get_tmp_ledger_path!(); @@ -1590,7 +1590,7 @@ pub(crate) mod tests { } = create_genesis_config(10_000); let bank = Bank::new_for_tests(&genesis_config); let rent_exempt_amount = bank.get_minimum_balance_for_rent_exemption(0); - let bank_forks = Arc::new(RwLock::new(BankForks::new(bank))); + let bank_forks = BankForks::new_rw_arc(bank); let optimistically_confirmed_bank = OptimisticallyConfirmedBank::locked_from_bank_forks_root(&bank_forks); let ledger_path = get_tmp_ledger_path!(); @@ -1708,7 +1708,7 @@ pub(crate) mod tests { } = create_genesis_config(10_000); let bank = Bank::new_for_tests(&genesis_config); let rent_exempt_amount = bank.get_minimum_balance_for_rent_exemption(0); - let bank_forks = Arc::new(RwLock::new(BankForks::new(bank))); + let bank_forks = BankForks::new_rw_arc(bank); let optimistically_confirmed_bank = OptimisticallyConfirmedBank::locked_from_bank_forks_root(&bank_forks); let ledger_path = get_tmp_ledger_path!(); @@ -1826,7 +1826,7 @@ pub(crate) mod tests { } = create_genesis_config(100); let bank = Bank::new_for_tests(&genesis_config); let blockhash = bank.last_blockhash(); - let bank_forks = Arc::new(RwLock::new(BankForks::new(bank))); + let bank_forks = BankForks::new_rw_arc(bank); let alice = Keypair::new(); let tx = system_transaction::create_account( &mint_keypair, @@ -1837,7 +1837,7 @@ pub(crate) mod tests { &stake::program::id(), ); bank_forks - .write() + .read() .unwrap() .get(0) .unwrap() @@ -1938,7 +1938,7 @@ pub(crate) mod tests { bank.lazy_rent_collection.store(true, Relaxed); let blockhash = bank.last_blockhash(); - let bank_forks = Arc::new(RwLock::new(BankForks::new(bank))); + let bank_forks = BankForks::new_rw_arc(bank); let bank0 = bank_forks.read().unwrap().get(0).unwrap(); let bank1 = Bank::new_from_parent(bank0, &Pubkey::default(), 1); @@ -2133,7 +2133,7 @@ pub(crate) mod tests { bank.lazy_rent_collection.store(true, Relaxed); let blockhash = bank.last_blockhash(); - let bank_forks = Arc::new(RwLock::new(BankForks::new(bank))); + let bank_forks = BankForks::new_rw_arc(bank); let bank0 = bank_forks.read().unwrap().get(0).unwrap(); let bank1 = Bank::new_from_parent(bank0, &Pubkey::default(), 1); @@ -2249,7 +2249,7 @@ pub(crate) mod tests { bank.lazy_rent_collection.store(true, Relaxed); let blockhash = bank.last_blockhash(); - let bank_forks = Arc::new(RwLock::new(BankForks::new(bank))); + let bank_forks = BankForks::new_rw_arc(bank); let bank0 = bank_forks.read().unwrap().get(0).unwrap(); let bank1 = Bank::new_from_parent(bank0, &Pubkey::default(), 1); @@ -2435,7 +2435,7 @@ pub(crate) mod tests { } = create_genesis_config(100); let bank = Bank::new_for_tests(&genesis_config); let blockhash = bank.last_blockhash(); - let mut bank_forks = BankForks::new(bank); + let bank_forks = BankForks::new_rw_arc(bank); let alice = Keypair::new(); let past_bank_tx = @@ -2446,26 +2446,28 @@ pub(crate) mod tests { system_transaction::transfer(&mint_keypair, &alice.pubkey(), 3, blockhash); bank_forks + .read() + .unwrap() .get(0) .unwrap() .process_transaction(&past_bank_tx) .unwrap(); let next_bank = Bank::new_from_parent( - bank_forks.get(0).unwrap(), + bank_forks.read().unwrap().get(0).unwrap(), &solana_sdk::pubkey::new_rand(), 1, ); - bank_forks.insert(next_bank); + bank_forks.write().unwrap().insert(next_bank); bank_forks + .read() + .unwrap() .get(1) .unwrap() .process_transaction(&processed_tx) .unwrap(); - let bank1 = bank_forks[1].clone(); - - let bank_forks = Arc::new(RwLock::new(bank_forks)); + let bank1 = bank_forks.read().unwrap().get(1).unwrap().clone(); let mut cache0 = BlockCommitment::default(); cache0.increase_confirmation_stake(1, 10); @@ -2659,7 +2661,7 @@ pub(crate) mod tests { let exit = Arc::new(AtomicBool::new(false)); let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(10_000); let bank = Bank::new_for_tests(&genesis_config); - let bank_forks = Arc::new(RwLock::new(BankForks::new(bank))); + let bank_forks = BankForks::new_rw_arc(bank); let optimistically_confirmed_bank = OptimisticallyConfirmedBank::locked_from_bank_forks_root(&bank_forks); let max_complete_transaction_status_slot = Arc::new(AtomicU64::default()); @@ -2706,7 +2708,7 @@ pub(crate) mod tests { let exit = Arc::new(AtomicBool::new(false)); let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(10_000); let bank = Bank::new_for_tests(&genesis_config); - let bank_forks = Arc::new(RwLock::new(BankForks::new(bank))); + let bank_forks = BankForks::new_rw_arc(bank); let optimistically_confirmed_bank = OptimisticallyConfirmedBank::locked_from_bank_forks_root(&bank_forks); let max_complete_transaction_status_slot = Arc::new(AtomicU64::default()); @@ -2755,7 +2757,7 @@ pub(crate) mod tests { } = create_genesis_config(100); let bank = Bank::new_for_tests(&genesis_config); let blockhash = bank.last_blockhash(); - let bank_forks = Arc::new(RwLock::new(BankForks::new(bank))); + let bank_forks = BankForks::new_rw_arc(bank); let bank0 = bank_forks.read().unwrap().get(0).unwrap(); let bank1 = Bank::new_from_parent(bank0.clone(), &Pubkey::default(), 1); bank_forks.write().unwrap().insert(bank1); @@ -2815,7 +2817,7 @@ pub(crate) mod tests { // Add the same transaction to the unfrozen 2nd bank bank_forks - .write() + .read() .unwrap() .get(2) .unwrap() @@ -2969,7 +2971,7 @@ pub(crate) mod tests { } = create_genesis_config(100); let bank = Bank::new_for_tests(&genesis_config); let blockhash = bank.last_blockhash(); - let bank_forks = Arc::new(RwLock::new(BankForks::new(bank))); + let bank_forks = BankForks::new_rw_arc(bank); let alice = Keypair::new(); @@ -3053,7 +3055,7 @@ pub(crate) mod tests { let bank = Bank::new_for_tests(&genesis_config); let max_complete_transaction_status_slot = Arc::new(AtomicU64::default()); let max_complete_rewards_slot = Arc::new(AtomicU64::default()); - let bank_forks = Arc::new(RwLock::new(BankForks::new(bank))); + let bank_forks = BankForks::new_rw_arc(bank); let subscriptions = Arc::new(RpcSubscriptions::default_with_bank_forks( max_complete_transaction_status_slot, max_complete_rewards_slot, diff --git a/runtime/benches/accounts.rs b/runtime/benches/accounts.rs index fcd784a21e1..afb75300685 100644 --- a/runtime/benches/accounts.rs +++ b/runtime/benches/accounts.rs @@ -42,7 +42,7 @@ fn deposit_many(bank: &Bank, pubkeys: &mut Vec, num: usize) -> Result<() AccountSharedData::new((t + 1) as u64, 0, AccountSharedData::default().owner()); pubkeys.push(pubkey); assert!(bank.get_account(&pubkey).is_none()); - bank.deposit(&pubkey, (t + 1) as u64)?; + test_utils::deposit(bank, &pubkey, (t + 1) as u64)?; assert_eq!(bank.get_account(&pubkey).unwrap(), account); } Ok(()) @@ -80,7 +80,7 @@ fn test_accounts_squash(bencher: &mut Bencher) { &Pubkey::default(), slot, )); - next_bank.deposit(&pubkeys[0], 1).unwrap(); + test_utils::deposit(&next_bank, &pubkeys[0], 1).unwrap(); next_bank.squash(); slot += 1; prev_bank = next_bank; diff --git a/runtime/benches/bank.rs b/runtime/benches/bank.rs index fc8dfbd4a4e..024df0d086c 100644 --- a/runtime/benches/bank.rs +++ b/runtime/benches/bank.rs @@ -125,13 +125,13 @@ fn do_bench_transactions( // freeze bank so that slot hashes is populated bank.freeze(); - declare_process_instruction!(process_instruction, 1, |_invoke_context| { + declare_process_instruction!(MockBuiltin, 1, |_invoke_context| { // Do nothing Ok(()) }); let mut bank = Bank::new_from_parent(Arc::new(bank), &Pubkey::default(), 1); - bank.add_mockup_builtin(Pubkey::from(BUILTIN_PROGRAM_ID), process_instruction); + bank.add_mockup_builtin(Pubkey::from(BUILTIN_PROGRAM_ID), MockBuiltin::vm); bank.add_builtin_account("solana_noop_program", &Pubkey::from(NOOP_PROGRAM_ID), false); let bank = Arc::new(bank); let bank_client = BankClient::new_shared(bank.clone()); diff --git a/runtime/benches/prioritization_fee_cache.rs b/runtime/benches/prioritization_fee_cache.rs index 95efc8cefd2..506aac4fb72 100644 --- a/runtime/benches/prioritization_fee_cache.rs +++ b/runtime/benches/prioritization_fee_cache.rs @@ -100,8 +100,8 @@ fn bench_process_transactions_multiple_slots(bencher: &mut Bencher) { let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(10_000); let bank0 = Bank::new_for_benches(&genesis_config); - let bank_forks = BankForks::new(bank0); - let bank = bank_forks.working_bank(); + let bank_forks = BankForks::new_rw_arc(bank0); + let bank = bank_forks.read().unwrap().working_bank(); let collector = solana_sdk::pubkey::new_rand(); let banks = (1..=NUM_SLOTS) .map(|n| Arc::new(Bank::new_from_parent(bank.clone(), &collector, n as u64))) diff --git a/runtime/src/accounts_background_service.rs b/runtime/src/accounts_background_service.rs index 627ccbf76ad..09f3758ab34 100644 --- a/runtime/src/accounts_background_service.rs +++ b/runtime/src/accounts_background_service.rs @@ -21,7 +21,7 @@ use { solana_accounts_db::{ accounts_db::CalcAccountsHashDataSource, accounts_hash::CalcAccountsHashConfig, }, - solana_measure::measure::Measure, + solana_measure::{measure::Measure, measure_us}, solana_sdk::clock::{BankId, Slot}, stats::StatsManager, std::{ @@ -383,6 +383,8 @@ impl SnapshotRequestHandler { snapshot_root_bank.clean_accounts(*last_full_snapshot_slot); clean_time.stop(); + let (_, shrink_ancient_time_us) = measure_us!(snapshot_root_bank.shrink_ancient_slots()); + let mut shrink_time = Measure::start("shrink_time"); snapshot_root_bank.shrink_candidate_slots(); shrink_time.stop(); @@ -464,6 +466,7 @@ impl SnapshotRequestHandler { ("snapshot_time", snapshot_time.as_us(), i64), ("total_us", total_time.as_us(), i64), ("non_snapshot_time_us", non_snapshot_time_us, i64), + ("shrink_ancient_time_us", shrink_ancient_time_us, i64), ); Ok(snapshot_root_bank.block_height()) } @@ -705,8 +708,21 @@ impl AccountsBackgroundService { bank.force_flush_accounts_cache(); bank.clean_accounts(last_full_snapshot_slot); last_cleaned_block_height = bank.block_height(); + // See justification below for why we skip 'shrink' here. + if bank.is_startup_verification_complete() { + bank.shrink_ancient_slots(); + } + } + // Do not 'shrink' until *after* the startup verification is complete. + // This is because startup verification needs to get the snapshot + // storages *as they existed at startup* (to calculate the accounts hash). + // If 'shrink' were to run, then it is possible startup verification + // (1) could race with 'shrink', and fail to assert that shrinking is not in + // progress, or (2) could get snapshot storages that were newer than what + // was in the snapshot itself. + if bank.is_startup_verification_complete() { + bank.shrink_candidate_slots(); } - bank.shrink_candidate_slots(); } stats.record_and_maybe_submit(start_time.elapsed()); sleep(Duration::from_millis(INTERVAL_MS)); diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index adc9da3a4b0..9a3c25b4b8e 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -39,6 +39,7 @@ pub use solana_sdk::reward_type::RewardType; use { crate::{ bank::metrics::*, + bank_forks::BankForks, builtins::{BuiltinPrototype, BUILTINS}, epoch_rewards_hasher::hash_rewards_into_partitions, epoch_stakes::{EpochStakes, NodeVoteAccounts}, @@ -67,7 +68,6 @@ use { }, solana_accounts_db::{ account_overrides::AccountOverrides, - account_rent_state::RentState, accounts::{ AccountAddressFilter, Accounts, LoadedTransaction, PubkeyAccountSlot, RewardInterval, TransactionLoadResult, @@ -100,12 +100,13 @@ use { }, solana_bpf_loader_program::syscalls::create_program_runtime_environment_v1, solana_cost_model::cost_tracker::CostTracker, + solana_loader_v4_program::create_program_runtime_environment_v2, solana_measure::{measure, measure::Measure, measure_us}, solana_perf::perf_libs, solana_program_runtime::{ accounts_data_meter::MAX_ACCOUNTS_DATA_LEN, compute_budget::{self, ComputeBudget}, - invoke_context::ProcessInstructionWithContext, + invoke_context::BuiltinFunctionWithContext, loaded_programs::{ LoadProgramMetrics, LoadedProgram, LoadedProgramMatchCriteria, LoadedProgramType, LoadedPrograms, LoadedProgramsForTxBatch, WorkingSlot, DELAY_VISIBILITY_SLOT_OFFSET, @@ -127,7 +128,8 @@ use { BankId, Epoch, Slot, SlotCount, SlotIndex, UnixTimestamp, DEFAULT_HASHES_PER_TICK, DEFAULT_TICKS_PER_SECOND, INITIAL_RENT_EPOCH, MAX_PROCESSING_AGE, MAX_TRANSACTION_FORWARDING_DELAY, MAX_TRANSACTION_FORWARDING_DELAY_GPU, - SECONDS_PER_DAY, + SECONDS_PER_DAY, UPDATED_HASHES_PER_TICK2, UPDATED_HASHES_PER_TICK3, + UPDATED_HASHES_PER_TICK4, UPDATED_HASHES_PER_TICK5, UPDATED_HASHES_PER_TICK6, }, epoch_info::EpochInfo, epoch_schedule::EpochSchedule, @@ -147,7 +149,6 @@ use { incinerator, inflation::Inflation, instruction::InstructionError, - lamports::LamportsError, loader_v4::{self, LoaderV4State, LoaderV4Status}, message::{AccountKeys, SanitizedMessage}, native_loader, @@ -183,7 +184,7 @@ use { borrow::Cow, cell::RefCell, collections::{HashMap, HashSet}, - convert::{TryFrom, TryInto}, + convert::TryFrom, fmt, mem, ops::{AddAssign, RangeInclusive}, path::PathBuf, @@ -214,6 +215,7 @@ mod address_lookup_table; pub mod bank_hash_details; mod builtin_programs; pub mod epoch_accounts_hash_utils; +mod fee_distribution; mod metrics; mod serde_snapshot; mod sysvar_cache; @@ -275,7 +277,6 @@ pub struct BankRc { #[cfg(RUSTC_WITH_SPECIALIZATION)] use solana_frozen_abi::abi_example::AbiExample; -use solana_program_runtime::loaded_programs::ExtractedPrograms; #[cfg(RUSTC_WITH_SPECIALIZATION)] impl AbiExample for BankRc { @@ -818,7 +819,7 @@ pub struct Bank { pub incremental_snapshot_persistence: Option, - pub loaded_programs_cache: Arc>, + pub loaded_programs_cache: Arc>>, pub check_program_modification_slot: bool, @@ -931,11 +932,14 @@ impl WorkingSlot for Bank { self.slot } + fn current_epoch(&self) -> Epoch { + self.epoch + } + fn is_ancestor(&self, other: Slot) -> bool { self.ancestors.contains_key(&other) } } - #[derive(Debug, Default)] /// result of calculating the stake rewards at end of epoch struct StakeRewardCalculation { @@ -1064,7 +1068,10 @@ impl Bank { accounts_data_size_delta_on_chain: AtomicI64::new(0), accounts_data_size_delta_off_chain: AtomicI64::new(0), fee_structure: FeeStructure::default(), - loaded_programs_cache: Arc::>::default(), + loaded_programs_cache: Arc::new(RwLock::new(LoadedPrograms::new( + Slot::default(), + Epoch::default(), + ))), check_program_modification_slot: false, epoch_reward_status: EpochRewardStatus::default(), }; @@ -1430,11 +1437,10 @@ impl Bank { }); // Following code may touch AccountsDb, requiring proper ancestors - let parent_epoch = parent.epoch(); let (_, update_epoch_time_us) = measure_us!({ - if parent_epoch < new.epoch() { + if parent.epoch() < new.epoch() { new.process_new_epoch( - parent_epoch, + parent.epoch(), parent.slot(), parent.block_height(), reward_calc_tracer, @@ -1449,11 +1455,71 @@ impl Bank { } }); + let (_, recompilation_time_us) = measure_us!({ + // Recompile loaded programs one at a time before the next epoch hits + let (_epoch, slot_index) = new.get_epoch_and_slot_index(new.slot()); + let slots_in_epoch = new.get_slots_in_epoch(new.epoch()); + let slots_in_recompilation_phase = + (solana_program_runtime::loaded_programs::MAX_LOADED_ENTRY_COUNT as u64) + .min(slots_in_epoch) + .checked_div(2) + .unwrap(); + let mut loaded_programs_cache = new.loaded_programs_cache.write().unwrap(); + if loaded_programs_cache.upcoming_environments.is_some() { + if let Some((key, program_to_recompile)) = + loaded_programs_cache.programs_to_recompile.pop() + { + drop(loaded_programs_cache); + let recompiled = new.load_program(&key, false, Some(program_to_recompile)); + let mut loaded_programs_cache = new.loaded_programs_cache.write().unwrap(); + loaded_programs_cache.replenish(key, recompiled); + } + } else if new.epoch() != loaded_programs_cache.latest_root_epoch + || slot_index.saturating_add(slots_in_recompilation_phase) >= slots_in_epoch + { + // Anticipate the upcoming program runtime environment for the next epoch, + // so we can try to recompile loaded programs before the feature transition hits. + drop(loaded_programs_cache); + let (feature_set, _new_feature_activations) = new.compute_active_feature_set(true); + let mut loaded_programs_cache = new.loaded_programs_cache.write().unwrap(); + let program_runtime_environment_v1 = create_program_runtime_environment_v1( + &feature_set, + &new.runtime_config.compute_budget.unwrap_or_default(), + false, /* deployment */ + false, /* debugging_features */ + ) + .unwrap(); + let program_runtime_environment_v2 = create_program_runtime_environment_v2( + &new.runtime_config.compute_budget.unwrap_or_default(), + false, /* debugging_features */ + ); + let mut upcoming_environments = loaded_programs_cache.environments.clone(); + let changed_program_runtime_v1 = + *upcoming_environments.program_runtime_v1 != program_runtime_environment_v1; + let changed_program_runtime_v2 = + *upcoming_environments.program_runtime_v2 != program_runtime_environment_v2; + if changed_program_runtime_v1 { + upcoming_environments.program_runtime_v1 = + Arc::new(program_runtime_environment_v1); + } + if changed_program_runtime_v2 { + upcoming_environments.program_runtime_v2 = + Arc::new(program_runtime_environment_v2); + } + loaded_programs_cache.upcoming_environments = Some(upcoming_environments); + loaded_programs_cache.programs_to_recompile = loaded_programs_cache + .get_entries_sorted_by_tx_usage( + changed_program_runtime_v1, + changed_program_runtime_v2, + ); + } + }); + // Update sysvars before processing transactions let (_, update_sysvars_time_us) = measure_us!({ new.update_slot_hashes(); - new.update_stake_history(Some(parent_epoch)); - new.update_clock(Some(parent_epoch)); + new.update_stake_history(Some(parent.epoch())); + new.update_clock(Some(parent.epoch())); new.update_fees(); new.update_last_restart_slot() }); @@ -1481,6 +1547,7 @@ impl Bank { feature_set_time_us, ancestors_time_us, update_epoch_time_us, + recompilation_time_us, update_sysvars_time_us, fill_sysvar_cache_time_us, }, @@ -1853,7 +1920,10 @@ impl Bank { accounts_data_size_delta_on_chain: AtomicI64::new(0), accounts_data_size_delta_off_chain: AtomicI64::new(0), fee_structure: FeeStructure::default(), - loaded_programs_cache: Arc::>::default(), + loaded_programs_cache: Arc::new(RwLock::new(LoadedPrograms::new( + fields.slot, + fields.epoch, + ))), check_program_modification_slot: false, epoch_reward_status: EpochRewardStatus::default(), }; @@ -3671,62 +3741,6 @@ impl Bank { stake_weighted_timestamp } - // Distribute collected transaction fees for this slot to collector_id (= current leader). - // - // Each validator is incentivized to process more transactions to earn more transaction fees. - // Transaction fees are rewarded for the computing resource utilization cost, directly - // proportional to their actual processing power. - // - // collector_id is rotated according to stake-weighted leader schedule. So the opportunity of - // earning transaction fees are fairly distributed by stake. And missing the opportunity - // (not producing a block as a leader) earns nothing. So, being online is incentivized as a - // form of transaction fees as well. - // - // On the other hand, rent fees are distributed under slightly different philosophy, while - // still being stake-weighted. - // Ref: distribute_rent_to_validators - fn collect_fees(&self) { - let collector_fees = self.collector_fees.load(Relaxed); - - if collector_fees != 0 { - let (deposit, mut burn) = self.fee_rate_governor.burn(collector_fees); - // burn a portion of fees - debug!( - "distributed fee: {} (rounded from: {}, burned: {})", - deposit, collector_fees, burn - ); - - match self.deposit(&self.collector_id, deposit) { - Ok(post_balance) => { - if deposit != 0 { - self.rewards.write().unwrap().push(( - self.collector_id, - RewardInfo { - reward_type: RewardType::Fee, - lamports: deposit as i64, - post_balance, - commission: None, - }, - )); - } - } - Err(_) => { - error!( - "Burning {} fee instead of crediting {}", - deposit, self.collector_id - ); - datapoint_error!( - "bank-burned_fee", - ("slot", self.slot(), i64), - ("num_lamports", deposit, i64) - ); - burn += deposit; - } - } - self.capitalization.fetch_sub(burn, Relaxed); - } - } - pub fn rehash(&self) { let mut hash = self.hash.write().unwrap(); let new = self.hash_internal_state(); @@ -3752,8 +3766,8 @@ impl Bank { if *hash == Hash::default() { // finish up any deferred changes to account state self.collect_rent_eagerly(); - self.collect_fees(); - self.distribute_rent(); + self.distribute_transaction_fees(); + self.distribute_rent_fees(); self.update_slot_history(); self.run_incinerator(); @@ -3834,6 +3848,16 @@ impl Bank { // Bootstrap validator collects fees until `new_from_parent` is called. self.fee_rate_governor = genesis_config.fee_rate_governor.clone(); + // Make sure to activate the account_hash_ignore_slot feature + // before calculating any account hashes. + if genesis_config + .accounts + .iter() + .any(|(pubkey, _)| pubkey == &feature_set::account_hash_ignore_slot::id()) + { + self.activate_feature(&feature_set::account_hash_ignore_slot::id()); + } + for (pubkey, account) in genesis_config.accounts.iter() { assert!( self.get_account(pubkey).is_none(), @@ -3856,12 +3880,14 @@ impl Bank { self.accounts_data_size_initial += account.data().len() as u64; } - // highest staked node is the first collector + // Highest staked node is the first collector but if a genesis config + // doesn't define any staked nodes, we assume this genesis config is for + // testing and set the collector id to a unique pubkey. self.collector_id = self .stakes_cache .stakes() .highest_staked_node() - .unwrap_or_default(); + .unwrap_or_else(Pubkey::new_unique); self.blockhash_queue.write().unwrap().genesis_hash( &genesis_config.hash(), @@ -4352,14 +4378,13 @@ impl Bank { &mut timings, Some(&account_overrides), None, + true, ); let post_simulation_accounts = loaded_transactions .into_iter() .next() - .unwrap() - .0 - .ok() + .and_then(|(loaded_transactions_res, _)| loaded_transactions_res.ok()) .map(|loaded_transaction| { loaded_transaction .accounts @@ -4379,7 +4404,12 @@ impl Bank { debug!("simulate_transaction: {:?}", timings); - let execution_result = execution_results.pop().unwrap(); + let execution_result = + execution_results + .pop() + .unwrap_or(TransactionExecutionResult::NotExecuted( + TransactionError::InvalidProgramForExecution, + )); let flattened_result = execution_result.flattened_result(); let (logs, return_data) = match execution_result { TransactionExecutionResult::Executed { details, .. } => { @@ -4659,20 +4689,25 @@ impl Bank { ProgramAccountLoadResult::InvalidAccountData } - pub fn load_program(&self, pubkey: &Pubkey, reload: bool) -> Arc { - let environments = self - .loaded_programs_cache - .read() - .unwrap() - .environments - .clone(); - + pub fn load_program( + &self, + pubkey: &Pubkey, + reload: bool, + recompile: Option>, + ) -> Arc { + let loaded_programs_cache = self.loaded_programs_cache.read().unwrap(); + let effective_epoch = if recompile.is_some() { + loaded_programs_cache.latest_root_epoch.saturating_add(1) + } else { + self.epoch + }; + let environments = loaded_programs_cache.get_environments_for_epoch(effective_epoch); let mut load_program_metrics = LoadProgramMetrics { program_id: pubkey.to_string(), ..LoadProgramMetrics::default() }; - let loaded_program = match self.load_program_accounts(pubkey) { + let mut loaded_program = match self.load_program_accounts(pubkey) { ProgramAccountLoadResult::AccountNotFound => Ok(LoadedProgram::new_tombstone( self.slot, LoadedProgramType::Closed, @@ -4758,25 +4793,37 @@ impl Bank { }) .unwrap_or(LoadedProgram::new_tombstone( self.slot, - LoadedProgramType::FailedVerification(environments.program_runtime_v2), + LoadedProgramType::FailedVerification( + environments.program_runtime_v2.clone(), + ), )); Ok(loaded_program) } ProgramAccountLoadResult::InvalidV4Program => Ok(LoadedProgram::new_tombstone( self.slot, - LoadedProgramType::FailedVerification(environments.program_runtime_v2), + LoadedProgramType::FailedVerification(environments.program_runtime_v2.clone()), )), } .unwrap_or_else(|_| { LoadedProgram::new_tombstone( self.slot, - LoadedProgramType::FailedVerification(environments.program_runtime_v1), + LoadedProgramType::FailedVerification(environments.program_runtime_v1.clone()), ) }); let mut timings = ExecuteDetailsTimings::default(); load_program_metrics.submit_datapoint(&mut timings); + if let Some(recompile) = recompile { + loaded_program.effective_slot = loaded_program.effective_slot.max( + self.epoch_schedule() + .get_first_slot_in_epoch(effective_epoch), + ); + loaded_program.tx_usage_counter = + AtomicU64::new(recompile.tx_usage_counter.load(Ordering::Relaxed)); + loaded_program.ix_usage_counter = + AtomicU64::new(recompile.ix_usage_counter.load(Ordering::Relaxed)); + } Arc::new(loaded_program) } @@ -4948,7 +4995,7 @@ impl Bank { let ExecutionRecord { accounts, - mut return_data, + return_data, touched_account_count, accounts_resize_delta, } = transaction_context.into(); @@ -4978,14 +5025,8 @@ impl Bank { accounts_data_len_delta = status.as_ref().map_or(0, |_| accounts_resize_delta); } - let return_data = if enable_return_data_recording { - if let Some(end_index) = return_data.data.iter().rposition(|&x| x != 0) { - let end_index = end_index.saturating_add(1); - return_data.data.truncate(end_index); - Some(return_data) - } else { - None - } + let return_data = if enable_return_data_recording && !return_data.data.is_empty() { + Some(return_data) } else { None }; @@ -5010,8 +5051,9 @@ impl Bank { fn replenish_program_cache( &self, program_accounts_map: &HashMap, + limit_to_load_programs: bool, ) -> LoadedProgramsForTxBatch { - let programs_and_slots: Vec<(Pubkey, (LoadedProgramMatchCriteria, u64))> = + let mut missing_programs: Vec<(Pubkey, (LoadedProgramMatchCriteria, u64))> = if self.check_program_modification_slot { program_accounts_map .iter() @@ -5037,53 +5079,66 @@ impl Bank { .collect() }; - let ExtractedPrograms { - loaded: mut loaded_programs_for_txs, - missing, - unloaded, - } = { - // Lock the global cache to figure out which programs need to be loaded - let loaded_programs_cache = self.loaded_programs_cache.read().unwrap(); - loaded_programs_cache.extract(self, programs_and_slots.into_iter()) - }; - - // Load missing programs while global cache is unlocked - let missing_programs: Vec<(Pubkey, Arc)> = missing - .iter() - .map(|(key, count)| { - let program = self.load_program(key, false); - program.tx_usage_counter.store(*count, Ordering::Relaxed); - (*key, program) - }) - .collect(); - - // Reload unloaded programs while global cache is unlocked - let unloaded_programs: Vec<(Pubkey, Arc)> = unloaded - .iter() - .map(|(key, count)| { - let program = self.load_program(key, true); - program.tx_usage_counter.store(*count, Ordering::Relaxed); - (*key, program) - }) - .collect(); + let mut loaded_programs_for_txs = None; + let mut program_to_store = None; + loop { + let (program_to_load, task_cookie, task_waiter) = { + // Lock the global cache. + let mut loaded_programs_cache = self.loaded_programs_cache.write().unwrap(); + // Initialize our local cache. + let is_first_round = loaded_programs_for_txs.is_none(); + if is_first_round { + loaded_programs_for_txs = Some(LoadedProgramsForTxBatch::new( + self.slot, + loaded_programs_cache + .get_environments_for_epoch(self.epoch) + .clone(), + )); + } + // Submit our last completed loading task. + if let Some((key, program)) = program_to_store.take() { + if loaded_programs_cache.finish_cooperative_loading_task( + self.slot(), + key, + program, + ) && limit_to_load_programs + { + let mut ret = LoadedProgramsForTxBatch::default(); + ret.hit_max_limit = true; + return ret; + } + } + // Figure out which program needs to be loaded next. + let program_to_load = loaded_programs_cache.extract( + self, + &mut missing_programs, + loaded_programs_for_txs.as_mut().unwrap(), + is_first_round, + ); + let task_waiter = Arc::clone(&loaded_programs_cache.loading_task_waiter); + (program_to_load, task_waiter.cookie(), task_waiter) + // Unlock the global cache again. + }; - // Lock the global cache again to replenish the missing programs - let mut loaded_programs_cache = self.loaded_programs_cache.write().unwrap(); - for (key, program) in missing_programs { - let (_was_occupied, entry) = loaded_programs_cache.replenish(key, program); - // Use the returned entry as that might have been deduplicated globally - loaded_programs_for_txs.replenish(key, entry); - } - for (key, program) in unloaded_programs { - let (_was_occupied, entry) = loaded_programs_cache.replenish(key, program); - // Use the returned entry as that might have been deduplicated globally - loaded_programs_for_txs.replenish(key, entry); + if let Some((key, count)) = program_to_load { + // Load, verify and compile one program. + let program = self.load_program(&key, false, None); + program.tx_usage_counter.store(count, Ordering::Relaxed); + program_to_store = Some((key, program)); + } else if missing_programs.is_empty() { + break; + } else { + // Sleep until the next finish_cooperative_loading_task() call. + // Once a task completes we'll wake up and try to load the + // missing programs inside the tx batch again. + let _new_cookie = task_waiter.wait(task_cookie); + } } - loaded_programs_for_txs + loaded_programs_for_txs.unwrap() } - #[allow(clippy::type_complexity)] + #[allow(clippy::too_many_arguments, clippy::type_complexity)] pub fn load_and_execute_transactions( &self, batch: &TransactionBatch, @@ -5094,6 +5149,7 @@ impl Bank { timings: &mut ExecuteTimings, account_overrides: Option<&AccountOverrides>, log_messages_bytes_limit: Option, + limit_to_load_programs: bool, ) -> LoadAndExecuteTransactionsOutput { let sanitized_txs = batch.sanitized_transactions(); debug!("processing transactions: {}", sanitized_txs.len()); @@ -5163,9 +5219,22 @@ impl Bank { } let programs_loaded_for_tx_batch = Rc::new(RefCell::new( - self.replenish_program_cache(&program_accounts_map), + self.replenish_program_cache(&program_accounts_map, limit_to_load_programs), )); + if programs_loaded_for_tx_batch.borrow().hit_max_limit { + return LoadAndExecuteTransactionsOutput { + loaded_transactions: vec![], + execution_results: vec![], + retryable_transaction_indexes: vec![], + executed_transactions_count: 0, + executed_non_vote_transactions_count: 0, + executed_with_successful_result_count: 0, + signature_count: 0, + error_counters, + }; + } + let mut load_time = Measure::start("accounts_load"); let mut loaded_transactions = self.rc.accounts.load_accounts( &self.ancestors, @@ -5667,183 +5736,6 @@ impl Bank { } } - // Distribute collected rent fees for this slot to staked validators (excluding stakers) - // according to stake. - // - // The nature of rent fee is the cost of doing business, every validator has to hold (or have - // access to) the same list of accounts, so we pay according to stake, which is a rough proxy for - // value to the network. - // - // Currently, rent distribution doesn't consider given validator's uptime at all (this might - // change). That's because rent should be rewarded for the storage resource utilization cost. - // It's treated differently from transaction fees, which is for the computing resource - // utilization cost. - // - // We can't use collector_id (which is rotated according to stake-weighted leader schedule) - // as an approximation to the ideal rent distribution to simplify and avoid this per-slot - // computation for the distribution (time: N log N, space: N acct. stores; N = # of - // validators). - // The reason is that rent fee doesn't need to be incentivized for throughput unlike transaction - // fees - // - // Ref: collect_fees - #[allow(clippy::needless_collect)] - fn distribute_rent_to_validators( - &self, - vote_accounts: &VoteAccountsHashMap, - rent_to_be_distributed: u64, - ) { - let mut total_staked = 0; - - // Collect the stake associated with each validator. - // Note that a validator may be present in this vector multiple times if it happens to have - // more than one staked vote account somehow - let mut validator_stakes = vote_accounts - .iter() - .filter_map(|(_vote_pubkey, (staked, account))| { - if *staked == 0 { - None - } else { - total_staked += *staked; - Some((account.node_pubkey()?, *staked)) - } - }) - .collect::>(); - - #[cfg(test)] - if validator_stakes.is_empty() { - // some tests bank.freezes() with bad staking state - self.capitalization - .fetch_sub(rent_to_be_distributed, Relaxed); - return; - } - #[cfg(not(test))] - assert!(!validator_stakes.is_empty()); - - // Sort first by stake and then by validator identity pubkey for determinism. - // If two items are still equal, their relative order does not matter since - // both refer to the same validator. - validator_stakes.sort_unstable_by(|(pubkey1, staked1), (pubkey2, staked2)| { - (staked1, pubkey1).cmp(&(staked2, pubkey2)).reverse() - }); - - let enforce_fix = self.no_overflow_rent_distribution_enabled(); - - let mut rent_distributed_in_initial_round = 0; - let validator_rent_shares = validator_stakes - .into_iter() - .map(|(pubkey, staked)| { - let rent_share = if !enforce_fix { - (((staked * rent_to_be_distributed) as f64) / (total_staked as f64)) as u64 - } else { - (((staked as u128) * (rent_to_be_distributed as u128)) / (total_staked as u128)) - .try_into() - .unwrap() - }; - rent_distributed_in_initial_round += rent_share; - (pubkey, rent_share) - }) - .collect::>(); - - // Leftover lamports after fraction calculation, will be paid to validators starting from highest stake - // holder - let mut leftover_lamports = rent_to_be_distributed - rent_distributed_in_initial_round; - - let mut rewards = vec![]; - validator_rent_shares - .into_iter() - .for_each(|(pubkey, rent_share)| { - let rent_to_be_paid = if leftover_lamports > 0 { - leftover_lamports -= 1; - rent_share + 1 - } else { - rent_share - }; - if !enforce_fix || rent_to_be_paid > 0 { - let mut account = self - .get_account_with_fixed_root(&pubkey) - .unwrap_or_default(); - let rent = self.rent_collector().rent; - let recipient_pre_rent_state = RentState::from_account(&account, &rent); - let distribution = account.checked_add_lamports(rent_to_be_paid); - let recipient_post_rent_state = RentState::from_account(&account, &rent); - let rent_state_transition_allowed = recipient_post_rent_state - .transition_allowed_from(&recipient_pre_rent_state); - if !rent_state_transition_allowed { - warn!( - "Rent distribution of {rent_to_be_paid} to {pubkey} results in \ - invalid RentState: {recipient_post_rent_state:?}" - ); - datapoint_warn!( - "bank-rent_distribution_invalid_state", - ("slot", self.slot(), i64), - ("pubkey", pubkey.to_string(), String), - ("rent_to_be_paid", rent_to_be_paid, i64) - ); - } - if distribution.is_err() - || (self.prevent_rent_paying_rent_recipients() - && !rent_state_transition_allowed) - { - // overflow adding lamports or resulting account is not rent-exempt - self.capitalization.fetch_sub(rent_to_be_paid, Relaxed); - error!( - "Burned {} rent lamports instead of sending to {}", - rent_to_be_paid, pubkey - ); - datapoint_error!( - "bank-burned_rent", - ("slot", self.slot(), i64), - ("num_lamports", rent_to_be_paid, i64) - ); - } else { - self.store_account(&pubkey, &account); - rewards.push(( - pubkey, - RewardInfo { - reward_type: RewardType::Rent, - lamports: rent_to_be_paid as i64, - post_balance: account.lamports(), - commission: None, - }, - )); - } - } - }); - self.rewards.write().unwrap().append(&mut rewards); - - if enforce_fix { - assert_eq!(leftover_lamports, 0); - } else if leftover_lamports != 0 { - warn!( - "There was leftover from rent distribution: {}", - leftover_lamports - ); - self.capitalization.fetch_sub(leftover_lamports, Relaxed); - } - } - - fn distribute_rent(&self) { - let total_rent_collected = self.collected_rent.load(Relaxed); - - let (burned_portion, rent_to_be_distributed) = self - .rent_collector - .rent - .calculate_burn(total_rent_collected); - - debug!( - "distributed rent: {} (rounded from: {}, burned: {})", - rent_to_be_distributed, total_rent_collected, burned_portion - ); - self.capitalization.fetch_sub(burned_portion, Relaxed); - - if rent_to_be_distributed == 0 { - return; - } - - self.distribute_rent_to_validators(&self.vote_accounts(), rent_to_be_distributed); - } - fn collect_rent( &self, execution_results: &[TransactionExecutionResult], @@ -6441,6 +6333,7 @@ impl Bank { timings, None, log_messages_bytes_limit, + false, ); let (last_blockhash, lamports_per_signature) = @@ -6752,19 +6645,6 @@ impl Bank { } } - pub fn deposit( - &self, - pubkey: &Pubkey, - lamports: u64, - ) -> std::result::Result { - // This doesn't collect rents intentionally. - // Rents should only be applied to actual TXes - let mut account = self.get_account_with_fixed_root(pubkey).unwrap_or_default(); - account.checked_add_lamports(lamports)?; - self.store_account(pubkey, &account); - Ok(account.lamports()) - } - pub fn accounts(&self) -> Arc { self.rc.accounts.clone() } @@ -6803,6 +6683,24 @@ impl Bank { } } + let mut loaded_programs_cache = self.loaded_programs_cache.write().unwrap(); + loaded_programs_cache.latest_root_slot = self.slot(); + loaded_programs_cache.latest_root_epoch = self.epoch(); + loaded_programs_cache.environments.program_runtime_v1 = Arc::new( + create_program_runtime_environment_v1( + &self.feature_set, + &self.runtime_config.compute_budget.unwrap_or_default(), + false, /* deployment */ + false, /* debugging_features */ + ) + .unwrap(), + ); + loaded_programs_cache.environments.program_runtime_v2 = + Arc::new(create_program_runtime_environment_v2( + &self.runtime_config.compute_budget.unwrap_or_default(), + false, /* debugging_features */ + )); + if self .feature_set .is_active(&feature_set::cap_accounts_data_len::id()) @@ -7610,12 +7508,13 @@ impl Bank { pub fn verify_snapshot_bank( &self, test_hash_calculation: bool, - accounts_db_skip_shrink: bool, + skip_shrink: bool, + force_clean: bool, last_full_snapshot_slot: Slot, base: Option<(Slot, /*capitalization*/ u64)>, ) -> bool { let (_, clean_time_us) = measure_us!({ - let should_clean = !accounts_db_skip_shrink && self.slot() > 0; + let should_clean = force_clean || (!skip_shrink && self.slot() > 0); if should_clean { info!("Cleaning..."); // We cannot clean past the last full snapshot's slot because we are about to @@ -7635,7 +7534,7 @@ impl Bank { }); let (_, shrink_time_us) = measure_us!({ - let should_shrink = !accounts_db_skip_shrink && self.slot() > 0; + let should_shrink = !skip_shrink && self.slot() > 0; if should_shrink { info!("Shrinking..."); self.rc.accounts.accounts_db.shrink_all_slots( @@ -7884,12 +7783,12 @@ impl Bank { pub fn add_mockup_builtin( &mut self, program_id: Pubkey, - entrypoint: ProcessInstructionWithContext, + builtin_function: BuiltinFunctionWithContext, ) { self.add_builtin( program_id, "mockup".to_string(), - LoadedProgram::new_builtin(self.slot, 0, entrypoint), + LoadedProgram::new_builtin(self.slot, 0, builtin_function), ); } @@ -7956,6 +7855,13 @@ impl Bank { .shrink_candidate_slots(self.epoch_schedule()) } + pub(crate) fn shrink_ancient_slots(&self) { + self.rc + .accounts + .accounts_db + .shrink_ancient_slots(self.epoch_schedule()) + } + pub fn no_overflow_rent_distribution_enabled(&self) -> bool { self.feature_set .is_active(&feature_set::no_overflow_rent_distribution::id()) @@ -7966,6 +7872,11 @@ impl Bank { .is_active(&feature_set::prevent_rent_paying_rent_recipients::id()) } + pub fn validate_fee_collector_account(&self) -> bool { + self.feature_set + .is_active(&feature_set::validate_fee_collector_account::id()) + } + pub fn read_cost_tracker(&self) -> LockResult> { self.cost_tracker.read() } @@ -8026,6 +7937,19 @@ impl Bank { self.compute_active_feature_set(allow_new_activations); self.feature_set = Arc::new(feature_set); + // Update activation slot of features in `new_feature_activations` + for feature_id in new_feature_activations.iter() { + if let Some(mut account) = self.get_account_with_fixed_root(feature_id) { + if let Some(mut feature) = feature::from_account(&account) { + feature.activated_at = Some(self.slot()); + if feature::to_account(&feature, &mut account).is_some() { + self.store_account(feature_id, &account); + } + info!("Feature {} activated at slot {}", feature_id, self.slot()); + } + } + } + if new_feature_activations.contains(&feature_set::pico_inflation::id()) { *self.inflation.write().unwrap() = Inflation::pico(); self.fee_rate_governor.burn_percent = 50; // 50% fee burn @@ -8054,6 +7978,26 @@ impl Bank { if new_feature_activations.contains(&feature_set::update_hashes_per_tick::id()) { self.apply_updated_hashes_per_tick(DEFAULT_HASHES_PER_TICK); } + + if new_feature_activations.contains(&feature_set::update_hashes_per_tick2::id()) { + self.apply_updated_hashes_per_tick(UPDATED_HASHES_PER_TICK2); + } + + if new_feature_activations.contains(&feature_set::update_hashes_per_tick3::id()) { + self.apply_updated_hashes_per_tick(UPDATED_HASHES_PER_TICK3); + } + + if new_feature_activations.contains(&feature_set::update_hashes_per_tick4::id()) { + self.apply_updated_hashes_per_tick(UPDATED_HASHES_PER_TICK4); + } + + if new_feature_activations.contains(&feature_set::update_hashes_per_tick5::id()) { + self.apply_updated_hashes_per_tick(UPDATED_HASHES_PER_TICK5); + } + + if new_feature_activations.contains(&feature_set::update_hashes_per_tick6::id()) { + self.apply_updated_hashes_per_tick(UPDATED_HASHES_PER_TICK6); + } } fn apply_updated_hashes_per_tick(&mut self, hashes_per_tick: u64) { @@ -8074,38 +8018,27 @@ impl Bank { /// Compute the active feature set based on the current bank state, /// and return it together with the set of newly activated features. - fn compute_active_feature_set( - &mut self, - allow_new_activations: bool, - ) -> (FeatureSet, HashSet) { + fn compute_active_feature_set(&self, include_pending: bool) -> (FeatureSet, HashSet) { let mut active = self.feature_set.active.clone(); let mut inactive = HashSet::new(); - let mut newly_activated = HashSet::new(); + let mut pending = HashSet::new(); let slot = self.slot(); for feature_id in &self.feature_set.inactive { let mut activated = None; - if let Some(mut account) = self.get_account_with_fixed_root(feature_id) { - if let Some(mut feature) = feature::from_account(&account) { + if let Some(account) = self.get_account_with_fixed_root(feature_id) { + if let Some(feature) = feature::from_account(&account) { match feature.activated_at { - None => { - if allow_new_activations { - // Feature has been requested, activate it now - feature.activated_at = Some(slot); - if feature::to_account(&feature, &mut account).is_some() { - self.store_account(feature_id, &account); - } - newly_activated.insert(*feature_id); - activated = Some(slot); - info!("Feature {} activated at slot {}", feature_id, slot); - } + None if include_pending => { + // Feature activation is pending + pending.insert(*feature_id); + activated = Some(slot); } - Some(activation_slot) => { - if slot >= activation_slot { - // Feature is already active - activated = Some(activation_slot); - } + Some(activation_slot) if slot >= activation_slot => { + // Feature has been activated already + activated = Some(activation_slot); } + _ => {} } } } @@ -8116,7 +8049,7 @@ impl Bank { } } - (FeatureSet { active, inactive }, newly_activated) + (FeatureSet { active, inactive }, pending) } fn apply_builtin_program_feature_transitions( @@ -8124,47 +8057,6 @@ impl Bank { only_apply_transitions_for_new_features: bool, new_feature_activations: &HashSet, ) { - const FEATURES_AFFECTING_RBPF: &[Pubkey] = &[ - feature_set::error_on_syscall_bpf_function_hash_collisions::id(), - feature_set::reject_callx_r10::id(), - feature_set::switch_to_new_elf_parser::id(), - feature_set::bpf_account_data_direct_mapping::id(), - feature_set::enable_alt_bn128_syscall::id(), - feature_set::enable_alt_bn128_compression_syscall::id(), - feature_set::enable_big_mod_exp_syscall::id(), - feature_set::blake3_syscall_enabled::id(), - feature_set::curve25519_syscall_enabled::id(), - feature_set::disable_fees_sysvar::id(), - feature_set::enable_partitioned_epoch_reward::id(), - feature_set::disable_deploy_of_alloc_free_syscall::id(), - feature_set::last_restart_slot_sysvar::id(), - feature_set::delay_visibility_of_program_deployment::id(), - feature_set::remaining_compute_units_syscall_enabled::id(), - ]; - if !only_apply_transitions_for_new_features - || FEATURES_AFFECTING_RBPF - .iter() - .any(|key| new_feature_activations.contains(key)) - { - let program_runtime_environment_v1 = create_program_runtime_environment_v1( - &self.feature_set, - &self.runtime_config.compute_budget.unwrap_or_default(), - false, /* deployment */ - false, /* debugging_features */ - ) - .unwrap(); - let mut loaded_programs_cache = self.loaded_programs_cache.write().unwrap(); - loaded_programs_cache.environments.program_runtime_v1 = - Arc::new(program_runtime_environment_v1); - let program_runtime_environment_v2 = - solana_loader_v4_program::create_program_runtime_environment_v2( - &self.runtime_config.compute_budget.unwrap_or_default(), - false, /* debugging_features */ - ); - loaded_programs_cache.environments.program_runtime_v2 = - Arc::new(program_runtime_environment_v2); - loaded_programs_cache.prune_feature_set_transition(); - } for builtin in BUILTINS.iter() { if let Some(feature_id) = builtin.feature_id { let should_apply_action_for_feature_transition = @@ -8504,7 +8396,12 @@ impl Drop for Bank { pub mod test_utils { use { super::Bank, - solana_sdk::{hash::hashv, pubkey::Pubkey}, + solana_sdk::{ + account::{ReadableAccount, WritableAccount}, + hash::hashv, + lamports::LamportsError, + pubkey::Pubkey, + }, solana_vote_program::vote_state::{self, BlockTimestamp, VoteStateVersions}, }; pub fn goto_end_of_slot(bank: &Bank) { @@ -8531,4 +8428,17 @@ pub mod test_utils { vote_state::to(&versioned, &mut vote_account).unwrap(); bank.store_account(vote_pubkey, &vote_account); } + + pub fn deposit( + bank: &Bank, + pubkey: &Pubkey, + lamports: u64, + ) -> std::result::Result { + // This doesn't collect rents intentionally. + // Rents should only be applied to actual TXes + let mut account = bank.get_account_with_fixed_root(pubkey).unwrap_or_default(); + account.checked_add_lamports(lamports)?; + bank.store_account(pubkey, &account); + Ok(account.lamports()) + } } diff --git a/runtime/src/bank/bank_hash_details.rs b/runtime/src/bank/bank_hash_details.rs index d4ea3f65651..aa251d2c49c 100644 --- a/runtime/src/bank/bank_hash_details.rs +++ b/runtime/src/bank/bank_hash_details.rs @@ -213,14 +213,10 @@ pub fn write_bank_hash_details_file(bank: &Bank) -> std::result::Result<(), Stri // path does not exist. So, call std::fs_create_dir_all first. // https://doc.rust-lang.org/std/fs/fn.write.html _ = std::fs::create_dir_all(parent_dir); - let file = std::fs::File::create(&path).map_err(|err| { - format!( - "Unable to create bank hash file at {}: {err}", - path.display() - ) - })?; + let file = std::fs::File::create(&path) + .map_err(|err| format!("Unable to create file at {}: {err}", path.display()))?; serde_json::to_writer_pretty(file, &details) - .map_err(|err| format!("Unable to write bank hash file contents: {err}"))?; + .map_err(|err| format!("Unable to write file at {}: {err}", path.display()))?; } Ok(()) } diff --git a/runtime/src/bank/fee_distribution.rs b/runtime/src/bank/fee_distribution.rs new file mode 100644 index 00000000000..e1d251c0bf4 --- /dev/null +++ b/runtime/src/bank/fee_distribution.rs @@ -0,0 +1,908 @@ +use { + super::Bank, + log::{debug, warn}, + solana_accounts_db::{account_rent_state::RentState, stake_rewards::RewardInfo}, + solana_sdk::{ + account::{ReadableAccount, WritableAccount}, + pubkey::Pubkey, + reward_type::RewardType, + system_program, + }, + solana_vote::vote_account::VoteAccountsHashMap, + std::{result::Result, sync::atomic::Ordering::Relaxed}, + thiserror::Error, +}; + +#[derive(Debug)] +struct DepositFeeOptions { + check_account_owner: bool, + check_rent_paying: bool, +} + +#[derive(Error, Debug, PartialEq)] +enum DepositFeeError { + #[error("fee account became rent paying")] + InvalidRentPayingAccount, + #[error("lamport overflow")] + LamportOverflow, + #[error("invalid fee account owner")] + InvalidAccountOwner, +} + +impl Bank { + // Distribute collected transaction fees for this slot to collector_id (= current leader). + // + // Each validator is incentivized to process more transactions to earn more transaction fees. + // Transaction fees are rewarded for the computing resource utilization cost, directly + // proportional to their actual processing power. + // + // collector_id is rotated according to stake-weighted leader schedule. So the opportunity of + // earning transaction fees are fairly distributed by stake. And missing the opportunity + // (not producing a block as a leader) earns nothing. So, being online is incentivized as a + // form of transaction fees as well. + // + // On the other hand, rent fees are distributed under slightly different philosophy, while + // still being stake-weighted. + // Ref: distribute_rent_to_validators + pub(super) fn distribute_transaction_fees(&self) { + let collector_fees = self.collector_fees.load(Relaxed); + if collector_fees != 0 { + let (deposit, mut burn) = self.fee_rate_governor.burn(collector_fees); + if deposit > 0 { + let validate_fee_collector = self.validate_fee_collector_account(); + match self.deposit_fees( + &self.collector_id, + deposit, + DepositFeeOptions { + check_account_owner: validate_fee_collector, + check_rent_paying: validate_fee_collector, + }, + ) { + Ok(post_balance) => { + self.rewards.write().unwrap().push(( + self.collector_id, + RewardInfo { + reward_type: RewardType::Fee, + lamports: deposit as i64, + post_balance, + commission: None, + }, + )); + } + Err(err) => { + debug!( + "Burned {} lamport tx fee instead of sending to {} due to {}", + deposit, self.collector_id, err + ); + datapoint_warn!( + "bank-burned_fee", + ("slot", self.slot(), i64), + ("num_lamports", deposit, i64), + ("error", err.to_string(), String), + ); + burn += deposit; + } + } + } + self.capitalization.fetch_sub(burn, Relaxed); + } + } + + // Deposits fees into a specified account and if successful, returns the new balance of that account + fn deposit_fees( + &self, + pubkey: &Pubkey, + fees: u64, + options: DepositFeeOptions, + ) -> Result { + let mut account = self.get_account_with_fixed_root(pubkey).unwrap_or_default(); + + if options.check_account_owner && !system_program::check_id(account.owner()) { + return Err(DepositFeeError::InvalidAccountOwner); + } + + let rent = self.rent_collector().rent; + let recipient_pre_rent_state = RentState::from_account(&account, &rent); + let distribution = account.checked_add_lamports(fees); + if distribution.is_err() { + return Err(DepositFeeError::LamportOverflow); + } + if options.check_rent_paying { + let recipient_post_rent_state = RentState::from_account(&account, &rent); + let rent_state_transition_allowed = + recipient_post_rent_state.transition_allowed_from(&recipient_pre_rent_state); + if !rent_state_transition_allowed { + return Err(DepositFeeError::InvalidRentPayingAccount); + } + } + + self.store_account(pubkey, &account); + Ok(account.lamports()) + } + + // Distribute collected rent fees for this slot to staked validators (excluding stakers) + // according to stake. + // + // The nature of rent fee is the cost of doing business, every validator has to hold (or have + // access to) the same list of accounts, so we pay according to stake, which is a rough proxy for + // value to the network. + // + // Currently, rent distribution doesn't consider given validator's uptime at all (this might + // change). That's because rent should be rewarded for the storage resource utilization cost. + // It's treated differently from transaction fees, which is for the computing resource + // utilization cost. + // + // We can't use collector_id (which is rotated according to stake-weighted leader schedule) + // as an approximation to the ideal rent distribution to simplify and avoid this per-slot + // computation for the distribution (time: N log N, space: N acct. stores; N = # of + // validators). + // The reason is that rent fee doesn't need to be incentivized for throughput unlike transaction + // fees + // + // Ref: distribute_transaction_fees + #[allow(clippy::needless_collect)] + fn distribute_rent_to_validators( + &self, + vote_accounts: &VoteAccountsHashMap, + rent_to_be_distributed: u64, + ) { + let mut total_staked = 0; + + // Collect the stake associated with each validator. + // Note that a validator may be present in this vector multiple times if it happens to have + // more than one staked vote account somehow + let mut validator_stakes = vote_accounts + .iter() + .filter_map(|(_vote_pubkey, (staked, account))| { + if *staked == 0 { + None + } else { + total_staked += *staked; + Some((account.node_pubkey()?, *staked)) + } + }) + .collect::>(); + + #[cfg(test)] + if validator_stakes.is_empty() { + // some tests bank.freezes() with bad staking state + self.capitalization + .fetch_sub(rent_to_be_distributed, Relaxed); + return; + } + #[cfg(not(test))] + assert!(!validator_stakes.is_empty()); + + // Sort first by stake and then by validator identity pubkey for determinism. + // If two items are still equal, their relative order does not matter since + // both refer to the same validator. + validator_stakes.sort_unstable_by(|(pubkey1, staked1), (pubkey2, staked2)| { + (staked1, pubkey1).cmp(&(staked2, pubkey2)).reverse() + }); + + let enforce_fix = self.no_overflow_rent_distribution_enabled(); + + let mut rent_distributed_in_initial_round = 0; + let validator_rent_shares = validator_stakes + .into_iter() + .map(|(pubkey, staked)| { + let rent_share = if !enforce_fix { + (((staked * rent_to_be_distributed) as f64) / (total_staked as f64)) as u64 + } else { + (((staked as u128) * (rent_to_be_distributed as u128)) / (total_staked as u128)) + .try_into() + .unwrap() + }; + rent_distributed_in_initial_round += rent_share; + (pubkey, rent_share) + }) + .collect::>(); + + // Leftover lamports after fraction calculation, will be paid to validators starting from highest stake + // holder + let mut leftover_lamports = rent_to_be_distributed - rent_distributed_in_initial_round; + + let mut rent_to_burn: u64 = 0; + let mut rewards = vec![]; + validator_rent_shares + .into_iter() + .for_each(|(pubkey, rent_share)| { + let rent_to_be_paid = if leftover_lamports > 0 { + leftover_lamports -= 1; + rent_share + 1 + } else { + rent_share + }; + if !enforce_fix || rent_to_be_paid > 0 { + let check_account_owner = self.validate_fee_collector_account(); + let check_rent_paying = self.prevent_rent_paying_rent_recipients(); + match self.deposit_fees( + &pubkey, + rent_to_be_paid, + DepositFeeOptions { + check_account_owner, + check_rent_paying, + }, + ) { + Ok(post_balance) => { + rewards.push(( + pubkey, + RewardInfo { + reward_type: RewardType::Rent, + lamports: rent_to_be_paid as i64, + post_balance, + commission: None, + }, + )); + } + Err(err) => { + debug!( + "Burned {} lamport rent fee instead of sending to {} due to {}", + rent_to_be_paid, pubkey, err + ); + + // overflow adding lamports or resulting account is invalid + // so burn lamports and track lamports burned per slot + rent_to_burn = rent_to_burn.saturating_add(rent_to_be_paid); + } + } + } + }); + self.rewards.write().unwrap().append(&mut rewards); + + if rent_to_burn > 0 { + self.capitalization.fetch_sub(rent_to_burn, Relaxed); + datapoint_warn!( + "bank-burned_rent", + ("slot", self.slot(), i64), + ("num_lamports", rent_to_burn, i64) + ); + } + + if enforce_fix { + assert_eq!(leftover_lamports, 0); + } else if leftover_lamports != 0 { + warn!( + "There was leftover from rent distribution: {}", + leftover_lamports + ); + self.capitalization.fetch_sub(leftover_lamports, Relaxed); + } + } + + pub(super) fn distribute_rent_fees(&self) { + let total_rent_collected = self.collected_rent.load(Relaxed); + + let (burned_portion, rent_to_be_distributed) = self + .rent_collector + .rent + .calculate_burn(total_rent_collected); + + debug!( + "distributed rent: {} (rounded from: {}, burned: {})", + rent_to_be_distributed, total_rent_collected, burned_portion + ); + self.capitalization.fetch_sub(burned_portion, Relaxed); + + if rent_to_be_distributed == 0 { + return; + } + + self.distribute_rent_to_validators(&self.vote_accounts(), rent_to_be_distributed); + } +} + +#[cfg(test)] +pub mod tests { + use { + super::*, + crate::genesis_utils::{ + create_genesis_config, create_genesis_config_with_leader, + create_genesis_config_with_vote_accounts, ValidatorVoteKeypairs, + }, + log::info, + solana_sdk::{ + account::AccountSharedData, feature_set, native_token::sol_to_lamports, pubkey, + rent::Rent, signature::Signer, + }, + }; + + #[test] + fn test_distribute_transaction_fees() { + #[derive(PartialEq)] + enum Scenario { + Normal, + InvalidOwner, + RentPaying, + } + + struct TestCase { + scenario: Scenario, + disable_checks: bool, + } + + impl TestCase { + fn new(scenario: Scenario, disable_checks: bool) -> Self { + Self { + scenario, + disable_checks, + } + } + } + + for test_case in [ + TestCase::new(Scenario::Normal, false), + TestCase::new(Scenario::Normal, true), + TestCase::new(Scenario::InvalidOwner, false), + TestCase::new(Scenario::InvalidOwner, true), + TestCase::new(Scenario::RentPaying, false), + TestCase::new(Scenario::RentPaying, true), + ] { + let mut genesis = create_genesis_config(0); + if test_case.disable_checks { + genesis + .genesis_config + .accounts + .remove(&feature_set::validate_fee_collector_account::id()) + .unwrap(); + } + let rent = Rent::default(); + let min_rent_exempt_balance = rent.minimum_balance(0); + genesis.genesis_config.rent = rent; // Ensure rent is non-zero, as genesis_utils sets Rent::free by default + let bank = Bank::new_for_tests(&genesis.genesis_config); + let transaction_fees = 100; + bank.collector_fees.fetch_add(transaction_fees, Relaxed); + assert_eq!(transaction_fees, bank.collector_fees.load(Relaxed)); + let (expected_collected_fees, burn_amount) = + bank.fee_rate_governor.burn(transaction_fees); + assert!(burn_amount > 0); + + if test_case.scenario == Scenario::RentPaying { + // ensure that account balance + collected fees will make it rent-paying + let initial_balance = 100; + let account = AccountSharedData::new(initial_balance, 0, &system_program::id()); + bank.store_account(bank.collector_id(), &account); + assert!(initial_balance + transaction_fees < min_rent_exempt_balance); + } else if test_case.scenario == Scenario::InvalidOwner { + // ensure that account owner is invalid and fee distribution will fail + let account = + AccountSharedData::new(min_rent_exempt_balance, 0, &Pubkey::new_unique()); + bank.store_account(bank.collector_id(), &account); + } else { + let account = + AccountSharedData::new(min_rent_exempt_balance, 0, &system_program::id()); + bank.store_account(bank.collector_id(), &account); + } + + let initial_capitalization = bank.capitalization(); + let initial_collector_id_balance = bank.get_balance(bank.collector_id()); + bank.distribute_transaction_fees(); + let new_collector_id_balance = bank.get_balance(bank.collector_id()); + + if test_case.scenario != Scenario::Normal && !test_case.disable_checks { + assert_eq!(initial_collector_id_balance, new_collector_id_balance); + assert_eq!( + initial_capitalization - transaction_fees, + bank.capitalization() + ); + let locked_rewards = bank.rewards.read().unwrap(); + assert!( + locked_rewards.is_empty(), + "There should be no rewards distributed" + ); + } else { + assert_eq!( + initial_collector_id_balance + expected_collected_fees, + new_collector_id_balance + ); + + assert_eq!(initial_capitalization - burn_amount, bank.capitalization()); + + let locked_rewards = bank.rewards.read().unwrap(); + assert_eq!( + locked_rewards.len(), + 1, + "There should be one reward distributed" + ); + + let reward_info = &locked_rewards[0]; + assert_eq!( + reward_info.1.lamports, expected_collected_fees as i64, + "The reward amount should match the expected deposit" + ); + assert_eq!( + reward_info.1.reward_type, + RewardType::Fee, + "The reward type should be Fee" + ); + } + } + } + + #[test] + fn test_distribute_transaction_fees_zero() { + let genesis = create_genesis_config(0); + let bank = Bank::new_for_tests(&genesis.genesis_config); + assert_eq!(bank.collector_fees.load(Relaxed), 0); + + let initial_capitalization = bank.capitalization(); + let initial_collector_id_balance = bank.get_balance(bank.collector_id()); + bank.distribute_transaction_fees(); + let new_collector_id_balance = bank.get_balance(bank.collector_id()); + + assert_eq!(initial_collector_id_balance, new_collector_id_balance); + assert_eq!(initial_capitalization, bank.capitalization()); + let locked_rewards = bank.rewards.read().unwrap(); + assert!( + locked_rewards.is_empty(), + "There should be no rewards distributed" + ); + } + + #[test] + fn test_distribute_transaction_fees_burn_all() { + let mut genesis = create_genesis_config(0); + genesis.genesis_config.fee_rate_governor.burn_percent = 100; + let bank = Bank::new_for_tests(&genesis.genesis_config); + let transaction_fees = 100; + bank.collector_fees.fetch_add(transaction_fees, Relaxed); + assert_eq!(transaction_fees, bank.collector_fees.load(Relaxed)); + + let initial_capitalization = bank.capitalization(); + let initial_collector_id_balance = bank.get_balance(bank.collector_id()); + bank.distribute_transaction_fees(); + let new_collector_id_balance = bank.get_balance(bank.collector_id()); + + assert_eq!(initial_collector_id_balance, new_collector_id_balance); + assert_eq!( + initial_capitalization - transaction_fees, + bank.capitalization() + ); + let locked_rewards = bank.rewards.read().unwrap(); + assert!( + locked_rewards.is_empty(), + "There should be no rewards distributed" + ); + } + + #[test] + fn test_distribute_transaction_fees_overflow_failure() { + let genesis = create_genesis_config(0); + let bank = Bank::new_for_tests(&genesis.genesis_config); + let transaction_fees = 100; + bank.collector_fees.fetch_add(transaction_fees, Relaxed); + assert_eq!(transaction_fees, bank.collector_fees.load(Relaxed)); + + // ensure that account balance will overflow and fee distribution will fail + let account = AccountSharedData::new(u64::MAX, 0, &system_program::id()); + bank.store_account(bank.collector_id(), &account); + + let initial_capitalization = bank.capitalization(); + let initial_collector_id_balance = bank.get_balance(bank.collector_id()); + bank.distribute_transaction_fees(); + let new_collector_id_balance = bank.get_balance(bank.collector_id()); + + assert_eq!(initial_collector_id_balance, new_collector_id_balance); + assert_eq!( + initial_capitalization - transaction_fees, + bank.capitalization() + ); + let locked_rewards = bank.rewards.read().unwrap(); + assert!( + locked_rewards.is_empty(), + "There should be no rewards distributed" + ); + } + + #[test] + fn test_deposit_fees() { + let initial_balance = 1_000_000_000; + let genesis = create_genesis_config(initial_balance); + let bank = Bank::new_for_tests(&genesis.genesis_config); + let pubkey = genesis.mint_keypair.pubkey(); + + let deposit_amount = 500; + let options = DepositFeeOptions { + check_account_owner: true, + check_rent_paying: true, + }; + + assert_eq!( + bank.deposit_fees(&pubkey, deposit_amount, options), + Ok(initial_balance + deposit_amount), + "New balance should be the sum of the initial balance and deposit amount" + ); + } + + #[test] + fn test_deposit_fees_with_overflow() { + let initial_balance = u64::MAX; + let genesis = create_genesis_config(initial_balance); + let bank = Bank::new_for_tests(&genesis.genesis_config); + let pubkey = genesis.mint_keypair.pubkey(); + + let deposit_amount = 500; + let options = DepositFeeOptions { + check_account_owner: false, + check_rent_paying: false, + }; + + assert_eq!( + bank.deposit_fees(&pubkey, deposit_amount, options), + Err(DepositFeeError::LamportOverflow), + "Expected an error due to lamport overflow" + ); + } + + #[test] + fn test_deposit_fees_invalid_account_owner() { + let initial_balance = 1000; + let genesis = create_genesis_config_with_leader(0, &pubkey::new_rand(), initial_balance); + let bank = Bank::new_for_tests(&genesis.genesis_config); + let pubkey = genesis.voting_keypair.pubkey(); + + let deposit_amount = 500; + + // enable check_account_owner + { + let options = DepositFeeOptions { + check_account_owner: true, // Intentionally checking for account owner + check_rent_paying: false, + }; + + assert_eq!( + bank.deposit_fees(&pubkey, deposit_amount, options), + Err(DepositFeeError::InvalidAccountOwner), + "Expected an error due to invalid account owner" + ); + } + + // disable check_account_owner + { + let options = DepositFeeOptions { + check_account_owner: false, + check_rent_paying: false, + }; + + assert_eq!( + bank.deposit_fees(&pubkey, deposit_amount, options), + Ok(initial_balance + deposit_amount), + "New balance should be the sum of the initial balance and deposit amount" + ); + } + } + + #[test] + fn test_deposit_fees_invalid_rent_paying() { + let initial_balance = 0; + let genesis = create_genesis_config(initial_balance); + let pubkey = genesis.mint_keypair.pubkey(); + let mut genesis_config = genesis.genesis_config; + let rent = Rent::default(); + genesis_config.rent = rent; // Ensure rent is non-zero, as genesis_utils sets Rent::free by default + let bank = Bank::new_for_tests(&genesis_config); + let min_rent_exempt_balance = rent.minimum_balance(0); + + let deposit_amount = 500; + assert!(initial_balance + deposit_amount < min_rent_exempt_balance); + + // enable check_rent_paying + { + let options = DepositFeeOptions { + check_account_owner: false, + check_rent_paying: true, + }; + + assert_eq!( + bank.deposit_fees(&pubkey, deposit_amount, options), + Err(DepositFeeError::InvalidRentPayingAccount), + "Expected an error due to invalid rent paying account" + ); + } + + // disable check_rent_paying + { + let options = DepositFeeOptions { + check_account_owner: false, + check_rent_paying: false, + }; + + assert_eq!( + bank.deposit_fees(&pubkey, deposit_amount, options), + Ok(initial_balance + deposit_amount), + "New balance should be the sum of the initial balance and deposit amount" + ); + } + } + + #[test] + fn test_distribute_rent_to_validators_overflow() { + solana_logger::setup(); + + // These values are taken from the real cluster (testnet) + const RENT_TO_BE_DISTRIBUTED: u64 = 120_525; + const VALIDATOR_STAKE: u64 = 374_999_998_287_840; + + let validator_pubkey = solana_sdk::pubkey::new_rand(); + let mut genesis_config = + create_genesis_config_with_leader(10, &validator_pubkey, VALIDATOR_STAKE) + .genesis_config; + + let bank = Bank::new_for_tests(&genesis_config); + let old_validator_lamports = bank.get_balance(&validator_pubkey); + bank.distribute_rent_to_validators(&bank.vote_accounts(), RENT_TO_BE_DISTRIBUTED); + let new_validator_lamports = bank.get_balance(&validator_pubkey); + assert_eq!( + new_validator_lamports, + old_validator_lamports + RENT_TO_BE_DISTRIBUTED + ); + + genesis_config + .accounts + .remove(&feature_set::no_overflow_rent_distribution::id()) + .unwrap(); + let bank = std::panic::AssertUnwindSafe(Bank::new_for_tests(&genesis_config)); + let old_validator_lamports = bank.get_balance(&validator_pubkey); + let new_validator_lamports = std::panic::catch_unwind(|| { + bank.distribute_rent_to_validators(&bank.vote_accounts(), RENT_TO_BE_DISTRIBUTED); + bank.get_balance(&validator_pubkey) + }); + + if let Ok(new_validator_lamports) = new_validator_lamports { + info!("asserting overflowing incorrect rent distribution"); + assert_ne!( + new_validator_lamports, + old_validator_lamports + RENT_TO_BE_DISTRIBUTED + ); + } else { + info!("NOT-asserting overflowing incorrect rent distribution"); + } + } + + #[test] + fn test_distribute_rent_to_validators_rent_paying() { + solana_logger::setup(); + + const RENT_PER_VALIDATOR: u64 = 55; + const TOTAL_RENT: u64 = RENT_PER_VALIDATOR * 4; + + let empty_validator = ValidatorVoteKeypairs::new_rand(); + let rent_paying_validator = ValidatorVoteKeypairs::new_rand(); + let becomes_rent_exempt_validator = ValidatorVoteKeypairs::new_rand(); + let rent_exempt_validator = ValidatorVoteKeypairs::new_rand(); + let keypairs = vec![ + &empty_validator, + &rent_paying_validator, + &becomes_rent_exempt_validator, + &rent_exempt_validator, + ]; + let genesis_config_info = create_genesis_config_with_vote_accounts( + sol_to_lamports(1000.), + &keypairs, + vec![sol_to_lamports(1000.); 4], + ); + let mut genesis_config = genesis_config_info.genesis_config; + genesis_config.rent = Rent::default(); // Ensure rent is non-zero, as genesis_utils sets Rent::free by default + + for deactivate_feature in [false, true] { + if deactivate_feature { + genesis_config + .accounts + .remove(&feature_set::prevent_rent_paying_rent_recipients::id()) + .unwrap(); + } + let bank = Bank::new_for_tests(&genesis_config); + let rent = bank.rent_collector().rent; + let rent_exempt_minimum = rent.minimum_balance(0); + + // Make one validator have an empty identity account + let mut empty_validator_account = bank + .get_account_with_fixed_root(&empty_validator.node_keypair.pubkey()) + .unwrap(); + empty_validator_account.set_lamports(0); + bank.store_account( + &empty_validator.node_keypair.pubkey(), + &empty_validator_account, + ); + + // Make one validator almost rent-exempt, less RENT_PER_VALIDATOR + let mut becomes_rent_exempt_validator_account = bank + .get_account_with_fixed_root(&becomes_rent_exempt_validator.node_keypair.pubkey()) + .unwrap(); + becomes_rent_exempt_validator_account + .set_lamports(rent_exempt_minimum - RENT_PER_VALIDATOR); + bank.store_account( + &becomes_rent_exempt_validator.node_keypair.pubkey(), + &becomes_rent_exempt_validator_account, + ); + + // Make one validator rent-exempt + let mut rent_exempt_validator_account = bank + .get_account_with_fixed_root(&rent_exempt_validator.node_keypair.pubkey()) + .unwrap(); + rent_exempt_validator_account.set_lamports(rent_exempt_minimum); + bank.store_account( + &rent_exempt_validator.node_keypair.pubkey(), + &rent_exempt_validator_account, + ); + + let get_rent_state = |bank: &Bank, address: &Pubkey| -> RentState { + let account = bank + .get_account_with_fixed_root(address) + .unwrap_or_default(); + RentState::from_account(&account, &rent) + }; + + // Assert starting RentStates + assert_eq!( + get_rent_state(&bank, &empty_validator.node_keypair.pubkey()), + RentState::Uninitialized + ); + assert_eq!( + get_rent_state(&bank, &rent_paying_validator.node_keypair.pubkey()), + RentState::RentPaying { + lamports: 42, + data_size: 0, + } + ); + assert_eq!( + get_rent_state(&bank, &becomes_rent_exempt_validator.node_keypair.pubkey()), + RentState::RentPaying { + lamports: rent_exempt_minimum - RENT_PER_VALIDATOR, + data_size: 0, + } + ); + assert_eq!( + get_rent_state(&bank, &rent_exempt_validator.node_keypair.pubkey()), + RentState::RentExempt + ); + + let old_empty_validator_lamports = + bank.get_balance(&empty_validator.node_keypair.pubkey()); + let old_rent_paying_validator_lamports = + bank.get_balance(&rent_paying_validator.node_keypair.pubkey()); + let old_becomes_rent_exempt_validator_lamports = + bank.get_balance(&becomes_rent_exempt_validator.node_keypair.pubkey()); + let old_rent_exempt_validator_lamports = + bank.get_balance(&rent_exempt_validator.node_keypair.pubkey()); + + bank.distribute_rent_to_validators(&bank.vote_accounts(), TOTAL_RENT); + + let new_empty_validator_lamports = + bank.get_balance(&empty_validator.node_keypair.pubkey()); + let new_rent_paying_validator_lamports = + bank.get_balance(&rent_paying_validator.node_keypair.pubkey()); + let new_becomes_rent_exempt_validator_lamports = + bank.get_balance(&becomes_rent_exempt_validator.node_keypair.pubkey()); + let new_rent_exempt_validator_lamports = + bank.get_balance(&rent_exempt_validator.node_keypair.pubkey()); + + // Assert ending balances; rent should be withheld if test is active and ending RentState + // is RentPaying, ie. empty_validator and rent_paying_validator + assert_eq!( + if deactivate_feature { + old_empty_validator_lamports + RENT_PER_VALIDATOR + } else { + old_empty_validator_lamports + }, + new_empty_validator_lamports + ); + + assert_eq!( + if deactivate_feature { + old_rent_paying_validator_lamports + RENT_PER_VALIDATOR + } else { + old_rent_paying_validator_lamports + }, + new_rent_paying_validator_lamports + ); + + assert_eq!( + old_becomes_rent_exempt_validator_lamports + RENT_PER_VALIDATOR, + new_becomes_rent_exempt_validator_lamports + ); + + assert_eq!( + old_rent_exempt_validator_lamports + RENT_PER_VALIDATOR, + new_rent_exempt_validator_lamports + ); + + // Assert ending RentStates + assert_eq!( + if deactivate_feature { + RentState::RentPaying { + lamports: RENT_PER_VALIDATOR, + data_size: 0, + } + } else { + RentState::Uninitialized + }, + get_rent_state(&bank, &empty_validator.node_keypair.pubkey()), + ); + assert_eq!( + if deactivate_feature { + RentState::RentPaying { + lamports: old_rent_paying_validator_lamports + RENT_PER_VALIDATOR, + data_size: 0, + } + } else { + RentState::RentPaying { + lamports: old_rent_paying_validator_lamports, + data_size: 0, + } + }, + get_rent_state(&bank, &rent_paying_validator.node_keypair.pubkey()), + ); + assert_eq!( + RentState::RentExempt, + get_rent_state(&bank, &becomes_rent_exempt_validator.node_keypair.pubkey()), + ); + assert_eq!( + RentState::RentExempt, + get_rent_state(&bank, &rent_exempt_validator.node_keypair.pubkey()), + ); + } + } + + #[test] + fn test_distribute_rent_to_validators_invalid_owner() { + struct TestCase { + disable_owner_check: bool, + use_invalid_owner: bool, + } + + impl TestCase { + fn new(disable_owner_check: bool, use_invalid_owner: bool) -> Self { + Self { + disable_owner_check, + use_invalid_owner, + } + } + } + + for test_case in [ + TestCase::new(false, false), + TestCase::new(false, true), + TestCase::new(true, false), + TestCase::new(true, true), + ] { + let genesis_config_info = + create_genesis_config_with_leader(0, &Pubkey::new_unique(), 100); + let mut genesis_config = genesis_config_info.genesis_config; + genesis_config.rent = Rent::default(); // Ensure rent is non-zero, as genesis_utils sets Rent::free by default + + if test_case.disable_owner_check { + genesis_config + .accounts + .remove(&feature_set::validate_fee_collector_account::id()) + .unwrap(); + } + let bank = Bank::new_for_tests(&genesis_config); + + let initial_balance = 1_000_000; + let account_owner = if test_case.use_invalid_owner { + Pubkey::new_unique() + } else { + system_program::id() + }; + let account = AccountSharedData::new(initial_balance, 0, &account_owner); + bank.store_account(bank.collector_id(), &account); + + let initial_capitalization = bank.capitalization(); + let rent_fees = 100; + bank.distribute_rent_to_validators(&bank.vote_accounts(), rent_fees); + let new_capitalization = bank.capitalization(); + let new_balance = bank.get_balance(bank.collector_id()); + + if test_case.use_invalid_owner && !test_case.disable_owner_check { + assert_eq!(initial_balance, new_balance); + assert_eq!(initial_capitalization - rent_fees, new_capitalization); + assert_eq!(bank.rewards.read().unwrap().len(), 0); + } else { + assert_eq!(initial_balance + rent_fees, new_balance); + assert_eq!(initial_capitalization, new_capitalization); + assert_eq!(bank.rewards.read().unwrap().len(), 1); + } + } + } +} diff --git a/runtime/src/bank/metrics.rs b/runtime/src/bank/metrics.rs index 1fa33b2e7f9..ccf8c483776 100644 --- a/runtime/src/bank/metrics.rs +++ b/runtime/src/bank/metrics.rs @@ -39,6 +39,7 @@ pub(crate) struct NewBankTimings { pub(crate) feature_set_time_us: u64, pub(crate) ancestors_time_us: u64, pub(crate) update_epoch_time_us: u64, + pub(crate) recompilation_time_us: u64, pub(crate) update_sysvars_time_us: u64, pub(crate) fill_sysvar_cache_time_us: u64, } @@ -144,6 +145,7 @@ pub(crate) fn report_new_bank_metrics( ("feature_set_us", timings.feature_set_time_us, i64), ("ancestors_us", timings.ancestors_time_us, i64), ("update_epoch_us", timings.update_epoch_time_us, i64), + ("recompilation_time_us", timings.recompilation_time_us, i64), ("update_sysvars_us", timings.update_sysvars_time_us, i64), ( "fill_sysvar_cache_us", diff --git a/runtime/src/bank/serde_snapshot.rs b/runtime/src/bank/serde_snapshot.rs index 2e0bdd3ecc7..ce8fac9eb74 100644 --- a/runtime/src/bank/serde_snapshot.rs +++ b/runtime/src/bank/serde_snapshot.rs @@ -3,8 +3,8 @@ mod tests { use { crate::{ bank::{ - epoch_accounts_hash_utils, Bank, BankTestConfig, EpochRewardStatus, - StartBlockHeightAndRewards, + epoch_accounts_hash_utils, test_utils as bank_test_utils, Bank, BankTestConfig, + EpochRewardStatus, StartBlockHeightAndRewards, }, genesis_utils::{activate_all_features, activate_feature}, runtime_config::RuntimeConfig, @@ -109,7 +109,7 @@ mod tests { // Create an account on a non-root fork let key1 = Keypair::new(); - bank1.deposit(&key1.pubkey(), 5).unwrap(); + bank_test_utils::deposit(&bank1, &key1.pubkey(), 5).unwrap(); // If setting an initial EAH, then the bank being snapshotted must be in the EAH calculation // window. Otherwise `bank_to_stream()` below will *not* include the EAH in the bank snapshot, @@ -123,11 +123,11 @@ mod tests { // Test new account let key2 = Keypair::new(); - bank2.deposit(&key2.pubkey(), 10).unwrap(); + bank_test_utils::deposit(&bank2, &key2.pubkey(), 10).unwrap(); assert_eq!(bank2.get_balance(&key2.pubkey()), 10); let key3 = Keypair::new(); - bank2.deposit(&key3.pubkey(), 0).unwrap(); + bank_test_utils::deposit(&bank2, &key3.pubkey(), 0).unwrap(); bank2.freeze(); bank2.squash(); @@ -496,6 +496,7 @@ mod tests { false, false, false, + false, Some(solana_accounts_db::accounts_db::ACCOUNTS_DB_CONFIG_FOR_TESTING), None, Arc::default(), diff --git a/runtime/src/bank/tests.rs b/runtime/src/bank/tests.rs index 3263eb9c41d..74334184952 100644 --- a/runtime/src/bank/tests.rs +++ b/runtime/src/bank/tests.rs @@ -9,6 +9,7 @@ use { crate::{ accounts_background_service::{PrunedBanksRequestHandler, SendDroppedBankCallback}, bank_client::BankClient, + bank_forks::BankForks, epoch_rewards_hasher::hash_rewards_into_partitions, genesis_utils::{ self, activate_all_features, activate_feature, bootstrap_validator_stake_lamports, @@ -58,6 +59,8 @@ use { clock::{ BankId, Epoch, Slot, UnixTimestamp, DEFAULT_HASHES_PER_TICK, DEFAULT_SLOTS_PER_EPOCH, DEFAULT_TICKS_PER_SLOT, INITIAL_RENT_EPOCH, MAX_PROCESSING_AGE, MAX_RECENT_BLOCKHASHES, + UPDATED_HASHES_PER_TICK2, UPDATED_HASHES_PER_TICK3, UPDATED_HASHES_PER_TICK4, + UPDATED_HASHES_PER_TICK5, UPDATED_HASHES_PER_TICK6, }, compute_budget::ComputeBudgetInstruction, entrypoint::MAX_PERMITTED_DATA_INCREASE, @@ -644,7 +647,7 @@ fn assert_capitalization_diff( } } -declare_process_instruction!(process_instruction, 1, |_invoke_context| { +declare_process_instruction!(MockBuiltin, 1, |_invoke_context| { // Default for all tests which don't bring their own processor Ok(()) }); @@ -970,232 +973,6 @@ fn test_rent_distribution() { ); } -#[test] -fn test_distribute_rent_to_validators_overflow() { - solana_logger::setup(); - - // These values are taken from the real cluster (testnet) - const RENT_TO_BE_DISTRIBUTED: u64 = 120_525; - const VALIDATOR_STAKE: u64 = 374_999_998_287_840; - - let validator_pubkey = solana_sdk::pubkey::new_rand(); - let mut genesis_config = - create_genesis_config_with_leader(10, &validator_pubkey, VALIDATOR_STAKE).genesis_config; - - let bank = Bank::new_for_tests(&genesis_config); - let old_validator_lamports = bank.get_balance(&validator_pubkey); - bank.distribute_rent_to_validators(&bank.vote_accounts(), RENT_TO_BE_DISTRIBUTED); - let new_validator_lamports = bank.get_balance(&validator_pubkey); - assert_eq!( - new_validator_lamports, - old_validator_lamports + RENT_TO_BE_DISTRIBUTED - ); - - genesis_config - .accounts - .remove(&feature_set::no_overflow_rent_distribution::id()) - .unwrap(); - let bank = std::panic::AssertUnwindSafe(Bank::new_for_tests(&genesis_config)); - let old_validator_lamports = bank.get_balance(&validator_pubkey); - let new_validator_lamports = std::panic::catch_unwind(|| { - bank.distribute_rent_to_validators(&bank.vote_accounts(), RENT_TO_BE_DISTRIBUTED); - bank.get_balance(&validator_pubkey) - }); - - if let Ok(new_validator_lamports) = new_validator_lamports { - info!("asserting overflowing incorrect rent distribution"); - assert_ne!( - new_validator_lamports, - old_validator_lamports + RENT_TO_BE_DISTRIBUTED - ); - } else { - info!("NOT-asserting overflowing incorrect rent distribution"); - } -} - -#[test] -fn test_distribute_rent_to_validators_rent_paying() { - solana_logger::setup(); - - const RENT_PER_VALIDATOR: u64 = 55; - const TOTAL_RENT: u64 = RENT_PER_VALIDATOR * 4; - - let empty_validator = ValidatorVoteKeypairs::new_rand(); - let rent_paying_validator = ValidatorVoteKeypairs::new_rand(); - let becomes_rent_exempt_validator = ValidatorVoteKeypairs::new_rand(); - let rent_exempt_validator = ValidatorVoteKeypairs::new_rand(); - let keypairs = vec![ - &empty_validator, - &rent_paying_validator, - &becomes_rent_exempt_validator, - &rent_exempt_validator, - ]; - let genesis_config_info = create_genesis_config_with_vote_accounts( - sol_to_lamports(1000.), - &keypairs, - vec![sol_to_lamports(1000.); 4], - ); - let mut genesis_config = genesis_config_info.genesis_config; - genesis_config.rent = Rent::default(); // Ensure rent is non-zero, as genesis_utils sets Rent::free by default - - for deactivate_feature in [false, true] { - if deactivate_feature { - genesis_config - .accounts - .remove(&feature_set::prevent_rent_paying_rent_recipients::id()) - .unwrap(); - } - let bank = Bank::new_for_tests(&genesis_config); - let rent = bank.rent_collector().rent; - let rent_exempt_minimum = rent.minimum_balance(0); - - // Make one validator have an empty identity account - let mut empty_validator_account = bank - .get_account_with_fixed_root(&empty_validator.node_keypair.pubkey()) - .unwrap(); - empty_validator_account.set_lamports(0); - bank.store_account( - &empty_validator.node_keypair.pubkey(), - &empty_validator_account, - ); - - // Make one validator almost rent-exempt, less RENT_PER_VALIDATOR - let mut becomes_rent_exempt_validator_account = bank - .get_account_with_fixed_root(&becomes_rent_exempt_validator.node_keypair.pubkey()) - .unwrap(); - becomes_rent_exempt_validator_account - .set_lamports(rent_exempt_minimum - RENT_PER_VALIDATOR); - bank.store_account( - &becomes_rent_exempt_validator.node_keypair.pubkey(), - &becomes_rent_exempt_validator_account, - ); - - // Make one validator rent-exempt - let mut rent_exempt_validator_account = bank - .get_account_with_fixed_root(&rent_exempt_validator.node_keypair.pubkey()) - .unwrap(); - rent_exempt_validator_account.set_lamports(rent_exempt_minimum); - bank.store_account( - &rent_exempt_validator.node_keypair.pubkey(), - &rent_exempt_validator_account, - ); - - let get_rent_state = |bank: &Bank, address: &Pubkey| -> RentState { - let account = bank - .get_account_with_fixed_root(address) - .unwrap_or_default(); - RentState::from_account(&account, &rent) - }; - - // Assert starting RentStates - assert_eq!( - get_rent_state(&bank, &empty_validator.node_keypair.pubkey()), - RentState::Uninitialized - ); - assert_eq!( - get_rent_state(&bank, &rent_paying_validator.node_keypair.pubkey()), - RentState::RentPaying { - lamports: 42, - data_size: 0, - } - ); - assert_eq!( - get_rent_state(&bank, &becomes_rent_exempt_validator.node_keypair.pubkey()), - RentState::RentPaying { - lamports: rent_exempt_minimum - RENT_PER_VALIDATOR, - data_size: 0, - } - ); - assert_eq!( - get_rent_state(&bank, &rent_exempt_validator.node_keypair.pubkey()), - RentState::RentExempt - ); - - let old_empty_validator_lamports = bank.get_balance(&empty_validator.node_keypair.pubkey()); - let old_rent_paying_validator_lamports = - bank.get_balance(&rent_paying_validator.node_keypair.pubkey()); - let old_becomes_rent_exempt_validator_lamports = - bank.get_balance(&becomes_rent_exempt_validator.node_keypair.pubkey()); - let old_rent_exempt_validator_lamports = - bank.get_balance(&rent_exempt_validator.node_keypair.pubkey()); - - bank.distribute_rent_to_validators(&bank.vote_accounts(), TOTAL_RENT); - - let new_empty_validator_lamports = bank.get_balance(&empty_validator.node_keypair.pubkey()); - let new_rent_paying_validator_lamports = - bank.get_balance(&rent_paying_validator.node_keypair.pubkey()); - let new_becomes_rent_exempt_validator_lamports = - bank.get_balance(&becomes_rent_exempt_validator.node_keypair.pubkey()); - let new_rent_exempt_validator_lamports = - bank.get_balance(&rent_exempt_validator.node_keypair.pubkey()); - - // Assert ending balances; rent should be withheld if test is active and ending RentState - // is RentPaying, ie. empty_validator and rent_paying_validator - assert_eq!( - if deactivate_feature { - old_empty_validator_lamports + RENT_PER_VALIDATOR - } else { - old_empty_validator_lamports - }, - new_empty_validator_lamports - ); - - assert_eq!( - if deactivate_feature { - old_rent_paying_validator_lamports + RENT_PER_VALIDATOR - } else { - old_rent_paying_validator_lamports - }, - new_rent_paying_validator_lamports - ); - - assert_eq!( - old_becomes_rent_exempt_validator_lamports + RENT_PER_VALIDATOR, - new_becomes_rent_exempt_validator_lamports - ); - - assert_eq!( - old_rent_exempt_validator_lamports + RENT_PER_VALIDATOR, - new_rent_exempt_validator_lamports - ); - - // Assert ending RentStates - assert_eq!( - if deactivate_feature { - RentState::RentPaying { - lamports: RENT_PER_VALIDATOR, - data_size: 0, - } - } else { - RentState::Uninitialized - }, - get_rent_state(&bank, &empty_validator.node_keypair.pubkey()), - ); - assert_eq!( - if deactivate_feature { - RentState::RentPaying { - lamports: old_rent_paying_validator_lamports + RENT_PER_VALIDATOR, - data_size: 0, - } - } else { - RentState::RentPaying { - lamports: old_rent_paying_validator_lamports, - data_size: 0, - } - }, - get_rent_state(&bank, &rent_paying_validator.node_keypair.pubkey()), - ); - assert_eq!( - RentState::RentExempt, - get_rent_state(&bank, &becomes_rent_exempt_validator.node_keypair.pubkey()), - ); - assert_eq!( - RentState::RentExempt, - get_rent_state(&bank, &rent_exempt_validator.node_keypair.pubkey()), - ); - } -} - #[test] fn test_rent_exempt_executable_account() { let (mut genesis_config, mint_keypair) = create_genesis_config(100_000); @@ -1237,7 +1014,7 @@ fn test_rent_complex() { Deduction, } - declare_process_instruction!(process_instruction, 1, |invoke_context| { + declare_process_instruction!(MockBuiltin, 1, |invoke_context| { let transaction_context = &invoke_context.transaction_context; let instruction_context = transaction_context.get_current_instruction_context()?; let instruction_data = instruction_context.get_instruction_data(); @@ -1272,7 +1049,7 @@ fn test_rent_complex() { root_bank.restore_old_behavior_for_fragile_tests(); let root_bank = Arc::new(root_bank); let mut bank = create_child_bank_for_rent_test(root_bank, &genesis_config); - bank.add_mockup_builtin(mock_program_id, process_instruction); + bank.add_mockup_builtin(mock_program_id, MockBuiltin::vm); assert_eq!(bank.last_blockhash(), genesis_config.hash()); @@ -2624,22 +2401,6 @@ fn test_transfer_to_sysvar() { assert_eq!(bank.get_balance(&sysvar_pubkey), 1_169_280); } -#[test] -fn test_bank_deposit() { - let bank = create_simple_test_bank(100); - - // Test new account - let key = solana_sdk::pubkey::new_rand(); - let new_balance = bank.deposit(&key, 10).unwrap(); - assert_eq!(new_balance, 10); - assert_eq!(bank.get_balance(&key), 10); - - // Existing account - let new_balance = bank.deposit(&key, 3).unwrap(); - assert_eq!(new_balance, 13); - assert_eq!(bank.get_balance(&key), 13); -} - #[test] fn test_bank_withdraw() { let bank = create_simple_test_bank(100); @@ -2651,7 +2412,7 @@ fn test_bank_withdraw() { Err(TransactionError::AccountNotFound) ); - bank.deposit(&key, 3).unwrap(); + test_utils::deposit(&bank, &key, 3).unwrap(); assert_eq!(bank.get_balance(&key), 3); // Low balance @@ -3602,11 +3363,11 @@ fn test_verify_snapshot_bank() { bank.freeze(); add_root_and_flush_write_cache(&bank); bank.update_accounts_hash_for_tests(); - assert!(bank.verify_snapshot_bank(true, false, bank.slot(), None)); + assert!(bank.verify_snapshot_bank(true, false, false, bank.slot(), None)); // tamper the bank after freeze! bank.increment_signature_count(1); - assert!(!bank.verify_snapshot_bank(true, false, bank.slot(), None)); + assert!(!bank.verify_snapshot_bank(true, false, false, bank.slot(), None)); } // Test that two bank forks with the same accounts should not hash to the same value. @@ -4675,7 +4436,7 @@ fn test_add_builtin() { fn mock_vote_program_id() -> Pubkey { Pubkey::from([42u8; 32]) } - declare_process_instruction!(process_instruction, 1, |invoke_context| { + declare_process_instruction!(MockBuiltin, 1, |invoke_context| { let transaction_context = &invoke_context.transaction_context; let instruction_context = transaction_context.get_current_instruction_context()?; let program_id = instruction_context.get_last_program_key(transaction_context)?; @@ -4686,7 +4447,7 @@ fn test_add_builtin() { }); assert!(bank.get_account(&mock_vote_program_id()).is_none()); - bank.add_mockup_builtin(mock_vote_program_id(), process_instruction); + bank.add_mockup_builtin(mock_vote_program_id(), MockBuiltin::vm); assert!(bank.get_account(&mock_vote_program_id()).is_some()); let mock_account = Keypair::new(); @@ -4731,7 +4492,7 @@ fn test_add_duplicate_static_program() { } = create_genesis_config_with_leader(500, &solana_sdk::pubkey::new_rand(), 0); let bank = Bank::new_for_tests(&genesis_config); - declare_process_instruction!(process_instruction, 1, |_invoke_context| { + declare_process_instruction!(MockBuiltin, 1, |_invoke_context| { Err(InstructionError::Custom(42)) }); @@ -4762,7 +4523,7 @@ fn test_add_duplicate_static_program() { let mut bank = Bank::new_from_parent(Arc::new(bank), &Pubkey::default(), slot); let vote_loader_account = bank.get_account(&solana_vote_program::id()).unwrap(); - bank.add_mockup_builtin(solana_vote_program::id(), process_instruction); + bank.add_mockup_builtin(solana_vote_program::id(), MockBuiltin::vm); let new_vote_loader_account = bank.get_account(&solana_vote_program::id()).unwrap(); // Vote loader account should not be updated since it was included in the genesis config. assert_eq!(vote_loader_account.data(), new_vote_loader_account.data()); @@ -4780,7 +4541,7 @@ fn test_add_instruction_processor_for_existing_unrelated_accounts() { for pass in 0..5 { let mut bank = create_simple_test_bank(500); - declare_process_instruction!(process_instruction, 1, |_invoke_context| { + declare_process_instruction!(MockBuiltin, 1, |_invoke_context| { Err(InstructionError::Custom(42)) }); @@ -4816,12 +4577,12 @@ fn test_add_instruction_processor_for_existing_unrelated_accounts() { bank.add_builtin( vote_id, "mock_program1".to_string(), - LoadedProgram::new_builtin(0, 0, process_instruction), + LoadedProgram::new_builtin(0, 0, MockBuiltin::vm), ); bank.add_builtin( stake_id, "mock_program2".to_string(), - LoadedProgram::new_builtin(0, 0, process_instruction), + LoadedProgram::new_builtin(0, 0, MockBuiltin::vm), ); { let stakes = bank.stakes_cache.stakes(); @@ -4845,8 +4606,8 @@ fn test_add_instruction_processor_for_existing_unrelated_accounts() { // Re-adding builtin programs should be no-op bank.update_accounts_hash_for_tests(); let old_hash = bank.get_accounts_hash().unwrap(); - bank.add_mockup_builtin(vote_id, process_instruction); - bank.add_mockup_builtin(stake_id, process_instruction); + bank.add_mockup_builtin(vote_id, MockBuiltin::vm); + bank.add_mockup_builtin(stake_id, MockBuiltin::vm); add_root_and_flush_write_cache(&bank); bank.update_accounts_hash_for_tests(); let new_hash = bank.get_accounts_hash().unwrap(); @@ -6077,7 +5838,7 @@ fn test_transaction_with_duplicate_accounts_in_instruction() { let (genesis_config, mint_keypair) = create_genesis_config(500); let mut bank = Bank::new_for_tests(&genesis_config); - declare_process_instruction!(process_instruction, 1, |invoke_context| { + declare_process_instruction!(MockBuiltin, 1, |invoke_context| { let transaction_context = &invoke_context.transaction_context; let instruction_context = transaction_context.get_current_instruction_context()?; let instruction_data = instruction_context.get_instruction_data(); @@ -6098,7 +5859,7 @@ fn test_transaction_with_duplicate_accounts_in_instruction() { }); let mock_program_id = Pubkey::from([2u8; 32]); - bank.add_mockup_builtin(mock_program_id, process_instruction); + bank.add_mockup_builtin(mock_program_id, MockBuiltin::vm); let from_pubkey = solana_sdk::pubkey::new_rand(); let to_pubkey = solana_sdk::pubkey::new_rand(); @@ -6134,7 +5895,7 @@ fn test_transaction_with_program_ids_passed_to_programs() { let mut bank = Bank::new_for_tests(&genesis_config); let mock_program_id = Pubkey::from([2u8; 32]); - bank.add_mockup_builtin(mock_program_id, process_instruction); + bank.add_mockup_builtin(mock_program_id, MockBuiltin::vm); let from_pubkey = solana_sdk::pubkey::new_rand(); let to_pubkey = solana_sdk::pubkey::new_rand(); @@ -6189,7 +5950,7 @@ fn test_account_ids_after_program_ids() { let slot = bank.slot().saturating_add(1); let mut bank = Bank::new_from_parent(Arc::new(bank), &Pubkey::default(), slot); - bank.add_mockup_builtin(solana_vote_program::id(), process_instruction); + bank.add_mockup_builtin(solana_vote_program::id(), MockBuiltin::vm); let result = bank.process_transaction(&tx); assert_eq!(result, Ok(())); let account = bank.get_account(&solana_vote_program::id()).unwrap(); @@ -6239,7 +6000,7 @@ fn test_duplicate_account_key() { AccountMeta::new(to_pubkey, false), ]; - bank.add_mockup_builtin(solana_vote_program::id(), process_instruction); + bank.add_mockup_builtin(solana_vote_program::id(), MockBuiltin::vm); let instruction = Instruction::new_with_bincode(solana_vote_program::id(), &10, account_metas); let mut tx = Transaction::new_signed_with_payer( @@ -6268,7 +6029,7 @@ fn test_process_transaction_with_too_many_account_locks() { AccountMeta::new(to_pubkey, false), ]; - bank.add_mockup_builtin(solana_vote_program::id(), process_instruction); + bank.add_mockup_builtin(solana_vote_program::id(), MockBuiltin::vm); let instruction = Instruction::new_with_bincode(solana_vote_program::id(), &10, account_metas); let mut tx = Transaction::new_signed_with_payer( @@ -6301,7 +6062,7 @@ fn test_program_id_as_payer() { AccountMeta::new(to_pubkey, false), ]; - bank.add_mockup_builtin(solana_vote_program::id(), process_instruction); + bank.add_mockup_builtin(solana_vote_program::id(), MockBuiltin::vm); let instruction = Instruction::new_with_bincode(solana_vote_program::id(), &10, account_metas); let mut tx = Transaction::new_signed_with_payer( @@ -6347,7 +6108,7 @@ fn test_ref_account_key_after_program_id() { let slot = bank.slot().saturating_add(1); let mut bank = Bank::new_from_parent(Arc::new(bank), &Pubkey::default(), slot); - bank.add_mockup_builtin(solana_vote_program::id(), process_instruction); + bank.add_mockup_builtin(solana_vote_program::id(), MockBuiltin::vm); let instruction = Instruction::new_with_bincode(solana_vote_program::id(), &10, account_metas); let mut tx = Transaction::new_signed_with_payer( @@ -6381,7 +6142,7 @@ fn test_fuzz_instructions() { bank.add_builtin( key, name.clone(), - LoadedProgram::new_builtin(0, 0, process_instruction), + LoadedProgram::new_builtin(0, 0, MockBuiltin::vm), ); (key, name.as_bytes().to_vec()) }) @@ -6573,8 +6334,8 @@ fn test_bank_hash_consistency() { } #[test] -fn test_same_program_id_uses_unqiue_executable_accounts() { - declare_process_instruction!(process_instruction, 1, |invoke_context| { +fn test_same_program_id_uses_unique_executable_accounts() { + declare_process_instruction!(MockBuiltin, 1, |invoke_context| { let transaction_context = &invoke_context.transaction_context; let instruction_context = transaction_context.get_current_instruction_context()?; instruction_context @@ -6587,7 +6348,7 @@ fn test_same_program_id_uses_unqiue_executable_accounts() { // Add a new program let program1_pubkey = solana_sdk::pubkey::new_rand(); - bank.add_mockup_builtin(program1_pubkey, process_instruction); + bank.add_mockup_builtin(program1_pubkey, MockBuiltin::vm); // Add a new program owned by the first let program2_pubkey = solana_sdk::pubkey::new_rand(); @@ -6669,7 +6430,7 @@ fn test_clean_nonrooted() { // Store some lamports in bank 1 let some_lamports = 123; let mut bank1 = Arc::new(Bank::new_from_parent(bank0.clone(), &Pubkey::default(), 1)); - bank1.deposit(&pubkey0, some_lamports).unwrap(); + test_utils::deposit(&bank1, &pubkey0, some_lamports).unwrap(); goto_end_of_slot(Arc::::get_mut(&mut bank1).unwrap()); bank1.freeze(); bank1.flush_accounts_cache_slot_for_tests(); @@ -6679,7 +6440,7 @@ fn test_clean_nonrooted() { // Store some lamports for pubkey1 in bank 2, root bank 2 // bank2's parent is bank0 let mut bank2 = Arc::new(Bank::new_from_parent(bank0, &Pubkey::default(), 2)); - bank2.deposit(&pubkey1, some_lamports).unwrap(); + test_utils::deposit(&bank2, &pubkey1, some_lamports).unwrap(); bank2.store_account(&pubkey0, &account_zero); goto_end_of_slot(Arc::::get_mut(&mut bank2).unwrap()); bank2.freeze(); @@ -6694,7 +6455,7 @@ fn test_clean_nonrooted() { bank2.clean_accounts_for_tests(); let mut bank3 = Arc::new(Bank::new_from_parent(bank2, &Pubkey::default(), 3)); - bank3.deposit(&pubkey1, some_lamports + 1).unwrap(); + test_utils::deposit(&bank3, &pubkey1, some_lamports + 1).unwrap(); goto_end_of_slot(Arc::::get_mut(&mut bank3).unwrap()); bank3.freeze(); bank3.squash(); @@ -6748,8 +6509,8 @@ fn test_shrink_candidate_slots_cached() { // Store some lamports in bank 1 let some_lamports = 123; let mut bank1 = Arc::new(new_from_parent(bank0)); - bank1.deposit(&pubkey1, some_lamports).unwrap(); - bank1.deposit(&pubkey2, some_lamports).unwrap(); + test_utils::deposit(&bank1, &pubkey1, some_lamports).unwrap(); + test_utils::deposit(&bank1, &pubkey2, some_lamports).unwrap(); goto_end_of_slot(Arc::::get_mut(&mut bank1).unwrap()); bank1.freeze(); bank1.squash(); @@ -6759,7 +6520,7 @@ fn test_shrink_candidate_slots_cached() { // Store some lamports for pubkey1 in bank 2, root bank 2 let mut bank2 = Arc::new(new_from_parent(bank1)); - bank2.deposit(&pubkey1, some_lamports).unwrap(); + test_utils::deposit(&bank2, &pubkey1, some_lamports).unwrap(); bank2.store_account(&pubkey0, &account0); goto_end_of_slot(Arc::::get_mut(&mut bank2).unwrap()); bank2.freeze(); @@ -6804,13 +6565,13 @@ fn test_add_builtin_no_overwrite() { Arc::get_mut(&mut bank) .unwrap() - .add_mockup_builtin(program_id, process_instruction); + .add_mockup_builtin(program_id, MockBuiltin::vm); assert_eq!(bank.get_account_modified_slot(&program_id).unwrap().1, slot); let mut bank = Arc::new(new_from_parent(bank)); Arc::get_mut(&mut bank) .unwrap() - .add_mockup_builtin(program_id, process_instruction); + .add_mockup_builtin(program_id, MockBuiltin::vm); assert_eq!(bank.get_account_modified_slot(&program_id).unwrap().1, slot); } @@ -6828,13 +6589,13 @@ fn test_add_builtin_loader_no_overwrite() { Arc::get_mut(&mut bank) .unwrap() - .add_mockup_builtin(loader_id, process_instruction); + .add_mockup_builtin(loader_id, MockBuiltin::vm); assert_eq!(bank.get_account_modified_slot(&loader_id).unwrap().1, slot); let mut bank = Arc::new(new_from_parent(bank)); Arc::get_mut(&mut bank) .unwrap() - .add_mockup_builtin(loader_id, process_instruction); + .add_mockup_builtin(loader_id, MockBuiltin::vm); assert_eq!(bank.get_account_modified_slot(&loader_id).unwrap().1, slot); } @@ -6956,7 +6717,7 @@ fn test_add_builtin_account_inherited_cap_while_replacing() { assert_ne!(bank.capitalization(), bank.calculate_capitalization(true)); continue; } - bank.deposit(&program_id, 10).unwrap(); + test_utils::deposit(&bank, &program_id, 10).unwrap(); if pass == 2 { add_root_and_flush_write_cache(&bank); assert_eq!(bank.capitalization(), bank.calculate_capitalization(true)); @@ -6983,7 +6744,7 @@ fn test_add_builtin_account_squatted_while_not_replacing() { assert_ne!(bank.capitalization(), bank.calculate_capitalization(true)); continue; } - bank.deposit(&program_id, 10).unwrap(); + test_utils::deposit(&bank, &program_id, 10).unwrap(); if pass == 1 { add_root_and_flush_write_cache(&bank); assert_eq!(bank.capitalization(), bank.calculate_capitalization(true)); @@ -7106,7 +6867,7 @@ fn test_add_precompiled_account_inherited_cap_while_replacing() { assert_ne!(bank.capitalization(), bank.calculate_capitalization(true)); continue; } - bank.deposit(&program_id, 10).unwrap(); + test_utils::deposit(&bank, &program_id, 10).unwrap(); if pass == 2 { add_root_and_flush_write_cache(&bank); assert_eq!(bank.capitalization(), bank.calculate_capitalization(true)); @@ -7134,7 +6895,7 @@ fn test_add_precompiled_account_squatted_while_not_replacing() { assert_ne!(bank.capitalization(), bank.calculate_capitalization(true)); continue; } - bank.deposit(&program_id, 10).unwrap(); + test_utils::deposit(&bank, &program_id, 10).unwrap(); if pass == 1 { add_root_and_flush_write_cache(&bank); assert_eq!(bank.capitalization(), bank.calculate_capitalization(true)); @@ -7225,7 +6986,7 @@ fn test_bank_load_program() { programdata_account.set_rent_epoch(1); bank.store_account_and_update_capitalization(&key1, &program_account); bank.store_account_and_update_capitalization(&programdata_key, &programdata_account); - let program = bank.load_program(&key1, false); + let program = bank.load_program(&key1, false, None); assert_matches!(program.program, LoadedProgramType::LegacyV1(_)); assert_eq!( program.account_size, @@ -7380,7 +7141,7 @@ fn test_bpf_loader_upgradeable_deploy_with_max_len() { assert_eq!(*elf.get(i).unwrap(), *byte); } - let loaded_program = bank.load_program(&program_keypair.pubkey(), false); + let loaded_program = bank.load_program(&program_keypair.pubkey(), false, None); // Invoke deployed program mock_process_instruction( @@ -7393,7 +7154,7 @@ fn test_bpf_loader_upgradeable_deploy_with_max_len() { ], Vec::new(), Ok(()), - solana_bpf_loader_program::process_instruction, + solana_bpf_loader_program::Entrypoint::vm, |invoke_context| { invoke_context .programs_modified_by_tx @@ -7970,7 +7731,7 @@ fn test_compute_active_feature_set() { assert!(!feature_set.is_active(&test_feature)); // Depositing into the `test_feature` account should do nothing - bank.deposit(&test_feature, 42).unwrap(); + test_utils::deposit(&bank, &test_feature, 42).unwrap(); let (feature_set, new_activations) = bank.compute_active_feature_set(true); assert!(new_activations.is_empty()); assert!(!feature_set.is_active(&test_feature)); @@ -7979,25 +7740,26 @@ fn test_compute_active_feature_set() { let feature = Feature::default(); assert_eq!(feature.activated_at, None); bank.store_account(&test_feature, &feature::create_account(&feature, 42)); + let feature = feature::from_account(&bank.get_account(&test_feature).expect("get_account")) + .expect("from_account"); + assert_eq!(feature.activated_at, None); - // Run `compute_active_feature_set` disallowing new activations + // Run `compute_active_feature_set` excluding pending activation let (feature_set, new_activations) = bank.compute_active_feature_set(false); assert!(new_activations.is_empty()); assert!(!feature_set.is_active(&test_feature)); - let feature = feature::from_account(&bank.get_account(&test_feature).expect("get_account")) - .expect("from_account"); - assert_eq!(feature.activated_at, None); - // Run `compute_active_feature_set` allowing new activations - let (feature_set, new_activations) = bank.compute_active_feature_set(true); + // Run `compute_active_feature_set` including pending activation + let (_feature_set, new_activations) = bank.compute_active_feature_set(true); assert_eq!(new_activations.len(), 1); - assert!(feature_set.is_active(&test_feature)); + assert!(new_activations.contains(&test_feature)); + + // Actually activate the pending activation + bank.apply_feature_activations(ApplyFeatureActivationsCaller::NewFromParent, true); let feature = feature::from_account(&bank.get_account(&test_feature).expect("get_account")) .expect("from_account"); assert_eq!(feature.activated_at, Some(1)); - // Running `compute_active_feature_set` will not cause new activations, but - // `test_feature` is now be active let (feature_set, new_activations) = bank.compute_active_feature_set(true); assert!(new_activations.is_empty()); assert!(feature_set.is_active(&test_feature)); @@ -9337,7 +9099,7 @@ fn test_tx_return_data() { ); let mut bank = Bank::new_for_tests(&genesis_config); - declare_process_instruction!(process_instruction, 1, |invoke_context| { + declare_process_instruction!(MockBuiltin, 1, |invoke_context| { let mock_program_id = Pubkey::from([2u8; 32]); let transaction_context = &mut invoke_context.transaction_context; let instruction_context = transaction_context.get_current_instruction_context()?; @@ -9345,9 +9107,9 @@ fn test_tx_return_data() { let mut return_data = [0u8; MAX_RETURN_DATA]; if !instruction_data.is_empty() { let index = usize::from_le_bytes(instruction_data.try_into().unwrap()); - return_data[index] = 1; + return_data[index / 2] = 1; transaction_context - .set_return_data(mock_program_id, return_data.to_vec()) + .set_return_data(mock_program_id, return_data[..index + 1].to_vec()) .unwrap(); } Ok(()) @@ -9355,7 +9117,7 @@ fn test_tx_return_data() { let mock_program_id = Pubkey::from([2u8; 32]); let blockhash = bank.last_blockhash(); - bank.add_mockup_builtin(mock_program_id, process_instruction); + bank.add_mockup_builtin(mock_program_id, MockBuiltin::vm); for index in [ None, @@ -9399,8 +9161,9 @@ fn test_tx_return_data() { if let Some(index) = index { let return_data = return_data.unwrap(); assert_eq!(return_data.program_id, mock_program_id); - let mut expected_data = vec![0u8; index]; - expected_data.push(1u8); + let mut expected_data = vec![0u8; index + 1]; + // include some trailing zeros + expected_data[index / 2] = 1; assert_eq!(return_data.data, expected_data); } else { assert!(return_data.is_none()); @@ -9534,7 +9297,7 @@ fn test_transfer_sysvar() { ); let mut bank = Bank::new_for_tests(&genesis_config); - declare_process_instruction!(process_instruction, 1, |invoke_context| { + declare_process_instruction!(MockBuiltin, 1, |invoke_context| { let transaction_context = &invoke_context.transaction_context; let instruction_context = transaction_context.get_current_instruction_context()?; instruction_context @@ -9544,7 +9307,7 @@ fn test_transfer_sysvar() { }); let program_id = solana_sdk::pubkey::new_rand(); - bank.add_mockup_builtin(program_id, process_instruction); + bank.add_mockup_builtin(program_id, MockBuiltin::vm); let blockhash = bank.last_blockhash(); #[allow(deprecated)] @@ -9743,7 +9506,7 @@ fn test_compute_budget_program_noop() { ); let mut bank = Bank::new_for_tests(&genesis_config); - declare_process_instruction!(process_instruction, 1, |invoke_context| { + declare_process_instruction!(MockBuiltin, 1, |invoke_context| { let compute_budget = invoke_context.get_compute_budget(); assert_eq!( *compute_budget, @@ -9756,7 +9519,7 @@ fn test_compute_budget_program_noop() { Ok(()) }); let program_id = solana_sdk::pubkey::new_rand(); - bank.add_mockup_builtin(program_id, process_instruction); + bank.add_mockup_builtin(program_id, MockBuiltin::vm); let message = Message::new( &[ @@ -9786,7 +9549,7 @@ fn test_compute_request_instruction() { ); let mut bank = Bank::new_for_tests(&genesis_config); - declare_process_instruction!(process_instruction, 1, |invoke_context| { + declare_process_instruction!(MockBuiltin, 1, |invoke_context| { let compute_budget = invoke_context.get_compute_budget(); assert_eq!( *compute_budget, @@ -9799,7 +9562,7 @@ fn test_compute_request_instruction() { Ok(()) }); let program_id = solana_sdk::pubkey::new_rand(); - bank.add_mockup_builtin(program_id, process_instruction); + bank.add_mockup_builtin(program_id, MockBuiltin::vm); let message = Message::new( &[ @@ -9836,7 +9599,7 @@ fn test_failed_compute_request_instruction() { bank.transfer(10, &mint_keypair, &payer1_keypair.pubkey()) .unwrap(); - declare_process_instruction!(process_instruction, 1, |invoke_context| { + declare_process_instruction!(MockBuiltin, 1, |invoke_context| { let compute_budget = invoke_context.get_compute_budget(); assert_eq!( *compute_budget, @@ -9849,7 +9612,7 @@ fn test_failed_compute_request_instruction() { Ok(()) }); let program_id = solana_sdk::pubkey::new_rand(); - bank.add_mockup_builtin(program_id, process_instruction); + bank.add_mockup_builtin(program_id, MockBuiltin::vm); // This message will not be executed because the compute budget request is invalid let message0 = Message::new( @@ -10453,7 +10216,7 @@ enum MockTransferInstruction { Transfer(u64), } -declare_process_instruction!(mock_transfer_process_instruction, 1, |invoke_context| { +declare_process_instruction!(MockTransferBuiltin, 1, |invoke_context| { let transaction_context = &invoke_context.transaction_context; let instruction_context = transaction_context.get_current_instruction_context()?; let instruction_data = instruction_context.get_instruction_data(); @@ -10536,7 +10299,7 @@ fn test_invalid_rent_state_changes_existing_accounts() { ); let mut bank = Bank::new_for_tests(&genesis_config); - bank.add_mockup_builtin(mock_program_id, mock_transfer_process_instruction); + bank.add_mockup_builtin(mock_program_id, MockTransferBuiltin::vm); let recent_blockhash = bank.last_blockhash(); let check_account_is_rent_exempt = |pubkey: &Pubkey| -> bool { @@ -10619,7 +10382,7 @@ fn test_invalid_rent_state_changes_new_accounts() { let rent_exempt_minimum = genesis_config.rent.minimum_balance(account_data_size); let mut bank = Bank::new_for_tests(&genesis_config); - bank.add_mockup_builtin(mock_program_id, mock_transfer_process_instruction); + bank.add_mockup_builtin(mock_program_id, MockTransferBuiltin::vm); let recent_blockhash = bank.last_blockhash(); let check_account_is_rent_exempt = |pubkey: &Pubkey| -> bool { @@ -10678,7 +10441,7 @@ fn test_drained_created_account() { let created_keypair = Keypair::new(); let mut bank = Bank::new_for_tests(&genesis_config); - bank.add_mockup_builtin(mock_program_id, mock_transfer_process_instruction); + bank.add_mockup_builtin(mock_program_id, MockTransferBuiltin::vm); let recent_blockhash = bank.last_blockhash(); // Create and drain a small data size account @@ -11206,7 +10969,7 @@ enum MockReallocInstruction { Realloc(usize, u64, Pubkey), } -declare_process_instruction!(mock_realloc_process_instruction, 1, |invoke_context| { +declare_process_instruction!(MockReallocBuiltin, 1, |invoke_context| { let transaction_context = &invoke_context.transaction_context; let instruction_context = transaction_context.get_current_instruction_context()?; let instruction_data = instruction_context.get_instruction_data(); @@ -11286,7 +11049,7 @@ fn test_resize_and_rent() { let mut bank = Bank::new_for_tests(&genesis_config); let mock_program_id = Pubkey::new_unique(); - bank.add_mockup_builtin(mock_program_id, mock_realloc_process_instruction); + bank.add_mockup_builtin(mock_program_id, MockReallocBuiltin::vm); let recent_blockhash = bank.last_blockhash(); let account_data_size_small = 1024; @@ -11557,7 +11320,7 @@ fn test_accounts_data_size_and_resize_transactions() { } = genesis_utils::create_genesis_config(100 * LAMPORTS_PER_SOL); let mut bank = Bank::new_for_tests(&genesis_config); let mock_program_id = Pubkey::new_unique(); - bank.add_mockup_builtin(mock_program_id, mock_realloc_process_instruction); + bank.add_mockup_builtin(mock_program_id, MockReallocBuiltin::vm); let recent_blockhash = bank.last_blockhash(); @@ -11865,6 +11628,80 @@ fn test_feature_activation_idempotent() { assert_eq!(bank.hashes_per_tick, Some(DEFAULT_HASHES_PER_TICK)); } +#[test] +fn test_feature_hashes_per_tick() { + let mut genesis_config = GenesisConfig::default(); + const HASHES_PER_TICK_START: u64 = 3; + genesis_config.poh_config.hashes_per_tick = Some(HASHES_PER_TICK_START); + + let mut bank = Bank::new_for_tests(&genesis_config); + assert_eq!(bank.hashes_per_tick, Some(HASHES_PER_TICK_START)); + + // Don't activate feature + bank.apply_feature_activations(ApplyFeatureActivationsCaller::NewFromParent, false); + assert_eq!(bank.hashes_per_tick, Some(HASHES_PER_TICK_START)); + + // Activate feature + let feature_account_balance = + std::cmp::max(genesis_config.rent.minimum_balance(Feature::size_of()), 1); + bank.store_account( + &feature_set::update_hashes_per_tick::id(), + &feature::create_account(&Feature { activated_at: None }, feature_account_balance), + ); + bank.apply_feature_activations(ApplyFeatureActivationsCaller::NewFromParent, false); + assert_eq!(bank.hashes_per_tick, Some(DEFAULT_HASHES_PER_TICK)); + + // Activate feature + let feature_account_balance = + std::cmp::max(genesis_config.rent.minimum_balance(Feature::size_of()), 1); + bank.store_account( + &feature_set::update_hashes_per_tick2::id(), + &feature::create_account(&Feature { activated_at: None }, feature_account_balance), + ); + bank.apply_feature_activations(ApplyFeatureActivationsCaller::NewFromParent, false); + assert_eq!(bank.hashes_per_tick, Some(UPDATED_HASHES_PER_TICK2)); + + // Activate feature + let feature_account_balance = + std::cmp::max(genesis_config.rent.minimum_balance(Feature::size_of()), 1); + bank.store_account( + &feature_set::update_hashes_per_tick3::id(), + &feature::create_account(&Feature { activated_at: None }, feature_account_balance), + ); + bank.apply_feature_activations(ApplyFeatureActivationsCaller::NewFromParent, false); + assert_eq!(bank.hashes_per_tick, Some(UPDATED_HASHES_PER_TICK3)); + + // Activate feature + let feature_account_balance = + std::cmp::max(genesis_config.rent.minimum_balance(Feature::size_of()), 1); + bank.store_account( + &feature_set::update_hashes_per_tick4::id(), + &feature::create_account(&Feature { activated_at: None }, feature_account_balance), + ); + bank.apply_feature_activations(ApplyFeatureActivationsCaller::NewFromParent, false); + assert_eq!(bank.hashes_per_tick, Some(UPDATED_HASHES_PER_TICK4)); + + // Activate feature + let feature_account_balance = + std::cmp::max(genesis_config.rent.minimum_balance(Feature::size_of()), 1); + bank.store_account( + &feature_set::update_hashes_per_tick5::id(), + &feature::create_account(&Feature { activated_at: None }, feature_account_balance), + ); + bank.apply_feature_activations(ApplyFeatureActivationsCaller::NewFromParent, false); + assert_eq!(bank.hashes_per_tick, Some(UPDATED_HASHES_PER_TICK5)); + + // Activate feature + let feature_account_balance = + std::cmp::max(genesis_config.rent.minimum_balance(Feature::size_of()), 1); + bank.store_account( + &feature_set::update_hashes_per_tick6::id(), + &feature::create_account(&Feature { activated_at: None }, feature_account_balance), + ); + bank.apply_feature_activations(ApplyFeatureActivationsCaller::NewFromParent, false); + assert_eq!(bank.hashes_per_tick, Some(UPDATED_HASHES_PER_TICK6)); +} + #[test_case(true)] #[test_case(false)] fn test_stake_account_consistency_with_rent_epoch_max_feature( @@ -12052,7 +11889,7 @@ fn test_is_in_slot_hashes_history() { } #[test] -fn test_runtime_feature_enable_with_program_cache() { +fn test_feature_activation_loaded_programs_recompilation_phase() { solana_logger::setup(); // Bank Setup @@ -12060,7 +11897,8 @@ fn test_runtime_feature_enable_with_program_cache() { genesis_config .accounts .remove(&feature_set::reject_callx_r10::id()); - let root_bank = Bank::new_for_tests(&genesis_config); + let bank_forks = BankForks::new_rw_arc(Bank::new_for_tests(&genesis_config)); + let root_bank = bank_forks.read().unwrap().root_bank(); // Test a basic transfer let amount = genesis_config.rent.minimum_balance(0); @@ -12090,7 +11928,7 @@ fn test_runtime_feature_enable_with_program_cache() { // Advance the bank so the next transaction can be submitted. goto_end_of_slot(&root_bank); - let mut bank = new_from_parent(Arc::new(root_bank)); + let bank = Arc::new(new_from_parent(root_bank)); // Compose second instruction using the same program with a different block hash let instruction2 = Instruction::new_with_bytes(program_keypair.pubkey(), &[], Vec::new()); @@ -12116,9 +11954,12 @@ fn test_runtime_feature_enable_with_program_cache() { &feature_set::reject_callx_r10::id(), &feature::create_account(&Feature { activated_at: None }, feature_account_balance), ); - bank.apply_feature_activations(ApplyFeatureActivationsCaller::NewFromParent, false); - // Execute after feature is enabled to check it was pruned and reverified. + goto_end_of_slot(&bank); + // Advance to next epoch, which starts the next recompilation phase + let bank = new_from_parent_next_epoch(bank, 1); + + // Execute after feature is enabled to check it was filtered out and reverified. let result_with_feature_enabled = bank.process_transaction(&transaction2); assert_eq!( result_with_feature_enabled, diff --git a/runtime/src/bank_forks.rs b/runtime/src/bank_forks.rs index b56f4d774d1..58e255a991d 100644 --- a/runtime/src/bank_forks.rs +++ b/runtime/src/bank_forks.rs @@ -8,14 +8,19 @@ use { }, log::*, solana_measure::measure::Measure, - solana_program_runtime::loaded_programs::{BlockRelation, ForkGraph, WorkingSlot}, - solana_sdk::{clock::Slot, feature_set, hash::Hash, timing}, + solana_program_runtime::loaded_programs::{BlockRelation, ForkGraph}, + solana_sdk::{ + clock::{Epoch, Slot}, + feature_set, + hash::Hash, + timing, + }, std::{ collections::{hash_map::Entry, HashMap, HashSet}, ops::Index, sync::{ atomic::{AtomicBool, AtomicU64, Ordering}, - Arc, + Arc, RwLock, }, time::Instant, }, @@ -55,6 +60,7 @@ struct SetRootTimings { prune_remove_ms: i64, } +#[derive(Debug)] pub struct BankForks { banks: HashMap>, descendants: HashMap>, @@ -76,7 +82,7 @@ impl Index for BankForks { } impl BankForks { - pub fn new(bank: Bank) -> Self { + pub fn new_rw_arc(bank: Bank) -> Arc> { let root = bank.slot(); Self::new_from_banks(&[Arc::new(bank)], root) } @@ -153,7 +159,7 @@ impl BankForks { self[self.root()].clone() } - pub fn new_from_banks(initial_forks: &[Arc], root: Slot) -> Self { + pub fn new_from_banks(initial_forks: &[Arc], root: Slot) -> Arc> { let mut banks = HashMap::new(); // Iterate through the heads of all the different forks @@ -174,7 +180,7 @@ impl BankForks { descendants.entry(parent).or_default().insert(*slot); } } - Self { + let bank_forks = Arc::new(RwLock::new(Self { root: Arc::new(AtomicSlot::new(root)), banks, descendants, @@ -183,7 +189,16 @@ impl BankForks { last_accounts_hash_slot: root, in_vote_only_mode: Arc::new(AtomicBool::new(false)), highest_slot_at_startup: 0, + })); + + for bank in bank_forks.read().unwrap().banks.values() { + bank.loaded_programs_cache + .write() + .unwrap() + .set_fork_graph(bank_forks.clone()); } + + bank_forks } pub fn insert(&mut self, mut bank: Bank) -> Arc { @@ -404,6 +419,16 @@ impl BankForks { ) } + pub fn prune_program_cache(&self, root: Slot) { + if let Some(root_bank) = self.banks.get(&root) { + root_bank + .loaded_programs_cache + .write() + .unwrap() + .prune(root, root_bank.epoch()); + } + } + pub fn set_root( &mut self, root: Slot, @@ -411,15 +436,6 @@ impl BankForks { highest_super_majority_root: Option, ) -> Vec> { let program_cache_prune_start = Instant::now(); - let root_bank = self - .banks - .get(&root) - .expect("root bank didn't exist in bank_forks"); - root_bank - .loaded_programs_cache - .write() - .unwrap() - .prune(self, root); let set_root_start = Instant::now(); let (removed_banks, set_root_metrics) = self.do_set_root_return_metrics( root, @@ -652,9 +668,11 @@ impl ForkGraph for BankForks { (a == b) .then_some(BlockRelation::Equal) .or_else(|| { - self.banks - .get(&b) - .and_then(|bank| bank.is_ancestor(a).then_some(BlockRelation::Ancestor)) + self.banks.get(&b).and_then(|bank| { + bank.ancestors + .contains_key(&a) + .then_some(BlockRelation::Ancestor) + }) }) .or_else(|| { self.descendants.get(&b).and_then(|slots| { @@ -665,6 +683,10 @@ impl ForkGraph for BankForks { }) .unwrap_or(BlockRelation::Unknown) } + + fn slot_epoch(&self, slot: Slot) -> Option { + self.banks.get(&slot).map(|bank| bank.epoch()) + } } #[cfg(test)] @@ -694,7 +716,8 @@ mod tests { fn test_bank_forks_new() { let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(10_000); let bank = Bank::new_for_tests(&genesis_config); - let mut bank_forks = BankForks::new(bank); + let bank_forks = BankForks::new_rw_arc(bank); + let mut bank_forks = bank_forks.write().unwrap(); let child_bank = Bank::new_from_parent(bank_forks[0].clone(), &Pubkey::default(), 1); child_bank.register_tick(&Hash::default()); bank_forks.insert(child_bank); @@ -709,19 +732,20 @@ mod tests { let child_bank = Arc::new(Bank::new_from_parent(bank.clone(), &Pubkey::default(), 1)); let bank_forks = BankForks::new_from_banks(&[bank.clone(), child_bank.clone()], 0); - assert_eq!(bank_forks.root(), 0); - assert_eq!(bank_forks.working_bank().slot(), 1); + assert_eq!(bank_forks.read().unwrap().root(), 0); + assert_eq!(bank_forks.read().unwrap().working_bank().slot(), 1); let bank_forks = BankForks::new_from_banks(&[child_bank, bank], 0); - assert_eq!(bank_forks.root(), 0); - assert_eq!(bank_forks.working_bank().slot(), 1); + assert_eq!(bank_forks.read().unwrap().root(), 0); + assert_eq!(bank_forks.read().unwrap().working_bank().slot(), 1); } #[test] fn test_bank_forks_descendants() { let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(10_000); let bank = Bank::new_for_tests(&genesis_config); - let mut bank_forks = BankForks::new(bank); + let bank_forks = BankForks::new_rw_arc(bank); + let mut bank_forks = bank_forks.write().unwrap(); let bank0 = bank_forks[0].clone(); let bank = Bank::new_from_parent(bank0.clone(), &Pubkey::default(), 1); bank_forks.insert(bank); @@ -738,7 +762,8 @@ mod tests { fn test_bank_forks_ancestors() { let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(10_000); let bank = Bank::new_for_tests(&genesis_config); - let mut bank_forks = BankForks::new(bank); + let bank_forks = BankForks::new_rw_arc(bank); + let mut bank_forks = bank_forks.write().unwrap(); let bank0 = bank_forks[0].clone(); let bank = Bank::new_from_parent(bank0.clone(), &Pubkey::default(), 1); bank_forks.insert(bank); @@ -756,7 +781,8 @@ mod tests { fn test_bank_forks_frozen_banks() { let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(10_000); let bank = Bank::new_for_tests(&genesis_config); - let mut bank_forks = BankForks::new(bank); + let bank_forks = BankForks::new_rw_arc(bank); + let mut bank_forks = bank_forks.write().unwrap(); let bank0 = bank_forks[0].clone(); let child_bank = Bank::new_from_parent(bank0, &Pubkey::default(), 1); bank_forks.insert(child_bank); @@ -768,7 +794,8 @@ mod tests { fn test_bank_forks_active_banks() { let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(10_000); let bank = Bank::new_for_tests(&genesis_config); - let mut bank_forks = BankForks::new(bank); + let bank_forks = BankForks::new_rw_arc(bank); + let mut bank_forks = bank_forks.write().unwrap(); let bank0 = bank_forks[0].clone(); let child_bank = Bank::new_from_parent(bank0, &Pubkey::default(), 1); bank_forks.insert(child_bank); @@ -820,11 +847,13 @@ mod tests { }; let bank0 = Bank::new_for_tests(&genesis_config); - let mut bank_forks0 = BankForks::new(bank0); + let bank_forks0 = BankForks::new_rw_arc(bank0); + let mut bank_forks0 = bank_forks0.write().unwrap(); bank_forks0.set_root(0, &abs_request_sender, None); let bank1 = Bank::new_for_tests(&genesis_config); - let mut bank_forks1 = BankForks::new(bank1); + let bank_forks1 = BankForks::new_rw_arc(bank1); + let mut bank_forks1 = bank_forks1.write().unwrap(); let additional_timestamp_secs = 2; @@ -880,10 +909,11 @@ mod tests { .collect() } - fn extend_bank_forks(bank_forks: &mut BankForks, parent_child_pairs: &[(Slot, Slot)]) { + fn extend_bank_forks(bank_forks: Arc>, parent_child_pairs: &[(Slot, Slot)]) { for (parent, child) in parent_child_pairs.iter() { - bank_forks.insert(Bank::new_from_parent( - bank_forks[*parent].clone(), + let parent: Arc = bank_forks.read().unwrap().banks[parent].clone(); + bank_forks.write().unwrap().insert(Bank::new_from_parent( + parent, &Pubkey::default(), *child, )); @@ -894,13 +924,13 @@ mod tests { fn test_bank_forks_with_set_root() { let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(10_000); let bank = Bank::new_for_tests(&genesis_config); - let mut bank_forks = BankForks::new(bank); + let bank_forks = BankForks::new_rw_arc(bank); let parent_child_pairs = vec![(0, 1), (1, 2), (0, 3), (3, 4)]; - extend_bank_forks(&mut bank_forks, &parent_child_pairs); + extend_bank_forks(bank_forks.clone(), &parent_child_pairs); assert_eq!( - bank_forks.ancestors(), + bank_forks.read().unwrap().ancestors(), make_hash_map(vec![ (0, vec![]), (1, vec![0]), @@ -910,7 +940,7 @@ mod tests { ]) ); assert_eq!( - bank_forks.descendants(), + bank_forks.read().unwrap().descendants(), make_hash_map(vec![ (0, vec![1, 2, 3, 4]), (1, vec![2]), @@ -919,26 +949,29 @@ mod tests { (4, vec![]), ]) ); - bank_forks.set_root( + bank_forks.write().unwrap().set_root( 2, &AbsRequestSender::default(), None, // highest confirmed root ); - bank_forks[2].squash(); - assert_eq!(bank_forks.ancestors(), make_hash_map(vec![(2, vec![]),])); + bank_forks.read().unwrap().get(2).unwrap().squash(); assert_eq!( - bank_forks.descendants(), + bank_forks.read().unwrap().ancestors(), + make_hash_map(vec![(2, vec![]),]) + ); + assert_eq!( + bank_forks.read().unwrap().descendants(), make_hash_map(vec![(0, vec![2]), (1, vec![2]), (2, vec![]),]) ); let parent_child_pairs = vec![(2, 5), (5, 6)]; - extend_bank_forks(&mut bank_forks, &parent_child_pairs); + extend_bank_forks(bank_forks.clone(), &parent_child_pairs); assert_eq!( - bank_forks.ancestors(), + bank_forks.read().unwrap().ancestors(), make_hash_map(vec![(2, vec![]), (5, vec![2]), (6, vec![2, 5])]) ); assert_eq!( - bank_forks.descendants(), + bank_forks.read().unwrap().descendants(), make_hash_map(vec![ (0, vec![2]), (1, vec![2]), @@ -954,13 +987,13 @@ mod tests { let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(10_000); let bank = Bank::new_for_tests(&genesis_config); assert_eq!(bank.slot(), 0); - let mut bank_forks = BankForks::new(bank); + let bank_forks = BankForks::new_rw_arc(bank); let parent_child_pairs = vec![(0, 1), (1, 2), (0, 3), (3, 4)]; - extend_bank_forks(&mut bank_forks, &parent_child_pairs); + extend_bank_forks(bank_forks.clone(), &parent_child_pairs); assert_eq!( - bank_forks.ancestors(), + bank_forks.read().unwrap().ancestors(), make_hash_map(vec![ (0, vec![]), (1, vec![0]), @@ -970,7 +1003,7 @@ mod tests { ]) ); assert_eq!( - bank_forks.descendants(), + bank_forks.read().unwrap().descendants(), make_hash_map(vec![ (0, vec![1, 2, 3, 4]), (1, vec![2]), @@ -979,25 +1012,25 @@ mod tests { (4, vec![]), ]) ); - bank_forks.set_root( + bank_forks.write().unwrap().set_root( 2, &AbsRequestSender::default(), Some(1), // highest confirmed root ); - bank_forks[2].squash(); + bank_forks.read().unwrap().get(2).unwrap().squash(); assert_eq!( - bank_forks.ancestors(), + bank_forks.read().unwrap().ancestors(), make_hash_map(vec![(1, vec![]), (2, vec![]),]) ); assert_eq!( - bank_forks.descendants(), + bank_forks.read().unwrap().descendants(), make_hash_map(vec![(0, vec![1, 2]), (1, vec![2]), (2, vec![]),]) ); let parent_child_pairs = vec![(2, 5), (5, 6)]; - extend_bank_forks(&mut bank_forks, &parent_child_pairs); + extend_bank_forks(bank_forks.clone(), &parent_child_pairs); assert_eq!( - bank_forks.ancestors(), + bank_forks.read().unwrap().ancestors(), make_hash_map(vec![ (1, vec![]), (2, vec![]), @@ -1006,7 +1039,7 @@ mod tests { ]) ); assert_eq!( - bank_forks.descendants(), + bank_forks.read().unwrap().descendants(), make_hash_map(vec![ (0, vec![1, 2]), (1, vec![2]), @@ -1021,7 +1054,7 @@ mod tests { fn test_fork_graph() { let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(10_000); let bank = Bank::new_for_tests(&genesis_config); - let mut bank_forks = BankForks::new(bank); + let bank_forks = BankForks::new_rw_arc(bank); let parent_child_pairs = vec![ (0, 1), @@ -1034,7 +1067,7 @@ mod tests { (4, 6), (6, 12), ]; - extend_bank_forks(&mut bank_forks, &parent_child_pairs); + extend_bank_forks(bank_forks.clone(), &parent_child_pairs); // Fork graph created for the test // 0 @@ -1046,7 +1079,7 @@ mod tests { // 8 5 6 // | | // 10 12 - + let mut bank_forks = bank_forks.write().unwrap(); assert_matches!(bank_forks.relationship(0, 3), BlockRelation::Ancestor); assert_matches!(bank_forks.relationship(0, 10), BlockRelation::Ancestor); assert_matches!(bank_forks.relationship(0, 12), BlockRelation::Ancestor); diff --git a/runtime/src/builtins.rs b/runtime/src/builtins.rs index 06a1709335b..2c7c36fa0ec 100644 --- a/runtime/src/builtins.rs +++ b/runtime/src/builtins.rs @@ -1,5 +1,5 @@ use { - solana_program_runtime::invoke_context::ProcessInstructionWithContext, + solana_program_runtime::invoke_context::BuiltinFunctionWithContext, solana_sdk::{ bpf_loader, bpf_loader_deprecated, bpf_loader_upgradeable, feature_set, pubkey::Pubkey, }, @@ -10,7 +10,7 @@ pub struct BuiltinPrototype { pub feature_id: Option, pub program_id: Pubkey, pub name: &'static str, - pub entrypoint: ProcessInstructionWithContext, + pub entrypoint: BuiltinFunctionWithContext, } impl std::fmt::Debug for BuiltinPrototype { @@ -27,7 +27,7 @@ impl std::fmt::Debug for BuiltinPrototype { impl solana_frozen_abi::abi_example::AbiExample for BuiltinPrototype { fn example() -> Self { // BuiltinPrototype isn't serializable by definition. - solana_program_runtime::declare_process_instruction!(entrypoint, 0, |_invoke_context| { + solana_program_runtime::declare_process_instruction!(MockBuiltin, 0, |_invoke_context| { // Do nothing Ok(()) }); @@ -35,7 +35,7 @@ impl solana_frozen_abi::abi_example::AbiExample for BuiltinPrototype { feature_id: None, program_id: Pubkey::default(), name: "", - entrypoint, + entrypoint: MockBuiltin::vm, } } } @@ -45,66 +45,66 @@ pub static BUILTINS: &[BuiltinPrototype] = &[ feature_id: None, program_id: solana_system_program::id(), name: "system_program", - entrypoint: solana_system_program::system_processor::process_instruction, + entrypoint: solana_system_program::system_processor::Entrypoint::vm, }, BuiltinPrototype { feature_id: None, program_id: solana_vote_program::id(), name: "vote_program", - entrypoint: solana_vote_program::vote_processor::process_instruction, + entrypoint: solana_vote_program::vote_processor::Entrypoint::vm, }, BuiltinPrototype { feature_id: None, program_id: solana_stake_program::id(), name: "stake_program", - entrypoint: solana_stake_program::stake_instruction::process_instruction, + entrypoint: solana_stake_program::stake_instruction::Entrypoint::vm, }, BuiltinPrototype { feature_id: None, program_id: solana_config_program::id(), name: "config_program", - entrypoint: solana_config_program::config_processor::process_instruction, + entrypoint: solana_config_program::config_processor::Entrypoint::vm, }, BuiltinPrototype { feature_id: None, program_id: bpf_loader_deprecated::id(), name: "solana_bpf_loader_deprecated_program", - entrypoint: solana_bpf_loader_program::process_instruction, + entrypoint: solana_bpf_loader_program::Entrypoint::vm, }, BuiltinPrototype { feature_id: None, program_id: bpf_loader::id(), name: "solana_bpf_loader_program", - entrypoint: solana_bpf_loader_program::process_instruction, + entrypoint: solana_bpf_loader_program::Entrypoint::vm, }, BuiltinPrototype { feature_id: None, program_id: bpf_loader_upgradeable::id(), name: "solana_bpf_loader_upgradeable_program", - entrypoint: solana_bpf_loader_program::process_instruction, + entrypoint: solana_bpf_loader_program::Entrypoint::vm, }, BuiltinPrototype { feature_id: None, program_id: solana_sdk::compute_budget::id(), name: "compute_budget_program", - entrypoint: solana_compute_budget_program::process_instruction, + entrypoint: solana_compute_budget_program::Entrypoint::vm, }, BuiltinPrototype { feature_id: None, program_id: solana_sdk::address_lookup_table::program::id(), name: "address_lookup_table_program", - entrypoint: solana_address_lookup_table_program::processor::process_instruction, + entrypoint: solana_address_lookup_table_program::processor::Entrypoint::vm, }, BuiltinPrototype { feature_id: Some(feature_set::zk_token_sdk_enabled::id()), program_id: solana_zk_token_sdk::zk_token_proof_program::id(), name: "zk_token_proof_program", - entrypoint: solana_zk_token_proof_program::process_instruction, + entrypoint: solana_zk_token_proof_program::Entrypoint::vm, }, BuiltinPrototype { feature_id: Some(feature_set::enable_program_runtime_v2_and_loader_v4::id()), program_id: solana_sdk::loader_v4::id(), name: "loader_v4", - entrypoint: solana_loader_v4_program::process_instruction, + entrypoint: solana_loader_v4_program::Entrypoint::vm, }, ]; diff --git a/runtime/src/loader_utils.rs b/runtime/src/loader_utils.rs index cddb37595ce..7f4650ae561 100644 --- a/runtime/src/loader_utils.rs +++ b/runtime/src/loader_utils.rs @@ -116,6 +116,7 @@ pub fn load_upgradeable_buffer( let program = load_program_from_file(name); let buffer_pubkey = buffer_keypair.pubkey(); let buffer_authority_pubkey = buffer_authority_keypair.pubkey(); + let program_buffer_bytes = UpgradeableLoaderState::size_of_buffer(program.len()); bank_client .send_and_confirm_message( @@ -127,7 +128,7 @@ pub fn load_upgradeable_buffer( &buffer_authority_pubkey, 1.max( bank_client - .get_minimum_balance_for_rent_exemption(program.len()) + .get_minimum_balance_for_rent_exemption(program_buffer_bytes) .unwrap(), ), program.len(), diff --git a/runtime/src/prioritization_fee_cache.rs b/runtime/src/prioritization_fee_cache.rs index 5f8e3d9220d..c41d5a72bd3 100644 --- a/runtime/src/prioritization_fee_cache.rs +++ b/runtime/src/prioritization_fee_cache.rs @@ -612,8 +612,8 @@ mod tests { let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(10_000); let bank0 = Bank::new_for_benches(&genesis_config); - let bank_forks = BankForks::new(bank0); - let bank = bank_forks.working_bank(); + let bank_forks = BankForks::new_rw_arc(bank0); + let bank = bank_forks.read().unwrap().working_bank(); let collector = solana_sdk::pubkey::new_rand(); let bank1 = Arc::new(Bank::new_from_parent(bank.clone(), &collector, 1)); let bank2 = Arc::new(Bank::new_from_parent(bank.clone(), &collector, 2)); @@ -864,8 +864,8 @@ mod tests { let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(10_000); let bank0 = Bank::new_for_benches(&genesis_config); - let bank_forks = BankForks::new(bank0); - let bank = bank_forks.working_bank(); + let bank_forks = BankForks::new_rw_arc(bank0); + let bank = bank_forks.read().unwrap().working_bank(); let collector = solana_sdk::pubkey::new_rand(); let slot: Slot = 999; let bank1 = Arc::new(Bank::new_from_parent(bank.clone(), &collector, slot)); diff --git a/runtime/src/root_bank_cache.rs b/runtime/src/root_bank_cache.rs index 8a9ff1b8b50..09a8f2690c4 100644 --- a/runtime/src/root_bank_cache.rs +++ b/runtime/src/root_bank_cache.rs @@ -60,7 +60,7 @@ mod tests { fn test_root_bank_cache() { let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(10_000); let bank = Bank::new_for_tests(&genesis_config); - let bank_forks = Arc::new(RwLock::new(BankForks::new(bank))); + let bank_forks = BankForks::new_rw_arc(bank); let mut root_bank_cache = RootBankCache::new(bank_forks.clone()); diff --git a/runtime/src/snapshot_bank_utils.rs b/runtime/src/snapshot_bank_utils.rs index 82d6f354a4e..9f237def741 100644 --- a/runtime/src/snapshot_bank_utils.rs +++ b/runtime/src/snapshot_bank_utils.rs @@ -271,6 +271,7 @@ pub fn bank_from_snapshot_archives( shrink_ratio: AccountShrinkThreshold, test_hash_calculation: bool, accounts_db_skip_shrink: bool, + accounts_db_force_initial_clean: bool, verify_index: bool, accounts_db_config: Option, accounts_update_notifier: Option, @@ -365,6 +366,7 @@ pub fn bank_from_snapshot_archives( if !bank.verify_snapshot_bank( test_hash_calculation, accounts_db_skip_shrink || !full_snapshot_archive_info.is_remote(), + accounts_db_force_initial_clean, full_snapshot_archive_info.slot(), base, ) && limit_load_slot_count_from_snapshot.is_none() @@ -427,6 +429,7 @@ pub fn bank_from_latest_snapshot_archives( shrink_ratio: AccountShrinkThreshold, test_hash_calculation: bool, accounts_db_skip_shrink: bool, + accounts_db_force_initial_clean: bool, verify_index: bool, accounts_db_config: Option, accounts_update_notifier: Option, @@ -459,6 +462,7 @@ pub fn bank_from_latest_snapshot_archives( shrink_ratio, test_hash_calculation, accounts_db_skip_shrink, + accounts_db_force_initial_clean, verify_index, accounts_db_config, accounts_update_notifier, @@ -1326,6 +1330,7 @@ mod tests { false, false, false, + false, Some(ACCOUNTS_DB_CONFIG_FOR_TESTING), None, Arc::default(), @@ -1437,6 +1442,7 @@ mod tests { false, false, false, + false, Some(ACCOUNTS_DB_CONFIG_FOR_TESTING), None, Arc::default(), @@ -1568,6 +1574,7 @@ mod tests { false, false, false, + false, Some(ACCOUNTS_DB_CONFIG_FOR_TESTING), None, Arc::default(), @@ -1689,6 +1696,7 @@ mod tests { false, false, false, + false, Some(ACCOUNTS_DB_CONFIG_FOR_TESTING), None, Arc::default(), @@ -1826,6 +1834,7 @@ mod tests { false, false, false, + false, Some(ACCOUNTS_DB_CONFIG_FOR_TESTING), None, Arc::default(), @@ -1891,6 +1900,7 @@ mod tests { false, false, false, + false, Some(ACCOUNTS_DB_CONFIG_FOR_TESTING), None, Arc::default(), @@ -2255,6 +2265,7 @@ mod tests { false, false, false, + false, Some(ACCOUNTS_DB_CONFIG_FOR_TESTING), None, Arc::default(), diff --git a/runtime/src/snapshot_utils.rs b/runtime/src/snapshot_utils.rs index 74c9b2421f4..0cf1aab09da 100644 --- a/runtime/src/snapshot_utils.rs +++ b/runtime/src/snapshot_utils.rs @@ -1469,7 +1469,7 @@ pub fn build_storage_from_snapshot_dir( let accounts_hardlinks = bank_snapshot_dir.join(SNAPSHOT_ACCOUNTS_HARDLINKS); let account_run_paths: HashSet<_> = HashSet::from_iter(account_paths); - for dir_entry in fs_err::read_dir(&accounts_hardlinks)? { + for dir_entry in fs_err::read_dir(accounts_hardlinks)? { let symlink_path = dir_entry?.path(); // The symlink point to /snapshot/ which contain the account files hardlinks // The corresponding run path should be /run/ diff --git a/runtime/src/snapshot_utils/archive_format.rs b/runtime/src/snapshot_utils/archive_format.rs index 4ef271cbb5b..d807f4447a2 100644 --- a/runtime/src/snapshot_utils/archive_format.rs +++ b/runtime/src/snapshot_utils/archive_format.rs @@ -3,7 +3,13 @@ use { strum::Display, }; -pub const SUPPORTED_ARCHIVE_COMPRESSION: &[&str] = &["bz2", "gzip", "zstd", "lz4", "tar", "none"]; +// SUPPORTED_ARCHIVE_COMPRESSION lists the compression types that can be +// specified on the command line. "zstd" and "lz4" are valid whereas "gzip", +// "bz2", "tar" and "none" have been deprecated. Thus, all newly created +// snapshots will either use "zstd" or "lz4". By keeping the deprecated types +// in the ArchiveFormat enum, pre-existing snapshot archives with the +// deprecated compression types can still be read. +pub const SUPPORTED_ARCHIVE_COMPRESSION: &[&str] = &["zstd", "lz4"]; pub const DEFAULT_ARCHIVE_COMPRESSION: &str = "zstd"; pub const TAR_BZIP2_EXTENSION: &str = "tar.bz2"; @@ -36,11 +42,8 @@ impl ArchiveFormat { pub fn from_cli_arg(archive_format_str: &str) -> Option { match archive_format_str { - "bz2" => Some(ArchiveFormat::TarBzip2), - "gzip" => Some(ArchiveFormat::TarGzip), "zstd" => Some(ArchiveFormat::TarZstd), "lz4" => Some(ArchiveFormat::TarLz4), - "tar" | "none" => Some(ArchiveFormat::Tar), _ => None, } } @@ -158,14 +161,7 @@ mod tests { #[test] fn test_from_cli_arg() { - let golden = [ - Some(ArchiveFormat::TarBzip2), - Some(ArchiveFormat::TarGzip), - Some(ArchiveFormat::TarZstd), - Some(ArchiveFormat::TarLz4), - Some(ArchiveFormat::Tar), - Some(ArchiveFormat::Tar), - ]; + let golden = [Some(ArchiveFormat::TarZstd), Some(ArchiveFormat::TarLz4)]; for (arg, expected) in zip(SUPPORTED_ARCHIVE_COMPRESSION.iter(), golden.into_iter()) { assert_eq!(ArchiveFormat::from_cli_arg(arg), expected); diff --git a/runtime/store-tool/src/main.rs b/runtime/store-tool/src/main.rs index 98140ed59ba..8c2cf801488 100644 --- a/runtime/store-tool/src/main.rs +++ b/runtime/store-tool/src/main.rs @@ -11,7 +11,7 @@ use { }; fn main() { - solana_logger::setup_with_default("solana=info"); + solana_logger::setup_with_default_filter(); let matches = App::new(crate_name!()) .about(crate_description!()) .version(solana_version::version!()) diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 7eb23c42c2a..8142c301269 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,2 +1,2 @@ [toolchain] -channel = "1.72.1" +channel = "1.73.0" diff --git a/scripts/solana-install-deploy.sh b/scripts/agave-install-deploy.sh similarity index 79% rename from scripts/solana-install-deploy.sh rename to scripts/agave-install-deploy.sh index ea77ca34bc9..dcdec14ffb6 100755 --- a/scripts/solana-install-deploy.sh +++ b/scripts/agave-install-deploy.sh @@ -26,7 +26,7 @@ if [[ -z $URL || -z $TAG ]]; then fi if [[ ! -f update_manifest_keypair.json ]]; then - "$SOLANA_ROOT"/scripts/solana-install-update-manifest-keypair.sh "$OS" + "$SOLANA_ROOT"/scripts/agave-install-update-manifest-keypair.sh "$OS" fi case "$OS" in @@ -57,10 +57,10 @@ esac case $TAG in edge|beta) - DOWNLOAD_URL=https://release.solana.com/"$TAG"/solana-release-$TARGET.tar.bz2 + DOWNLOAD_URL=https://release.anza.xyz/"$TAG"/solana-release-$TARGET.tar.bz2 ;; *) - DOWNLOAD_URL=https://github.com/solana-labs/solana/releases/download/"$TAG"/solana-release-$TARGET.tar.bz2 + DOWNLOAD_URL=https://github.com/anza-xyz/agave/releases/download/"$TAG"/solana-release-$TARGET.tar.bz2 ;; esac @@ -76,4 +76,4 @@ if [[ $balance = "0 lamports" ]]; then fi # shellcheck disable=SC2086 # Don't want to double quote $maybeKeypair -solana-install deploy $maybeKeypair --url "$URL" "$DOWNLOAD_URL" update_manifest_keypair.json +agave-install deploy $maybeKeypair --url "$URL" "$DOWNLOAD_URL" update_manifest_keypair.json diff --git a/scripts/solana-install-update-manifest-keypair.sh b/scripts/agave-install-update-manifest-keypair.sh similarity index 100% rename from scripts/solana-install-update-manifest-keypair.sh rename to scripts/agave-install-update-manifest-keypair.sh diff --git a/scripts/cargo-clippy-nightly.sh b/scripts/cargo-clippy-nightly.sh new file mode 100755 index 00000000000..5393225542e --- /dev/null +++ b/scripts/cargo-clippy-nightly.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash + +set -o errexit + +here="$(dirname "$0")" +cargo="$(readlink -f "${here}/../cargo")" + +if [[ -z $cargo ]]; then + echo >&2 "Failed to find cargo. Mac readlink doesn't support -f. Consider switching + to gnu readlink with 'brew install coreutils' and then symlink greadlink as + /usr/local/bin/readlink." + exit 1 +fi + +# shellcheck source=ci/rust-version.sh +source "$here/../ci/rust-version.sh" nightly + +# Use nightly clippy, as frozen-abi proc-macro generates a lot of code across +# various crates in this whole monorepo (frozen-abi is enabled only under nightly +# due to the use of unstable rust feature). Likewise, frozen-abi(-macro) crates' +# unit tests are only compiled under nightly. +# Similarly, nightly is desired to run clippy over all of bench files because +# the bench itself isn't stabilized yet... +# ref: https://github.com/rust-lang/rust/issues/66287 +"$here/cargo-for-all-lock-files.sh" -- \ + "+${rust_nightly}" clippy \ + --workspace --all-targets --features dummy-for-ci-check -- \ + --deny=warnings \ + --deny=clippy::default_trait_access \ + --deny=clippy::arithmetic_side_effects \ + --deny=clippy::manual_let_else \ + --deny=clippy::used_underscore_binding \ + --allow=clippy::redundant_clone diff --git a/scripts/cargo-clippy-stable.sh b/scripts/cargo-clippy-stable.sh new file mode 100755 index 00000000000..ed564503e6a --- /dev/null +++ b/scripts/cargo-clippy-stable.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash + +set -o errexit + +here="$(dirname "$0")" +cargo="$(readlink -f "${here}/../cargo")" + +if [[ -z $cargo ]]; then + >&2 echo "Failed to find cargo. Mac readlink doesn't support -f. Consider switching + to gnu readlink with 'brew install coreutils' and then symlink greadlink as + /usr/local/bin/readlink." + exit 1 +fi + +# shellcheck source=ci/rust-version.sh +source "$here/../ci/rust-version.sh" stable + +# temporarily run stable clippy as well to scan the codebase for +# `redundant_clone`s, which is disabled as nightly clippy is buggy: +# https://github.com/solana-labs/solana/issues/31834 +# +# can't use --all-targets: +# error[E0554]: `#![feature]` may not be used on the stable release channel +"$here/cargo-for-all-lock-files.sh" -- \ + clippy \ + --workspace --tests --bins --examples --features dummy-for-ci-check -- \ + --deny=warnings \ + --deny=clippy::default_trait_access \ + --deny=clippy::arithmetic_side_effects \ + --deny=clippy::manual_let_else \ + --deny=clippy::used_underscore_binding diff --git a/scripts/cargo-clippy.sh b/scripts/cargo-clippy.sh new file mode 100755 index 00000000000..2be51de54fb --- /dev/null +++ b/scripts/cargo-clippy.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash + +# Runs `cargo clippy` in all individual workspaces in the repository. +# +# We have a number of clippy parameters that we want to enforce across the +# code base. They are defined here. +# +# This script is run by the CI, so if you want to replicate what the CI is +# doing, better run this script, rather than calling `cargo clippy` manually. +# +# TODO It would be nice to provide arguments to narrow clippy checks to a single +# workspace and/or package. To speed up the interactive workflow. + +set -o errexit + +here="$(dirname "$0")" + +# stable +"$here/cargo-clippy-stable.sh" + +# nightly +"$here/cargo-clippy-nightly.sh" diff --git a/scripts/cargo-install-all.sh b/scripts/cargo-install-all.sh index e1cbe9fc13d..029b1fbf279 100755 --- a/scripts/cargo-install-all.sh +++ b/scripts/cargo-install-all.sh @@ -17,7 +17,8 @@ if [[ $OSTYPE == darwin* ]]; then fi fi -cargo="$("${readlink_cmd}" -f "${here}/../cargo")" +SOLANA_ROOT="$("${readlink_cmd}" -f "${here}/..")" +cargo="${SOLANA_ROOT}/cargo" set -e @@ -28,22 +29,29 @@ usage() { echo "Error: $*" fi cat <] [--debug] [--validator-only] +usage: $0 [+] [--debug] [--validator-only] [--release-with-debug] EOF exit $exitcode } maybeRustVersion= installDir= -buildVariant=release -maybeReleaseFlag=--release +# buildProfileArg and buildProfile duplicate some information because cargo +# doesn't allow '--profile debug' but we still need to know that the binaries +# will be in target/debug +buildProfileArg='--profile release' +buildProfile='release' validatorOnly= while [[ -n $1 ]]; do if [[ ${1:0:1} = - ]]; then if [[ $1 = --debug ]]; then - maybeReleaseFlag= - buildVariant=debug + buildProfileArg= # the default cargo profile is 'debug' + buildProfile='debug' + shift + elif [[ $1 = --release-with-debug ]]; then + buildProfileArg='--profile release-with-debug' + buildProfile='release-with-debug' shift elif [[ $1 = --validator-only ]]; then validatorOnly=true @@ -68,7 +76,7 @@ fi installDir="$(mkdir -p "$installDir"; cd "$installDir"; pwd)" mkdir -p "$installDir/bin/deps" -echo "Install location: $installDir ($buildVariant)" +echo "Install location: $installDir ($buildProfile)" cd "$(dirname "$0")"/.. @@ -83,8 +91,8 @@ if [[ $CI_OS_NAME = windows ]]; then cargo-test-bpf cargo-test-sbf solana - solana-install - solana-install-init + agave-install + agave-install-init solana-keygen solana-stake-accounts solana-test-validator @@ -98,12 +106,12 @@ else solana-bench-tps solana-faucet solana-gossip - solana-install + agave-install solana-keygen - solana-ledger-tool + agave-ledger-tool solana-log-analyzer solana-net-shaper - solana-validator + agave-validator rbpf-cli ) @@ -115,11 +123,11 @@ else cargo-test-bpf cargo-test-sbf solana-dos - solana-install-init + agave-install-init solana-stake-accounts solana-test-validator solana-tokens - solana-watchtower + agave-watchtower ) fi @@ -138,21 +146,24 @@ mkdir -p "$installDir/bin" ( set -x # shellcheck disable=SC2086 # Don't want to double quote $rust_version - "$cargo" $maybeRustVersion build $maybeReleaseFlag "${binArgs[@]}" + "$cargo" $maybeRustVersion build $buildProfileArg "${binArgs[@]}" # Exclude `spl-token` binary for net.sh builds if [[ -z "$validatorOnly" ]]; then + # shellcheck source=scripts/spl-token-cli-version.sh + source "$SOLANA_ROOT"/scripts/spl-token-cli-version.sh + # the patch-related configs are needed for rust 1.69+ on Windows; see Cargo.toml # shellcheck disable=SC2086 # Don't want to double quote $rust_version "$cargo" $maybeRustVersion \ --config 'patch.crates-io.ntapi.git="https://github.com/solana-labs/ntapi"' \ --config 'patch.crates-io.ntapi.rev="97ede981a1777883ff86d142b75024b023f04fad"' \ - install --locked spl-token-cli --root "$installDir" + install --locked spl-token-cli --root "$installDir" $maybeSplTokenCliVersionArg fi ) for bin in "${BINS[@]}"; do - cp -fv "target/$buildVariant/$bin" "$installDir"/bin + cp -fv "target/$buildProfile/$bin" "$installDir"/bin done if [[ -d target/perf-libs ]]; then @@ -171,7 +182,7 @@ fi # Add Solidity Compiler if [[ -z "$validatorOnly" ]]; then base="https://github.com/hyperledger/solang/releases/download" - version="v0.3.2" + version="v0.3.3" curlopt="-sSfL --retry 5 --retry-delay 2 --retry-connrefused" case $(uname -s) in @@ -206,7 +217,7 @@ fi set -x # deps dir can be empty shopt -s nullglob - for dep in target/"$buildVariant"/deps/libsolana*program.*; do + for dep in target/"$buildProfile"/deps/libsolana*program.*; do cp -fv "$dep" "$installDir/bin/deps" done ) diff --git a/scripts/run.sh b/scripts/run.sh index a890aa10c17..70994c921f4 100755 --- a/scripts/run.sh +++ b/scripts/run.sh @@ -23,9 +23,11 @@ fi PATH=$PWD/target/$profile:$PATH ok=true -for program in solana-{faucet,genesis,keygen,validator}; do +for program in solana-{faucet,genesis,keygen}; do $program -V || ok=false done +agave-validator -V || ok=false + $ok || { echo echo "Unable to locate required programs. Try building them first with:" @@ -35,7 +37,7 @@ $ok || { exit 1 } -export RUST_LOG=${RUST_LOG:-solana=info,solana_runtime::message_processor=debug} # if RUST_LOG is unset, default to info +export RUST_LOG=${RUST_LOG:-solana=info,agave=info,solana_runtime::message_processor=debug} # if RUST_LOG is unset, default to info export RUST_BACKTRACE=1 dataDir=$PWD/config/"$(basename "$0" .sh)" ledgerDir=$PWD/config/ledger @@ -110,13 +112,12 @@ args=( --enable-rpc-transaction-history --enable-extended-tx-metadata-storage --init-complete-file "$dataDir"/init-completed - --snapshot-compression none --require-tower --no-wait-for-vote-to-start-leader --no-os-network-limits-test ) # shellcheck disable=SC2086 -solana-validator "${args[@]}" $SOLANA_RUN_SH_VALIDATOR_ARGS & +agave-validator "${args[@]}" $SOLANA_RUN_SH_VALIDATOR_ARGS & validator=$! wait "$validator" diff --git a/scripts/spl-token-cli-version.sh b/scripts/spl-token-cli-version.sh new file mode 100644 index 00000000000..cbee878c443 --- /dev/null +++ b/scripts/spl-token-cli-version.sh @@ -0,0 +1,8 @@ +# populate this on the stable branch +splTokenCliVersion=3.3.0 + +maybeSplTokenCliVersionArg= +if [[ -n "$splTokenCliVersion" ]]; then + # shellcheck disable=SC2034 + maybeSplTokenCliVersionArg="--version $splTokenCliVersion" +fi diff --git a/sdk/cargo-build-sbf/tests/crates/fail/Cargo.toml b/sdk/cargo-build-sbf/tests/crates/fail/Cargo.toml index a5ecf6a3a38..75683c2e17c 100644 --- a/sdk/cargo-build-sbf/tests/crates/fail/Cargo.toml +++ b/sdk/cargo-build-sbf/tests/crates/fail/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "fail" -version = "1.17.0" +version = "1.17.30" description = "Solana SBF test program written in Rust" authors = ["Solana Labs Maintainers "] repository = "https://github.com/solana-labs/solana" @@ -10,7 +10,7 @@ edition = "2021" publish = false [dependencies] -solana-program = { path = "../../../../program", version = "=1.17.0" } +solana-program = { path = "../../../../program", version = "=1.17.30" } [lib] crate-type = ["cdylib"] diff --git a/sdk/cargo-build-sbf/tests/crates/noop/Cargo.toml b/sdk/cargo-build-sbf/tests/crates/noop/Cargo.toml index a05351e3772..f032115b8cc 100644 --- a/sdk/cargo-build-sbf/tests/crates/noop/Cargo.toml +++ b/sdk/cargo-build-sbf/tests/crates/noop/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "noop" -version = "1.17.0" +version = "1.17.30" description = "Solana SBF test program written in Rust" authors = ["Solana Labs Maintainers "] repository = "https://github.com/solana-labs/solana" @@ -10,7 +10,7 @@ edition = "2021" publish = false [dependencies] -solana-program = { path = "../../../../program", version = "=1.17.0" } +solana-program = { path = "../../../../program", version = "=1.17.30" } [lib] crate-type = ["cdylib"] diff --git a/sdk/docker-solana/build.sh b/sdk/docker-solana/build.sh index 77160d73edb..dba9c1c6e5b 100755 --- a/sdk/docker-solana/build.sh +++ b/sdk/docker-solana/build.sh @@ -30,7 +30,7 @@ cp -f ../../fetch-spl.sh usr/bin/ ./fetch-spl.sh ) -docker build -t solanalabs/solana:"$CHANNEL_OR_TAG" . +docker build -t anzaxyz/agave:"$CHANNEL_OR_TAG" . maybeEcho= if [[ -z $CI ]]; then @@ -44,4 +44,4 @@ else fi ) fi -$maybeEcho docker push solanalabs/solana:"$CHANNEL_OR_TAG" +$maybeEcho docker push anzaxyz/agave:"$CHANNEL_OR_TAG" diff --git a/sdk/program/src/clock.rs b/sdk/program/src/clock.rs index 45f49f218b1..e988bafb21d 100644 --- a/sdk/program/src/clock.rs +++ b/sdk/program/src/clock.rs @@ -44,10 +44,43 @@ pub const DEFAULT_TICKS_PER_SLOT: u64 = 64; // GCP n1-standard hardware and also a xeon e5-2520 v4 are about this rate of hashes/s pub const DEFAULT_HASHES_PER_SECOND: u64 = 2_000_000; +// Empirical sampling of mainnet validator hash rate showed the following stake +// percentages can exceed the designated hash rates as of July 2023: +// 97.6% +pub const UPDATED_HASHES_PER_SECOND_2: u64 = 2_800_000; +// 96.2% +pub const UPDATED_HASHES_PER_SECOND_3: u64 = 4_400_000; +// 96.2% +pub const UPDATED_HASHES_PER_SECOND_4: u64 = 7_600_000; +// 96.2% +pub const UPDATED_HASHES_PER_SECOND_5: u64 = 9_200_000; +// 96.2% +pub const UPDATED_HASHES_PER_SECOND_6: u64 = 10_000_000; + #[cfg(test)] static_assertions::const_assert_eq!(DEFAULT_HASHES_PER_TICK, 12_500); pub const DEFAULT_HASHES_PER_TICK: u64 = DEFAULT_HASHES_PER_SECOND / DEFAULT_TICKS_PER_SECOND; +#[cfg(test)] +static_assertions::const_assert_eq!(UPDATED_HASHES_PER_TICK2, 17_500); +pub const UPDATED_HASHES_PER_TICK2: u64 = UPDATED_HASHES_PER_SECOND_2 / DEFAULT_TICKS_PER_SECOND; + +#[cfg(test)] +static_assertions::const_assert_eq!(UPDATED_HASHES_PER_TICK3, 27_500); +pub const UPDATED_HASHES_PER_TICK3: u64 = UPDATED_HASHES_PER_SECOND_3 / DEFAULT_TICKS_PER_SECOND; + +#[cfg(test)] +static_assertions::const_assert_eq!(UPDATED_HASHES_PER_TICK4, 47_500); +pub const UPDATED_HASHES_PER_TICK4: u64 = UPDATED_HASHES_PER_SECOND_4 / DEFAULT_TICKS_PER_SECOND; + +#[cfg(test)] +static_assertions::const_assert_eq!(UPDATED_HASHES_PER_TICK5, 57_500); +pub const UPDATED_HASHES_PER_TICK5: u64 = UPDATED_HASHES_PER_SECOND_5 / DEFAULT_TICKS_PER_SECOND; + +#[cfg(test)] +static_assertions::const_assert_eq!(UPDATED_HASHES_PER_TICK6, 62_500); +pub const UPDATED_HASHES_PER_TICK6: u64 = UPDATED_HASHES_PER_SECOND_6 / DEFAULT_TICKS_PER_SECOND; + // 1 Dev Epoch = 400 ms * 8192 ~= 55 minutes pub const DEFAULT_DEV_SLOTS_PER_EPOCH: u64 = 8192; diff --git a/sdk/program/src/feature.rs b/sdk/program/src/feature.rs index 25a3ec8a45b..b46704ebcb9 100644 --- a/sdk/program/src/feature.rs +++ b/sdk/program/src/feature.rs @@ -30,9 +30,10 @@ impl Feature { pub fn from_account_info(account_info: &AccountInfo) -> Result { if *account_info.owner != id() { - return Err(ProgramError::InvalidArgument); + return Err(ProgramError::InvalidAccountOwner); } - bincode::deserialize(&account_info.data.borrow()).map_err(|_| ProgramError::InvalidArgument) + bincode::deserialize(&account_info.data.borrow()) + .map_err(|_| ProgramError::InvalidAccountData) } } diff --git a/sdk/program/src/message/legacy.rs b/sdk/program/src/message/legacy.rs index e81c7c485ff..7c00813fcf6 100644 --- a/sdk/program/src/message/legacy.rs +++ b/sdk/program/src/message/legacy.rs @@ -26,7 +26,7 @@ use { }; lazy_static! { - // Copied keys over since direct references create cyclical dependency. + // This will be deprecated and so this list shouldn't be modified pub static ref BUILTIN_PROGRAMS_KEYS: [Pubkey; 10] = { let parse = |s| Pubkey::from_str(s).unwrap(); [ diff --git a/sdk/program/src/message/sanitized.rs b/sdk/program/src/message/sanitized.rs index 640159a7ad2..066f4505e40 100644 --- a/sdk/program/src/message/sanitized.rs +++ b/sdk/program/src/message/sanitized.rs @@ -347,23 +347,75 @@ impl SanitizedMessage { } pub fn num_signatures(&self) -> u64 { - let mut num_signatures = u64::from(self.header().num_required_signatures); - // This next part is really calculating the number of pre-processor - // operations being done and treating them like a signature + self.get_signature_details().total_signatures() + } + + pub fn num_write_locks(&self) -> u64 { + self.account_keys() + .len() + .saturating_sub(self.num_readonly_accounts()) as u64 + } + + /// return detailed signature counts + pub fn get_signature_details(&self) -> TransactionSignatureDetails { + let mut transaction_signature_details = TransactionSignatureDetails { + num_transaction_signatures: u64::from(self.header().num_required_signatures), + ..TransactionSignatureDetails::default() + }; + + // counting the number of pre-processor operations separately for (program_id, instruction) in self.program_instructions_iter() { - if secp256k1_program::check_id(program_id) || ed25519_program::check_id(program_id) { + if secp256k1_program::check_id(program_id) { if let Some(num_verifies) = instruction.data.first() { - num_signatures = num_signatures.saturating_add(u64::from(*num_verifies)); + transaction_signature_details.num_secp256k1_instruction_signatures = + transaction_signature_details + .num_secp256k1_instruction_signatures + .saturating_add(u64::from(*num_verifies)); + } + } else if ed25519_program::check_id(program_id) { + if let Some(num_verifies) = instruction.data.first() { + transaction_signature_details.num_ed25519_instruction_signatures = + transaction_signature_details + .num_ed25519_instruction_signatures + .saturating_add(u64::from(*num_verifies)); } } } - num_signatures + + transaction_signature_details } +} - pub fn num_write_locks(&self) -> u64 { - self.account_keys() - .len() - .saturating_sub(self.num_readonly_accounts()) as u64 +#[derive(Default)] +/// Transaction signature details including the number of transaction signatures +/// and precompile signatures. +pub struct TransactionSignatureDetails { + num_transaction_signatures: u64, + num_secp256k1_instruction_signatures: u64, + num_ed25519_instruction_signatures: u64, +} + +impl TransactionSignatureDetails { + /// return total number of signature, treating pre-processor operations as signature + pub(crate) fn total_signatures(&self) -> u64 { + self.num_transaction_signatures + .saturating_add(self.num_secp256k1_instruction_signatures) + .saturating_add(self.num_ed25519_instruction_signatures) + } + + /// return the number of transaction signatures + pub fn num_transaction_signatures(&self) -> u64 { + self.num_transaction_signatures + } + + /// return the number of secp256k1 instruction signatures + pub fn num_secp256k1_instruction_signatures(&self) -> u64 { + self.num_secp256k1_instruction_signatures + } + + /// return the number of ed25519 instruction signatures + pub fn num_ed25519_instruction_signatures(&self) -> u64 { + self.num_ed25519_instruction_signatures } } @@ -559,4 +611,44 @@ mod tests { } } } + + #[test] + fn test_get_signature_details() { + let key0 = Pubkey::new_unique(); + let key1 = Pubkey::new_unique(); + let loader_key = Pubkey::new_unique(); + + let loader_instr = CompiledInstruction::new(2, &(), vec![0, 1]); + let mock_secp256k1_instr = CompiledInstruction::new(3, &[1u8; 10], vec![]); + let mock_ed25519_instr = CompiledInstruction::new(4, &[5u8; 10], vec![]); + + let message = SanitizedMessage::try_from(legacy::Message::new_with_compiled_instructions( + 2, + 1, + 2, + vec![ + key0, + key1, + loader_key, + secp256k1_program::id(), + ed25519_program::id(), + ], + Hash::default(), + vec![ + loader_instr, + mock_secp256k1_instr.clone(), + mock_ed25519_instr, + mock_secp256k1_instr, + ], + )) + .unwrap(); + + let signature_details = message.get_signature_details(); + // expect 2 required transaction signatures + assert_eq!(2, signature_details.num_transaction_signatures); + // expect 2 secp256k1 instruction signatures - 1 for each mock_secp2561k1_instr + assert_eq!(2, signature_details.num_secp256k1_instruction_signatures); + // expect 5 ed25519 instruction signatures from mock_ed25519_instr + assert_eq!(5, signature_details.num_ed25519_instruction_signatures); + } } diff --git a/sdk/program/src/message/versions/mod.rs b/sdk/program/src/message/versions/mod.rs index ccef565fd51..301490a2aa7 100644 --- a/sdk/program/src/message/versions/mod.rs +++ b/sdk/program/src/message/versions/mod.rs @@ -8,7 +8,7 @@ use { short_vec, }, serde::{ - de::{self, Deserializer, SeqAccess, Visitor}, + de::{self, Deserializer, SeqAccess, Unexpected, Visitor}, ser::{SerializeTuple, Serializer}, Deserialize, Serialize, }, @@ -198,7 +198,16 @@ impl<'de> Deserialize<'de> for MessagePrefix { formatter.write_str("message prefix byte") } - fn visit_u8(self, byte: u8) -> Result { + // Serde's integer visitors bubble up to u64 so check the prefix + // with this function instead of visit_u8. This approach is + // necessary because serde_json directly calls visit_u64 for + // unsigned integers. + fn visit_u64(self, value: u64) -> Result { + if value > u8::MAX as u64 { + Err(de::Error::invalid_type(Unexpected::Unsigned(value), &self))?; + } + + let byte = value as u8; if byte & MESSAGE_VERSION_PREFIX != 0 { Ok(MessagePrefix::Versioned(byte & !MESSAGE_VERSION_PREFIX)) } else { @@ -331,26 +340,32 @@ mod tests { let mut message = LegacyMessage::new(&instructions, Some(&id1)); message.recent_blockhash = Hash::new_unique(); + let wrapped_message = VersionedMessage::Legacy(message.clone()); - let bytes1 = bincode::serialize(&message).unwrap(); - let bytes2 = bincode::serialize(&VersionedMessage::Legacy(message.clone())).unwrap(); + // bincode + { + let bytes = bincode::serialize(&message).unwrap(); + assert_eq!(bytes, bincode::serialize(&wrapped_message).unwrap()); - assert_eq!(bytes1, bytes2); + let message_from_bytes: LegacyMessage = bincode::deserialize(&bytes).unwrap(); + let wrapped_message_from_bytes: VersionedMessage = + bincode::deserialize(&bytes).unwrap(); - let message1: LegacyMessage = bincode::deserialize(&bytes1).unwrap(); - let message2: VersionedMessage = bincode::deserialize(&bytes2).unwrap(); + assert_eq!(message, message_from_bytes); + assert_eq!(wrapped_message, wrapped_message_from_bytes); + } - if let VersionedMessage::Legacy(message2) = message2 { - assert_eq!(message, message1); - assert_eq!(message1, message2); - } else { - panic!("should deserialize to legacy message"); + // serde_json + { + let string = serde_json::to_string(&message).unwrap(); + let message_from_string: LegacyMessage = serde_json::from_str(&string).unwrap(); + assert_eq!(message, message_from_string); } } #[test] fn test_versioned_message_serialization() { - let message = v0::Message { + let message = VersionedMessage::V0(v0::Message { header: MessageHeader { num_required_signatures: 1, num_readonly_signed_accounts: 0, @@ -375,15 +390,14 @@ mod tests { accounts: vec![0, 2, 3, 4], data: vec![], }], - }; + }); - let bytes = bincode::serialize(&VersionedMessage::V0(message.clone())).unwrap(); + let bytes = bincode::serialize(&message).unwrap(); let message_from_bytes: VersionedMessage = bincode::deserialize(&bytes).unwrap(); + assert_eq!(message, message_from_bytes); - if let VersionedMessage::V0(message_from_bytes) = message_from_bytes { - assert_eq!(message, message_from_bytes); - } else { - panic!("should deserialize to versioned message"); - } + let string = serde_json::to_string(&message).unwrap(); + let message_from_string: VersionedMessage = serde_json::from_str(&string).unwrap(); + assert_eq!(message, message_from_string); } } diff --git a/sdk/program/src/poseidon.rs b/sdk/program/src/poseidon.rs index c23cded6db9..9c02fe90bc8 100644 --- a/sdk/program/src/poseidon.rs +++ b/sdk/program/src/poseidon.rs @@ -21,12 +21,16 @@ pub enum PoseidonSyscallError { "Invalid length of the input. The length matching the modulus of the prime field is 32." )] InvalidInputLength, + #[error("Failed to convert bytest into a prime field element.")] + BytesToPrimeFieldElement, #[error("Input is larger than the modulus of the prime field.")] InputLargerThanModulus, #[error("Failed to convert a vector of bytes into an array.")] VecToArray, #[error("Failed to convert the number of inputs from u64 to u8.")] U64Tou8, + #[error("Failed to convert bytes to BigInt")] + BytesToBigInt, #[error("Invalid width. Choose a width between 2 and 16 for 1 to 15 inputs.")] InvalidWidthCircom, #[error("Unexpected error")] @@ -41,10 +45,12 @@ impl From for PoseidonSyscallError { 3 => PoseidonSyscallError::InvalidNumberOfInputs, 4 => PoseidonSyscallError::EmptyInput, 5 => PoseidonSyscallError::InvalidInputLength, - 6 => PoseidonSyscallError::InputLargerThanModulus, - 7 => PoseidonSyscallError::VecToArray, - 8 => PoseidonSyscallError::U64Tou8, - 9 => PoseidonSyscallError::InvalidWidthCircom, + 6 => PoseidonSyscallError::BytesToPrimeFieldElement, + 7 => PoseidonSyscallError::InputLargerThanModulus, + 8 => PoseidonSyscallError::VecToArray, + 9 => PoseidonSyscallError::U64Tou8, + 10 => PoseidonSyscallError::BytesToBigInt, + 11 => PoseidonSyscallError::InvalidWidthCircom, _ => PoseidonSyscallError::Unexpected, } } @@ -58,11 +64,13 @@ impl From for u64 { PoseidonSyscallError::InvalidNumberOfInputs => 3, PoseidonSyscallError::EmptyInput => 4, PoseidonSyscallError::InvalidInputLength => 5, - PoseidonSyscallError::InputLargerThanModulus => 6, - PoseidonSyscallError::VecToArray => 7, - PoseidonSyscallError::U64Tou8 => 8, - PoseidonSyscallError::InvalidWidthCircom => 9, - PoseidonSyscallError::Unexpected => 10, + PoseidonSyscallError::BytesToPrimeFieldElement => 6, + PoseidonSyscallError::InputLargerThanModulus => 7, + PoseidonSyscallError::VecToArray => 8, + PoseidonSyscallError::U64Tou8 => 9, + PoseidonSyscallError::BytesToBigInt => 10, + PoseidonSyscallError::InvalidWidthCircom => 11, + PoseidonSyscallError::Unexpected => 12, } } } @@ -210,25 +218,25 @@ pub fn hashv( impl From for PoseidonSyscallError { fn from(error: PoseidonError) -> Self { match error { - PoseidonError::InvalidNumberOfInputs { - inputs: _, - max_limit: _, - width: _, - } => PoseidonSyscallError::InvalidNumberOfInputs, + PoseidonError::InvalidNumberOfInputs { .. } => { + PoseidonSyscallError::InvalidNumberOfInputs + } PoseidonError::EmptyInput => PoseidonSyscallError::EmptyInput, - PoseidonError::InvalidInputLength { - len: _, - modulus_bytes_len: _, - } => PoseidonSyscallError::InvalidInputLength, + PoseidonError::InvalidInputLength { .. } => { + PoseidonSyscallError::InvalidInputLength + } + PoseidonError::BytesToPrimeFieldElement { .. } => { + PoseidonSyscallError::BytesToPrimeFieldElement + } PoseidonError::InputLargerThanModulus => { PoseidonSyscallError::InputLargerThanModulus } PoseidonError::VecToArray => PoseidonSyscallError::VecToArray, PoseidonError::U64Tou8 => PoseidonSyscallError::U64Tou8, - PoseidonError::InvalidWidthCircom { - width: _, - max_limit: _, - } => PoseidonSyscallError::InvalidWidthCircom, + PoseidonError::BytesToBigInt => PoseidonSyscallError::BytesToBigInt, + PoseidonError::InvalidWidthCircom { .. } => { + PoseidonSyscallError::InvalidWidthCircom + } } } } diff --git a/sdk/program/src/program_error.rs b/sdk/program/src/program_error.rs index a0d217a96b8..6eb7e9ecd71 100644 --- a/sdk/program/src/program_error.rs +++ b/sdk/program/src/program_error.rs @@ -59,6 +59,10 @@ pub enum ProgramError { MaxInstructionTraceLengthExceeded, #[error("Builtin programs must consume compute units")] BuiltinProgramsMustConsumeComputeUnits, + #[error("Invalid account owner")] + InvalidAccountOwner, + #[error("Program arithmetic overflowed")] + ArithmeticOverflow, } pub trait PrintProgramError { @@ -107,6 +111,8 @@ impl PrintProgramError for ProgramError { Self::BuiltinProgramsMustConsumeComputeUnits => { msg!("Error: BuiltinProgramsMustConsumeComputeUnits") } + Self::InvalidAccountOwner => msg!("Error: InvalidAccountOwner"), + Self::ArithmeticOverflow => msg!("Error: ArithmeticOverflow"), } } } @@ -141,6 +147,8 @@ pub const MAX_ACCOUNTS_DATA_ALLOCATIONS_EXCEEDED: u64 = to_builtin!(19); pub const INVALID_ACCOUNT_DATA_REALLOC: u64 = to_builtin!(20); pub const MAX_INSTRUCTION_TRACE_LENGTH_EXCEEDED: u64 = to_builtin!(21); pub const BUILTIN_PROGRAMS_MUST_CONSUME_COMPUTE_UNITS: u64 = to_builtin!(22); +pub const INVALID_ACCOUNT_OWNER: u64 = to_builtin!(23); +pub const ARITHMETIC_OVERFLOW: u64 = to_builtin!(24); // Warning: Any new program errors added here must also be: // - Added to the below conversions // - Added as an equivalent to InstructionError @@ -177,6 +185,8 @@ impl From for u64 { ProgramError::BuiltinProgramsMustConsumeComputeUnits => { BUILTIN_PROGRAMS_MUST_CONSUME_COMPUTE_UNITS } + ProgramError::InvalidAccountOwner => INVALID_ACCOUNT_OWNER, + ProgramError::ArithmeticOverflow => ARITHMETIC_OVERFLOW, ProgramError::Custom(error) => { if error == 0 { CUSTOM_ZERO @@ -215,6 +225,8 @@ impl From for ProgramError { BUILTIN_PROGRAMS_MUST_CONSUME_COMPUTE_UNITS => { Self::BuiltinProgramsMustConsumeComputeUnits } + INVALID_ACCOUNT_OWNER => Self::InvalidAccountOwner, + ARITHMETIC_OVERFLOW => Self::ArithmeticOverflow, _ => Self::Custom(error as u32), } } @@ -253,6 +265,8 @@ impl TryFrom for ProgramError { Self::Error::BuiltinProgramsMustConsumeComputeUnits => { Ok(Self::BuiltinProgramsMustConsumeComputeUnits) } + Self::Error::InvalidAccountOwner => Ok(Self::InvalidAccountOwner), + Self::Error::ArithmeticOverflow => Ok(Self::ArithmeticOverflow), _ => Err(error), } } @@ -289,6 +303,8 @@ where BUILTIN_PROGRAMS_MUST_CONSUME_COMPUTE_UNITS => { Self::BuiltinProgramsMustConsumeComputeUnits } + INVALID_ACCOUNT_OWNER => Self::InvalidAccountOwner, + ARITHMETIC_OVERFLOW => Self::ArithmeticOverflow, _ => { // A valid custom error has no bits set in the upper 32 if error >> BUILTIN_BIT_SHIFT == 0 { diff --git a/sdk/program/src/sysvar/mod.rs b/sdk/program/src/sysvar/mod.rs index 1bb7c12b33a..5a5afec8bcf 100644 --- a/sdk/program/src/sysvar/mod.rs +++ b/sdk/program/src/sysvar/mod.rs @@ -100,6 +100,7 @@ pub mod slot_history; pub mod stake_history; lazy_static! { + // This will be deprecated and so this list shouldn't be modified pub static ref ALL_IDS: Vec = vec![ clock::id(), epoch_schedule::id(), @@ -113,8 +114,6 @@ lazy_static! { slot_history::id(), stake_history::id(), instructions::id(), - epoch_rewards::id(), - last_restart_slot::id(), ]; } @@ -138,12 +137,6 @@ macro_rules! declare_sysvar_id( check_id(pubkey) } } - - #[cfg(test)] - #[test] - fn test_sysvar_id() { - assert!($crate::sysvar::is_sysvar_id(&id()), "sysvar::is_sysvar_id() doesn't know about {}", $name); - } ) ); @@ -164,12 +157,6 @@ macro_rules! declare_deprecated_sysvar_id( check_id(pubkey) } } - - #[cfg(test)] - #[test] - fn test_sysvar_id() { - assert!($crate::sysvar::is_sysvar_id(&id()), "sysvar::is_sysvar_id() doesn't know about {}", $name); - } ) ); diff --git a/sdk/program/src/vote/state/mod.rs b/sdk/program/src/vote/state/mod.rs index 6d77d3ab5d9..eb9ec9282f4 100644 --- a/sdk/program/src/vote/state/mod.rs +++ b/sdk/program/src/vote/state/mod.rs @@ -325,6 +325,24 @@ impl VoteState { } } + pub fn new_rand_for_tests(node_pubkey: Pubkey, root_slot: Slot) -> Self { + let votes = (1..32) + .map(|x| LandedVote { + latency: 0, + lockout: Lockout::new_with_confirmation_count( + u64::from(x).saturating_add(root_slot), + 32_u32.saturating_sub(x), + ), + }) + .collect(); + Self { + node_pubkey, + root_slot: Some(root_slot), + votes, + ..VoteState::default() + } + } + pub fn get_authorized_voter(&self, epoch: Epoch) -> Option { self.authorized_voters.get_authorized_voter(epoch) } @@ -430,6 +448,8 @@ impl VoteState { next_vote_slot: Slot, epoch: Epoch, current_slot: Slot, + timely_vote_credits: bool, + deprecate_unused_legacy_vote_plumbing: bool, ) { // Ignore votes for slots earlier than we already have votes for if self @@ -442,13 +462,21 @@ impl VoteState { self.pop_expired_votes(next_vote_slot); let landed_vote = LandedVote { - latency: Self::compute_vote_latency(next_vote_slot, current_slot), + latency: if timely_vote_credits || !deprecate_unused_legacy_vote_plumbing { + Self::compute_vote_latency(next_vote_slot, current_slot) + } else { + 0 + }, lockout: Lockout::new(next_vote_slot), }; // Once the stack is full, pop the oldest lockout and distribute rewards if self.votes.len() == MAX_LOCKOUT_HISTORY { - let credits = self.credits_for_vote_at_index(0); + let credits = self.credits_for_vote_at_index( + 0, + timely_vote_credits, + deprecate_unused_legacy_vote_plumbing, + ); let landed_vote = self.votes.pop_front().unwrap(); self.root_slot = Some(landed_vote.slot()); @@ -493,7 +521,12 @@ impl VoteState { } /// Returns the credits to award for a vote at the given lockout slot index - pub fn credits_for_vote_at_index(&self, index: usize) -> u64 { + pub fn credits_for_vote_at_index( + &self, + index: usize, + timely_vote_credits: bool, + deprecate_unused_legacy_vote_plumbing: bool, + ) -> u64 { let latency = self .votes .get(index) @@ -501,7 +534,7 @@ impl VoteState { // If latency is 0, this means that the Lockout was created and stored from a software version that did not // store vote latencies; in this case, 1 credit is awarded - if latency == 0 { + if latency == 0 || (deprecate_unused_legacy_vote_plumbing && !timely_vote_credits) { 1 } else { match latency.checked_sub(VOTE_CREDITS_GRACE_SLOTS) { diff --git a/sdk/src/feature_set.rs b/sdk/src/feature_set.rs index b414a5f6ab4..86c0b9e6678 100644 --- a/sdk/src/feature_set.rs +++ b/sdk/src/feature_set.rs @@ -146,6 +146,10 @@ pub mod curve25519_syscall_enabled { solana_sdk::declare_id!("7rcw5UtqgDTBBv2EcynNfYckgdAaH1MAsCjKgXMkN7Ri"); } +pub mod curve25519_restrict_msm_length { + solana_sdk::declare_id!("eca6zf6JJRjQsYYPkBHF3N32MTzur4n2WL4QiiacPCL"); +} + pub mod versioned_tx_message_enabled { solana_sdk::declare_id!("3KZZ6Ks1885aGBQ45fwRcPXVBCtzUvxhUTkwKMR41Tca"); } @@ -283,7 +287,7 @@ pub mod stake_deactivate_delinquent_instruction { } pub mod stake_redelegate_instruction { - solana_sdk::declare_id!("GUrp5BKMyDazsAp9mBoVD6orE5ihXNRPC3jkBRfx6Lq7"); + solana_sdk::declare_id!("2KKG3C6RBnxQo9jVVrbzsoSh41TDXLK7gBc9gduyxSzW"); } pub mod vote_withdraw_authority_may_change_authorized_voter { @@ -370,7 +374,7 @@ pub mod update_rewards_from_cached_accounts { solana_sdk::declare_id!("28s7i3htzhahXQKqmS2ExzbEoUypg9krwvtK2M9UWXh9"); } pub mod enable_partitioned_epoch_reward { - solana_sdk::declare_id!("HCnE3xQoZtDz9dSVm3jKwJXioTb6zMRbgwCmGg3PHHk8"); + solana_sdk::declare_id!("41tVp5qR1XwWRt5WifvtSQyuxtqQWJgEK8w91AtBqSwP"); } pub mod spl_token_v3_4_0 { @@ -399,7 +403,7 @@ pub mod stake_raise_minimum_delegation_to_1_sol { } pub mod stake_minimum_delegation_for_rewards { - solana_sdk::declare_id!("ELjxSXwNsyXGfAh8TqX8ih22xeT8huF6UngQirbLKYKH"); + solana_sdk::declare_id!("G6ANXD6ptCSyNd9znZm7j4dEczAJCfx7Cy43oBx3rKHJ"); } pub mod add_set_compute_unit_price_ix { @@ -700,6 +704,54 @@ pub mod better_error_codes_for_tx_lamport_check { solana_sdk::declare_id!("Ffswd3egL3tccB6Rv3XY6oqfdzn913vUcjCSnpvCKpfx"); } +pub mod update_hashes_per_tick2 { + solana_sdk::declare_id!("EWme9uFqfy1ikK1jhJs8fM5hxWnK336QJpbscNtizkTU"); +} + +pub mod update_hashes_per_tick3 { + solana_sdk::declare_id!("8C8MCtsab5SsfammbzvYz65HHauuUYdbY2DZ4sznH6h5"); +} + +pub mod update_hashes_per_tick4 { + solana_sdk::declare_id!("8We4E7DPwF2WfAN8tRTtWQNhi98B99Qpuj7JoZ3Aikgg"); +} + +pub mod update_hashes_per_tick5 { + solana_sdk::declare_id!("BsKLKAn1WM4HVhPRDsjosmqSg2J8Tq5xP2s2daDS6Ni4"); +} + +pub mod update_hashes_per_tick6 { + solana_sdk::declare_id!("FKu1qYwLQSiehz644H6Si65U5ZQ2cp9GxsyFUfYcuADv"); +} + +pub mod validate_fee_collector_account { + solana_sdk::declare_id!("prpFrMtgNmzaNzkPJg9o753fVvbHKqNrNTm76foJ2wm"); +} + +pub mod enable_zk_transfer_with_fee { + solana_sdk::declare_id!("zkNLP7EQALfC1TYeB3biDU7akDckj8iPkvh9y2Mt2K3"); +} + +pub mod drop_legacy_shreds { + solana_sdk::declare_id!("GV49KKQdBNaiv2pgqhS2Dy3GWYJGXMTVYbYkdk91orRy"); +} + +pub mod consume_blockstore_duplicate_proofs { + solana_sdk::declare_id!("6YsBCejwK96GZCkJ6mkZ4b68oP63z2PLoQmWjC7ggTqZ"); +} + +pub mod index_erasure_conflict_duplicate_proofs { + solana_sdk::declare_id!("dupPajaLy2SSn8ko42aZz4mHANDNrLe8Nw8VQgFecLa"); +} + +pub mod disable_bpf_loader_instructions { + solana_sdk::declare_id!("7WeS1vfPRgeeoXArLh7879YcB9mgE9ktjPDtajXeWfXn"); +} + +pub mod deprecate_unused_legacy_vote_plumbing { + solana_sdk::declare_id!("6Uf8S75PVh91MYgPQSHnjRAPQq6an5BDv9vomrCwDqLe"); +} + lazy_static! { /// Map of feature identifiers to user-visible description pub static ref FEATURE_NAMES: HashMap = [ @@ -870,6 +922,19 @@ lazy_static! { (require_rent_exempt_split_destination::id(), "Require stake split destination account to be rent exempt"), (better_error_codes_for_tx_lamport_check::id(), "better error codes for tx lamport check #33353"), (enable_alt_bn128_compression_syscall::id(), "add alt_bn128 compression syscalls"), + (update_hashes_per_tick2::id(), "Update desired hashes per tick to 2.8M"), + (update_hashes_per_tick3::id(), "Update desired hashes per tick to 4.4M"), + (update_hashes_per_tick4::id(), "Update desired hashes per tick to 7.6M"), + (update_hashes_per_tick5::id(), "Update desired hashes per tick to 9.2M"), + (update_hashes_per_tick6::id(), "Update desired hashes per tick to 10M"), + (validate_fee_collector_account::id(), "validate fee collector account #33888"), + (enable_zk_transfer_with_fee::id(), "enable Zk Token proof program transfer with fee"), + (drop_legacy_shreds::id(), "drops legacy shreds #34328"), + (consume_blockstore_duplicate_proofs::id(), "consume duplicate proofs from blockstore in consensus #34372"), + (index_erasure_conflict_duplicate_proofs::id(), "generate duplicate proofs for index and erasure conflicts #34360"), + (curve25519_restrict_msm_length::id(), "restrict curve25519 multiscalar multiplication vector lengths #34763"), + (disable_bpf_loader_instructions::id(), "disable bpf loader management instructions #34194"), + (deprecate_unused_legacy_vote_plumbing::id(), "Deprecate unused legacy vote tx plumbing"), /*************** ADD NEW FEATURES HERE ***************/ ] .iter() diff --git a/sdk/src/packet.rs b/sdk/src/packet.rs index b70d8adae8a..faea9ab4753 100644 --- a/sdk/src/packet.rs +++ b/sdk/src/packet.rs @@ -36,7 +36,7 @@ bitflags! { } } -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, AbiExample)] #[repr(C)] pub struct Meta { pub size: usize, @@ -45,6 +45,21 @@ pub struct Meta { pub flags: PacketFlags, } +#[cfg(RUSTC_WITH_SPECIALIZATION)] +impl ::solana_frozen_abi::abi_example::AbiExample for PacketFlags { + fn example() -> Self { + Self::empty() + } +} + +#[cfg(RUSTC_WITH_SPECIALIZATION)] +impl ::solana_frozen_abi::abi_example::IgnoreAsHelper for PacketFlags {} + +#[cfg(RUSTC_WITH_SPECIALIZATION)] +impl ::solana_frozen_abi::abi_example::EvenAsOpaque for PacketFlags { + const TYPE_NAME_MATCHER: &'static str = "::_::InternalBitFlags"; +} + // serde_as is used as a work around because array isn't supported by serde // (and serde_bytes). // @@ -71,7 +86,7 @@ pub struct Meta { // ryoqun's dirty experiments: // https://github.com/ryoqun/serde-array-comparisons #[serde_as] -#[derive(Clone, Eq, Serialize, Deserialize)] +#[derive(Clone, Eq, Serialize, Deserialize, AbiExample)] #[repr(C)] pub struct Packet { // Bytes past Packet.meta.size are not valid to read from. diff --git a/sdk/src/quic.rs b/sdk/src/quic.rs index dd75efccf70..d304d8fe6c5 100644 --- a/sdk/src/quic.rs +++ b/sdk/src/quic.rs @@ -26,12 +26,12 @@ pub const QUIC_CONNECTION_HANDSHAKE_TIMEOUT: Duration = Duration::from_secs(60); /// The receive window for QUIC connection from unstaked nodes is /// set to this ratio times [`solana_sdk::packet::PACKET_DATA_SIZE`] -pub const QUIC_UNSTAKED_RECEIVE_WINDOW_RATIO: u64 = 1; +pub const QUIC_UNSTAKED_RECEIVE_WINDOW_RATIO: u64 = 128; /// The receive window for QUIC connection from minimum staked nodes is /// set to this ratio times [`solana_sdk::packet::PACKET_DATA_SIZE`] -pub const QUIC_MIN_STAKED_RECEIVE_WINDOW_RATIO: u64 = 2; +pub const QUIC_MIN_STAKED_RECEIVE_WINDOW_RATIO: u64 = 128; /// The receive window for QUIC connection from maximum staked nodes is /// set to this ratio times [`solana_sdk::packet::PACKET_DATA_SIZE`] -pub const QUIC_MAX_STAKED_RECEIVE_WINDOW_RATIO: u64 = 10; +pub const QUIC_MAX_STAKED_RECEIVE_WINDOW_RATIO: u64 = 512; diff --git a/sdk/src/signer/keypair.rs b/sdk/src/signer/keypair.rs index 3183dac88e8..1873996a399 100644 --- a/sdk/src/signer/keypair.rs +++ b/sdk/src/signer/keypair.rs @@ -44,6 +44,11 @@ impl Keypair { /// Recovers a `Keypair` from a byte array pub fn from_bytes(bytes: &[u8]) -> Result { + if bytes.len() < ed25519_dalek::KEYPAIR_LENGTH { + return Err(ed25519_dalek::SignatureError::from_source(String::from( + "candidate keypair byte array is too short", + ))); + } let secret = ed25519_dalek::SecretKey::from_bytes(&bytes[..ed25519_dalek::SECRET_KEY_LENGTH])?; let public = diff --git a/send-transaction-service/src/send_transaction_service.rs b/send-transaction-service/src/send_transaction_service.rs index 137160844da..3aec459a73c 100644 --- a/send-transaction-service/src/send_transaction_service.rs +++ b/send-transaction-service/src/send_transaction_service.rs @@ -114,6 +114,7 @@ pub struct Config { pub batch_size: usize, /// How frequently batches are sent pub batch_send_rate_ms: u64, + pub tpu_peers: Option>, } impl Default for Config { @@ -125,6 +126,7 @@ impl Default for Config { service_max_retries: DEFAULT_SERVICE_MAX_RETRIES, batch_size: DEFAULT_TRANSACTION_BATCH_SIZE, batch_send_rate_ms: DEFAULT_BATCH_SEND_RATE_MS, + tpu_peers: None, } } } @@ -565,12 +567,18 @@ impl SendTransactionService { stats: &SendTransactionServiceStats, ) { // Processing the transactions in batch - let addresses = Self::get_tpu_addresses_with_slots( + let mut addresses = config + .tpu_peers + .as_ref() + .map(|addrs| addrs.iter().map(|a| (a, 0)).collect::>()) + .unwrap_or_default(); + let leader_addresses = Self::get_tpu_addresses_with_slots( tpu_address, leader_info, config, connection_cache.protocol(), ); + addresses.extend(leader_addresses); let wire_transactions = transactions .iter() @@ -583,8 +591,8 @@ impl SendTransactionService { }) .collect::>(); - for address in &addresses { - Self::send_transactions(address.0, &wire_transactions, connection_cache, stats); + for (address, _) in &addresses { + Self::send_transactions(address, &wire_transactions, connection_cache, stats); } } @@ -701,14 +709,20 @@ impl SendTransactionService { let iter = wire_transactions.chunks(config.batch_size); for chunk in iter { + let mut addresses = config + .tpu_peers + .as_ref() + .map(|addrs| addrs.iter().collect::>()) + .unwrap_or_default(); let mut leader_info_provider = leader_info_provider.lock().unwrap(); let leader_info = leader_info_provider.get_leader_info(); - let addresses = Self::get_tpu_addresses( + let leader_addresses = Self::get_tpu_addresses( tpu_address, leader_info, config, connection_cache.protocol(), ); + addresses.extend(leader_addresses); for address in &addresses { Self::send_transactions(address, chunk, connection_cache, stats); @@ -826,7 +840,7 @@ mod test { fn service_exit() { let tpu_address = "127.0.0.1:0".parse().unwrap(); let bank = Bank::default_for_tests(); - let bank_forks = Arc::new(RwLock::new(BankForks::new(bank))); + let bank_forks = BankForks::new_rw_arc(bank); let (sender, receiver) = unbounded(); let connection_cache = Arc::new(ConnectionCache::new("connection_cache_test")); @@ -849,7 +863,7 @@ mod test { fn validator_exit() { let tpu_address = "127.0.0.1:0".parse().unwrap(); let bank = Bank::default_for_tests(); - let bank_forks = Arc::new(RwLock::new(BankForks::new(bank))); + let bank_forks = BankForks::new_rw_arc(bank); let (sender, receiver) = bounded(0); let dummy_tx_info = || TransactionInfo { @@ -893,7 +907,7 @@ mod test { let (genesis_config, mint_keypair) = create_genesis_config(4); let bank = Bank::new_for_tests(&genesis_config); - let bank_forks = Arc::new(RwLock::new(BankForks::new(bank))); + let bank_forks = BankForks::new_rw_arc(bank); let tpu_address = "127.0.0.1:0".parse().unwrap(); let config = Config { leader_forward_count: 1, @@ -1159,7 +1173,7 @@ mod test { let (genesis_config, mint_keypair) = create_genesis_config(4); let bank = Bank::new_for_tests(&genesis_config); - let bank_forks = Arc::new(RwLock::new(BankForks::new(bank))); + let bank_forks = BankForks::new_rw_arc(bank); let tpu_address = "127.0.0.1:0".parse().unwrap(); let config = Config { leader_forward_count: 1, diff --git a/storage-bigtable/proto/google.api.rs b/storage-bigtable/proto/google.api.rs index 16b9ba5f262..96a336431a3 100644 --- a/storage-bigtable/proto/google.api.rs +++ b/storage-bigtable/proto/google.api.rs @@ -187,15 +187,18 @@ pub struct Http { /// 1. Leaf request fields (recursive expansion nested messages in the request /// message) are classified into three categories: /// - Fields referred by the path template. They are passed via the URL path. -/// - Fields referred by the \[HttpRule.body][google.api.HttpRule.body\]. They are passed via the HTTP +/// - Fields referred by the \[HttpRule.body][google.api.HttpRule.body\]. They +/// are passed via the HTTP /// request body. /// - All other fields are passed via the URL query parameters, and the /// parameter name is the field path in the request message. A repeated /// field can be represented as multiple query parameters under the same /// name. -/// 2. If \[HttpRule.body][google.api.HttpRule.body\] is "*", there is no URL query parameter, all fields +/// 2. If \[HttpRule.body][google.api.HttpRule.body\] is "*", there is no URL +/// query parameter, all fields /// are passed via URL path and HTTP request body. -/// 3. If \[HttpRule.body][google.api.HttpRule.body\] is omitted, there is no HTTP request body, all +/// 3. If \[HttpRule.body][google.api.HttpRule.body\] is omitted, there is no HTTP +/// request body, all /// fields are passed via URL path and URL query parameters. /// /// ### Path template syntax @@ -292,7 +295,8 @@ pub struct Http { pub struct HttpRule { /// Selects a method to which this rule applies. /// - /// Refer to \[selector][google.api.DocumentationRule.selector\] for syntax details. + /// Refer to \[selector][google.api.DocumentationRule.selector\] for syntax + /// details. #[prost(string, tag = "1")] pub selector: ::prost::alloc::string::String, /// The name of the request field whose value is mapped to the HTTP request @@ -365,6 +369,479 @@ pub struct CustomHttpPattern { #[prost(string, tag = "2")] pub path: ::prost::alloc::string::String, } +/// The launch stage as defined by [Google Cloud Platform +/// Launch Stages](). +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] +#[repr(i32)] +pub enum LaunchStage { + /// Do not use this default value. + Unspecified = 0, + /// The feature is not yet implemented. Users can not use it. + Unimplemented = 6, + /// Prelaunch features are hidden from users and are only visible internally. + Prelaunch = 7, + /// Early Access features are limited to a closed group of testers. To use + /// these features, you must sign up in advance and sign a Trusted Tester + /// agreement (which includes confidentiality provisions). These features may + /// be unstable, changed in backward-incompatible ways, and are not + /// guaranteed to be released. + EarlyAccess = 1, + /// Alpha is a limited availability test for releases before they are cleared + /// for widespread use. By Alpha, all significant design issues are resolved + /// and we are in the process of verifying functionality. Alpha customers + /// need to apply for access, agree to applicable terms, and have their + /// projects allowlisted. Alpha releases don't have to be feature complete, + /// no SLAs are provided, and there are no technical support obligations, but + /// they will be far enough along that customers can actually use them in + /// test environments or for limited-use tests -- just like they would in + /// normal production cases. + Alpha = 2, + /// Beta is the point at which we are ready to open a release for any + /// customer to use. There are no SLA or technical support obligations in a + /// Beta release. Products will be complete from a feature perspective, but + /// may have some open outstanding issues. Beta releases are suitable for + /// limited production use cases. + Beta = 3, + /// GA features are open to all developers and are considered stable and + /// fully qualified for production use. + Ga = 4, + /// Deprecated features are scheduled to be shut down and removed. For more + /// information, see the "Deprecation Policy" section of our [Terms of + /// Service]() + /// and the [Google Cloud Platform Subject to the Deprecation + /// Policy]() documentation. + Deprecated = 5, +} +impl LaunchStage { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + LaunchStage::Unspecified => "LAUNCH_STAGE_UNSPECIFIED", + LaunchStage::Unimplemented => "UNIMPLEMENTED", + LaunchStage::Prelaunch => "PRELAUNCH", + LaunchStage::EarlyAccess => "EARLY_ACCESS", + LaunchStage::Alpha => "ALPHA", + LaunchStage::Beta => "BETA", + LaunchStage::Ga => "GA", + LaunchStage::Deprecated => "DEPRECATED", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "LAUNCH_STAGE_UNSPECIFIED" => Some(Self::Unspecified), + "UNIMPLEMENTED" => Some(Self::Unimplemented), + "PRELAUNCH" => Some(Self::Prelaunch), + "EARLY_ACCESS" => Some(Self::EarlyAccess), + "ALPHA" => Some(Self::Alpha), + "BETA" => Some(Self::Beta), + "GA" => Some(Self::Ga), + "DEPRECATED" => Some(Self::Deprecated), + _ => None, + } + } +} +/// Required information for every language. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct CommonLanguageSettings { + /// Link to automatically generated reference documentation. Example: + /// + #[deprecated] + #[prost(string, tag = "1")] + pub reference_docs_uri: ::prost::alloc::string::String, + /// The destination where API teams want this client library to be published. + #[prost(enumeration = "ClientLibraryDestination", repeated, tag = "2")] + pub destinations: ::prost::alloc::vec::Vec, +} +/// Details about how and where to publish client libraries. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ClientLibrarySettings { + /// Version of the API to apply these settings to. This is the full protobuf + /// package for the API, ending in the version element. + /// Examples: "google.cloud.speech.v1" and "google.spanner.admin.database.v1". + #[prost(string, tag = "1")] + pub version: ::prost::alloc::string::String, + /// Launch stage of this version of the API. + #[prost(enumeration = "LaunchStage", tag = "2")] + pub launch_stage: i32, + /// When using transport=rest, the client request will encode enums as + /// numbers rather than strings. + #[prost(bool, tag = "3")] + pub rest_numeric_enums: bool, + /// Settings for legacy Java features, supported in the Service YAML. + #[prost(message, optional, tag = "21")] + pub java_settings: ::core::option::Option, + /// Settings for C++ client libraries. + #[prost(message, optional, tag = "22")] + pub cpp_settings: ::core::option::Option, + /// Settings for PHP client libraries. + #[prost(message, optional, tag = "23")] + pub php_settings: ::core::option::Option, + /// Settings for Python client libraries. + #[prost(message, optional, tag = "24")] + pub python_settings: ::core::option::Option, + /// Settings for Node client libraries. + #[prost(message, optional, tag = "25")] + pub node_settings: ::core::option::Option, + /// Settings for .NET client libraries. + #[prost(message, optional, tag = "26")] + pub dotnet_settings: ::core::option::Option, + /// Settings for Ruby client libraries. + #[prost(message, optional, tag = "27")] + pub ruby_settings: ::core::option::Option, + /// Settings for Go client libraries. + #[prost(message, optional, tag = "28")] + pub go_settings: ::core::option::Option, +} +/// This message configures the settings for publishing [Google Cloud Client +/// libraries]() +/// generated from the service config. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Publishing { + /// A list of API method settings, e.g. the behavior for methods that use the + /// long-running operation pattern. + #[prost(message, repeated, tag = "2")] + pub method_settings: ::prost::alloc::vec::Vec, + /// Link to a *public* URI where users can report issues. Example: + /// + #[prost(string, tag = "101")] + pub new_issue_uri: ::prost::alloc::string::String, + /// Link to product home page. Example: + /// + #[prost(string, tag = "102")] + pub documentation_uri: ::prost::alloc::string::String, + /// Used as a tracking tag when collecting data about the APIs developer + /// relations artifacts like docs, packages delivered to package managers, + /// etc. Example: "speech". + #[prost(string, tag = "103")] + pub api_short_name: ::prost::alloc::string::String, + /// GitHub label to apply to issues and pull requests opened for this API. + #[prost(string, tag = "104")] + pub github_label: ::prost::alloc::string::String, + /// GitHub teams to be added to CODEOWNERS in the directory in GitHub + /// containing source code for the client libraries for this API. + #[prost(string, repeated, tag = "105")] + pub codeowner_github_teams: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, + /// A prefix used in sample code when demarking regions to be included in + /// documentation. + #[prost(string, tag = "106")] + pub doc_tag_prefix: ::prost::alloc::string::String, + /// For whom the client library is being published. + #[prost(enumeration = "ClientLibraryOrganization", tag = "107")] + pub organization: i32, + /// Client library settings. If the same version string appears multiple + /// times in this list, then the last one wins. Settings from earlier + /// settings with the same version string are discarded. + #[prost(message, repeated, tag = "109")] + pub library_settings: ::prost::alloc::vec::Vec, + /// Optional link to proto reference documentation. Example: + /// + #[prost(string, tag = "110")] + pub proto_reference_documentation_uri: ::prost::alloc::string::String, +} +/// Settings for Java client libraries. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct JavaSettings { + /// The package name to use in Java. Clobbers the java_package option + /// set in the protobuf. This should be used **only** by APIs + /// who have already set the language_settings.java.package_name" field + /// in gapic.yaml. API teams should use the protobuf java_package option + /// where possible. + /// + /// Example of a YAML configuration:: + /// + /// publishing: + /// java_settings: + /// library_package: com.google.cloud.pubsub.v1 + #[prost(string, tag = "1")] + pub library_package: ::prost::alloc::string::String, + /// Configure the Java class name to use instead of the service's for its + /// corresponding generated GAPIC client. Keys are fully-qualified + /// service names as they appear in the protobuf (including the full + /// the language_settings.java.interface_names" field in gapic.yaml. API + /// teams should otherwise use the service name as it appears in the + /// protobuf. + /// + /// Example of a YAML configuration:: + /// + /// publishing: + /// java_settings: + /// service_class_names: + /// - google.pubsub.v1.Publisher: TopicAdmin + /// - google.pubsub.v1.Subscriber: SubscriptionAdmin + #[prost(map = "string, string", tag = "2")] + pub service_class_names: ::std::collections::HashMap< + ::prost::alloc::string::String, + ::prost::alloc::string::String, + >, + /// Some settings. + #[prost(message, optional, tag = "3")] + pub common: ::core::option::Option, +} +/// Settings for C++ client libraries. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct CppSettings { + /// Some settings. + #[prost(message, optional, tag = "1")] + pub common: ::core::option::Option, +} +/// Settings for Php client libraries. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct PhpSettings { + /// Some settings. + #[prost(message, optional, tag = "1")] + pub common: ::core::option::Option, +} +/// Settings for Python client libraries. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct PythonSettings { + /// Some settings. + #[prost(message, optional, tag = "1")] + pub common: ::core::option::Option, +} +/// Settings for Node client libraries. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct NodeSettings { + /// Some settings. + #[prost(message, optional, tag = "1")] + pub common: ::core::option::Option, +} +/// Settings for Dotnet client libraries. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct DotnetSettings { + /// Some settings. + #[prost(message, optional, tag = "1")] + pub common: ::core::option::Option, + /// Map from original service names to renamed versions. + /// This is used when the default generated types + /// would cause a naming conflict. (Neither name is + /// fully-qualified.) + /// Example: Subscriber to SubscriberServiceApi. + #[prost(map = "string, string", tag = "2")] + pub renamed_services: ::std::collections::HashMap< + ::prost::alloc::string::String, + ::prost::alloc::string::String, + >, + /// Map from full resource types to the effective short name + /// for the resource. This is used when otherwise resource + /// named from different services would cause naming collisions. + /// Example entry: + /// "datalabeling.googleapis.com/Dataset": "DataLabelingDataset" + #[prost(map = "string, string", tag = "3")] + pub renamed_resources: ::std::collections::HashMap< + ::prost::alloc::string::String, + ::prost::alloc::string::String, + >, + /// List of full resource types to ignore during generation. + /// This is typically used for API-specific Location resources, + /// which should be handled by the generator as if they were actually + /// the common Location resources. + /// Example entry: "documentai.googleapis.com/Location" + #[prost(string, repeated, tag = "4")] + pub ignored_resources: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, + /// Namespaces which must be aliased in snippets due to + /// a known (but non-generator-predictable) naming collision + #[prost(string, repeated, tag = "5")] + pub forced_namespace_aliases: ::prost::alloc::vec::Vec< + ::prost::alloc::string::String, + >, + /// Method signatures (in the form "service.method(signature)") + /// which are provided separately, so shouldn't be generated. + /// Snippets *calling* these methods are still generated, however. + #[prost(string, repeated, tag = "6")] + pub handwritten_signatures: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, +} +/// Settings for Ruby client libraries. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct RubySettings { + /// Some settings. + #[prost(message, optional, tag = "1")] + pub common: ::core::option::Option, +} +/// Settings for Go client libraries. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GoSettings { + /// Some settings. + #[prost(message, optional, tag = "1")] + pub common: ::core::option::Option, +} +/// Describes the generator configuration for a method. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct MethodSettings { + /// The fully qualified name of the method, for which the options below apply. + /// This is used to find the method to apply the options. + #[prost(string, tag = "1")] + pub selector: ::prost::alloc::string::String, + /// Describes settings to use for long-running operations when generating + /// API methods for RPCs. Complements RPCs that use the annotations in + /// google/longrunning/operations.proto. + /// + /// Example of a YAML configuration:: + /// + /// publishing: + /// method_settings: + /// - selector: google.cloud.speech.v2.Speech.BatchRecognize + /// long_running: + /// initial_poll_delay: + /// seconds: 60 # 1 minute + /// poll_delay_multiplier: 1.5 + /// max_poll_delay: + /// seconds: 360 # 6 minutes + /// total_poll_timeout: + /// seconds: 54000 # 90 minutes + #[prost(message, optional, tag = "2")] + pub long_running: ::core::option::Option, + /// List of top-level fields of the request message, that should be + /// automatically populated by the client libraries based on their + /// (google.api.field_info).format. Currently supported format: UUID4. + /// + /// Example of a YAML configuration: + /// + /// publishing: + /// method_settings: + /// - selector: google.example.v1.ExampleService.CreateExample + /// auto_populated_fields: + /// - request_id + #[prost(string, repeated, tag = "3")] + pub auto_populated_fields: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, +} +/// Nested message and enum types in `MethodSettings`. +pub mod method_settings { + /// Describes settings to use when generating API methods that use the + /// long-running operation pattern. + /// All default values below are from those used in the client library + /// generators (e.g. + /// \[Java\]()). + #[allow(clippy::derive_partial_eq_without_eq)] + #[derive(Clone, PartialEq, ::prost::Message)] + pub struct LongRunning { + /// Initial delay after which the first poll request will be made. + /// Default value: 5 seconds. + #[prost(message, optional, tag = "1")] + pub initial_poll_delay: ::core::option::Option<::prost_types::Duration>, + /// Multiplier to gradually increase delay between subsequent polls until it + /// reaches max_poll_delay. + /// Default value: 1.5. + #[prost(float, tag = "2")] + pub poll_delay_multiplier: f32, + /// Maximum time between two subsequent poll requests. + /// Default value: 45 seconds. + #[prost(message, optional, tag = "3")] + pub max_poll_delay: ::core::option::Option<::prost_types::Duration>, + /// Total polling timeout. + /// Default value: 5 minutes. + #[prost(message, optional, tag = "4")] + pub total_poll_timeout: ::core::option::Option<::prost_types::Duration>, + } +} +/// The organization for which the client libraries are being published. +/// Affects the url where generated docs are published, etc. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] +#[repr(i32)] +pub enum ClientLibraryOrganization { + /// Not useful. + Unspecified = 0, + /// Google Cloud Platform Org. + Cloud = 1, + /// Ads (Advertising) Org. + Ads = 2, + /// Photos Org. + Photos = 3, + /// Street View Org. + StreetView = 4, + /// Shopping Org. + Shopping = 5, + /// Geo Org. + Geo = 6, + /// Generative AI - + GenerativeAi = 7, +} +impl ClientLibraryOrganization { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + ClientLibraryOrganization::Unspecified => { + "CLIENT_LIBRARY_ORGANIZATION_UNSPECIFIED" + } + ClientLibraryOrganization::Cloud => "CLOUD", + ClientLibraryOrganization::Ads => "ADS", + ClientLibraryOrganization::Photos => "PHOTOS", + ClientLibraryOrganization::StreetView => "STREET_VIEW", + ClientLibraryOrganization::Shopping => "SHOPPING", + ClientLibraryOrganization::Geo => "GEO", + ClientLibraryOrganization::GenerativeAi => "GENERATIVE_AI", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "CLIENT_LIBRARY_ORGANIZATION_UNSPECIFIED" => Some(Self::Unspecified), + "CLOUD" => Some(Self::Cloud), + "ADS" => Some(Self::Ads), + "PHOTOS" => Some(Self::Photos), + "STREET_VIEW" => Some(Self::StreetView), + "SHOPPING" => Some(Self::Shopping), + "GEO" => Some(Self::Geo), + "GENERATIVE_AI" => Some(Self::GenerativeAi), + _ => None, + } + } +} +/// To where should client libraries be published? +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] +#[repr(i32)] +pub enum ClientLibraryDestination { + /// Client libraries will neither be generated nor published to package + /// managers. + Unspecified = 0, + /// Generate the client library in a repo under github.com/googleapis, + /// but don't publish it to package managers. + Github = 10, + /// Publish the library to package managers like nuget.org and npmjs.com. + PackageManager = 20, +} +impl ClientLibraryDestination { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + ClientLibraryDestination::Unspecified => { + "CLIENT_LIBRARY_DESTINATION_UNSPECIFIED" + } + ClientLibraryDestination::Github => "GITHUB", + ClientLibraryDestination::PackageManager => "PACKAGE_MANAGER", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "CLIENT_LIBRARY_DESTINATION_UNSPECIFIED" => Some(Self::Unspecified), + "GITHUB" => Some(Self::Github), + "PACKAGE_MANAGER" => Some(Self::PackageManager), + _ => None, + } + } +} /// An indicator of the behavior of a given field (for example, that a field /// is required in requests, or given as output but ignored as input). /// This **does not** change the behavior in protocol buffers itself; it only @@ -407,6 +884,19 @@ pub enum FieldBehavior { /// a non-empty value will be returned. The user will not be aware of what /// non-empty value to expect. NonEmptyDefault = 7, + /// Denotes that the field in a resource (a message annotated with + /// google.api.resource) is used in the resource name to uniquely identify the + /// resource. For AIP-compliant APIs, this should only be applied to the + /// `name` field on the resource. + /// + /// This behavior should not be applied to references to other resources within + /// the message. + /// + /// The identifier field of resources often have different field behavior + /// depending on the request it is embedded in (e.g. for Create methods name + /// is optional and unused, while for Update methods it is required). Instead + /// of method-specific annotations, only `IDENTIFIER` is required. + Identifier = 8, } impl FieldBehavior { /// String value of the enum field names used in the ProtoBuf definition. @@ -423,6 +913,22 @@ impl FieldBehavior { FieldBehavior::Immutable => "IMMUTABLE", FieldBehavior::UnorderedList => "UNORDERED_LIST", FieldBehavior::NonEmptyDefault => "NON_EMPTY_DEFAULT", + FieldBehavior::Identifier => "IDENTIFIER", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "FIELD_BEHAVIOR_UNSPECIFIED" => Some(Self::Unspecified), + "OPTIONAL" => Some(Self::Optional), + "REQUIRED" => Some(Self::Required), + "OUTPUT_ONLY" => Some(Self::OutputOnly), + "INPUT_ONLY" => Some(Self::InputOnly), + "IMMUTABLE" => Some(Self::Immutable), + "UNORDERED_LIST" => Some(Self::UnorderedList), + "NON_EMPTY_DEFAULT" => Some(Self::NonEmptyDefault), + "IDENTIFIER" => Some(Self::Identifier), + _ => None, } } } @@ -440,11 +946,7 @@ impl FieldBehavior { /// // For Kubernetes resources, the format is {api group}/{kind}. /// option (google.api.resource) = { /// type: "pubsub.googleapis.com/Topic" -/// name_descriptor: { -/// pattern: "projects/{project}/topics/{topic}" -/// parent_type: "cloudresourcemanager.googleapis.com/Project" -/// parent_name_extractor: "projects/{project}" -/// } +/// pattern: "projects/{project}/topics/{topic}" /// }; /// } /// @@ -452,10 +954,7 @@ impl FieldBehavior { /// /// resources: /// - type: "pubsub.googleapis.com/Topic" -/// name_descriptor: -/// - pattern: "projects/{project}/topics/{topic}" -/// parent_type: "cloudresourcemanager.googleapis.com/Project" -/// parent_name_extractor: "projects/{project}" +/// pattern: "projects/{project}/topics/{topic}" /// /// Sometimes, resources have multiple patterns, typically because they can /// live under multiple parents. @@ -465,26 +964,10 @@ impl FieldBehavior { /// message LogEntry { /// option (google.api.resource) = { /// type: "logging.googleapis.com/LogEntry" -/// name_descriptor: { -/// pattern: "projects/{project}/logs/{log}" -/// parent_type: "cloudresourcemanager.googleapis.com/Project" -/// parent_name_extractor: "projects/{project}" -/// } -/// name_descriptor: { -/// pattern: "folders/{folder}/logs/{log}" -/// parent_type: "cloudresourcemanager.googleapis.com/Folder" -/// parent_name_extractor: "folders/{folder}" -/// } -/// name_descriptor: { -/// pattern: "organizations/{organization}/logs/{log}" -/// parent_type: "cloudresourcemanager.googleapis.com/Organization" -/// parent_name_extractor: "organizations/{organization}" -/// } -/// name_descriptor: { -/// pattern: "billingAccounts/{billing_account}/logs/{log}" -/// parent_type: "billing.googleapis.com/BillingAccount" -/// parent_name_extractor: "billingAccounts/{billing_account}" -/// } +/// pattern: "projects/{project}/logs/{log}" +/// pattern: "folders/{folder}/logs/{log}" +/// pattern: "organizations/{organization}/logs/{log}" +/// pattern: "billingAccounts/{billing_account}/logs/{log}" /// }; /// } /// @@ -492,48 +975,10 @@ impl FieldBehavior { /// /// resources: /// - type: 'logging.googleapis.com/LogEntry' -/// name_descriptor: -/// - pattern: "projects/{project}/logs/{log}" -/// parent_type: "cloudresourcemanager.googleapis.com/Project" -/// parent_name_extractor: "projects/{project}" -/// - pattern: "folders/{folder}/logs/{log}" -/// parent_type: "cloudresourcemanager.googleapis.com/Folder" -/// parent_name_extractor: "folders/{folder}" -/// - pattern: "organizations/{organization}/logs/{log}" -/// parent_type: "cloudresourcemanager.googleapis.com/Organization" -/// parent_name_extractor: "organizations/{organization}" -/// - pattern: "billingAccounts/{billing_account}/logs/{log}" -/// parent_type: "billing.googleapis.com/BillingAccount" -/// parent_name_extractor: "billingAccounts/{billing_account}" -/// -/// For flexible resources, the resource name doesn't contain parent names, but -/// the resource itself has parents for policy evaluation. -/// -/// Example: -/// -/// message Shelf { -/// option (google.api.resource) = { -/// type: "library.googleapis.com/Shelf" -/// name_descriptor: { -/// pattern: "shelves/{shelf}" -/// parent_type: "cloudresourcemanager.googleapis.com/Project" -/// } -/// name_descriptor: { -/// pattern: "shelves/{shelf}" -/// parent_type: "cloudresourcemanager.googleapis.com/Folder" -/// } -/// }; -/// } -/// -/// The ResourceDescriptor Yaml config will look like: -/// -/// resources: -/// - type: 'library.googleapis.com/Shelf' -/// name_descriptor: -/// - pattern: "shelves/{shelf}" -/// parent_type: "cloudresourcemanager.googleapis.com/Project" -/// - pattern: "shelves/{shelf}" -/// parent_type: "cloudresourcemanager.googleapis.com/Folder" +/// pattern: "projects/{project}/logs/{log}" +/// pattern: "folders/{folder}/logs/{log}" +/// pattern: "organizations/{organization}/logs/{log}" +/// pattern: "billingAccounts/{billing_account}/logs/{log}" #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ResourceDescriptor { @@ -651,6 +1096,15 @@ pub mod resource_descriptor { History::FutureMultiPattern => "FUTURE_MULTI_PATTERN", } } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "HISTORY_UNSPECIFIED" => Some(Self::Unspecified), + "ORIGINALLY_SINGLE_PATTERN" => Some(Self::OriginallySinglePattern), + "FUTURE_MULTI_PATTERN" => Some(Self::FutureMultiPattern), + _ => None, + } + } } /// A flag representing a specific style that a resource claims to conform to. #[derive( @@ -689,6 +1143,14 @@ pub mod resource_descriptor { Style::DeclarativeFriendly => "DECLARATIVE_FRIENDLY", } } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "STYLE_UNSPECIFIED" => Some(Self::Unspecified), + "DECLARATIVE_FRIENDLY" => Some(Self::DeclarativeFriendly), + _ => None, + } + } } } /// Defines a proto annotation that describes a string field that refers to @@ -732,3 +1194,438 @@ pub struct ResourceReference { #[prost(string, tag = "2")] pub child_type: ::prost::alloc::string::String, } +/// Specifies the routing information that should be sent along with the request +/// in the form of routing header. +/// **NOTE:** All service configuration rules follow the "last one wins" order. +/// +/// The examples below will apply to an RPC which has the following request type: +/// +/// Message Definition: +/// +/// message Request { +/// // The name of the Table +/// // Values can be of the following formats: +/// // - `projects//tables/` +/// // - `projects//instances//tables/
` +/// // - `region//zones//tables/
` +/// string table_name = 1; +/// +/// // This value specifies routing for replication. +/// // It can be in the following formats: +/// // - `profiles/` +/// // - a legacy `profile_id` that can be any string +/// string app_profile_id = 2; +/// } +/// +/// Example message: +/// +/// { +/// table_name: projects/proj_foo/instances/instance_bar/table/table_baz, +/// app_profile_id: profiles/prof_qux +/// } +/// +/// The routing header consists of one or multiple key-value pairs. Every key +/// and value must be percent-encoded, and joined together in the format of +/// `key1=value1&key2=value2`. +/// In the examples below I am skipping the percent-encoding for readablity. +/// +/// Example 1 +/// +/// Extracting a field from the request to put into the routing header +/// unchanged, with the key equal to the field name. +/// +/// annotation: +/// +/// option (google.api.routing) = { +/// // Take the `app_profile_id`. +/// routing_parameters { +/// field: "app_profile_id" +/// } +/// }; +/// +/// result: +/// +/// x-goog-request-params: app_profile_id=profiles/prof_qux +/// +/// Example 2 +/// +/// Extracting a field from the request to put into the routing header +/// unchanged, with the key different from the field name. +/// +/// annotation: +/// +/// option (google.api.routing) = { +/// // Take the `app_profile_id`, but name it `routing_id` in the header. +/// routing_parameters { +/// field: "app_profile_id" +/// path_template: "{routing_id=**}" +/// } +/// }; +/// +/// result: +/// +/// x-goog-request-params: routing_id=profiles/prof_qux +/// +/// Example 3 +/// +/// Extracting a field from the request to put into the routing +/// header, while matching a path template syntax on the field's value. +/// +/// NB: it is more useful to send nothing than to send garbage for the purpose +/// of dynamic routing, since garbage pollutes cache. Thus the matching. +/// +/// Sub-example 3a +/// +/// The field matches the template. +/// +/// annotation: +/// +/// option (google.api.routing) = { +/// // Take the `table_name`, if it's well-formed (with project-based +/// // syntax). +/// routing_parameters { +/// field: "table_name" +/// path_template: "{table_name=projects/*/instances/*/**}" +/// } +/// }; +/// +/// result: +/// +/// x-goog-request-params: +/// table_name=projects/proj_foo/instances/instance_bar/table/table_baz +/// +/// Sub-example 3b +/// +/// The field does not match the template. +/// +/// annotation: +/// +/// option (google.api.routing) = { +/// // Take the `table_name`, if it's well-formed (with region-based +/// // syntax). +/// routing_parameters { +/// field: "table_name" +/// path_template: "{table_name=regions/*/zones/*/**}" +/// } +/// }; +/// +/// result: +/// +/// +/// +/// Sub-example 3c +/// +/// Multiple alternative conflictingly named path templates are +/// specified. The one that matches is used to construct the header. +/// +/// annotation: +/// +/// option (google.api.routing) = { +/// // Take the `table_name`, if it's well-formed, whether +/// // using the region- or projects-based syntax. +/// +/// routing_parameters { +/// field: "table_name" +/// path_template: "{table_name=regions/*/zones/*/**}" +/// } +/// routing_parameters { +/// field: "table_name" +/// path_template: "{table_name=projects/*/instances/*/**}" +/// } +/// }; +/// +/// result: +/// +/// x-goog-request-params: +/// table_name=projects/proj_foo/instances/instance_bar/table/table_baz +/// +/// Example 4 +/// +/// Extracting a single routing header key-value pair by matching a +/// template syntax on (a part of) a single request field. +/// +/// annotation: +/// +/// option (google.api.routing) = { +/// // Take just the project id from the `table_name` field. +/// routing_parameters { +/// field: "table_name" +/// path_template: "{routing_id=projects/*}/**" +/// } +/// }; +/// +/// result: +/// +/// x-goog-request-params: routing_id=projects/proj_foo +/// +/// Example 5 +/// +/// Extracting a single routing header key-value pair by matching +/// several conflictingly named path templates on (parts of) a single request +/// field. The last template to match "wins" the conflict. +/// +/// annotation: +/// +/// option (google.api.routing) = { +/// // If the `table_name` does not have instances information, +/// // take just the project id for routing. +/// // Otherwise take project + instance. +/// +/// routing_parameters { +/// field: "table_name" +/// path_template: "{routing_id=projects/*}/**" +/// } +/// routing_parameters { +/// field: "table_name" +/// path_template: "{routing_id=projects/*/instances/*}/**" +/// } +/// }; +/// +/// result: +/// +/// x-goog-request-params: +/// routing_id=projects/proj_foo/instances/instance_bar +/// +/// Example 6 +/// +/// Extracting multiple routing header key-value pairs by matching +/// several non-conflicting path templates on (parts of) a single request field. +/// +/// Sub-example 6a +/// +/// Make the templates strict, so that if the `table_name` does not +/// have an instance information, nothing is sent. +/// +/// annotation: +/// +/// option (google.api.routing) = { +/// // The routing code needs two keys instead of one composite +/// // but works only for the tables with the "project-instance" name +/// // syntax. +/// +/// routing_parameters { +/// field: "table_name" +/// path_template: "{project_id=projects/*}/instances/*/**" +/// } +/// routing_parameters { +/// field: "table_name" +/// path_template: "projects/*/{instance_id=instances/*}/**" +/// } +/// }; +/// +/// result: +/// +/// x-goog-request-params: +/// project_id=projects/proj_foo&instance_id=instances/instance_bar +/// +/// Sub-example 6b +/// +/// Make the templates loose, so that if the `table_name` does not +/// have an instance information, just the project id part is sent. +/// +/// annotation: +/// +/// option (google.api.routing) = { +/// // The routing code wants two keys instead of one composite +/// // but will work with just the `project_id` for tables without +/// // an instance in the `table_name`. +/// +/// routing_parameters { +/// field: "table_name" +/// path_template: "{project_id=projects/*}/**" +/// } +/// routing_parameters { +/// field: "table_name" +/// path_template: "projects/*/{instance_id=instances/*}/**" +/// } +/// }; +/// +/// result (is the same as 6a for our example message because it has the instance +/// information): +/// +/// x-goog-request-params: +/// project_id=projects/proj_foo&instance_id=instances/instance_bar +/// +/// Example 7 +/// +/// Extracting multiple routing header key-value pairs by matching +/// several path templates on multiple request fields. +/// +/// NB: note that here there is no way to specify sending nothing if one of the +/// fields does not match its template. E.g. if the `table_name` is in the wrong +/// format, the `project_id` will not be sent, but the `routing_id` will be. +/// The backend routing code has to be aware of that and be prepared to not +/// receive a full complement of keys if it expects multiple. +/// +/// annotation: +/// +/// option (google.api.routing) = { +/// // The routing needs both `project_id` and `routing_id` +/// // (from the `app_profile_id` field) for routing. +/// +/// routing_parameters { +/// field: "table_name" +/// path_template: "{project_id=projects/*}/**" +/// } +/// routing_parameters { +/// field: "app_profile_id" +/// path_template: "{routing_id=**}" +/// } +/// }; +/// +/// result: +/// +/// x-goog-request-params: +/// project_id=projects/proj_foo&routing_id=profiles/prof_qux +/// +/// Example 8 +/// +/// Extracting a single routing header key-value pair by matching +/// several conflictingly named path templates on several request fields. The +/// last template to match "wins" the conflict. +/// +/// annotation: +/// +/// option (google.api.routing) = { +/// // The `routing_id` can be a project id or a region id depending on +/// // the table name format, but only if the `app_profile_id` is not set. +/// // If `app_profile_id` is set it should be used instead. +/// +/// routing_parameters { +/// field: "table_name" +/// path_template: "{routing_id=projects/*}/**" +/// } +/// routing_parameters { +/// field: "table_name" +/// path_template: "{routing_id=regions/*}/**" +/// } +/// routing_parameters { +/// field: "app_profile_id" +/// path_template: "{routing_id=**}" +/// } +/// }; +/// +/// result: +/// +/// x-goog-request-params: routing_id=profiles/prof_qux +/// +/// Example 9 +/// +/// Bringing it all together. +/// +/// annotation: +/// +/// option (google.api.routing) = { +/// // For routing both `table_location` and a `routing_id` are needed. +/// // +/// // table_location can be either an instance id or a region+zone id. +/// // +/// // For `routing_id`, take the value of `app_profile_id` +/// // - If it's in the format `profiles/`, send +/// // just the `` part. +/// // - If it's any other literal, send it as is. +/// // If the `app_profile_id` is empty, and the `table_name` starts with +/// // the project_id, send that instead. +/// +/// routing_parameters { +/// field: "table_name" +/// path_template: "projects/*/{table_location=instances/*}/tables/*" +/// } +/// routing_parameters { +/// field: "table_name" +/// path_template: "{table_location=regions/*/zones/*}/tables/*" +/// } +/// routing_parameters { +/// field: "table_name" +/// path_template: "{routing_id=projects/*}/**" +/// } +/// routing_parameters { +/// field: "app_profile_id" +/// path_template: "{routing_id=**}" +/// } +/// routing_parameters { +/// field: "app_profile_id" +/// path_template: "profiles/{routing_id=*}" +/// } +/// }; +/// +/// result: +/// +/// x-goog-request-params: +/// table_location=instances/instance_bar&routing_id=prof_qux +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct RoutingRule { + /// A collection of Routing Parameter specifications. + /// **NOTE:** If multiple Routing Parameters describe the same key + /// (via the `path_template` field or via the `field` field when + /// `path_template` is not provided), "last one wins" rule + /// determines which Parameter gets used. + /// See the examples for more details. + #[prost(message, repeated, tag = "2")] + pub routing_parameters: ::prost::alloc::vec::Vec, +} +/// A projection from an input message to the GRPC or REST header. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct RoutingParameter { + /// A request field to extract the header key-value pair from. + #[prost(string, tag = "1")] + pub field: ::prost::alloc::string::String, + /// A pattern matching the key-value field. Optional. + /// If not specified, the whole field specified in the `field` field will be + /// taken as value, and its name used as key. If specified, it MUST contain + /// exactly one named segment (along with any number of unnamed segments) The + /// pattern will be matched over the field specified in the `field` field, then + /// if the match is successful: + /// - the name of the single named segment will be used as a header name, + /// - the match value of the segment will be used as a header value; + /// if the match is NOT successful, nothing will be sent. + /// + /// Example: + /// + /// -- This is a field in the request message + /// | that the header value will be extracted from. + /// | + /// | -- This is the key name in the + /// | | routing header. + /// V | + /// field: "table_name" v + /// path_template: "projects/*/{table_location=instances/*}/tables/*" + /// ^ ^ + /// | | + /// In the {} brackets is the pattern that -- | + /// specifies what to extract from the | + /// field as a value to be sent. | + /// | + /// The string in the field must match the whole pattern -- + /// before brackets, inside brackets, after brackets. + /// + /// When looking at this specific example, we can see that: + /// - A key-value pair with the key `table_location` + /// and the value matching `instances/*` should be added + /// to the x-goog-request-params routing header. + /// - The value is extracted from the request message's `table_name` field + /// if it matches the full pattern specified: + /// `projects/*/instances/*/tables/*`. + /// + /// **NB:** If the `path_template` field is not provided, the key name is + /// equal to the field name, and the whole field should be sent as a value. + /// This makes the pattern for the field and the value functionally equivalent + /// to `**`, and the configuration + /// + /// { + /// field: "table_name" + /// } + /// + /// is a functionally equivalent shorthand to: + /// + /// { + /// field: "table_name" + /// path_template: "{table_name=**}" + /// } + /// + /// See Example 1 for more details. + #[prost(string, tag = "2")] + pub path_template: ::prost::alloc::string::String, +} diff --git a/storage-bigtable/proto/google.bigtable.v2.rs b/storage-bigtable/proto/google.bigtable.v2.rs index 362d06070dc..4b23bf0e77b 100644 --- a/storage-bigtable/proto/google.bigtable.v2.rs +++ b/storage-bigtable/proto/google.bigtable.v2.rs @@ -246,7 +246,7 @@ pub mod value_range { /// RowFilter.Chain and RowFilter.Interleave documentation. /// /// The total serialized size of a RowFilter message must not -/// exceed 4096 bytes, and RowFilters may not be nested within each other +/// exceed 20480 bytes, and RowFilters may not be nested within each other /// (in Chains or Interleaves) to a depth of more than 20. #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -623,6 +623,130 @@ pub mod read_modify_write_rule { IncrementAmount(i64), } } +/// NOTE: This API is intended to be used by Apache Beam BigtableIO. +/// A partition of a change stream. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct StreamPartition { + /// The row range covered by this partition and is specified by + /// [`start_key_closed`, `end_key_open`). + #[prost(message, optional, tag = "1")] + pub row_range: ::core::option::Option, +} +/// NOTE: This API is intended to be used by Apache Beam BigtableIO. +/// The information required to continue reading the data from multiple +/// `StreamPartitions` from where a previous read left off. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct StreamContinuationTokens { + /// List of continuation tokens. + #[prost(message, repeated, tag = "1")] + pub tokens: ::prost::alloc::vec::Vec, +} +/// NOTE: This API is intended to be used by Apache Beam BigtableIO. +/// The information required to continue reading the data from a +/// `StreamPartition` from where a previous read left off. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct StreamContinuationToken { + /// The partition that this token applies to. + #[prost(message, optional, tag = "1")] + pub partition: ::core::option::Option, + /// An encoded position in the stream to restart reading from. + #[prost(string, tag = "2")] + pub token: ::prost::alloc::string::String, +} +/// ReadIterationStats captures information about the iteration of rows or cells +/// over the course of a read, e.g. how many results were scanned in a read +/// operation versus the results returned. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ReadIterationStats { + /// The rows seen (scanned) as part of the request. This includes the count of + /// rows returned, as captured below. + #[prost(int64, tag = "1")] + pub rows_seen_count: i64, + /// The rows returned as part of the request. + #[prost(int64, tag = "2")] + pub rows_returned_count: i64, + /// The cells seen (scanned) as part of the request. This includes the count of + /// cells returned, as captured below. + #[prost(int64, tag = "3")] + pub cells_seen_count: i64, + /// The cells returned as part of the request. + #[prost(int64, tag = "4")] + pub cells_returned_count: i64, +} +/// RequestLatencyStats provides a measurement of the latency of the request as +/// it interacts with different systems over its lifetime, e.g. how long the +/// request took to execute within a frontend server. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct RequestLatencyStats { + /// The latency measured by the frontend server handling this request, from + /// when the request was received, to when this value is sent back in the + /// response. For more context on the component that is measuring this latency, + /// see: + /// + /// Note: This value may be slightly shorter than the value reported into + /// aggregate latency metrics in Monitoring for this request + /// () as this value + /// needs to be sent in the response before the latency measurement including + /// that transmission is finalized. + /// + /// Note: This value includes the end-to-end latency of contacting nodes in + /// the targeted cluster, e.g. measuring from when the first byte arrives at + /// the frontend server, to when this value is sent back as the last value in + /// the response, including any latency incurred by contacting nodes, waiting + /// for results from nodes, and finally sending results from nodes back to the + /// caller. + #[prost(message, optional, tag = "1")] + pub frontend_server_latency: ::core::option::Option<::prost_types::Duration>, +} +/// FullReadStatsView captures all known information about a read. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct FullReadStatsView { + /// Iteration stats describe how efficient the read is, e.g. comparing + /// rows seen vs. rows returned or cells seen vs cells returned can provide an + /// indication of read efficiency (the higher the ratio of seen to retuned the + /// better). + #[prost(message, optional, tag = "1")] + pub read_iteration_stats: ::core::option::Option, + /// Request latency stats describe the time taken to complete a request, from + /// the server side. + #[prost(message, optional, tag = "2")] + pub request_latency_stats: ::core::option::Option, +} +/// RequestStats is the container for additional information pertaining to a +/// single request, helpful for evaluating the performance of the sent request. +/// Currently, there are the following supported methods: +/// * google.bigtable.v2.ReadRows +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct RequestStats { + /// Information pertaining to each request type received. The type is chosen + /// based on the requested view. + /// + /// See the messages above for additional context. + #[prost(oneof = "request_stats::StatsView", tags = "1")] + pub stats_view: ::core::option::Option, +} +/// Nested message and enum types in `RequestStats`. +pub mod request_stats { + /// Information pertaining to each request type received. The type is chosen + /// based on the requested view. + /// + /// See the messages above for additional context. + #[allow(clippy::derive_partial_eq_without_eq)] + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum StatsView { + /// Available with the ReadRowsRequest.RequestStatsView.REQUEST_STATS_FULL + /// view, see package google.bigtable.v2. + #[prost(message, tag = "1")] + FullReadStatsView(super::FullReadStatsView), + } +} /// Request message for Bigtable.ReadRows. #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -636,17 +760,85 @@ pub struct ReadRowsRequest { /// "default" application profile will be used. #[prost(string, tag = "5")] pub app_profile_id: ::prost::alloc::string::String, - /// The row keys and/or ranges to read. If not specified, reads from all rows. + /// The row keys and/or ranges to read sequentially. If not specified, reads + /// from all rows. #[prost(message, optional, tag = "2")] pub rows: ::core::option::Option, /// The filter to apply to the contents of the specified row(s). If unset, /// reads the entirety of each row. #[prost(message, optional, tag = "3")] pub filter: ::core::option::Option, - /// The read will terminate after committing to N rows' worth of results. The + /// The read will stop after committing to N rows' worth of results. The /// default (zero) is to return all results. #[prost(int64, tag = "4")] pub rows_limit: i64, + /// The view into RequestStats, as described above. + #[prost(enumeration = "read_rows_request::RequestStatsView", tag = "6")] + pub request_stats_view: i32, + /// Experimental API - Please note that this API is currently experimental + /// and can change in the future. + /// + /// Return rows in lexiographical descending order of the row keys. The row + /// contents will not be affected by this flag. + /// + /// Example result set: + ///```ignore + /// [ + /// {key: "k2", "f:col1": "v1", "f:col2": "v1"}, + /// {key: "k1", "f:col1": "v2", "f:col2": "v2"} + /// ] + #[prost(bool, tag = "7")] + pub reversed: bool, +} +/// Nested message and enum types in `ReadRowsRequest`. +pub mod read_rows_request { + /// The desired view into RequestStats that should be returned in the response. + /// + /// See also: RequestStats message. + #[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + Hash, + PartialOrd, + Ord, + ::prost::Enumeration + )] + #[repr(i32)] + pub enum RequestStatsView { + /// The default / unset value. The API will default to the NONE option below. + Unspecified = 0, + /// Do not include any RequestStats in the response. This will leave the + /// RequestStats embedded message unset in the response. + RequestStatsNone = 1, + /// Include the full set of available RequestStats in the response, + /// applicable to this read. + RequestStatsFull = 2, + } + impl RequestStatsView { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + RequestStatsView::Unspecified => "REQUEST_STATS_VIEW_UNSPECIFIED", + RequestStatsView::RequestStatsNone => "REQUEST_STATS_NONE", + RequestStatsView::RequestStatsFull => "REQUEST_STATS_FULL", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "REQUEST_STATS_VIEW_UNSPECIFIED" => Some(Self::Unspecified), + "REQUEST_STATS_NONE" => Some(Self::RequestStatsNone), + "REQUEST_STATS_FULL" => Some(Self::RequestStatsFull), + _ => None, + } + } + } } /// Response message for Bigtable.ReadRows. #[allow(clippy::derive_partial_eq_without_eq)] @@ -664,6 +856,28 @@ pub struct ReadRowsResponse { /// key, allowing the client to skip that work on a retry. #[prost(bytes = "vec", tag = "2")] pub last_scanned_row_key: ::prost::alloc::vec::Vec, + /// + /// If requested, provide enhanced query performance statistics. The semantics + /// dictate: + /// * request_stats is empty on every (streamed) response, except + /// * request_stats has non-empty information after all chunks have been + /// streamed, where the ReadRowsResponse message only contains + /// request_stats. + /// * For example, if a read request would have returned an empty + /// response instead a single ReadRowsResponse is streamed with empty + /// chunks and request_stats filled. + /// + /// Visually, response messages will stream as follows: + /// ... -> {chunks: \[...\]} -> {chunks: [], request_stats: {...}} + /// \______________________/ \________________________________/ + /// Primary response Trailer of RequestStats info + /// + /// Or if the read did not return any values: + /// {chunks: [], request_stats: {...}} + /// \________________________________/ + /// Trailer of RequestStats info + #[prost(message, optional, tag = "3")] + pub request_stats: ::core::option::Option, } /// Nested message and enum types in `ReadRowsResponse`. pub mod read_rows_response { @@ -780,8 +994,8 @@ pub struct SampleRowKeysResponse { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct MutateRowRequest { - /// Required. The unique name of the table to which the mutation should be applied. - /// Values are of the form + /// Required. The unique name of the table to which the mutation should be + /// applied. Values are of the form /// `projects//instances//tables/
`. #[prost(string, tag = "1")] pub table_name: ::prost::alloc::string::String, @@ -792,9 +1006,9 @@ pub struct MutateRowRequest { /// Required. The key of the row to which the mutation should be applied. #[prost(bytes = "vec", tag = "2")] pub row_key: ::prost::alloc::vec::Vec, - /// Required. Changes to be atomically applied to the specified row. Entries are applied - /// in order, meaning that earlier mutations can be masked by later ones. - /// Must contain at least one entry and at most 100000. + /// Required. Changes to be atomically applied to the specified row. Entries + /// are applied in order, meaning that earlier mutations can be masked by later + /// ones. Must contain at least one entry and at most 100000. #[prost(message, repeated, tag = "3")] pub mutations: ::prost::alloc::vec::Vec, } @@ -806,7 +1020,8 @@ pub struct MutateRowResponse {} #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct MutateRowsRequest { - /// Required. The unique name of the table to which the mutations should be applied. + /// Required. The unique name of the table to which the mutations should be + /// applied. #[prost(string, tag = "1")] pub table_name: ::prost::alloc::string::String, /// This value specifies routing for replication. If not specified, the @@ -830,10 +1045,9 @@ pub mod mutate_rows_request { /// The key of the row to which the `mutations` should be applied. #[prost(bytes = "vec", tag = "1")] pub row_key: ::prost::alloc::vec::Vec, - /// Required. Changes to be atomically applied to the specified row. Mutations are - /// applied in order, meaning that earlier mutations can be masked by - /// later ones. - /// You must specify at least one mutation. + /// Required. Changes to be atomically applied to the specified row. + /// Mutations are applied in order, meaning that earlier mutations can be + /// masked by later ones. You must specify at least one mutation. #[prost(message, repeated, tag = "2")] pub mutations: ::prost::alloc::vec::Vec, } @@ -845,6 +1059,11 @@ pub struct MutateRowsResponse { /// One or more results for Entries from the batch request. #[prost(message, repeated, tag = "1")] pub entries: ::prost::alloc::vec::Vec, + /// Information about how client should limit the rate (QPS). Primirily used by + /// supported official Cloud Bigtable clients. If unset, the rate limit info is + /// not provided by the server. + #[prost(message, optional, tag = "3")] + pub rate_limit_info: ::core::option::Option, } /// Nested message and enum types in `MutateRowsResponse`. pub mod mutate_rows_response { @@ -864,13 +1083,36 @@ pub mod mutate_rows_response { pub status: ::core::option::Option, } } +/// Information about how client should adjust the load to Bigtable. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct RateLimitInfo { + /// Time that clients should wait before adjusting the target rate again. + /// If clients adjust rate too frequently, the impact of the previous + /// adjustment may not have been taken into account and may + /// over-throttle or under-throttle. If clients adjust rate too slowly, they + /// will not be responsive to load changes on server side, and may + /// over-throttle or under-throttle. + #[prost(message, optional, tag = "1")] + pub period: ::core::option::Option<::prost_types::Duration>, + /// If it has been at least one `period` since the last load adjustment, the + /// client should multiply the current load by this value to get the new target + /// load. For example, if the current load is 100 and `factor` is 0.8, the new + /// target load should be 80. After adjusting, the client should ignore + /// `factor` until another `period` has passed. + /// + /// The client can measure its load using any unit that's comparable over time + /// For example, QPS can be used as long as each request involves a similar + /// amount of work. + #[prost(double, tag = "2")] + pub factor: f64, +} /// Request message for Bigtable.CheckAndMutateRow. #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct CheckAndMutateRowRequest { - /// Required. The unique name of the table to which the conditional mutation should be - /// applied. - /// Values are of the form + /// Required. The unique name of the table to which the conditional mutation + /// should be applied. Values are of the form /// `projects//instances//tables/
`. #[prost(string, tag = "1")] pub table_name: ::prost::alloc::string::String, @@ -878,7 +1120,8 @@ pub struct CheckAndMutateRowRequest { /// "default" application profile will be used. #[prost(string, tag = "7")] pub app_profile_id: ::prost::alloc::string::String, - /// Required. The key of the row to which the conditional mutation should be applied. + /// Required. The key of the row to which the conditional mutation should be + /// applied. #[prost(bytes = "vec", tag = "2")] pub row_key: ::prost::alloc::vec::Vec, /// The filter to be applied to the contents of the specified row. Depending @@ -911,13 +1154,30 @@ pub struct CheckAndMutateRowResponse { #[prost(bool, tag = "1")] pub predicate_matched: bool, } +/// Request message for client connection keep-alive and warming. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct PingAndWarmRequest { + /// Required. The unique name of the instance to check permissions for as well + /// as respond. Values are of the form + /// `projects//instances/`. + #[prost(string, tag = "1")] + pub name: ::prost::alloc::string::String, + /// This value specifies routing for replication. If not specified, the + /// "default" application profile will be used. + #[prost(string, tag = "2")] + pub app_profile_id: ::prost::alloc::string::String, +} +/// Response message for Bigtable.PingAndWarm connection keepalive and warming. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct PingAndWarmResponse {} /// Request message for Bigtable.ReadModifyWriteRow. #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ReadModifyWriteRowRequest { - /// Required. The unique name of the table to which the read/modify/write rules should be - /// applied. - /// Values are of the form + /// Required. The unique name of the table to which the read/modify/write rules + /// should be applied. Values are of the form /// `projects//instances//tables/
`. #[prost(string, tag = "1")] pub table_name: ::prost::alloc::string::String, @@ -925,12 +1185,13 @@ pub struct ReadModifyWriteRowRequest { /// "default" application profile will be used. #[prost(string, tag = "4")] pub app_profile_id: ::prost::alloc::string::String, - /// Required. The key of the row to which the read/modify/write rules should be applied. + /// Required. The key of the row to which the read/modify/write rules should be + /// applied. #[prost(bytes = "vec", tag = "2")] pub row_key: ::prost::alloc::vec::Vec, - /// Required. Rules specifying how the specified row's contents are to be transformed - /// into writes. Entries are applied in order, meaning that earlier rules will - /// affect the results of later ones. + /// Required. Rules specifying how the specified row's contents are to be + /// transformed into writes. Entries are applied in order, meaning that earlier + /// rules will affect the results of later ones. #[prost(message, repeated, tag = "3")] pub rules: ::prost::alloc::vec::Vec, } @@ -942,6 +1203,312 @@ pub struct ReadModifyWriteRowResponse { #[prost(message, optional, tag = "1")] pub row: ::core::option::Option, } +/// NOTE: This API is intended to be used by Apache Beam BigtableIO. +/// Request message for Bigtable.GenerateInitialChangeStreamPartitions. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GenerateInitialChangeStreamPartitionsRequest { + /// Required. The unique name of the table from which to get change stream + /// partitions. Values are of the form + /// `projects//instances//tables/
`. + /// Change streaming must be enabled on the table. + #[prost(string, tag = "1")] + pub table_name: ::prost::alloc::string::String, + /// This value specifies routing for replication. If not specified, the + /// "default" application profile will be used. + /// Single cluster routing must be configured on the profile. + #[prost(string, tag = "2")] + pub app_profile_id: ::prost::alloc::string::String, +} +/// NOTE: This API is intended to be used by Apache Beam BigtableIO. +/// Response message for Bigtable.GenerateInitialChangeStreamPartitions. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GenerateInitialChangeStreamPartitionsResponse { + /// A partition of the change stream. + #[prost(message, optional, tag = "1")] + pub partition: ::core::option::Option, +} +/// NOTE: This API is intended to be used by Apache Beam BigtableIO. +/// Request message for Bigtable.ReadChangeStream. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ReadChangeStreamRequest { + /// Required. The unique name of the table from which to read a change stream. + /// Values are of the form + /// `projects//instances//tables/
`. + /// Change streaming must be enabled on the table. + #[prost(string, tag = "1")] + pub table_name: ::prost::alloc::string::String, + /// This value specifies routing for replication. If not specified, the + /// "default" application profile will be used. + /// Single cluster routing must be configured on the profile. + #[prost(string, tag = "2")] + pub app_profile_id: ::prost::alloc::string::String, + /// The partition to read changes from. + #[prost(message, optional, tag = "3")] + pub partition: ::core::option::Option, + /// If specified, OK will be returned when the stream advances beyond + /// this time. Otherwise, changes will be continuously delivered on the stream. + /// This value is inclusive and will be truncated to microsecond granularity. + #[prost(message, optional, tag = "5")] + pub end_time: ::core::option::Option<::prost_types::Timestamp>, + /// If specified, the duration between `Heartbeat` messages on the stream. + /// Otherwise, defaults to 5 seconds. + #[prost(message, optional, tag = "7")] + pub heartbeat_duration: ::core::option::Option<::prost_types::Duration>, + /// Options for describing where we want to start reading from the stream. + #[prost(oneof = "read_change_stream_request::StartFrom", tags = "4, 6")] + pub start_from: ::core::option::Option, +} +/// Nested message and enum types in `ReadChangeStreamRequest`. +pub mod read_change_stream_request { + /// Options for describing where we want to start reading from the stream. + #[allow(clippy::derive_partial_eq_without_eq)] + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum StartFrom { + /// Start reading the stream at the specified timestamp. This timestamp must + /// be within the change stream retention period, less than or equal to the + /// current time, and after change stream creation, whichever is greater. + /// This value is inclusive and will be truncated to microsecond granularity. + #[prost(message, tag = "4")] + StartTime(::prost_types::Timestamp), + /// Tokens that describe how to resume reading a stream where reading + /// previously left off. If specified, changes will be read starting at the + /// the position. Tokens are delivered on the stream as part of `Heartbeat` + /// and `CloseStream` messages. + /// + /// If a single token is provided, the token’s partition must exactly match + /// the request’s partition. If multiple tokens are provided, as in the case + /// of a partition merge, the union of the token partitions must exactly + /// cover the request’s partition. Otherwise, INVALID_ARGUMENT will be + /// returned. + #[prost(message, tag = "6")] + ContinuationTokens(super::StreamContinuationTokens), + } +} +/// NOTE: This API is intended to be used by Apache Beam BigtableIO. +/// Response message for Bigtable.ReadChangeStream. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ReadChangeStreamResponse { + /// The data or control message on the stream. + #[prost(oneof = "read_change_stream_response::StreamRecord", tags = "1, 2, 3")] + pub stream_record: ::core::option::Option, +} +/// Nested message and enum types in `ReadChangeStreamResponse`. +pub mod read_change_stream_response { + /// A partial or complete mutation. + #[allow(clippy::derive_partial_eq_without_eq)] + #[derive(Clone, PartialEq, ::prost::Message)] + pub struct MutationChunk { + /// If set, then the mutation is a `SetCell` with a chunked value across + /// multiple messages. + #[prost(message, optional, tag = "1")] + pub chunk_info: ::core::option::Option, + /// If this is a continuation of a chunked message (`chunked_value_offset` > + /// 0), ignore all fields except the `SetCell`'s value and merge it with + /// the previous message by concatenating the value fields. + #[prost(message, optional, tag = "2")] + pub mutation: ::core::option::Option, + } + /// Nested message and enum types in `MutationChunk`. + pub mod mutation_chunk { + /// Information about the chunking of this mutation. + /// Only `SetCell` mutations can be chunked, and all chunks for a `SetCell` + /// will be delivered contiguously with no other mutation types interleaved. + #[allow(clippy::derive_partial_eq_without_eq)] + #[derive(Clone, PartialEq, ::prost::Message)] + pub struct ChunkInfo { + /// The total value size of all the chunks that make up the `SetCell`. + #[prost(int32, tag = "1")] + pub chunked_value_size: i32, + /// The byte offset of this chunk into the total value size of the + /// mutation. + #[prost(int32, tag = "2")] + pub chunked_value_offset: i32, + /// When true, this is the last chunk of a chunked `SetCell`. + #[prost(bool, tag = "3")] + pub last_chunk: bool, + } + } + /// A message corresponding to one or more mutations to the partition + /// being streamed. A single logical `DataChange` message may also be split + /// across a sequence of multiple individual messages. Messages other than + /// the first in a sequence will only have the `type` and `chunks` fields + /// populated, with the final message in the sequence also containing `done` + /// set to true. + #[allow(clippy::derive_partial_eq_without_eq)] + #[derive(Clone, PartialEq, ::prost::Message)] + pub struct DataChange { + /// The type of the mutation. + #[prost(enumeration = "data_change::Type", tag = "1")] + pub r#type: i32, + /// The cluster where the mutation was applied. + /// Not set when `type` is `GARBAGE_COLLECTION`. + #[prost(string, tag = "2")] + pub source_cluster_id: ::prost::alloc::string::String, + /// The row key for all mutations that are part of this `DataChange`. + /// If the `DataChange` is chunked across multiple messages, then this field + /// will only be set for the first message. + #[prost(bytes = "vec", tag = "3")] + pub row_key: ::prost::alloc::vec::Vec, + /// The timestamp at which the mutation was applied on the Bigtable server. + #[prost(message, optional, tag = "4")] + pub commit_timestamp: ::core::option::Option<::prost_types::Timestamp>, + /// A value that lets stream consumers reconstruct Bigtable's + /// conflict resolution semantics. + /// + /// In the event that the same row key, column family, column qualifier, + /// timestamp are modified on different clusters at the same + /// `commit_timestamp`, the mutation with the larger `tiebreaker` will be the + /// one chosen for the eventually consistent state of the system. + #[prost(int32, tag = "5")] + pub tiebreaker: i32, + /// The mutations associated with this change to the partition. + /// May contain complete mutations or chunks of a multi-message chunked + /// `DataChange` record. + #[prost(message, repeated, tag = "6")] + pub chunks: ::prost::alloc::vec::Vec, + /// When true, indicates that the entire `DataChange` has been read + /// and the client can safely process the message. + #[prost(bool, tag = "8")] + pub done: bool, + /// An encoded position for this stream's partition to restart reading from. + /// This token is for the StreamPartition from the request. + #[prost(string, tag = "9")] + pub token: ::prost::alloc::string::String, + /// An estimate of the commit timestamp that is usually lower than or equal + /// to any timestamp for a record that will be delivered in the future on the + /// stream. It is possible that, under particular circumstances that a future + /// record has a timestamp is is lower than a previously seen timestamp. For + /// an example usage see + /// + #[prost(message, optional, tag = "10")] + pub estimated_low_watermark: ::core::option::Option<::prost_types::Timestamp>, + } + /// Nested message and enum types in `DataChange`. + pub mod data_change { + /// The type of mutation. + #[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + Hash, + PartialOrd, + Ord, + ::prost::Enumeration + )] + #[repr(i32)] + pub enum Type { + /// The type is unspecified. + Unspecified = 0, + /// A user-initiated mutation. + User = 1, + /// A system-initiated mutation as part of garbage collection. + /// + GarbageCollection = 2, + /// This is a continuation of a multi-message change. + Continuation = 3, + } + impl Type { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + Type::Unspecified => "TYPE_UNSPECIFIED", + Type::User => "USER", + Type::GarbageCollection => "GARBAGE_COLLECTION", + Type::Continuation => "CONTINUATION", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "TYPE_UNSPECIFIED" => Some(Self::Unspecified), + "USER" => Some(Self::User), + "GARBAGE_COLLECTION" => Some(Self::GarbageCollection), + "CONTINUATION" => Some(Self::Continuation), + _ => None, + } + } + } + } + /// A periodic message with information that can be used to checkpoint + /// the state of a stream. + #[allow(clippy::derive_partial_eq_without_eq)] + #[derive(Clone, PartialEq, ::prost::Message)] + pub struct Heartbeat { + /// A token that can be provided to a subsequent `ReadChangeStream` call + /// to pick up reading at the current stream position. + #[prost(message, optional, tag = "1")] + pub continuation_token: ::core::option::Option, + /// An estimate of the commit timestamp that is usually lower than or equal + /// to any timestamp for a record that will be delivered in the future on the + /// stream. It is possible that, under particular circumstances that a future + /// record has a timestamp is is lower than a previously seen timestamp. For + /// an example usage see + /// + #[prost(message, optional, tag = "2")] + pub estimated_low_watermark: ::core::option::Option<::prost_types::Timestamp>, + } + /// A message indicating that the client should stop reading from the stream. + /// If status is OK and `continuation_tokens` & `new_partitions` are empty, the + /// stream has finished (for example if there was an `end_time` specified). + /// If `continuation_tokens` & `new_partitions` are present, then a change in + /// partitioning requires the client to open a new stream for each token to + /// resume reading. Example: + /// [B, D) ends + /// | + /// v + /// new_partitions: [A, C) [C, E) + /// continuation_tokens.partitions: [B,C) [C,D) + /// ^---^ ^---^ + /// ^ ^ + /// | | + /// | StreamContinuationToken 2 + /// | + /// StreamContinuationToken 1 + /// To read the new partition [A,C), supply the continuation tokens whose + /// ranges cover the new partition, for example ContinuationToken[A,B) & + /// ContinuationToken[B,C). + #[allow(clippy::derive_partial_eq_without_eq)] + #[derive(Clone, PartialEq, ::prost::Message)] + pub struct CloseStream { + /// The status of the stream. + #[prost(message, optional, tag = "1")] + pub status: ::core::option::Option, + /// If non-empty, contains the information needed to resume reading their + /// associated partitions. + #[prost(message, repeated, tag = "2")] + pub continuation_tokens: ::prost::alloc::vec::Vec< + super::StreamContinuationToken, + >, + /// If non-empty, contains the new partitions to start reading from, which + /// are related to but not necessarily identical to the partitions for the + /// above `continuation_tokens`. + #[prost(message, repeated, tag = "3")] + pub new_partitions: ::prost::alloc::vec::Vec, + } + /// The data or control message on the stream. + #[allow(clippy::derive_partial_eq_without_eq)] + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum StreamRecord { + /// A mutation to the partition. + #[prost(message, tag = "1")] + DataChange(DataChange), + /// A periodic heartbeat message. + #[prost(message, tag = "2")] + Heartbeat(Heartbeat), + /// An indication that the stream should be closed. + #[prost(message, tag = "3")] + CloseStream(CloseStream), + } +} /// Generated client implementations. pub mod bigtable_client { #![allow(unused_variables, dead_code, missing_docs, clippy::let_unit_value)] @@ -956,7 +1523,7 @@ pub mod bigtable_client { /// Attempt to create a new client by connecting to a given endpoint. pub async fn connect(dst: D) -> Result where - D: std::convert::TryInto, + D: TryInto, D::Error: Into, { let conn = tonic::transport::Endpoint::new(dst)?.connect().await?; @@ -1012,6 +1579,22 @@ pub mod bigtable_client { self.inner = self.inner.accept_compressed(encoding); self } + /// Limits the maximum size of a decoded message. + /// + /// Default: `4MB` + #[must_use] + pub fn max_decoding_message_size(mut self, limit: usize) -> Self { + self.inner = self.inner.max_decoding_message_size(limit); + self + } + /// Limits the maximum size of an encoded message. + /// + /// Default: `usize::MAX` + #[must_use] + pub fn max_encoding_message_size(mut self, limit: usize) -> Self { + self.inner = self.inner.max_encoding_message_size(limit); + self + } /// Streams back the contents of all requested rows in key order, optionally /// applying the same Reader filter to each. Depending on their size, /// rows and cells may be broken up across multiple responses, but @@ -1020,7 +1603,7 @@ pub mod bigtable_client { pub async fn read_rows( &mut self, request: impl tonic::IntoRequest, - ) -> Result< + ) -> std::result::Result< tonic::Response>, tonic::Status, > { @@ -1037,7 +1620,10 @@ pub mod bigtable_client { let path = http::uri::PathAndQuery::from_static( "/google.bigtable.v2.Bigtable/ReadRows", ); - self.inner.server_streaming(request.into_request(), path, codec).await + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("google.bigtable.v2.Bigtable", "ReadRows")); + self.inner.server_streaming(req, path, codec).await } /// Returns a sample of row keys in the table. The returned row keys will /// delimit contiguous sections of the table of approximately equal size, @@ -1046,7 +1632,7 @@ pub mod bigtable_client { pub async fn sample_row_keys( &mut self, request: impl tonic::IntoRequest, - ) -> Result< + ) -> std::result::Result< tonic::Response>, tonic::Status, > { @@ -1063,14 +1649,20 @@ pub mod bigtable_client { let path = http::uri::PathAndQuery::from_static( "/google.bigtable.v2.Bigtable/SampleRowKeys", ); - self.inner.server_streaming(request.into_request(), path, codec).await + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("google.bigtable.v2.Bigtable", "SampleRowKeys")); + self.inner.server_streaming(req, path, codec).await } /// Mutates a row atomically. Cells already present in the row are left /// unchanged unless explicitly changed by `mutation`. pub async fn mutate_row( &mut self, request: impl tonic::IntoRequest, - ) -> Result, tonic::Status> { + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { self.inner .ready() .await @@ -1084,7 +1676,10 @@ pub mod bigtable_client { let path = http::uri::PathAndQuery::from_static( "/google.bigtable.v2.Bigtable/MutateRow", ); - self.inner.unary(request.into_request(), path, codec).await + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("google.bigtable.v2.Bigtable", "MutateRow")); + self.inner.unary(req, path, codec).await } /// Mutates multiple rows in a batch. Each individual row is mutated /// atomically as in MutateRow, but the entire batch is not executed @@ -1092,7 +1687,7 @@ pub mod bigtable_client { pub async fn mutate_rows( &mut self, request: impl tonic::IntoRequest, - ) -> Result< + ) -> std::result::Result< tonic::Response>, tonic::Status, > { @@ -1109,13 +1704,19 @@ pub mod bigtable_client { let path = http::uri::PathAndQuery::from_static( "/google.bigtable.v2.Bigtable/MutateRows", ); - self.inner.server_streaming(request.into_request(), path, codec).await + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("google.bigtable.v2.Bigtable", "MutateRows")); + self.inner.server_streaming(req, path, codec).await } /// Mutates a row atomically based on the output of a predicate Reader filter. pub async fn check_and_mutate_row( &mut self, request: impl tonic::IntoRequest, - ) -> Result, tonic::Status> { + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { self.inner .ready() .await @@ -1129,7 +1730,39 @@ pub mod bigtable_client { let path = http::uri::PathAndQuery::from_static( "/google.bigtable.v2.Bigtable/CheckAndMutateRow", ); - self.inner.unary(request.into_request(), path, codec).await + let mut req = request.into_request(); + req.extensions_mut() + .insert( + GrpcMethod::new("google.bigtable.v2.Bigtable", "CheckAndMutateRow"), + ); + self.inner.unary(req, path, codec).await + } + /// Warm up associated instance metadata for this connection. + /// This call is not required but may be useful for connection keep-alive. + pub async fn ping_and_warm( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::new( + tonic::Code::Unknown, + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/google.bigtable.v2.Bigtable/PingAndWarm", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("google.bigtable.v2.Bigtable", "PingAndWarm")); + self.inner.unary(req, path, codec).await } /// Modifies a row atomically on the server. The method reads the latest /// existing timestamp and value from the specified columns and writes a new @@ -1139,7 +1772,10 @@ pub mod bigtable_client { pub async fn read_modify_write_row( &mut self, request: impl tonic::IntoRequest, - ) -> Result, tonic::Status> { + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { self.inner .ready() .await @@ -1153,7 +1789,85 @@ pub mod bigtable_client { let path = http::uri::PathAndQuery::from_static( "/google.bigtable.v2.Bigtable/ReadModifyWriteRow", ); - self.inner.unary(request.into_request(), path, codec).await + let mut req = request.into_request(); + req.extensions_mut() + .insert( + GrpcMethod::new("google.bigtable.v2.Bigtable", "ReadModifyWriteRow"), + ); + self.inner.unary(req, path, codec).await + } + /// NOTE: This API is intended to be used by Apache Beam BigtableIO. + /// Returns the current list of partitions that make up the table's + /// change stream. The union of partitions will cover the entire keyspace. + /// Partitions can be read with `ReadChangeStream`. + pub async fn generate_initial_change_stream_partitions( + &mut self, + request: impl tonic::IntoRequest< + super::GenerateInitialChangeStreamPartitionsRequest, + >, + ) -> std::result::Result< + tonic::Response< + tonic::codec::Streaming< + super::GenerateInitialChangeStreamPartitionsResponse, + >, + >, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::new( + tonic::Code::Unknown, + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/google.bigtable.v2.Bigtable/GenerateInitialChangeStreamPartitions", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert( + GrpcMethod::new( + "google.bigtable.v2.Bigtable", + "GenerateInitialChangeStreamPartitions", + ), + ); + self.inner.server_streaming(req, path, codec).await + } + /// NOTE: This API is intended to be used by Apache Beam BigtableIO. + /// Reads changes from a table's change stream. Changes will + /// reflect both user-initiated mutations and mutations that are caused by + /// garbage collection. + pub async fn read_change_stream( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response< + tonic::codec::Streaming, + >, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::new( + tonic::Code::Unknown, + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/google.bigtable.v2.Bigtable/ReadChangeStream", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert( + GrpcMethod::new("google.bigtable.v2.Bigtable", "ReadChangeStream"), + ); + self.inner.server_streaming(req, path, codec).await } } } diff --git a/storage-bigtable/proto/google.rpc.rs b/storage-bigtable/proto/google.rpc.rs index 8f3d2b98eee..e20cb148349 100644 --- a/storage-bigtable/proto/google.rpc.rs +++ b/storage-bigtable/proto/google.rpc.rs @@ -8,12 +8,14 @@ #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct Status { - /// The status code, which should be an enum value of \[google.rpc.Code][google.rpc.Code\]. + /// The status code, which should be an enum value of + /// \[google.rpc.Code][google.rpc.Code\]. #[prost(int32, tag = "1")] pub code: i32, /// A developer-facing error message, which should be in English. Any /// user-facing error message should be localized and sent in the - /// \[google.rpc.Status.details][google.rpc.Status.details\] field, or localized by the client. + /// \[google.rpc.Status.details][google.rpc.Status.details\] field, or localized + /// by the client. #[prost(string, tag = "2")] pub message: ::prost::alloc::string::String, /// A list of messages that carry the error details. There is a common set of diff --git a/storage-bigtable/src/access_token.rs b/storage-bigtable/src/access_token.rs index f4d5e9ade98..8881f594ace 100644 --- a/storage-bigtable/src/access_token.rs +++ b/storage-bigtable/src/access_token.rs @@ -91,41 +91,49 @@ impl AccessToken { } /// Call this function regularly to ensure the access token does not expire - pub async fn refresh(&self) { + pub fn refresh(&self) { // Check if it's time to try a token refresh - { - let token_r = self.token.read().unwrap(); - if token_r.1.elapsed().as_secs() < token_r.0.expires_in() as u64 / 2 { - return; - } + let token_r = self.token.read().unwrap(); + if token_r.1.elapsed().as_secs() < token_r.0.expires_in() as u64 / 2 { + debug!("Token is not expired yet"); + return; + } + drop(token_r); - #[allow(deprecated)] - if self - .refresh_active - .compare_and_swap(false, true, Ordering::Relaxed) - { - // Refresh already pending - return; - } + // Refresh already is progress + let refresh_progress = + self.refresh_active + .compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed); + if refresh_progress.is_err() { + debug!("Token update is already in progress"); + return; } - info!("Refreshing token"); - match time::timeout( - time::Duration::from_secs(5), - Self::get_token(&self.credentials, &self.scope), - ) - .await - { - Ok(new_token) => match (new_token, self.token.write()) { - (Ok(new_token), Ok(mut token_w)) => *token_w = new_token, - (Ok(_new_token), Err(err)) => warn!("{}", err), - (Err(err), _) => warn!("{}", err), - }, - Err(_) => { - warn!("Token refresh timeout") + let credentials = self.credentials.clone(); + let scope = self.scope.clone(); + let refresh_active = Arc::clone(&self.refresh_active); + let token = Arc::clone(&self.token); + tokio::spawn(async move { + match time::timeout( + time::Duration::from_secs(5), + Self::get_token(&credentials, &scope), + ) + .await + { + Ok(new_token) => match new_token { + Ok(new_token) => { + let mut token_w = token.write().unwrap(); + *token_w = new_token; + } + Err(err) => error!("Failed to fetch new token: {}", err), + }, + Err(_timeout) => { + warn!("Token refresh timeout") + } } - } - self.refresh_active.store(false, Ordering::Relaxed); + refresh_active.store(false, Ordering::Relaxed); + info!("Token refreshed"); + }); } /// Return an access token suitable for use in an HTTP authorization header diff --git a/storage-bigtable/src/bigtable.rs b/storage-bigtable/src/bigtable.rs index fd17cfa8dd2..3b5b46d0825 100644 --- a/storage-bigtable/src/bigtable.rs +++ b/storage-bigtable/src/bigtable.rs @@ -112,6 +112,7 @@ pub struct BigTableConnection { table_prefix: String, app_profile_id: String, timeout: Option, + max_message_size: usize, } impl BigTableConnection { @@ -132,11 +133,18 @@ impl BigTableConnection { read_only: bool, timeout: Option, credential_type: CredentialType, + max_message_size: usize, ) -> Result { match std::env::var("BIGTABLE_EMULATOR_HOST") { Ok(endpoint) => { info!("Connecting to bigtable emulator at {}", endpoint); - Self::new_for_emulator(instance_name, app_profile_id, &endpoint, timeout) + Self::new_for_emulator( + instance_name, + app_profile_id, + &endpoint, + timeout, + max_message_size, + ) } Err(_) => { @@ -201,6 +209,7 @@ impl BigTableConnection { table_prefix, app_profile_id: app_profile_id.to_string(), timeout, + max_message_size, }) } } @@ -211,6 +220,7 @@ impl BigTableConnection { app_profile_id: &str, endpoint: &str, timeout: Option, + max_message_size: usize, ) -> Result { Ok(Self { access_token: None, @@ -220,6 +230,7 @@ impl BigTableConnection { table_prefix: format!("projects/emulator/instances/{instance_name}/tables/"), app_profile_id: app_profile_id.to_string(), timeout, + max_message_size, }) } @@ -245,7 +256,9 @@ impl BigTableConnection { } Ok(req) }, - ); + ) + .max_decoding_message_size(self.max_message_size) + .max_encoding_message_size(self.max_message_size); BigTable { access_token: self.access_token.clone(), client, @@ -399,9 +412,9 @@ impl) -> InterceptedRequestResult> BigTable { Ok(rows) } - async fn refresh_access_token(&self) { + fn refresh_access_token(&self) { if let Some(ref access_token) = self.access_token { - access_token.refresh().await; + access_token.refresh(); } } @@ -423,7 +436,7 @@ impl) -> InterceptedRequestResult> BigTable { if rows_limit == 0 { return Ok(vec![]); } - self.refresh_access_token().await; + self.refresh_access_token(); let response = self .client .read_rows(ReadRowsRequest { @@ -458,6 +471,8 @@ impl) -> InterceptedRequestResult> BigTable { ], })), }), + request_stats_view: 0, + reversed: false, }) .await? .into_inner(); @@ -468,7 +483,7 @@ impl) -> InterceptedRequestResult> BigTable { /// Check whether a row key exists in a `table` pub async fn row_key_exists(&mut self, table_name: &str, row_key: RowKey) -> Result { - self.refresh_access_token().await; + self.refresh_access_token(); let response = self .client @@ -483,6 +498,8 @@ impl) -> InterceptedRequestResult> BigTable { filter: Some(RowFilter { filter: Some(row_filter::Filter::StripValueTransformer(true)), }), + request_stats_view: 0, + reversed: false, }) .await? .into_inner(); @@ -513,7 +530,7 @@ impl) -> InterceptedRequestResult> BigTable { if rows_limit == 0 { return Ok(vec![]); } - self.refresh_access_token().await; + self.refresh_access_token(); let response = self .client .read_rows(ReadRowsRequest { @@ -534,6 +551,8 @@ impl) -> InterceptedRequestResult> BigTable { // Only return the latest version of each cell filter: Some(row_filter::Filter::CellsPerColumnLimitFilter(1)), }), + request_stats_view: 0, + reversed: false, }) .await? .into_inner(); @@ -547,7 +566,7 @@ impl) -> InterceptedRequestResult> BigTable { table_name: &str, row_keys: &[RowKey], ) -> Result> { - self.refresh_access_token().await; + self.refresh_access_token(); let response = self .client @@ -566,6 +585,8 @@ impl) -> InterceptedRequestResult> BigTable { // Only return the latest version of each cell filter: Some(row_filter::Filter::CellsPerColumnLimitFilter(1)), }), + request_stats_view: 0, + reversed: false, }) .await? .into_inner(); @@ -583,7 +604,7 @@ impl) -> InterceptedRequestResult> BigTable { table_name: &str, row_key: RowKey, ) -> Result { - self.refresh_access_token().await; + self.refresh_access_token(); let response = self .client @@ -599,6 +620,8 @@ impl) -> InterceptedRequestResult> BigTable { // Only return the latest version of each cell filter: Some(row_filter::Filter::CellsPerColumnLimitFilter(1)), }), + request_stats_view: 0, + reversed: false, }) .await? .into_inner(); @@ -612,7 +635,7 @@ impl) -> InterceptedRequestResult> BigTable { /// Delete one or more `table` rows async fn delete_rows(&mut self, table_name: &str, row_keys: &[RowKey]) -> Result<()> { - self.refresh_access_token().await; + self.refresh_access_token(); let mut entries = vec![]; for row_key in row_keys { @@ -658,7 +681,7 @@ impl) -> InterceptedRequestResult> BigTable { family_name: &str, row_data: &[(&RowKey, RowData)], ) -> Result<()> { - self.refresh_access_token().await; + self.refresh_access_token(); let mut entries = vec![]; for (row_key, row_data) in row_data { diff --git a/storage-bigtable/src/lib.rs b/storage-bigtable/src/lib.rs index 0b8ed4d3a59..1b6b769f040 100644 --- a/storage-bigtable/src/lib.rs +++ b/storage-bigtable/src/lib.rs @@ -376,6 +376,7 @@ impl From for TransactionByAddrInfo { pub const DEFAULT_INSTANCE_NAME: &str = "solana-ledger"; pub const DEFAULT_APP_PROFILE_ID: &str = "default"; +pub const DEFAULT_MAX_MESSAGE_SIZE: usize = 64 * 1024 * 1024; // 64MB #[derive(Debug)] pub enum CredentialType { @@ -390,6 +391,7 @@ pub struct LedgerStorageConfig { pub credential_type: CredentialType, pub instance_name: String, pub app_profile_id: String, + pub max_message_size: usize, } impl Default for LedgerStorageConfig { @@ -400,6 +402,7 @@ impl Default for LedgerStorageConfig { credential_type: CredentialType::Filepath(None), instance_name: DEFAULT_INSTANCE_NAME.to_string(), app_profile_id: DEFAULT_APP_PROFILE_ID.to_string(), + max_message_size: DEFAULT_MAX_MESSAGE_SIZE, } } } @@ -466,6 +469,7 @@ impl LedgerStorage { app_profile_id, endpoint, timeout, + LedgerStorageConfig::default().max_message_size, )?, stats, }) @@ -479,6 +483,7 @@ impl LedgerStorage { instance_name, app_profile_id, credential_type, + max_message_size, } = config; let connection = bigtable::BigTableConnection::new( instance_name.as_str(), @@ -486,6 +491,7 @@ impl LedgerStorage { read_only, timeout, credential_type, + max_message_size, ) .await?; Ok(Self { stats, connection }) diff --git a/streamer/Cargo.toml b/streamer/Cargo.toml index 21ae96d11fd..576ec0d51fa 100644 --- a/streamer/Cargo.toml +++ b/streamer/Cargo.toml @@ -13,6 +13,7 @@ edition = { workspace = true } async-channel = { workspace = true } bytes = { workspace = true } crossbeam-channel = { workspace = true } +futures = { workspace = true } futures-util = { workspace = true } histogram = { workspace = true } indexmap = { workspace = true } @@ -37,6 +38,7 @@ x509-parser = { workspace = true } [dev-dependencies] assert_matches = { workspace = true } +socket2 = { workspace = true } solana-logger = { workspace = true } [lib] diff --git a/streamer/src/nonblocking/quic.rs b/streamer/src/nonblocking/quic.rs index d238552abb0..a7f12baaf93 100644 --- a/streamer/src/nonblocking/quic.rs +++ b/streamer/src/nonblocking/quic.rs @@ -1,6 +1,6 @@ use { crate::{ - quic::{configure_server, QuicServerError, StreamStats}, + quic::{configure_server, QuicServerError, StreamStats, MAX_UNSTAKED_CONNECTIONS}, streamer::StakedNodes, tls_certificates::get_pubkey_from_tls_certificate, }, @@ -9,9 +9,10 @@ use { }, bytes::Bytes, crossbeam_channel::Sender, + futures::{stream::FuturesUnordered, Future, StreamExt as _}, indexmap::map::{Entry, IndexMap}, percentage::Percentage, - quinn::{Connecting, Connection, Endpoint, EndpointConfig, TokioRuntime, VarInt}, + quinn::{Accept, Connecting, Connection, Endpoint, EndpointConfig, TokioRuntime, VarInt}, quinn_proto::VarIntBoundsExceeded, rand::{thread_rng, Rng}, solana_perf::packet::{PacketBatch, PACKETS_PER_BATCH}, @@ -30,15 +31,35 @@ use { std::{ iter::repeat_with, net::{IpAddr, SocketAddr, UdpSocket}, + pin::Pin, + // CAUTION: be careful not to introduce any awaits while holding an RwLock. sync::{ atomic::{AtomicBool, AtomicU64, Ordering}, - Arc, Mutex, MutexGuard, RwLock, + Arc, RwLock, }, + task::Poll, time::{Duration, Instant}, }, - tokio::{task::JoinHandle, time::timeout}, + tokio::{ + // CAUTION: It's kind of sketch that we're mixing async and sync locks (see the RwLock above). + // This is done so that sync code can also access the stake table. + // Make sure we don't hold a sync lock across an await - including the await to + // lock an async Mutex. This does not happen now and should not happen as long as we + // don't hold an async Mutex and sync RwLock at the same time (currently true) + // but if we do, the scope of the RwLock must always be a subset of the async Mutex + // (i.e. lock order is always async Mutex -> RwLock). Also, be careful not to + // introduce any other awaits while holding the RwLock. + select, + sync::{Mutex, MutexGuard}, + task::JoinHandle, + time::timeout, + }, }; +/// Limit to 500K PPS +const MAX_STREAMS_PER_100MS: u64 = 500_000 / 10; +const MAX_UNSTAKED_STREAMS_PERCENT: u64 = 20; +const STREAM_THROTTLING_INTERVAL: Duration = Duration::from_millis(100); const WAIT_FOR_STREAM_TIMEOUT: Duration = Duration::from_millis(100); pub const DEFAULT_WAIT_FOR_CHUNK_TIMEOUT: Duration = Duration::from_secs(10); @@ -55,6 +76,7 @@ const CONNECTION_CLOSE_REASON_EXCEED_MAX_STREAM_COUNT: &[u8] = b"exceed_max_stre const CONNECTION_CLOSE_CODE_TOO_MANY: u32 = 4; const CONNECTION_CLOSE_REASON_TOO_MANY: &[u8] = b"too_many"; +const STREAM_STOP_CODE_THROTTLING: u32 = 15; // A sequence of bytes that is part of a packet // along with where in the packet it is @@ -95,20 +117,57 @@ pub fn spawn_server( wait_for_chunk_timeout: Duration, coalesce: Duration, ) -> Result<(Endpoint, Arc, JoinHandle<()>), QuicServerError> { - info!("Start {name} quic server on {sock:?}"); + spawn_server_multi( + name, + vec![sock], + keypair, + gossip_host, + packet_sender, + exit, + max_connections_per_peer, + staked_nodes, + max_staked_connections, + max_unstaked_connections, + wait_for_chunk_timeout, + coalesce, + ) + .map(|(mut endpoints, stats, handle)| (endpoints.remove(0), stats, handle)) +} + +#[allow(clippy::too_many_arguments, clippy::type_complexity)] +pub fn spawn_server_multi( + name: &'static str, + sockets: Vec, + keypair: &Keypair, + gossip_host: IpAddr, + packet_sender: Sender, + exit: Arc, + max_connections_per_peer: usize, + staked_nodes: Arc>, + max_staked_connections: usize, + max_unstaked_connections: usize, + wait_for_chunk_timeout: Duration, + coalesce: Duration, +) -> Result<(Vec, Arc, JoinHandle<()>), QuicServerError> { + info!("Start {name} quic server on {sockets:?}"); let (config, _cert) = configure_server(keypair, gossip_host)?; - let endpoint = Endpoint::new( - EndpointConfig::default(), - Some(config), - sock, - Arc::new(TokioRuntime), - ) - .map_err(QuicServerError::EndpointFailed)?; + let endpoints = sockets + .into_iter() + .map(|sock| { + Endpoint::new( + EndpointConfig::default(), + Some(config.clone()), + sock, + Arc::new(TokioRuntime), + ) + .map_err(QuicServerError::EndpointFailed) + }) + .collect::, _>>()?; let stats = Arc::::default(); let handle = tokio::spawn(run_server( name, - endpoint.clone(), + endpoints.clone(), packet_sender, exit, max_connections_per_peer, @@ -119,13 +178,13 @@ pub fn spawn_server( wait_for_chunk_timeout, coalesce, )); - Ok((endpoint, stats, handle)) + Ok((endpoints, stats, handle)) } #[allow(clippy::too_many_arguments)] async fn run_server( name: &'static str, - incoming: Endpoint, + incoming: Vec, packet_sender: Sender, exit: Arc, max_connections_per_peer: usize, @@ -152,8 +211,37 @@ async fn run_server( stats.clone(), coalesce, )); + + let mut accepts = incoming + .iter() + .enumerate() + .map(|(i, incoming)| { + Box::pin(EndpointAccept { + accept: incoming.accept(), + endpoint: i, + }) + }) + .collect::>(); while !exit.load(Ordering::Relaxed) { - let timeout_connection = timeout(WAIT_FOR_CONNECTION_TIMEOUT, incoming.accept()).await; + let timeout_connection = select! { + ready = accepts.next() => { + if let Some((connecting, i)) = ready { + accepts.push( + Box::pin(EndpointAccept { + accept: incoming[i].accept(), + endpoint: i, + } + )); + Ok(connecting) + } else { + // we can't really get here - we never poll an empty FuturesUnordered + continue + } + } + _ = tokio::time::sleep(WAIT_FOR_CONNECTION_TIMEOUT) => { + Err(()) + } + }; if last_datapoint.elapsed().as_secs() >= 5 { stats.report(name); @@ -264,6 +352,7 @@ enum ConnectionHandlerError { MaxStreamError, } +#[derive(Clone)] struct NewConnectionHandlerParams { // In principle, the code can be made to work with a crossbeam channel // as long as we're careful never to use a blocking recv or send call @@ -348,13 +437,11 @@ fn handle_and_cache_new_connection( drop(connection_table_l); tokio::spawn(handle_connection( connection, - params.packet_sender.clone(), remote_addr, - params.remote_pubkey, last_update, connection_table, stream_exit, - params.stats.clone(), + params.clone(), peer_type, wait_for_chunk_timeout, )); @@ -379,7 +466,7 @@ fn handle_and_cache_new_connection( } } -fn prune_unstaked_connections_and_add_new_connection( +async fn prune_unstaked_connections_and_add_new_connection( connection: Connection, connection_table: Arc>, max_connections: usize, @@ -389,7 +476,7 @@ fn prune_unstaked_connections_and_add_new_connection( let stats = params.stats.clone(); if max_connections > 0 { let connection_table_clone = connection_table.clone(); - let mut connection_table = connection_table.lock().unwrap(); + let mut connection_table = connection_table.lock().await; prune_unstaked_connection_table(&mut connection_table, max_connections, stats); handle_and_cache_new_connection( connection, @@ -492,7 +579,8 @@ async fn setup_connection( ); if params.stake > 0 { - let mut connection_table_l = staked_connection_table.lock().unwrap(); + let mut connection_table_l = staked_connection_table.lock().await; + if connection_table_l.total_size >= max_staked_connections { let num_pruned = connection_table_l.prune_random(PRUNE_RANDOM_SAMPLE_SIZE, params.stake); @@ -521,7 +609,9 @@ async fn setup_connection( max_unstaked_connections, ¶ms, wait_for_chunk_timeout, - ) { + ) + .await + { stats .connection_added_from_staked_peer .fetch_add(1, Ordering::Relaxed); @@ -540,7 +630,9 @@ async fn setup_connection( max_unstaked_connections, ¶ms, wait_for_chunk_timeout, - ) { + ) + .await + { stats .connection_added_from_unstaked_peer .fetch_add(1, Ordering::Relaxed); @@ -681,19 +773,46 @@ async fn packet_batch_sender( } } -#[allow(clippy::too_many_arguments)] +fn max_streams_for_connection_in_100ms( + connection_type: ConnectionPeerType, + stake: u64, + total_stake: u64, +) -> u64 { + if matches!(connection_type, ConnectionPeerType::Unstaked) || stake == 0 { + Percentage::from(MAX_UNSTAKED_STREAMS_PERCENT) + .apply_to(MAX_STREAMS_PER_100MS) + .saturating_div(MAX_UNSTAKED_CONNECTIONS as u64) + } else { + const MIN_STAKED_STREAMS: u64 = 8; + let max_total_staked_streams: u64 = MAX_STREAMS_PER_100MS + - Percentage::from(MAX_UNSTAKED_STREAMS_PERCENT).apply_to(MAX_STREAMS_PER_100MS); + std::cmp::max( + MIN_STAKED_STREAMS, + ((max_total_staked_streams as f64 / total_stake as f64) * stake as f64) as u64, + ) + } +} + +fn reset_throttling_params_if_needed(last_instant: &mut tokio::time::Instant) -> bool { + if tokio::time::Instant::now().duration_since(*last_instant) > STREAM_THROTTLING_INTERVAL { + *last_instant = tokio::time::Instant::now(); + true + } else { + false + } +} + async fn handle_connection( connection: Connection, - packet_sender: AsyncSender, remote_addr: SocketAddr, - remote_pubkey: Option, last_update: Arc, connection_table: Arc>, stream_exit: Arc, - stats: Arc, + params: NewConnectionHandlerParams, peer_type: ConnectionPeerType, wait_for_chunk_timeout: Duration, ) { + let stats = params.stats; debug!( "quic new connection {} streams: {} connections: {}", remote_addr, @@ -702,17 +821,29 @@ async fn handle_connection( ); let stable_id = connection.stable_id(); stats.total_connections.fetch_add(1, Ordering::Relaxed); + let max_streams_per_100ms = + max_streams_for_connection_in_100ms(peer_type, params.stake, params.total_stake); + let mut last_throttling_instant = tokio::time::Instant::now(); + let mut streams_in_current_interval = 0; while !stream_exit.load(Ordering::Relaxed) { if let Ok(stream) = tokio::time::timeout(WAIT_FOR_STREAM_TIMEOUT, connection.accept_uni()).await { match stream { Ok(mut stream) => { + if reset_throttling_params_if_needed(&mut last_throttling_instant) { + streams_in_current_interval = 0; + } else if streams_in_current_interval >= max_streams_per_100ms { + stats.throttled_streams.fetch_add(1, Ordering::Relaxed); + let _ = stream.stop(VarInt::from_u32(STREAM_STOP_CODE_THROTTLING)); + continue; + } + streams_in_current_interval = streams_in_current_interval.saturating_add(1); stats.total_streams.fetch_add(1, Ordering::Relaxed); stats.total_new_streams.fetch_add(1, Ordering::Relaxed); let stream_exit = stream_exit.clone(); let stats = stats.clone(); - let packet_sender = packet_sender.clone(); + let packet_sender = params.packet_sender.clone(); let last_update = last_update.clone(); tokio::spawn(async move { let mut maybe_batch = None; @@ -764,8 +895,8 @@ async fn handle_connection( } } - let removed_connection_count = connection_table.lock().unwrap().remove_connection( - ConnectionTableKey::new(remote_addr.ip(), remote_pubkey), + let removed_connection_count = connection_table.lock().await.remove_connection( + ConnectionTableKey::new(remote_addr.ip(), params.remote_pubkey), remote_addr.port(), stable_id, ); @@ -1087,6 +1218,25 @@ impl ConnectionTable { } } +struct EndpointAccept<'a> { + endpoint: usize, + accept: Accept<'a>, +} + +impl<'a> Future for EndpointAccept<'a> { + type Output = (Option, usize); + + fn poll(self: Pin<&mut Self>, cx: &mut std::task::Context) -> Poll { + let i = self.endpoint; + // Safety: + // self is pinned and accept is a field so it can't get moved out. See safety docs of + // map_unchecked_mut. + unsafe { self.map_unchecked_mut(|this| &mut this.accept) } + .poll(cx) + .map(|r| (r, i)) + } +} + #[cfg(test)] pub mod test { use { @@ -1167,16 +1317,43 @@ pub mod test { SocketAddr, Arc, ) { - let s = UdpSocket::bind("127.0.0.1:0").unwrap(); + let sockets = { + #[cfg(not(any(target_os = "macos", target_os = "windows")))] + { + use std::{ + os::fd::{FromRawFd, IntoRawFd}, + str::FromStr as _, + }; + (0..10) + .map(|_| { + let sock = socket2::Socket::new( + socket2::Domain::IPV4, + socket2::Type::DGRAM, + Some(socket2::Protocol::UDP), + ) + .unwrap(); + sock.set_reuse_port(true).unwrap(); + sock.bind(&SocketAddr::from_str("127.0.0.1:0").unwrap().into()) + .unwrap(); + unsafe { UdpSocket::from_raw_fd(sock.into_raw_fd()) } + }) + .collect::>() + } + #[cfg(any(target_os = "macos", target_os = "windows"))] + { + vec![UdpSocket::bind("127.0.0.1:0").unwrap()] + } + }; + let exit = Arc::new(AtomicBool::new(false)); let (sender, receiver) = unbounded(); let keypair = Keypair::new(); - let ip = "127.0.0.1".parse().unwrap(); - let server_address = s.local_addr().unwrap(); + let server_address = sockets[0].local_addr().unwrap(); + let ip = server_address.ip(); let staked_nodes = Arc::new(RwLock::new(option_staked_nodes.unwrap_or_default())); - let (_, stats, t) = spawn_server( - "quic_streamer_test", - s, + let (_, stats, t) = spawn_server_multi( + "one-million-sol", + sockets, &keypair, ip, sender, @@ -1989,4 +2166,47 @@ pub mod test { compute_receive_window_ratio_for_staked_node(max_stake, min_stake, max_stake + 10); assert_eq!(ratio, max_ratio); } + + #[test] + fn test_max_streams_for_connection_in_100ms() { + // 50K packets per ms * 20% / 500 max unstaked connections + assert_eq!( + max_streams_for_connection_in_100ms(ConnectionPeerType::Unstaked, 0, 10000), + 20 + ); + + // 50K packets per ms * 20% / 500 max unstaked connections + assert_eq!( + max_streams_for_connection_in_100ms(ConnectionPeerType::Unstaked, 10, 10000), + 20 + ); + + // If stake is 0, same limits as unstaked connections will apply. + // 50K packets per ms * 20% / 500 max unstaked connections + assert_eq!( + max_streams_for_connection_in_100ms(ConnectionPeerType::Staked, 0, 10000), + 20 + ); + + // max staked streams = 50K packets per ms * 80% = 40K + // function = 40K * stake / total_stake + assert_eq!( + max_streams_for_connection_in_100ms(ConnectionPeerType::Staked, 15, 10000), + 60 + ); + + // max staked streams = 50K packets per ms * 80% = 40K + // function = 40K * stake / total_stake + assert_eq!( + max_streams_for_connection_in_100ms(ConnectionPeerType::Staked, 1000, 10000), + 4000 + ); + + // max staked streams = 50K packets per ms * 80% = 40K + // minimum staked streams. + assert_eq!( + max_streams_for_connection_in_100ms(ConnectionPeerType::Staked, 1, 50000), + 8 + ); + } } diff --git a/streamer/src/quic.rs b/streamer/src/quic.rs index fee0db110f1..8d8d2e46854 100644 --- a/streamer/src/quic.rs +++ b/streamer/src/quic.rs @@ -156,6 +156,7 @@ pub struct StreamStats { pub(crate) connection_setup_error_locally_closed: AtomicUsize, pub(crate) connection_removed: AtomicUsize, pub(crate) connection_remove_failed: AtomicUsize, + pub(crate) throttled_streams: AtomicUsize, } impl StreamStats { @@ -386,6 +387,11 @@ impl StreamStats { self.total_stream_read_timeouts.swap(0, Ordering::Relaxed), i64 ), + ( + "throttled_streams", + self.throttled_streams.swap(0, Ordering::Relaxed), + i64 + ), ); } } @@ -405,12 +411,44 @@ pub fn spawn_server( wait_for_chunk_timeout: Duration, coalesce: Duration, ) -> Result<(Endpoint, thread::JoinHandle<()>), QuicServerError> { + spawn_server_multi( + name, + vec![sock], + keypair, + gossip_host, + packet_sender, + exit, + max_connections_per_peer, + staked_nodes, + max_staked_connections, + max_unstaked_connections, + wait_for_chunk_timeout, + coalesce, + ) + .map(|(mut endpoints, thread_handle)| (endpoints.remove(0), thread_handle)) +} + +#[allow(clippy::too_many_arguments)] +pub fn spawn_server_multi( + name: &'static str, + sockets: Vec, + keypair: &Keypair, + gossip_host: IpAddr, + packet_sender: Sender, + exit: Arc, + max_connections_per_peer: usize, + staked_nodes: Arc>, + max_staked_connections: usize, + max_unstaked_connections: usize, + wait_for_chunk_timeout: Duration, + coalesce: Duration, +) -> Result<(Vec, thread::JoinHandle<()>), QuicServerError> { let runtime = rt(); - let (endpoint, _stats, task) = { + let (endpoints, _stats, task) = { let _guard = runtime.enter(); - crate::nonblocking::quic::spawn_server( + crate::nonblocking::quic::spawn_server_multi( name, - sock, + sockets, keypair, gossip_host, packet_sender, @@ -431,7 +469,7 @@ pub fn spawn_server( } }) .unwrap(); - Ok((endpoint, handle)) + Ok((endpoints, handle)) } #[cfg(test)] diff --git a/system-test/abi-testcases/mixed-validator-test.sh b/system-test/abi-testcases/mixed-validator-test.sh index 8ab673b26a3..c0400560dc5 100755 --- a/system-test/abi-testcases/mixed-validator-test.sh +++ b/system-test/abi-testcases/mixed-validator-test.sh @@ -30,14 +30,14 @@ solanaInstallGlobalOpts=( bootstrapInstall() { declare v=$1 if [[ ! -h $solanaInstallDataDir/active_release ]]; then - sh "$SOLANA_ROOT"/install/solana-install-init.sh "$v" "${solanaInstallGlobalOpts[@]}" + sh "$SOLANA_ROOT"/install/agave-install-init.sh "$v" "${solanaInstallGlobalOpts[@]}" fi export PATH="$solanaInstallDataDir/active_release/bin/:$PATH" } bootstrapInstall "$baselineVersion" for v in "${otherVersions[@]}"; do - solana-install-init "${solanaInstallGlobalOpts[@]}" "$v" + agave-install-init "${solanaInstallGlobalOpts[@]}" "$v" solana -V done @@ -113,7 +113,7 @@ for v in "${otherVersions[@]}"; do ( set -x tmux new-window -t abi -n "$v" " \ - $SOLANA_BIN/solana-validator \ + $SOLANA_BIN/agave-validator \ --ledger $ledger \ --no-snapshot-fetch \ --entrypoint 127.0.0.1:8001 \ diff --git a/system-test/stability-testcases/gossip-dos-test.sh b/system-test/stability-testcases/gossip-dos-test.sh index f8afade75dc..68c3c540d59 100755 --- a/system-test/stability-testcases/gossip-dos-test.sh +++ b/system-test/stability-testcases/gossip-dos-test.sh @@ -19,14 +19,14 @@ solanaInstallGlobalOpts=( bootstrapInstall() { declare v=$1 if [[ ! -h $solanaInstallDataDir/active_release ]]; then - sh "$SOLANA_ROOT"/install/solana-install-init.sh "$v" "${solanaInstallGlobalOpts[@]}" + sh "$SOLANA_ROOT"/install/agave-install-init.sh "$v" "${solanaInstallGlobalOpts[@]}" fi export PATH="$solanaInstallDataDir/active_release/bin/:$PATH" } bootstrapInstall "edge" -solana-install-init --version -solana-install-init edge +agave-install-init --version +agave-install-init edge solana-gossip --version solana-dos --version diff --git a/tokens/src/arg_parser.rs b/tokens/src/arg_parser.rs index 924c4e3e8ee..024bdf52832 100644 --- a/tokens/src/arg_parser.rs +++ b/tokens/src/arg_parser.rs @@ -652,7 +652,7 @@ where { let matches = get_matches(args); let config_file = matches.value_of("config_file").unwrap().to_string(); - let url = matches.value_of("url").map(|x| x.to_string()); + let url = matches.value_of("json_rpc_url").map(|x| x.to_string()); let command = match matches.subcommand() { ("distribute-tokens", Some(matches)) => { diff --git a/transaction-dos/src/main.rs b/transaction-dos/src/main.rs index 5d69e9e291b..11f8548227c 100644 --- a/transaction-dos/src/main.rs +++ b/transaction-dos/src/main.rs @@ -425,7 +425,7 @@ fn run_transactions_dos( } fn main() { - solana_logger::setup_with_default("solana=info"); + solana_logger::setup_with_default_filter(); let matches = App::new(crate_name!()) .about(crate_description!()) .version(solana_version::version!()) diff --git a/transaction-status/src/parse_token.rs b/transaction-status/src/parse_token.rs index ee9a04db3a7..c7111ee622a 100644 --- a/transaction-status/src/parse_token.rs +++ b/transaction-status/src/parse_token.rs @@ -4,9 +4,9 @@ use { }, extension::{ confidential_transfer::*, confidential_transfer_fee::*, cpi_guard::*, - default_account_state::*, interest_bearing_mint::*, memo_transfer::*, metadata_pointer::*, - mint_close_authority::*, permanent_delegate::*, reallocate::*, transfer_fee::*, - transfer_hook::*, + default_account_state::*, group_member_pointer::*, group_pointer::*, + interest_bearing_mint::*, memo_transfer::*, metadata_pointer::*, mint_close_authority::*, + permanent_delegate::*, reallocate::*, transfer_fee::*, transfer_hook::*, }, serde_json::{json, Map, Value}, solana_account_decoder::parse_token::{token_amount_to_ui_amount, UiAccountState}, @@ -233,7 +233,9 @@ pub fn parse_token( | AuthorityType::ConfidentialTransferMint | AuthorityType::TransferHookProgramId | AuthorityType::ConfidentialTransferFeeConfig - | AuthorityType::MetadataPointer => "mint", + | AuthorityType::MetadataPointer + | AuthorityType::GroupPointer + | AuthorityType::GroupMemberPointer => "mint", AuthorityType::AccountOwner | AuthorityType::CloseAccount => "account", }; let mut value = json!({ @@ -650,6 +652,30 @@ pub fn parse_token( account_keys, ) } + TokenInstruction::GroupPointerExtension => { + if instruction.data.len() < 2 { + return Err(ParseInstructionError::InstructionNotParsable( + ParsableProgram::SplToken, + )); + } + parse_group_pointer_instruction( + &instruction.data[1..], + &instruction.accounts, + account_keys, + ) + } + TokenInstruction::GroupMemberPointerExtension => { + if instruction.data.len() < 2 { + return Err(ParseInstructionError::InstructionNotParsable( + ParsableProgram::SplToken, + )); + } + parse_group_member_pointer_instruction( + &instruction.data[1..], + &instruction.accounts, + account_keys, + ) + } } } @@ -669,6 +695,8 @@ pub enum UiAuthorityType { TransferHookProgramId, ConfidentialTransferFeeConfig, MetadataPointer, + GroupPointer, + GroupMemberPointer, } impl From for UiAuthorityType { @@ -689,6 +717,8 @@ impl From for UiAuthorityType { UiAuthorityType::ConfidentialTransferFeeConfig } AuthorityType::MetadataPointer => UiAuthorityType::MetadataPointer, + AuthorityType::GroupPointer => UiAuthorityType::GroupPointer, + AuthorityType::GroupMemberPointer => UiAuthorityType::GroupMemberPointer, } } } @@ -716,6 +746,10 @@ pub enum UiExtensionType { ConfidentialTransferFeeAmount, MetadataPointer, TokenMetadata, + GroupPointer, + GroupMemberPointer, + TokenGroup, + TokenGroupMember, } impl From for UiExtensionType { @@ -747,6 +781,10 @@ impl From for UiExtensionType { } ExtensionType::MetadataPointer => UiExtensionType::MetadataPointer, ExtensionType::TokenMetadata => UiExtensionType::TokenMetadata, + ExtensionType::GroupPointer => UiExtensionType::GroupPointer, + ExtensionType::GroupMemberPointer => UiExtensionType::GroupMemberPointer, + ExtensionType::TokenGroup => UiExtensionType::TokenGroup, + ExtensionType::TokenGroupMember => UiExtensionType::TokenGroupMember, } } } diff --git a/transaction-status/src/parse_token/extension/group_member_pointer.rs b/transaction-status/src/parse_token/extension/group_member_pointer.rs new file mode 100644 index 00000000000..24d0503dc51 --- /dev/null +++ b/transaction-status/src/parse_token/extension/group_member_pointer.rs @@ -0,0 +1,189 @@ +use { + super::*, + spl_token_2022::{ + extension::group_member_pointer::instruction::*, + instruction::{decode_instruction_data, decode_instruction_type}, + }, +}; + +pub(in crate::parse_token) fn parse_group_member_pointer_instruction( + instruction_data: &[u8], + account_indexes: &[u8], + account_keys: &AccountKeys, +) -> Result { + match decode_instruction_type(instruction_data) + .map_err(|_| ParseInstructionError::InstructionNotParsable(ParsableProgram::SplToken))? + { + GroupMemberPointerInstruction::Initialize => { + check_num_token_accounts(account_indexes, 1)?; + let InitializeInstructionData { + authority, + member_address, + } = *decode_instruction_data(instruction_data).map_err(|_| { + ParseInstructionError::InstructionNotParsable(ParsableProgram::SplToken) + })?; + let mut value = json!({ + "mint": account_keys[account_indexes[0] as usize].to_string(), + }); + let map = value.as_object_mut().unwrap(); + if let Some(authority) = Option::::from(authority) { + map.insert("authority".to_string(), json!(authority.to_string())); + } + if let Some(member_address) = Option::::from(member_address) { + map.insert( + "memberAddress".to_string(), + json!(member_address.to_string()), + ); + } + Ok(ParsedInstructionEnum { + instruction_type: "initializeGroupMemberPointer".to_string(), + info: value, + }) + } + GroupMemberPointerInstruction::Update => { + check_num_token_accounts(account_indexes, 2)?; + let UpdateInstructionData { member_address } = + *decode_instruction_data(instruction_data).map_err(|_| { + ParseInstructionError::InstructionNotParsable(ParsableProgram::SplToken) + })?; + let mut value = json!({ + "mint": account_keys[account_indexes[0] as usize].to_string(), + }); + let map = value.as_object_mut().unwrap(); + if let Some(member_address) = Option::::from(member_address) { + map.insert( + "memberAddress".to_string(), + json!(member_address.to_string()), + ); + } + parse_signers( + map, + 1, + account_keys, + account_indexes, + "authority", + "multisigAuthority", + ); + Ok(ParsedInstructionEnum { + instruction_type: "updateGroupMemberPointer".to_string(), + info: value, + }) + } + } +} + +#[cfg(test)] +mod test { + use {super::*, solana_sdk::pubkey::Pubkey, spl_token_2022::solana_program::message::Message}; + + #[test] + fn test_parse_group_member_pointer_instruction() { + let mint_pubkey = Pubkey::new_unique(); + let authority = Pubkey::new_unique(); + let member_address = Pubkey::new_unique(); + + // Initialize variations + let init_ix = initialize( + &spl_token_2022::id(), + &mint_pubkey, + Some(authority), + Some(member_address), + ) + .unwrap(); + let mut message = Message::new(&[init_ix], None); + let compiled_instruction = &mut message.instructions[0]; + assert_eq!( + parse_token( + compiled_instruction, + &AccountKeys::new(&message.account_keys, None) + ) + .unwrap(), + ParsedInstructionEnum { + instruction_type: "initializeGroupMemberPointer".to_string(), + info: json!({ + "mint": mint_pubkey.to_string(), + "authority": authority.to_string(), + "memberAddress": member_address.to_string(), + }) + } + ); + + let init_ix = initialize(&spl_token_2022::id(), &mint_pubkey, None, None).unwrap(); + let mut message = Message::new(&[init_ix], None); + let compiled_instruction = &mut message.instructions[0]; + assert_eq!( + parse_token( + compiled_instruction, + &AccountKeys::new(&message.account_keys, None) + ) + .unwrap(), + ParsedInstructionEnum { + instruction_type: "initializeGroupMemberPointer".to_string(), + info: json!({ + "mint": mint_pubkey.to_string(), + }) + } + ); + + // Single owner Update + let update_ix = update( + &spl_token_2022::id(), + &mint_pubkey, + &authority, + &[], + Some(member_address), + ) + .unwrap(); + let mut message = Message::new(&[update_ix], None); + let compiled_instruction = &mut message.instructions[0]; + assert_eq!( + parse_token( + compiled_instruction, + &AccountKeys::new(&message.account_keys, None) + ) + .unwrap(), + ParsedInstructionEnum { + instruction_type: "updateGroupMemberPointer".to_string(), + info: json!({ + "mint": mint_pubkey.to_string(), + "authority": authority.to_string(), + "memberAddress": member_address.to_string(), + }) + } + ); + + // Multisig Update + let multisig_pubkey = Pubkey::new_unique(); + let multisig_signer0 = Pubkey::new_unique(); + let multisig_signer1 = Pubkey::new_unique(); + let update_ix = update( + &spl_token_2022::id(), + &mint_pubkey, + &multisig_pubkey, + &[&multisig_signer0, &multisig_signer1], + Some(member_address), + ) + .unwrap(); + let mut message = Message::new(&[update_ix], None); + let compiled_instruction = &mut message.instructions[0]; + assert_eq!( + parse_token( + compiled_instruction, + &AccountKeys::new(&message.account_keys, None) + ) + .unwrap(), + ParsedInstructionEnum { + instruction_type: "updateGroupMemberPointer".to_string(), + info: json!({ + "mint": mint_pubkey.to_string(), + "memberAddress": member_address.to_string(), + "multisigAuthority": multisig_pubkey.to_string(), + "signers": vec![ + multisig_signer0.to_string(), + multisig_signer1.to_string(), + ], + }) + } + ); + } +} diff --git a/transaction-status/src/parse_token/extension/group_pointer.rs b/transaction-status/src/parse_token/extension/group_pointer.rs new file mode 100644 index 00000000000..5800a2fd885 --- /dev/null +++ b/transaction-status/src/parse_token/extension/group_pointer.rs @@ -0,0 +1,183 @@ +use { + super::*, + spl_token_2022::{ + extension::group_pointer::instruction::*, + instruction::{decode_instruction_data, decode_instruction_type}, + }, +}; + +pub(in crate::parse_token) fn parse_group_pointer_instruction( + instruction_data: &[u8], + account_indexes: &[u8], + account_keys: &AccountKeys, +) -> Result { + match decode_instruction_type(instruction_data) + .map_err(|_| ParseInstructionError::InstructionNotParsable(ParsableProgram::SplToken))? + { + GroupPointerInstruction::Initialize => { + check_num_token_accounts(account_indexes, 1)?; + let InitializeInstructionData { + authority, + group_address, + } = *decode_instruction_data(instruction_data).map_err(|_| { + ParseInstructionError::InstructionNotParsable(ParsableProgram::SplToken) + })?; + let mut value = json!({ + "mint": account_keys[account_indexes[0] as usize].to_string(), + }); + let map = value.as_object_mut().unwrap(); + if let Some(authority) = Option::::from(authority) { + map.insert("authority".to_string(), json!(authority.to_string())); + } + if let Some(group_address) = Option::::from(group_address) { + map.insert("groupAddress".to_string(), json!(group_address.to_string())); + } + Ok(ParsedInstructionEnum { + instruction_type: "initializeGroupPointer".to_string(), + info: value, + }) + } + GroupPointerInstruction::Update => { + check_num_token_accounts(account_indexes, 2)?; + let UpdateInstructionData { group_address } = + *decode_instruction_data(instruction_data).map_err(|_| { + ParseInstructionError::InstructionNotParsable(ParsableProgram::SplToken) + })?; + let mut value = json!({ + "mint": account_keys[account_indexes[0] as usize].to_string(), + }); + let map = value.as_object_mut().unwrap(); + if let Some(group_address) = Option::::from(group_address) { + map.insert("groupAddress".to_string(), json!(group_address.to_string())); + } + parse_signers( + map, + 1, + account_keys, + account_indexes, + "authority", + "multisigAuthority", + ); + Ok(ParsedInstructionEnum { + instruction_type: "updateGroupPointer".to_string(), + info: value, + }) + } + } +} + +#[cfg(test)] +mod test { + use {super::*, solana_sdk::pubkey::Pubkey, spl_token_2022::solana_program::message::Message}; + + #[test] + fn test_parse_group_pointer_instruction() { + let mint_pubkey = Pubkey::new_unique(); + let authority = Pubkey::new_unique(); + let group_address = Pubkey::new_unique(); + + // Initialize variations + let init_ix = initialize( + &spl_token_2022::id(), + &mint_pubkey, + Some(authority), + Some(group_address), + ) + .unwrap(); + let mut message = Message::new(&[init_ix], None); + let compiled_instruction = &mut message.instructions[0]; + assert_eq!( + parse_token( + compiled_instruction, + &AccountKeys::new(&message.account_keys, None) + ) + .unwrap(), + ParsedInstructionEnum { + instruction_type: "initializeGroupPointer".to_string(), + info: json!({ + "mint": mint_pubkey.to_string(), + "authority": authority.to_string(), + "groupAddress": group_address.to_string(), + }) + } + ); + + let init_ix = initialize(&spl_token_2022::id(), &mint_pubkey, None, None).unwrap(); + let mut message = Message::new(&[init_ix], None); + let compiled_instruction = &mut message.instructions[0]; + assert_eq!( + parse_token( + compiled_instruction, + &AccountKeys::new(&message.account_keys, None) + ) + .unwrap(), + ParsedInstructionEnum { + instruction_type: "initializeGroupPointer".to_string(), + info: json!({ + "mint": mint_pubkey.to_string(), + }) + } + ); + + // Single owner Update + let update_ix = update( + &spl_token_2022::id(), + &mint_pubkey, + &authority, + &[], + Some(group_address), + ) + .unwrap(); + let mut message = Message::new(&[update_ix], None); + let compiled_instruction = &mut message.instructions[0]; + assert_eq!( + parse_token( + compiled_instruction, + &AccountKeys::new(&message.account_keys, None) + ) + .unwrap(), + ParsedInstructionEnum { + instruction_type: "updateGroupPointer".to_string(), + info: json!({ + "mint": mint_pubkey.to_string(), + "authority": authority.to_string(), + "groupAddress": group_address.to_string(), + }) + } + ); + + // Multisig Update + let multisig_pubkey = Pubkey::new_unique(); + let multisig_signer0 = Pubkey::new_unique(); + let multisig_signer1 = Pubkey::new_unique(); + let update_ix = update( + &spl_token_2022::id(), + &mint_pubkey, + &multisig_pubkey, + &[&multisig_signer0, &multisig_signer1], + Some(group_address), + ) + .unwrap(); + let mut message = Message::new(&[update_ix], None); + let compiled_instruction = &mut message.instructions[0]; + assert_eq!( + parse_token( + compiled_instruction, + &AccountKeys::new(&message.account_keys, None) + ) + .unwrap(), + ParsedInstructionEnum { + instruction_type: "updateGroupPointer".to_string(), + info: json!({ + "mint": mint_pubkey.to_string(), + "groupAddress": group_address.to_string(), + "multisigAuthority": multisig_pubkey.to_string(), + "signers": vec![ + multisig_signer0.to_string(), + multisig_signer1.to_string(), + ], + }) + } + ); + } +} diff --git a/transaction-status/src/parse_token/extension/mod.rs b/transaction-status/src/parse_token/extension/mod.rs index 8e65ddfcfc6..19dd4f14b2f 100644 --- a/transaction-status/src/parse_token/extension/mod.rs +++ b/transaction-status/src/parse_token/extension/mod.rs @@ -4,6 +4,8 @@ pub(super) mod confidential_transfer; pub(super) mod confidential_transfer_fee; pub(super) mod cpi_guard; pub(super) mod default_account_state; +pub(super) mod group_member_pointer; +pub(super) mod group_pointer; pub(super) mod interest_bearing_mint; pub(super) mod memo_transfer; pub(super) mod metadata_pointer; diff --git a/turbine/benches/cluster_info.rs b/turbine/benches/cluster_info.rs index 954e32903c3..1f15137175a 100644 --- a/turbine/benches/cluster_info.rs +++ b/turbine/benches/cluster_info.rs @@ -25,12 +25,7 @@ use { }, cluster_nodes::ClusterNodesCache, }, - std::{ - collections::HashMap, - net::UdpSocket, - sync::{Arc, RwLock}, - time::Duration, - }, + std::{collections::HashMap, net::UdpSocket, sync::Arc, time::Duration}, test::Bencher, }; @@ -49,7 +44,7 @@ fn broadcast_shreds_bench(bencher: &mut Bencher) { let socket = UdpSocket::bind("0.0.0.0:0").unwrap(); let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(10_000); let bank = Bank::new_for_benches(&genesis_config); - let bank_forks = Arc::new(RwLock::new(BankForks::new(bank))); + let bank_forks = BankForks::new_rw_arc(bank); const NUM_SHREDS: usize = 32; let shred = Shred::new_from_data(0, 0, 0, &[], ShredFlags::empty(), 0, 0, 0); diff --git a/turbine/benches/retransmit_stage.rs b/turbine/benches/retransmit_stage.rs index b0dd67db822..bfd68239fee 100644 --- a/turbine/benches/retransmit_stage.rs +++ b/turbine/benches/retransmit_stage.rs @@ -32,7 +32,7 @@ use { net::{Ipv4Addr, UdpSocket}, sync::{ atomic::{AtomicUsize, Ordering}, - Arc, RwLock, + Arc, }, thread::{sleep, Builder}, time::Duration, @@ -74,9 +74,8 @@ fn bench_retransmitter(bencher: &mut Bencher) { let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(100_000); let bank0 = Bank::new_for_benches(&genesis_config); - let bank_forks = BankForks::new(bank0); - let bank = bank_forks.working_bank(); - let bank_forks = Arc::new(RwLock::new(bank_forks)); + let bank_forks = BankForks::new_rw_arc(bank0); + let bank = bank_forks.read().unwrap().working_bank(); let (shreds_sender, shreds_receiver) = unbounded(); const NUM_THREADS: usize = 2; let sockets = (0..NUM_THREADS) diff --git a/turbine/src/broadcast_stage.rs b/turbine/src/broadcast_stage.rs index 07be0d0bfd6..cccda977b16 100644 --- a/turbine/src/broadcast_stage.rs +++ b/turbine/src/broadcast_stage.rs @@ -668,7 +668,7 @@ pub mod test { let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(10_000); let bank = Bank::new_for_tests(&genesis_config); - let bank_forks = Arc::new(RwLock::new(BankForks::new(bank))); + let bank_forks = BankForks::new_rw_arc(bank); let bank = bank_forks.read().unwrap().root_bank(); // Start up the broadcast stage diff --git a/turbine/src/broadcast_stage/standard_broadcast_run.rs b/turbine/src/broadcast_stage/standard_broadcast_run.rs index 031e7201234..37850d6ba99 100644 --- a/turbine/src/broadcast_stage/standard_broadcast_run.rs +++ b/turbine/src/broadcast_stage/standard_broadcast_run.rs @@ -544,7 +544,7 @@ mod test { genesis_config.ticks_per_slot = max_ticks_per_n_shreds(num_shreds_per_slot, None) + 1; let bank = Bank::new_for_tests(&genesis_config); - let bank_forks = Arc::new(RwLock::new(BankForks::new(bank))); + let bank_forks = BankForks::new_rw_arc(bank); let bank0 = bank_forks.read().unwrap().root_bank(); ( blockstore, diff --git a/turbine/src/cluster_nodes.rs b/turbine/src/cluster_nodes.rs index 57676a34b75..124d6d8c99c 100644 --- a/turbine/src/cluster_nodes.rs +++ b/turbine/src/cluster_nodes.rs @@ -513,7 +513,7 @@ fn enable_turbine_fanout_experiments(shred_slot: Slot, root_bank: &Bank) -> bool // Returns true if the feature is effective for the shred slot. #[must_use] -fn check_feature_activation(feature: &Pubkey, shred_slot: Slot, root_bank: &Bank) -> bool { +pub fn check_feature_activation(feature: &Pubkey, shred_slot: Slot, root_bank: &Bank) -> bool { match root_bank.feature_set.activated_slot(feature) { None => false, Some(feature_slot) => { diff --git a/turbine/src/quic_endpoint.rs b/turbine/src/quic_endpoint.rs index 0f93391e042..a947f212296 100644 --- a/turbine/src/quic_endpoint.rs +++ b/turbine/src/quic_endpoint.rs @@ -5,60 +5,76 @@ use { log::error, quinn::{ ClientConfig, ConnectError, Connecting, Connection, ConnectionError, Endpoint, - EndpointConfig, SendDatagramError, ServerConfig, TokioRuntime, TransportConfig, VarInt, + EndpointConfig, IdleTimeout, SendDatagramError, ServerConfig, TokioRuntime, + TransportConfig, VarInt, }, rcgen::RcgenError, rustls::{Certificate, PrivateKey}, solana_quic_client::nonblocking::quic_client::SkipServerVerification, + solana_runtime::bank_forks::BankForks, solana_sdk::{pubkey::Pubkey, signature::Keypair}, solana_streamer::{ quic::SkipClientVerification, tls_certificates::new_self_signed_tls_certificate, }, std::{ + cmp::Reverse, collections::{hash_map::Entry, HashMap}, io::Error as IoError, net::{IpAddr, SocketAddr, UdpSocket}, - ops::Deref, - sync::Arc, + sync::{ + atomic::{AtomicBool, AtomicU64, Ordering}, + Arc, RwLock, + }, + time::Duration, }, thiserror::Error, tokio::{ sync::{ - mpsc::{Receiver as AsyncReceiver, Sender as AsyncSender}, - RwLock, + mpsc::{error::TrySendError, Receiver as AsyncReceiver, Sender as AsyncSender}, + Mutex, RwLock as AsyncRwLock, }, task::JoinHandle, }, }; -const CLIENT_CHANNEL_CAPACITY: usize = 1 << 20; -const INITIAL_MAXIMUM_TRANSMISSION_UNIT: u16 = 1280; +const CLIENT_CHANNEL_BUFFER: usize = 1 << 14; +const ROUTER_CHANNEL_BUFFER: usize = 64; +const CONNECTION_CACHE_CAPACITY: usize = 3072; const ALPN_TURBINE_PROTOCOL_ID: &[u8] = b"solana-turbine"; const CONNECT_SERVER_NAME: &str = "solana-turbine"; +// Transport config. +const DATAGRAM_RECEIVE_BUFFER_SIZE: usize = 256 * 1024 * 1024; +const DATAGRAM_SEND_BUFFER_SIZE: usize = 128 * 1024 * 1024; +const INITIAL_MAXIMUM_TRANSMISSION_UNIT: u16 = MINIMUM_MAXIMUM_TRANSMISSION_UNIT; +const KEEP_ALIVE_INTERVAL: Duration = Duration::from_secs(4); +const MAX_IDLE_TIMEOUT: Duration = Duration::from_secs(10); +const MINIMUM_MAXIMUM_TRANSMISSION_UNIT: u16 = 1280; + const CONNECTION_CLOSE_ERROR_CODE_SHUTDOWN: VarInt = VarInt::from_u32(1); const CONNECTION_CLOSE_ERROR_CODE_DROPPED: VarInt = VarInt::from_u32(2); const CONNECTION_CLOSE_ERROR_CODE_INVALID_IDENTITY: VarInt = VarInt::from_u32(3); const CONNECTION_CLOSE_ERROR_CODE_REPLACED: VarInt = VarInt::from_u32(4); +const CONNECTION_CLOSE_ERROR_CODE_PRUNED: VarInt = VarInt::from_u32(5); const CONNECTION_CLOSE_REASON_SHUTDOWN: &[u8] = b"SHUTDOWN"; const CONNECTION_CLOSE_REASON_DROPPED: &[u8] = b"DROPPED"; const CONNECTION_CLOSE_REASON_INVALID_IDENTITY: &[u8] = b"INVALID_IDENTITY"; const CONNECTION_CLOSE_REASON_REPLACED: &[u8] = b"REPLACED"; +const CONNECTION_CLOSE_REASON_PRUNED: &[u8] = b"PRUNED"; pub type AsyncTryJoinHandle = TryJoin, JoinHandle<()>>; -type ConnectionCache = HashMap<(SocketAddr, Option), Arc>>>; #[derive(Error, Debug)] pub enum Error { #[error(transparent)] CertificateError(#[from] RcgenError), + #[error("Channel Send Error")] + ChannelSendError, #[error(transparent)] ConnectError(#[from] ConnectError), #[error(transparent)] ConnectionError(#[from] ConnectionError), - #[error("Channel Send Error")] - ChannelSendError, #[error("Invalid Identity: {0:?}")] InvalidIdentity(SocketAddr), #[error(transparent)] @@ -69,6 +85,12 @@ pub enum Error { TlsError(#[from] rustls::Error), } +macro_rules! add_metric { + ($metric: expr) => {{ + $metric.fetch_add(1, Ordering::Relaxed); + }}; +} + #[allow(clippy::type_complexity)] pub fn new_quic_endpoint( runtime: &tokio::runtime::Handle, @@ -76,6 +98,7 @@ pub fn new_quic_endpoint( socket: UdpSocket, address: IpAddr, sender: Sender<(Pubkey, SocketAddr, Bytes)>, + bank_forks: Arc>, ) -> Result< ( Endpoint, @@ -99,10 +122,27 @@ pub fn new_quic_endpoint( )? }; endpoint.set_default_client_config(client_config); - let cache = Arc::>::default(); - let (client_sender, client_receiver) = tokio::sync::mpsc::channel(CLIENT_CHANNEL_CAPACITY); - let server_task = runtime.spawn(run_server(endpoint.clone(), sender.clone(), cache.clone())); - let client_task = runtime.spawn(run_client(endpoint.clone(), client_receiver, sender, cache)); + let prune_cache_pending = Arc::::default(); + let cache = Arc::>>::default(); + let router = Arc::>>>::default(); + let (client_sender, client_receiver) = tokio::sync::mpsc::channel(CLIENT_CHANNEL_BUFFER); + let server_task = runtime.spawn(run_server( + endpoint.clone(), + sender.clone(), + bank_forks.clone(), + prune_cache_pending.clone(), + router.clone(), + cache.clone(), + )); + let client_task = runtime.spawn(run_client( + endpoint.clone(), + client_receiver, + sender, + bank_forks, + prune_cache_pending, + router, + cache, + )); let task = futures::future::try_join(server_task, client_task); Ok((endpoint, client_sender, task)) } @@ -141,55 +181,136 @@ fn new_client_config(cert: Certificate, key: PrivateKey) -> Result TransportConfig { + let max_idle_timeout = IdleTimeout::try_from(MAX_IDLE_TIMEOUT).unwrap(); let mut config = TransportConfig::default(); config + .datagram_receive_buffer_size(Some(DATAGRAM_RECEIVE_BUFFER_SIZE)) + .datagram_send_buffer_size(DATAGRAM_SEND_BUFFER_SIZE) + .initial_mtu(INITIAL_MAXIMUM_TRANSMISSION_UNIT) + .keep_alive_interval(Some(KEEP_ALIVE_INTERVAL)) .max_concurrent_bidi_streams(VarInt::from(0u8)) .max_concurrent_uni_streams(VarInt::from(0u8)) - .initial_mtu(INITIAL_MAXIMUM_TRANSMISSION_UNIT); + .max_idle_timeout(Some(max_idle_timeout)) + .min_mtu(MINIMUM_MAXIMUM_TRANSMISSION_UNIT) + .mtu_discovery_config(None); config } async fn run_server( endpoint: Endpoint, sender: Sender<(Pubkey, SocketAddr, Bytes)>, - cache: Arc>, + bank_forks: Arc>, + prune_cache_pending: Arc, + router: Arc>>>, + cache: Arc>>, ) { + let stats = Arc::::default(); + let report_metrics_task = + tokio::task::spawn(report_metrics_task("repair_quic_server", stats.clone())); while let Some(connecting) = endpoint.accept().await { - tokio::task::spawn(handle_connecting_error( + tokio::task::spawn(handle_connecting_task( endpoint.clone(), connecting, sender.clone(), + bank_forks.clone(), + prune_cache_pending.clone(), + router.clone(), cache.clone(), + stats.clone(), )); } + report_metrics_task.abort(); } async fn run_client( endpoint: Endpoint, mut receiver: AsyncReceiver<(SocketAddr, Bytes)>, sender: Sender<(Pubkey, SocketAddr, Bytes)>, - cache: Arc>, + bank_forks: Arc>, + prune_cache_pending: Arc, + router: Arc>>>, + cache: Arc>>, ) { + let stats = Arc::::default(); + let report_metrics_task = + tokio::task::spawn(report_metrics_task("repair_quic_client", stats.clone())); while let Some((remote_address, bytes)) = receiver.recv().await { - tokio::task::spawn(send_datagram_task( + let Some(bytes) = try_route_bytes(&remote_address, bytes, &*router.read().await, &stats) + else { + continue; + }; + let receiver = { + let mut router = router.write().await; + let Some(bytes) = try_route_bytes(&remote_address, bytes, &router, &stats) else { + continue; + }; + let (sender, receiver) = tokio::sync::mpsc::channel(ROUTER_CHANNEL_BUFFER); + sender.try_send(bytes).unwrap(); + router.insert(remote_address, sender); + receiver + }; + tokio::task::spawn(make_connection_task( endpoint.clone(), remote_address, - bytes, sender.clone(), + receiver, + bank_forks.clone(), + prune_cache_pending.clone(), + router.clone(), cache.clone(), + stats.clone(), )); } close_quic_endpoint(&endpoint); + // Drop sender channels to unblock threads waiting on the receiving end. + router.write().await.clear(); + report_metrics_task.abort(); } -async fn handle_connecting_error( +fn try_route_bytes( + remote_address: &SocketAddr, + bytes: Bytes, + router: &HashMap>, + stats: &TurbineQuicStats, +) -> Option { + match router.get(remote_address) { + None => Some(bytes), + Some(sender) => match sender.try_send(bytes) { + Ok(()) => None, + Err(TrySendError::Full(_)) => { + debug!("TrySendError::Full {remote_address}"); + add_metric!(stats.router_try_send_error_full); + None + } + Err(TrySendError::Closed(bytes)) => Some(bytes), + }, + } +} + +async fn handle_connecting_task( endpoint: Endpoint, connecting: Connecting, sender: Sender<(Pubkey, SocketAddr, Bytes)>, - cache: Arc>, + bank_forks: Arc>, + prune_cache_pending: Arc, + router: Arc>>>, + cache: Arc>>, + stats: Arc, ) { - if let Err(err) = handle_connecting(endpoint, connecting, sender, cache).await { - error!("handle_connecting: {err:?}"); + if let Err(err) = handle_connecting( + endpoint, + connecting, + sender, + bank_forks, + prune_cache_pending, + router, + cache, + stats.clone(), + ) + .await + { + debug!("handle_connecting: {err:?}"); + record_error(&err, &stats); } } @@ -197,52 +318,97 @@ async fn handle_connecting( endpoint: Endpoint, connecting: Connecting, sender: Sender<(Pubkey, SocketAddr, Bytes)>, - cache: Arc>, + bank_forks: Arc>, + prune_cache_pending: Arc, + router: Arc>>>, + cache: Arc>>, + stats: Arc, ) -> Result<(), Error> { let connection = connecting.await?; let remote_address = connection.remote_address(); let remote_pubkey = get_remote_pubkey(&connection)?; - handle_connection_error( + let receiver = { + let (sender, receiver) = tokio::sync::mpsc::channel(ROUTER_CHANNEL_BUFFER); + router.write().await.insert(remote_address, sender); + receiver + }; + handle_connection( endpoint, remote_address, remote_pubkey, connection, sender, + receiver, + bank_forks, + prune_cache_pending, + router, cache, + stats, ) .await; Ok(()) } -async fn handle_connection_error( +#[allow(clippy::too_many_arguments)] +async fn handle_connection( endpoint: Endpoint, remote_address: SocketAddr, remote_pubkey: Pubkey, connection: Connection, sender: Sender<(Pubkey, SocketAddr, Bytes)>, - cache: Arc>, + receiver: AsyncReceiver, + bank_forks: Arc>, + prune_cache_pending: Arc, + router: Arc>>>, + cache: Arc>>, + stats: Arc, ) { - cache_connection(remote_address, remote_pubkey, connection.clone(), &cache).await; - if let Err(err) = handle_connection( - &endpoint, - remote_address, + cache_connection( remote_pubkey, - &connection, - &sender, + connection.clone(), + bank_forks, + prune_cache_pending, + router.clone(), + cache.clone(), ) - .await - { - drop_connection(remote_address, remote_pubkey, &connection, &cache).await; - error!("handle_connection: {remote_pubkey}, {remote_address}, {err:?}"); + .await; + let send_datagram_task = tokio::task::spawn(send_datagram_task(connection.clone(), receiver)); + let read_datagram_task = tokio::task::spawn(read_datagram_task( + endpoint, + remote_address, + remote_pubkey, + connection.clone(), + sender, + stats.clone(), + )); + match futures::future::try_join(send_datagram_task, read_datagram_task).await { + Err(err) => error!("handle_connection: {remote_pubkey}, {remote_address}, {err:?}"), + Ok(out) => { + if let (Err(ref err), _) = out { + debug!("send_datagram_task: {remote_pubkey}, {remote_address}, {err:?}"); + record_error(err, &stats); + } + if let (_, Err(ref err)) = out { + debug!("read_datagram_task: {remote_pubkey}, {remote_address}, {err:?}"); + record_error(err, &stats); + } + } + } + drop_connection(remote_pubkey, &connection, &cache).await; + if let Entry::Occupied(entry) = router.write().await.entry(remote_address) { + if entry.get().is_closed() { + entry.remove(); + } } } -async fn handle_connection( - endpoint: &Endpoint, +async fn read_datagram_task( + endpoint: Endpoint, remote_address: SocketAddr, remote_pubkey: Pubkey, - connection: &Connection, - sender: &Sender<(Pubkey, SocketAddr, Bytes)>, + connection: Connection, + sender: Sender<(Pubkey, SocketAddr, Bytes)>, + stats: Arc, ) -> Result<(), Error> { // Assert that send won't block. debug_assert_eq!(sender.capacity(), None); @@ -250,7 +416,7 @@ async fn handle_connection( match connection.read_datagram().await { Ok(bytes) => { if let Err(err) = sender.send((remote_pubkey, remote_address, bytes)) { - close_quic_endpoint(endpoint); + close_quic_endpoint(&endpoint); return Err(Error::from(err)); } } @@ -258,74 +424,92 @@ async fn handle_connection( if let Some(err) = connection.close_reason() { return Err(Error::from(err)); } - error!("connection.read_datagram: {remote_pubkey}, {remote_address}, {err:?}"); + debug!("connection.read_datagram: {remote_pubkey}, {remote_address}, {err:?}"); + record_error(&Error::from(err), &stats); } }; } } async fn send_datagram_task( + connection: Connection, + mut receiver: AsyncReceiver, +) -> Result<(), Error> { + tokio::pin! { + let connection_closed = connection.closed(); + } + loop { + tokio::select! { + biased; + bytes = receiver.recv() => { + match bytes { + None => return Ok(()), + Some(bytes) => connection.send_datagram(bytes)?, + } + } + err = &mut connection_closed => return Err(Error::from(err)), + } + } +} + +async fn make_connection_task( endpoint: Endpoint, remote_address: SocketAddr, - bytes: Bytes, sender: Sender<(Pubkey, SocketAddr, Bytes)>, - cache: Arc>, + receiver: AsyncReceiver, + bank_forks: Arc>, + prune_cache_pending: Arc, + router: Arc>>>, + cache: Arc>>, + stats: Arc, ) { - if let Err(err) = send_datagram(&endpoint, remote_address, bytes, sender, cache).await { - error!("send_datagram: {remote_address}, {err:?}"); + if let Err(err) = make_connection( + endpoint, + remote_address, + sender, + receiver, + bank_forks, + prune_cache_pending, + router, + cache, + stats.clone(), + ) + .await + { + debug!("make_connection: {remote_address}, {err:?}"); + record_error(&err, &stats); } } -async fn send_datagram( - endpoint: &Endpoint, +async fn make_connection( + endpoint: Endpoint, remote_address: SocketAddr, - bytes: Bytes, sender: Sender<(Pubkey, SocketAddr, Bytes)>, - cache: Arc>, + receiver: AsyncReceiver, + bank_forks: Arc>, + prune_cache_pending: Arc, + router: Arc>>>, + cache: Arc>>, + stats: Arc, ) -> Result<(), Error> { - let connection = get_connection(endpoint, remote_address, sender, cache).await?; - connection.send_datagram(bytes)?; - Ok(()) -} - -async fn get_connection( - endpoint: &Endpoint, - remote_address: SocketAddr, - sender: Sender<(Pubkey, SocketAddr, Bytes)>, - cache: Arc>, -) -> Result { - let entry = get_cache_entry(remote_address, &cache).await; - { - let connection: Option = entry.read().await.clone(); - if let Some(connection) = connection { - if connection.close_reason().is_none() { - return Ok(connection); - } - } - } - let connection = { - // Need to write lock here so that only one task initiates - // a new connection to the same remote_address. - let mut entry = entry.write().await; - if let Some(connection) = entry.deref() { - if connection.close_reason().is_none() { - return Ok(connection.clone()); - } - } - let connection = endpoint - .connect(remote_address, CONNECT_SERVER_NAME)? - .await?; - entry.insert(connection).clone() - }; - tokio::task::spawn(handle_connection_error( - endpoint.clone(), + let connection = endpoint + .connect(remote_address, CONNECT_SERVER_NAME)? + .await?; + handle_connection( + endpoint, connection.remote_address(), get_remote_pubkey(&connection)?, - connection.clone(), + connection, sender, + receiver, + bank_forks, + prune_cache_pending, + router, cache, - )); - Ok(connection) + stats, + ) + .await; + Ok(()) } fn get_remote_pubkey(connection: &Connection) -> Result { @@ -341,62 +525,95 @@ fn get_remote_pubkey(connection: &Connection) -> Result { } } -async fn get_cache_entry( - remote_address: SocketAddr, - cache: &RwLock, -) -> Arc>> { - let key = (remote_address, /*remote_pubkey:*/ None); - if let Some(entry) = cache.read().await.get(&key) { - return entry.clone(); - } - cache.write().await.entry(key).or_default().clone() -} - async fn cache_connection( - remote_address: SocketAddr, remote_pubkey: Pubkey, connection: Connection, - cache: &RwLock, + bank_forks: Arc>, + prune_cache_pending: Arc, + router: Arc>>>, + cache: Arc>>, ) { - let entries: [Arc>>; 2] = { - let mut cache = cache.write().await; - [Some(remote_pubkey), None].map(|remote_pubkey| { - let key = (remote_address, remote_pubkey); - cache.entry(key).or_default().clone() - }) + let (old, should_prune_cache) = { + let mut cache = cache.lock().await; + ( + cache.insert(remote_pubkey, connection), + cache.len() >= CONNECTION_CACHE_CAPACITY.saturating_mul(2), + ) }; - let mut entry = entries[0].write().await; - *entries[1].write().await = Some(connection.clone()); - if let Some(old) = entry.replace(connection) { - drop(entry); + if let Some(old) = old { old.close( CONNECTION_CLOSE_ERROR_CODE_REPLACED, CONNECTION_CLOSE_REASON_REPLACED, ); } + if should_prune_cache && !prune_cache_pending.swap(true, Ordering::Relaxed) { + tokio::task::spawn(prune_connection_cache( + bank_forks, + prune_cache_pending, + router, + cache, + )); + } } async fn drop_connection( - remote_address: SocketAddr, remote_pubkey: Pubkey, connection: &Connection, - cache: &RwLock, + cache: &Mutex>, ) { - if connection.close_reason().is_none() { - connection.close( - CONNECTION_CLOSE_ERROR_CODE_DROPPED, - CONNECTION_CLOSE_REASON_DROPPED, - ); - } - let key = (remote_address, Some(remote_pubkey)); - if let Entry::Occupied(entry) = cache.write().await.entry(key) { - if matches!(entry.get().read().await.deref(), - Some(entry) if entry.stable_id() == connection.stable_id()) - { + connection.close( + CONNECTION_CLOSE_ERROR_CODE_DROPPED, + CONNECTION_CLOSE_REASON_DROPPED, + ); + if let Entry::Occupied(entry) = cache.lock().await.entry(remote_pubkey) { + if entry.get().stable_id() == connection.stable_id() { entry.remove(); } } - // Cache entry for (remote_address, None) will be lazily evicted. +} + +async fn prune_connection_cache( + bank_forks: Arc>, + prune_cache_pending: Arc, + router: Arc>>>, + cache: Arc>>, +) { + debug_assert!(prune_cache_pending.load(Ordering::Relaxed)); + let staked_nodes = { + let root_bank = bank_forks.read().unwrap().root_bank(); + root_bank.staked_nodes() + }; + { + let mut cache = cache.lock().await; + if cache.len() < CONNECTION_CACHE_CAPACITY.saturating_mul(2) { + prune_cache_pending.store(false, Ordering::Relaxed); + return; + } + let mut connections: Vec<_> = cache + .drain() + .filter(|(_, connection)| connection.close_reason().is_none()) + .map(|entry @ (pubkey, _)| { + let stake = staked_nodes.get(&pubkey).copied().unwrap_or_default(); + (stake, entry) + }) + .collect(); + connections + .select_nth_unstable_by_key(CONNECTION_CACHE_CAPACITY, |&(stake, _)| Reverse(stake)); + for (_, (_, connection)) in &connections[CONNECTION_CACHE_CAPACITY..] { + connection.close( + CONNECTION_CLOSE_ERROR_CODE_PRUNED, + CONNECTION_CLOSE_REASON_PRUNED, + ); + } + cache.extend( + connections + .into_iter() + .take(CONNECTION_CACHE_CAPACITY) + .map(|(_, entry)| entry), + ); + prune_cache_pending.store(false, Ordering::Relaxed); + } + router.write().await.retain(|_, sender| !sender.is_closed()); } impl From> for Error { @@ -405,11 +622,182 @@ impl From> for Error { } } +#[derive(Default)] +struct TurbineQuicStats { + connect_error_invalid_remote_address: AtomicU64, + connect_error_other: AtomicU64, + connect_error_too_many_connections: AtomicU64, + connection_error_application_closed: AtomicU64, + connection_error_connection_closed: AtomicU64, + connection_error_locally_closed: AtomicU64, + connection_error_reset: AtomicU64, + connection_error_timed_out: AtomicU64, + connection_error_transport_error: AtomicU64, + connection_error_version_mismatch: AtomicU64, + invalid_identity: AtomicU64, + router_try_send_error_full: AtomicU64, + send_datagram_error_connection_lost: AtomicU64, + send_datagram_error_too_large: AtomicU64, + send_datagram_error_unsupported_by_peer: AtomicU64, +} + +async fn report_metrics_task(name: &'static str, stats: Arc) { + loop { + tokio::time::sleep(Duration::from_secs(2)).await; + report_metrics(name, &stats); + } +} + +fn record_error(err: &Error, stats: &TurbineQuicStats) { + match err { + Error::CertificateError(_) => (), + Error::ChannelSendError => (), + Error::ConnectError(ConnectError::EndpointStopping) => { + add_metric!(stats.connect_error_other) + } + Error::ConnectError(ConnectError::TooManyConnections) => { + add_metric!(stats.connect_error_too_many_connections) + } + Error::ConnectError(ConnectError::InvalidDnsName(_)) => { + add_metric!(stats.connect_error_other) + } + Error::ConnectError(ConnectError::InvalidRemoteAddress(_)) => { + add_metric!(stats.connect_error_invalid_remote_address) + } + Error::ConnectError(ConnectError::NoDefaultClientConfig) => { + add_metric!(stats.connect_error_other) + } + Error::ConnectError(ConnectError::UnsupportedVersion) => { + add_metric!(stats.connect_error_other) + } + Error::ConnectionError(ConnectionError::VersionMismatch) => { + add_metric!(stats.connection_error_version_mismatch) + } + Error::ConnectionError(ConnectionError::TransportError(_)) => { + add_metric!(stats.connection_error_transport_error) + } + Error::ConnectionError(ConnectionError::ConnectionClosed(_)) => { + add_metric!(stats.connection_error_connection_closed) + } + Error::ConnectionError(ConnectionError::ApplicationClosed(_)) => { + add_metric!(stats.connection_error_application_closed) + } + Error::ConnectionError(ConnectionError::Reset) => add_metric!(stats.connection_error_reset), + Error::ConnectionError(ConnectionError::TimedOut) => { + add_metric!(stats.connection_error_timed_out) + } + Error::ConnectionError(ConnectionError::LocallyClosed) => { + add_metric!(stats.connection_error_locally_closed) + } + Error::InvalidIdentity(_) => add_metric!(stats.invalid_identity), + Error::IoError(_) => (), + Error::SendDatagramError(SendDatagramError::UnsupportedByPeer) => { + add_metric!(stats.send_datagram_error_unsupported_by_peer) + } + Error::SendDatagramError(SendDatagramError::Disabled) => (), + Error::SendDatagramError(SendDatagramError::TooLarge) => { + add_metric!(stats.send_datagram_error_too_large) + } + Error::SendDatagramError(SendDatagramError::ConnectionLost(_)) => { + add_metric!(stats.send_datagram_error_connection_lost) + } + Error::TlsError(_) => (), + } +} + +fn report_metrics(name: &'static str, stats: &TurbineQuicStats) { + macro_rules! reset_metric { + ($metric: expr) => { + $metric.swap(0, Ordering::Relaxed) + }; + } + datapoint_info!( + name, + ( + "connect_error_invalid_remote_address", + reset_metric!(stats.connect_error_invalid_remote_address), + i64 + ), + ( + "connect_error_other", + reset_metric!(stats.connect_error_other), + i64 + ), + ( + "connect_error_too_many_connections", + reset_metric!(stats.connect_error_too_many_connections), + i64 + ), + ( + "connection_error_application_closed", + reset_metric!(stats.connection_error_application_closed), + i64 + ), + ( + "connection_error_connection_closed", + reset_metric!(stats.connection_error_connection_closed), + i64 + ), + ( + "connection_error_locally_closed", + reset_metric!(stats.connection_error_locally_closed), + i64 + ), + ( + "connection_error_reset", + reset_metric!(stats.connection_error_reset), + i64 + ), + ( + "connection_error_timed_out", + reset_metric!(stats.connection_error_timed_out), + i64 + ), + ( + "connection_error_transport_error", + reset_metric!(stats.connection_error_transport_error), + i64 + ), + ( + "connection_error_version_mismatch", + reset_metric!(stats.connection_error_version_mismatch), + i64 + ), + ( + "invalid_identity", + reset_metric!(stats.invalid_identity), + i64 + ), + ( + "router_try_send_error_full", + reset_metric!(stats.router_try_send_error_full), + i64 + ), + ( + "send_datagram_error_connection_lost", + reset_metric!(stats.send_datagram_error_connection_lost), + i64 + ), + ( + "send_datagram_error_too_large", + reset_metric!(stats.send_datagram_error_too_large), + i64 + ), + ( + "send_datagram_error_unsupported_by_peer", + reset_metric!(stats.send_datagram_error_unsupported_by_peer), + i64 + ), + ); +} + #[cfg(test)] mod tests { use { super::*, itertools::{izip, multiunzip}, + solana_ledger::genesis_utils::{create_genesis_config, GenesisConfigInfo}, + solana_runtime::bank::Bank, solana_sdk::signature::Signer, std::{iter::repeat_with, net::Ipv4Addr, time::Duration}, }; @@ -437,6 +825,12 @@ mod tests { repeat_with(crossbeam_channel::unbounded::<(Pubkey, SocketAddr, Bytes)>) .take(NUM_ENDPOINTS) .unzip(); + let bank_forks = { + let GenesisConfigInfo { genesis_config, .. } = + create_genesis_config(/*mint_lamports:*/ 100_000); + let bank = Bank::new_for_tests(&genesis_config); + BankForks::new_rw_arc(bank) + }; let (endpoints, senders, tasks): (Vec<_>, Vec<_>, Vec<_>) = multiunzip(keypairs.iter().zip(sockets).zip(senders).map( |((keypair, socket), sender)| { @@ -446,6 +840,7 @@ mod tests { socket, IpAddr::V4(Ipv4Addr::LOCALHOST), sender, + bank_forks.clone(), ) .unwrap() }, diff --git a/turbine/src/sigverify_shreds.rs b/turbine/src/sigverify_shreds.rs index 35e4977de85..76088a8115a 100644 --- a/turbine/src/sigverify_shreds.rs +++ b/turbine/src/sigverify_shreds.rs @@ -284,7 +284,7 @@ mod tests { &create_genesis_config_with_leader(100, &leader_pubkey, 10).genesis_config, ); let leader_schedule_cache = LeaderScheduleCache::new_from_bank(&bank); - let bank_forks = RwLock::new(BankForks::new(bank)); + let bank_forks = BankForks::new_rw_arc(bank); let batch_size = 2; let mut batch = PacketBatch::with_capacity(batch_size); batch.resize(batch_size, Packet::default()); diff --git a/validator/Cargo.toml b/validator/Cargo.toml index 845bdda7eea..7882762c6a0 100644 --- a/validator/Cargo.toml +++ b/validator/Cargo.toml @@ -1,8 +1,8 @@ [package] -name = "solana-validator" +name = "agave-validator" description = "Blockchain, Rebuilt for Scale" -documentation = "https://docs.rs/solana-validator" -default-run = "solana-validator" +documentation = "https://docs.rs/agave-validator" +default-run = "agave-validator" version = { workspace = true } authors = { workspace = true } repository = { workspace = true } @@ -11,6 +11,7 @@ license = { workspace = true } edition = { workspace = true } [dependencies] +agave-geyser-plugin-interface = { workspace = true } chrono = { workspace = true, features = ["default", "serde"] } clap = { workspace = true } console = { workspace = true } @@ -41,7 +42,6 @@ solana-download-utils = { workspace = true } solana-entry = { workspace = true } solana-faucet = { workspace = true } solana-genesis-utils = { workspace = true } -solana-geyser-plugin-interface = { workspace = true } solana-geyser-plugin-manager = { workspace = true } solana-gossip = { workspace = true } solana-ledger = { workspace = true } diff --git a/validator/src/admin_rpc_service.rs b/validator/src/admin_rpc_service.rs index e10ab05ea01..69584822097 100644 --- a/validator/src/admin_rpc_service.rs +++ b/validator/src/admin_rpc_service.rs @@ -917,10 +917,7 @@ mod tests { } = create_genesis_config(1_000_000_000); let bank = Bank::new_for_tests_with_config(&genesis_config, config); - ( - Arc::new(RwLock::new(BankForks::new(bank))), - Arc::new(voting_keypair), - ) + (BankForks::new_rw_arc(bank), Arc::new(voting_keypair)) } #[test] diff --git a/validator/src/bin/solana-test-validator.rs b/validator/src/bin/solana-test-validator.rs index 39f206116ec..139ad6804f0 100644 --- a/validator/src/bin/solana-test-validator.rs +++ b/validator/src/bin/solana-test-validator.rs @@ -1,4 +1,8 @@ use { + agave_validator::{ + admin_rpc_service, cli, dashboard::Dashboard, ledger_lockfile, lock_ledger, + println_name_value, redirect_stderr_to_file, + }, clap::{crate_name, value_t, value_t_or_exit, values_t_or_exit}, crossbeam_channel::unbounded, itertools::Itertools, @@ -28,10 +32,6 @@ use { }, solana_streamer::socket::SocketAddrSpace, solana_test_validator::*, - solana_validator::{ - admin_rpc_service, cli, dashboard::Dashboard, ledger_lockfile, lock_ledger, - println_name_value, redirect_stderr_to_file, - }, std::{ collections::HashSet, fs, io, @@ -430,6 +430,7 @@ fn main() { String ), timeout: None, + ..RpcBigtableConfig::default() }) } else { None diff --git a/validator/src/bootstrap.rs b/validator/src/bootstrap.rs index 88a45fdad50..95db216d802 100644 --- a/validator/src/bootstrap.rs +++ b/validator/src/bootstrap.rs @@ -86,11 +86,11 @@ fn verify_reachable_ports( } if verify_address(&node.info.tpu(Protocol::UDP).ok()) { udp_sockets.extend(node.sockets.tpu.iter()); - udp_sockets.push(&node.sockets.tpu_quic); + udp_sockets.extend(&node.sockets.tpu_quic); } if verify_address(&node.info.tpu_forwards(Protocol::UDP).ok()) { udp_sockets.extend(node.sockets.tpu_forwards.iter()); - udp_sockets.push(&node.sockets.tpu_forwards_quic); + udp_sockets.extend(&node.sockets.tpu_forwards_quic); } if verify_address(&node.info.tpu_vote().ok()) { udp_sockets.extend(node.sockets.tpu_vote.iter()); @@ -444,7 +444,7 @@ pub fn attempt_download_genesis_and_snapshot( ) .unwrap_or_else(|err| { // Consider failures here to be more likely due to user error (eg, - // incorrect `solana-validator` command-line arguments) rather than the + // incorrect `agave-validator` command-line arguments) rather than the // RPC node failing. // // Power users can always use the `--no-check-vote-account` option to diff --git a/validator/src/cli.rs b/validator/src/cli.rs index 1eef3453551..5a79463bd81 100644 --- a/validator/src/cli.rs +++ b/validator/src/cli.rs @@ -258,11 +258,9 @@ pub fn app<'a>(version: &'a str, default_args: &'a DefaultArgs) -> App<'a, 'a> { .value_name("SLOT_DISTANCE") .takes_value(true) .default_value(&default_args.health_check_slot_distance) - .help("If --known-validators are specified, report this validator healthy \ - if its latest account hash is no further behind than this number of \ - slots from the latest known validator account hash. \ - If no --known-validators are specified, the validator will always \ - report itself to be healthy") + .help("Report this validator healthy if its latest optimistically confirmed slot \ + that has been replayed is no further behind than this number of slots from \ + the cluster latest optimistically confirmed slot") ) .arg( Arg::with_name("rpc_faucet_addr") @@ -468,7 +466,8 @@ pub fn app<'a>(version: &'a str, default_args: &'a DefaultArgs) -> App<'a, 'a> { .value_name("NUMBER") .takes_value(true) .default_value(&default_args.full_snapshot_archive_interval_slots) - .help("Number of slots between generating full snapshots") + .help("Number of slots between generating full snapshots. \ + Must be a multiple of the incremental snapshot interval.") ) .arg( Arg::with_name("maximum_full_snapshots_to_retain") @@ -879,6 +878,15 @@ pub fn app<'a>(version: &'a str, default_args: &'a DefaultArgs) -> App<'a, 'a> { .default_value(&default_args.rpc_bigtable_app_profile_id) .help("Bigtable application profile id to use in requests") ) + .arg( + Arg::with_name("rpc_bigtable_max_message_size") + .long("rpc-bigtable-max-message-size") + .value_name("BYTES") + .validator(is_parsable::) + .takes_value(true) + .default_value(&default_args.rpc_bigtable_max_message_size) + .help("Max encoding and decoding message size used in Bigtable Grpc client"), + ) .arg( Arg::with_name("rpc_pubsub_worker_threads") .long("rpc-pubsub-worker-threads") @@ -1037,6 +1045,22 @@ pub fn app<'a>(version: &'a str, default_args: &'a DefaultArgs) -> App<'a, 'a> { .default_value(&default_args.rpc_send_transaction_batch_size) .help("The size of transactions to be sent in batch."), ) + .arg( + Arg::with_name("rpc_send_transaction_tpu_peer") + .long("rpc-send-transaction-tpu-peer") + .takes_value(true) + .number_of_values(1) + .multiple(true) + .value_name("HOST:PORT") + .validator(solana_net_utils::is_host_port) + .help("Peer(s) to broadcast transactions to instead of the current leader") + ) + .arg( + Arg::with_name("rpc_send_transaction_also_leader") + .long("rpc-send-transaction-also-leader") + .requires("rpc_send_transaction_tpu_peer") + .help("With `--rpc-send-transaction-tpu-peer HOST:PORT`, also send to the current leader") + ) .arg( Arg::with_name("rpc_scan_and_fix_roots") .long("rpc-scan-and-fix-roots") @@ -1195,6 +1219,13 @@ pub fn app<'a>(version: &'a str, default_args: &'a DefaultArgs) -> App<'a, 'a> { .help("Debug option to scan all append vecs and verify account index refcounts prior to clean") .hidden(hidden_unless_forced()) ) + .arg( + Arg::with_name("no_skip_initial_accounts_db_clean") + .long("no-skip-initial-accounts-db-clean") + .help("Do not skip the initial cleaning of accounts when verifying snapshot bank") + .hidden(hidden_unless_forced()) + .conflicts_with("accounts_db_skip_shrink") + ) .arg( Arg::with_name("accounts_db_create_ancient_storage_packed") .long("accounts-db-create-ancient-storage-packed") @@ -1234,12 +1265,6 @@ pub fn app<'a>(version: &'a str, default_args: &'a DefaultArgs) -> App<'a, 'a> { .takes_value(true) .help("How much memory the accounts index can consume. If this is exceeded, some account index entries will be stored on disk."), ) - .arg( - Arg::with_name("disable_accounts_disk_index") - .long("disable-accounts-disk-index") - .help("Disable the disk-based accounts index if it is enabled by default.") - .conflicts_with("accounts_index_memory_limit_mb") - ) .arg( Arg::with_name("accounts_index_bins") .long("accounts-index-bins") @@ -1279,7 +1304,8 @@ pub fn app<'a>(version: &'a str, default_args: &'a DefaultArgs) -> App<'a, 'a> { .validator(is_parsable::) .takes_value(true) .default_value(&default_args.accounts_filler_count) - .help("How many accounts to add to stress the system. Accounts are ignored in operations related to correctness.")) + .help("How many accounts to add to stress the system. Accounts are ignored in operations related to correctness.") + .hidden(hidden_unless_forced())) .arg(Arg::with_name("accounts_filler_size") .long("accounts-filler-size") .value_name("BYTES") @@ -1287,7 +1313,8 @@ pub fn app<'a>(version: &'a str, default_args: &'a DefaultArgs) -> App<'a, 'a> { .takes_value(true) .default_value(&default_args.accounts_filler_size) .requires("accounts_filler_count") - .help("Size per filler account in bytes.")) + .help("Size per filler account in bytes.") + .hidden(hidden_unless_forced())) .arg( Arg::with_name("accounts_db_test_hash_calculation") .long("accounts-db-test-hash-calculation") @@ -1351,9 +1378,17 @@ pub fn app<'a>(version: &'a str, default_args: &'a DefaultArgs) -> App<'a, 'a> { // explicitly given, similar to --limit-ledger-size. // see configure_banking_trace_dir_byte_limit() for this. .default_value(&default_args.banking_trace_dir_byte_limit) - .help("Write trace files for simulate-leader-blocks, retaining \ - up to the default or specified total bytes in the \ - ledger") + .help("Enables the banking trace explicitly, which is enabled by default and \ + writes trace files for simulate-leader-blocks, retaining up to the default \ + or specified total bytes in the ledger. This flag can be used to override \ + its byte limit.") + ) + .arg( + Arg::with_name("disable_banking_trace") + .long("disable-banking-trace") + .conflicts_with("banking_trace_dir_byte_limit") + .takes_value(false) + .help("Disables the banking trace") ) .arg( Arg::with_name("block_verification_method") @@ -1415,6 +1450,11 @@ pub fn app<'a>(version: &'a str, default_args: &'a DefaultArgs) -> App<'a, 'a> { .long("skip-new-snapshot-check") .help("Skip check for a new snapshot") ) + .arg( + Arg::with_name("skip_health_check") + .long("skip-health-check") + .help("Skip health check") + ) ) .subcommand( SubCommand::with_name("authorized-voter") @@ -1630,6 +1670,11 @@ pub fn app<'a>(version: &'a str, default_args: &'a DefaultArgs) -> App<'a, 'a> { .long("skip-new-snapshot-check") .help("Skip check for a new snapshot") ) + .arg( + Arg::with_name("skip_health_check") + .long("skip-health-check") + .help("Skip health check") + ) .after_help("Note: If this command exits with a non-zero status \ then this not a good time for a restart") ). @@ -1734,6 +1779,10 @@ fn deprecated_arguments() -> Vec { Ok(()) } })); + add_arg!(Arg::with_name("disable_accounts_disk_index") + .long("disable-accounts-disk-index") + .help("Disable the disk-based accounts index if it is enabled by default.") + .conflicts_with("accounts_index_memory_limit_mb")); add_arg!( Arg::with_name("disable_quic_servers") .long("disable-quic-servers") @@ -1884,6 +1933,7 @@ pub struct DefaultArgs { pub rpc_bigtable_timeout: String, pub rpc_bigtable_instance_name: String, pub rpc_bigtable_app_profile_id: String, + pub rpc_bigtable_max_message_size: String, pub rpc_max_request_body_size: String, pub rpc_pubsub_worker_threads: String, @@ -1969,6 +2019,8 @@ impl DefaultArgs { rpc_bigtable_instance_name: solana_storage_bigtable::DEFAULT_INSTANCE_NAME.to_string(), rpc_bigtable_app_profile_id: solana_storage_bigtable::DEFAULT_APP_PROFILE_ID .to_string(), + rpc_bigtable_max_message_size: solana_storage_bigtable::DEFAULT_MAX_MESSAGE_SIZE + .to_string(), rpc_pubsub_worker_threads: "4".to_string(), accountsdb_repl_threads: num_cpus::get().to_string(), accounts_filler_count: "0".to_string(), diff --git a/validator/src/lib.rs b/validator/src/lib.rs index 4e7ed43ec78..83fbb1c82d0 100644 --- a/validator/src/lib.rs +++ b/validator/src/lib.rs @@ -46,10 +46,9 @@ pub fn redirect_stderr_to_file(logfile: Option) -> Option env::set_var("RUST_BACKTRACE", "1") } - let filter = "solana=info"; match logfile { None => { - solana_logger::setup_with_default(filter); + solana_logger::setup_with_default_filter(); None } Some(logfile) => { @@ -63,7 +62,7 @@ pub fn redirect_stderr_to_file(logfile: Option) -> Option exit(1); }); - solana_logger::setup_with_default(filter); + solana_logger::setup_with_default_filter(); redirect_stderr(&logfile); Some( std::thread::Builder::new() @@ -83,7 +82,7 @@ pub fn redirect_stderr_to_file(logfile: Option) -> Option #[cfg(not(unix))] { println!("logrotate is not supported on this platform"); - solana_logger::setup_file_with_default(&logfile, filter); + solana_logger::setup_file_with_default(&logfile, solana_logger::DEFAULT_FILTER); None } } diff --git a/validator/src/main.rs b/validator/src/main.rs index db9e8396108..f101f103bbc 100644 --- a/validator/src/main.rs +++ b/validator/src/main.rs @@ -2,6 +2,15 @@ #[cfg(not(target_env = "msvc"))] use jemallocator::Jemalloc; use { + agave_validator::{ + admin_rpc_service, + admin_rpc_service::{load_staked_nodes_overrides, StakedNodesOverrides}, + bootstrap, + cli::{app, warn_for_deprecated_arguments, DefaultArgs}, + dashboard::Dashboard, + ledger_lockfile, lock_ledger, new_spinner_progress_bar, println_name_value, + redirect_stderr_to_file, + }, clap::{crate_name, value_t, value_t_or_exit, values_t, values_t_or_exit, ArgMatches}, console::style, crossbeam_channel::unbounded, @@ -65,15 +74,6 @@ use { solana_send_transaction_service::send_transaction_service, solana_streamer::socket::SocketAddrSpace, solana_tpu_client::tpu_client::DEFAULT_TPU_ENABLE_UDP, - solana_validator::{ - admin_rpc_service, - admin_rpc_service::{load_staked_nodes_overrides, StakedNodesOverrides}, - bootstrap, - cli::{app, warn_for_deprecated_arguments, DefaultArgs}, - dashboard::Dashboard, - ledger_lockfile, lock_ledger, new_spinner_progress_bar, println_name_value, - redirect_stderr_to_file, - }, std::{ collections::{HashSet, VecDeque}, env, @@ -118,6 +118,7 @@ fn wait_for_restart_window( min_idle_time_in_minutes: usize, max_delinquency_percentage: u8, skip_new_snapshot_check: bool, + skip_health_check: bool, ) -> Result<(), Box> { let sleep_interval = Duration::from_secs(5); @@ -161,7 +162,7 @@ fn wait_for_restart_window( seen_incremential_snapshot |= snapshot_slot_info_has_incremential; let epoch_info = rpc_client.get_epoch_info_with_commitment(CommitmentConfig::processed())?; - let healthy = rpc_client.get_health().ok().is_some(); + let healthy = skip_health_check || rpc_client.get_health().ok().is_some(); let delinquent_stake_percentage = { let vote_accounts = rpc_client.get_vote_accounts()?; let current_stake: u64 = vote_accounts @@ -448,15 +449,16 @@ fn configure_banking_trace_dir_byte_limit( validator_config: &mut ValidatorConfig, matches: &ArgMatches, ) { - validator_config.banking_trace_dir_byte_limit = - if matches.occurrences_of("banking_trace_dir_byte_limit") == 0 { - // disable with no explicit flag; then, this effectively becomes `opt-in` even if we're - // specifying a default value in clap configuration. - DISABLED_BAKING_TRACE_DIR - } else { - // BANKING_TRACE_DIR_DEFAULT_BYTE_LIMIT or user-supplied override value - value_t_or_exit!(matches, "banking_trace_dir_byte_limit", u64) - }; + validator_config.banking_trace_dir_byte_limit = if matches.is_present("disable_banking_trace") { + // disable with an explicit flag; This effectively becomes `opt-out` by reseting to + // DISABLED_BAKING_TRACE_DIR, while allowing us to specify a default sensible limit in clap + // configuration for cli help. + DISABLED_BAKING_TRACE_DIR + } else { + // a default value in clap configuration (BANKING_TRACE_DIR_DEFAULT_BYTE_LIMIT) or + // explicit user-supplied override value + value_t_or_exit!(matches, "banking_trace_dir_byte_limit", u64) + }; } pub fn main() { @@ -648,6 +650,7 @@ pub fn main() { let force = subcommand_matches.is_present("force"); let monitor = subcommand_matches.is_present("monitor"); let skip_new_snapshot_check = subcommand_matches.is_present("skip_new_snapshot_check"); + let skip_health_check = subcommand_matches.is_present("skip_health_check"); let max_delinquent_stake = value_t_or_exit!(subcommand_matches, "max_delinquent_stake", u8); @@ -658,6 +661,7 @@ pub fn main() { min_idle_time, max_delinquent_stake, skip_new_snapshot_check, + skip_health_check, ) .unwrap_or_else(|err| { println!("{err}"); @@ -776,6 +780,7 @@ pub fn main() { let max_delinquent_stake = value_t_or_exit!(subcommand_matches, "max_delinquent_stake", u8); let skip_new_snapshot_check = subcommand_matches.is_present("skip_new_snapshot_check"); + let skip_health_check = subcommand_matches.is_present("skip_health_check"); wait_for_restart_window( &ledger_path, @@ -783,6 +788,7 @@ pub fn main() { min_idle_time, max_delinquent_stake, skip_new_snapshot_check, + skip_health_check, ) .unwrap_or_else(|err| { println!("{err}"); @@ -893,7 +899,7 @@ pub fn main() { let logfile = matches .value_of("logfile") .map(|s| s.into()) - .unwrap_or_else(|| format!("solana-validator-{}.log", identity_keypair.pubkey())); + .unwrap_or_else(|| format!("agave-validator-{}.log", identity_keypair.pubkey())); if logfile == "-" { None @@ -1231,6 +1237,7 @@ pub fn main() { timeout: value_t!(matches, "rpc_bigtable_timeout", u64) .ok() .map(Duration::from_secs), + max_message_size: value_t_or_exit!(matches, "rpc_bigtable_max_message_size", usize), }) } else { None @@ -1259,6 +1266,27 @@ pub fn main() { ); exit(1); } + let rpc_send_transaction_tpu_peers = matches + .values_of("rpc_send_transaction_tpu_peer") + .map(|values| { + values + .map(solana_net_utils::parse_host_port) + .collect::, String>>() + }) + .transpose() + .unwrap_or_else(|e| { + eprintln!("failed to parse rpc send-transaction-service tpu peer address: {e}"); + exit(1); + }); + let rpc_send_transaction_also_leader = matches.is_present("rpc_send_transaction_also_leader"); + let leader_forward_count = + if rpc_send_transaction_tpu_peers.is_some() && !rpc_send_transaction_also_leader { + // rpc-sts is configured to send only to specific tpu peers. disable leader forwards + 0 + } else { + value_t_or_exit!(matches, "rpc_send_transaction_leader_forward_count", u64) + }; + let full_api = matches.is_present("full_rpc_api"); let mut validator_config = ValidatorConfig { @@ -1293,6 +1321,7 @@ pub fn main() { "health_check_slot_distance", u64 ), + disable_health_check: false, rpc_threads: value_t_or_exit!(matches, "rpc_threads", usize), rpc_niceness_adj: value_t_or_exit!(matches, "rpc_niceness_adj", i8), account_indexes: account_indexes.clone(), @@ -1351,11 +1380,7 @@ pub fn main() { contact_debug_interval, send_transaction_service_config: send_transaction_service::Config { retry_rate_ms: rpc_send_retry_rate_ms, - leader_forward_count: value_t_or_exit!( - matches, - "rpc_send_transaction_leader_forward_count", - u64 - ), + leader_forward_count, default_max_retries: value_t!( matches, "rpc_send_transaction_default_max_retries", @@ -1369,6 +1394,7 @@ pub fn main() { ), batch_send_rate_ms: rpc_send_batch_send_rate_ms, batch_size: rpc_send_batch_size, + tpu_peers: rpc_send_transaction_tpu_peers, }, no_poh_speed_test: matches.is_present("no_poh_speed_test"), no_os_memory_stats_reporting: matches.is_present("no_os_memory_stats_reporting"), @@ -1384,6 +1410,7 @@ pub fn main() { accounts_db_test_hash_calculation: matches.is_present("accounts_db_test_hash_calculation"), accounts_db_config, accounts_db_skip_shrink: true, + accounts_db_force_initial_clean: matches.is_present("no_skip_initial_accounts_db_clean"), tpu_coalesce, no_wait_for_vote_to_start_leader: matches.is_present("no_wait_for_vote_to_start_leader"), accounts_shrink_ratio, @@ -1587,17 +1614,14 @@ pub fn main() { validator_config.accounts_hash_interval_slots, ) { eprintln!("Invalid snapshot configuration provided: snapshot intervals are incompatible. \ - \n\t- full snapshot interval MUST be a multiple of accounts hash interval (if enabled) \ - \n\t- incremental snapshot interval MUST be a multiple of accounts hash interval (if enabled) \ + \n\t- full snapshot interval MUST be a multiple of incremental snapshot interval (if enabled) \ \n\t- full snapshot interval MUST be larger than incremental snapshot interval (if enabled) \ \nSnapshot configuration values: \ \n\tfull snapshot interval: {} \ - \n\tincremental snapshot interval: {} \ - \n\taccounts hash interval: {}", + \n\tincremental snapshot interval: {}", if full_snapshot_archive_interval_slots == DISABLED_SNAPSHOT_ARCHIVE_INTERVAL { "disabled".to_string() } else { full_snapshot_archive_interval_slots.to_string() }, if incremental_snapshot_archive_interval_slots == DISABLED_SNAPSHOT_ARCHIVE_INTERVAL { "disabled".to_string() } else { incremental_snapshot_archive_interval_slots.to_string() }, - validator_config.accounts_hash_interval_slots); - + ); exit(1); } diff --git a/version/src/lib.rs b/version/src/lib.rs index edeca08c960..7a59406cf06 100644 --- a/version/src/lib.rs +++ b/version/src/lib.rs @@ -17,6 +17,7 @@ enum ClientId { SolanaLabs, JitoLabs, Firedancer, + Agave, // If new variants are added, update From and TryFrom. Unknown(u16), } @@ -63,7 +64,7 @@ impl Default for Version { commit: compute_commit(option_env!("CI_COMMIT")).unwrap_or_default(), feature_set, // Other client implementations need to modify this line. - client: u16::try_from(ClientId::SolanaLabs).unwrap(), + client: u16::try_from(ClientId::Agave).unwrap(), } } } @@ -97,6 +98,7 @@ impl From for ClientId { 0u16 => Self::SolanaLabs, 1u16 => Self::JitoLabs, 2u16 => Self::Firedancer, + 3u16 => Self::Agave, _ => Self::Unknown(client), } } @@ -110,7 +112,8 @@ impl TryFrom for u16 { ClientId::SolanaLabs => Ok(0u16), ClientId::JitoLabs => Ok(1u16), ClientId::Firedancer => Ok(2u16), - ClientId::Unknown(client @ 0u16..=2u16) => Err(format!("Invalid client: {client}")), + ClientId::Agave => Ok(3u16), + ClientId::Unknown(client @ 0u16..=3u16) => Err(format!("Invalid client: {client}")), ClientId::Unknown(client) => Ok(client), } } @@ -147,19 +150,21 @@ mod test { assert_eq!(ClientId::from(0u16), ClientId::SolanaLabs); assert_eq!(ClientId::from(1u16), ClientId::JitoLabs); assert_eq!(ClientId::from(2u16), ClientId::Firedancer); - for client in 3u16..=u16::MAX { + assert_eq!(ClientId::from(3u16), ClientId::Agave); + for client in 4u16..=u16::MAX { assert_eq!(ClientId::from(client), ClientId::Unknown(client)); } assert_eq!(u16::try_from(ClientId::SolanaLabs), Ok(0u16)); assert_eq!(u16::try_from(ClientId::JitoLabs), Ok(1u16)); assert_eq!(u16::try_from(ClientId::Firedancer), Ok(2u16)); - for client in 0..=2u16 { + assert_eq!(u16::try_from(ClientId::Agave), Ok(3u16)); + for client in 0..=3u16 { assert_eq!( u16::try_from(ClientId::Unknown(client)), Err(format!("Invalid client: {client}")) ); } - for client in 3u16..=u16::MAX { + for client in 4u16..=u16::MAX { assert_eq!(u16::try_from(ClientId::Unknown(client)), Ok(client)); } } diff --git a/watchtower/Cargo.toml b/watchtower/Cargo.toml index d8bad3cf4d1..4088ee7d9b5 100644 --- a/watchtower/Cargo.toml +++ b/watchtower/Cargo.toml @@ -1,7 +1,7 @@ [package] -name = "solana-watchtower" +name = "agave-watchtower" description = "Blockchain, Rebuilt for Scale" -documentation = "https://docs.rs/solana-watchtower" +documentation = "https://docs.rs/agave-watchtower" version = { workspace = true } authors = { workspace = true } repository = { workspace = true } diff --git a/watchtower/README.md b/watchtower/README.md index 33bad3e458c..5c548d405a2 100644 --- a/watchtower/README.md +++ b/watchtower/README.md @@ -1,4 +1,4 @@ -The `solana-watchtower` program is used to monitor the health of a cluster. It +The `agave-watchtower` program is used to monitor the health of a cluster. It periodically polls the cluster over an RPC API to confirm that the transaction count is advancing, new blockhashes are available, and no validators are delinquent. Results are reported as InfluxDB metrics, with an optional push diff --git a/watchtower/src/main.rs b/watchtower/src/main.rs index f42acdaadaa..11dd70e2728 100644 --- a/watchtower/src/main.rs +++ b/watchtower/src/main.rs @@ -47,7 +47,7 @@ fn get_config() -> Config { .version(solana_version::version!()) .after_help("ADDITIONAL HELP: To receive a Slack, Discord, PagerDuty and/or Telegram notification on sanity failure, - define environment variables before running `solana-watchtower`: + define environment variables before running `agave-watchtower`: export SLACK_WEBHOOK=... export DISCORD_WEBHOOK=... @@ -63,7 +63,7 @@ fn get_config() -> Config { To receive a Twilio SMS notification on failure, having a Twilio account, and a sending number owned by that account, - define environment variable before running `solana-watchtower`: + define environment variable before running `agave-watchtower`: export TWILIO_CONFIG='ACCOUNT=,TOKEN=,TO=,FROM='") .arg({ @@ -166,7 +166,7 @@ fn get_config() -> Config { .value_name("SUFFIX") .takes_value(true) .default_value("") - .help("Add this string into all notification messages after \"solana-watchtower\"") + .help("Add this string into all notification messages after \"agave-watchtower\"") ) .get_matches(); @@ -246,7 +246,7 @@ fn get_cluster_info( } fn main() -> Result<(), Box> { - solana_logger::setup_with_default("solana=info"); + solana_logger::setup_with_default_filter(); solana_metrics::set_panic_hook("watchtower", /*version:*/ None); let config = get_config(); @@ -381,7 +381,7 @@ fn main() -> Result<(), Box> { if let Some((failure_test_name, failure_error_message)) = &failure { let notification_msg = format!( - "solana-watchtower{}: Error: {}: {}", + "agave-watchtower{}: Error: {}: {}", config.name_suffix, failure_test_name, failure_error_message ); num_consecutive_failures += 1; @@ -415,7 +415,7 @@ fn main() -> Result<(), Box> { ); info!("{}", all_clear_msg); notifier.send( - &format!("solana-watchtower{}: {}", config.name_suffix, all_clear_msg), + &format!("agave-watchtower{}: {}", config.name_suffix, all_clear_msg), &NotificationType::Resolve { incident }, ); } diff --git a/zk-token-sdk/src/encryption/auth_encryption.rs b/zk-token-sdk/src/encryption/auth_encryption.rs index 4445a40dfc1..046f529ca4e 100644 --- a/zk-token-sdk/src/encryption/auth_encryption.rs +++ b/zk-token-sdk/src/encryption/auth_encryption.rs @@ -50,6 +50,8 @@ pub enum AuthenticatedEncryptionError { DerivationMethodNotSupported, #[error("seed length too short for derivation")] SeedLengthTooShort, + #[error("seed length too long for derivation")] + SeedLengthTooLong, } struct AuthenticatedEncryption; @@ -172,10 +174,14 @@ impl EncodableKey for AeKey { impl SeedDerivable for AeKey { fn from_seed(seed: &[u8]) -> Result> { const MINIMUM_SEED_LEN: usize = AE_KEY_LEN; + const MAXIMUM_SEED_LEN: usize = 65535; if seed.len() < MINIMUM_SEED_LEN { return Err(AuthenticatedEncryptionError::SeedLengthTooShort.into()); } + if seed.len() > MAXIMUM_SEED_LEN { + return Err(AuthenticatedEncryptionError::SeedLengthTooLong.into()); + } let mut hasher = Sha3_512::new(); hasher.update(seed); @@ -278,4 +284,16 @@ mod tests { let null_signer = NullSigner::new(&Pubkey::default()); assert!(AeKey::new_from_signer(&null_signer, Pubkey::default().as_ref()).is_err()); } + + #[test] + fn test_aes_key_from_seed() { + let good_seed = vec![0; 32]; + assert!(AeKey::from_seed(&good_seed).is_ok()); + + let too_short_seed = vec![0; 15]; + assert!(AeKey::from_seed(&too_short_seed).is_err()); + + let too_long_seed = vec![0; 65536]; + assert!(AeKey::from_seed(&too_long_seed).is_err()); + } } diff --git a/zk-token-sdk/src/encryption/discrete_log.rs b/zk-token-sdk/src/encryption/discrete_log.rs index 7f989188232..b3e02a74625 100644 --- a/zk-token-sdk/src/encryption/discrete_log.rs +++ b/zk-token-sdk/src/encryption/discrete_log.rs @@ -130,7 +130,7 @@ impl DiscreteLog { &mut self, compression_batch_size: usize, ) -> Result<(), DiscreteLogError> { - if compression_batch_size >= TWO16 as usize { + if compression_batch_size >= TWO16 as usize || compression_batch_size == 0 { return Err(DiscreteLogError::DiscreteLogBatchSize); } self.compression_batch_size = compression_batch_size; diff --git a/zk-token-sdk/src/encryption/elgamal.rs b/zk-token-sdk/src/encryption/elgamal.rs index c57a10b7400..bee5cb39c30 100644 --- a/zk-token-sdk/src/encryption/elgamal.rs +++ b/zk-token-sdk/src/encryption/elgamal.rs @@ -76,6 +76,8 @@ pub enum ElGamalError { DerivationMethodNotSupported, #[error("seed length too short for derivation")] SeedLengthTooShort, + #[error("seed length too long for derivation")] + SeedLengthTooLong, } /// Algorithm handle for the twisted ElGamal encryption scheme @@ -449,10 +451,14 @@ impl ElGamalSecretKey { /// Derive an ElGamal secret key from an entropy seed. pub fn from_seed(seed: &[u8]) -> Result { const MINIMUM_SEED_LEN: usize = ELGAMAL_SECRET_KEY_LEN; + const MAXIMUM_SEED_LEN: usize = 65535; if seed.len() < MINIMUM_SEED_LEN { return Err(ElGamalError::SeedLengthTooShort); } + if seed.len() > MAXIMUM_SEED_LEN { + return Err(ElGamalError::SeedLengthTooLong); + } Ok(ElGamalSecretKey(Scalar::hash_from_bytes::(seed))) } @@ -1026,6 +1032,9 @@ mod tests { let too_short_seed = vec![0; 31]; assert!(ElGamalKeypair::from_seed(&too_short_seed).is_err()); + + let too_long_seed = vec![0; 65536]; + assert!(ElGamalKeypair::from_seed(&too_long_seed).is_err()); } #[test] diff --git a/zk-token-sdk/src/errors.rs b/zk-token-sdk/src/errors.rs index e9e36e43f73..a5cdaeb39d0 100644 --- a/zk-token-sdk/src/errors.rs +++ b/zk-token-sdk/src/errors.rs @@ -20,6 +20,8 @@ pub enum ProofError { PubkeyDeserialization, #[error("ciphertext does not exist in instruction data")] MissingCiphertext, + #[error("illegal commitment length")] + IllegalCommitmentLength, } #[derive(Clone, Debug, Eq, PartialEq)] diff --git a/zk-token-sdk/src/instruction/batched_range_proof/batched_range_proof_u128.rs b/zk-token-sdk/src/instruction/batched_range_proof/batched_range_proof_u128.rs index 8867823cfee..b1d6dfbc91d 100644 --- a/zk-token-sdk/src/instruction/batched_range_proof/batched_range_proof_u128.rs +++ b/zk-token-sdk/src/instruction/batched_range_proof/batched_range_proof_u128.rs @@ -5,6 +5,7 @@ use { crate::{ encryption::pedersen::{PedersenCommitment, PedersenOpening}, errors::ProofError, + instruction::batched_range_proof::MAX_COMMITMENTS, range_proof::RangeProof, }, std::convert::TryInto, @@ -58,7 +59,9 @@ impl BatchedRangeProofU128Data { BatchedRangeProofContext::new(&commitments, &amounts, &bit_lengths, &openings)?; let mut transcript = context.new_transcript(); - let proof = RangeProof::new(amounts, bit_lengths, openings, &mut transcript).try_into()?; + let proof = RangeProof::new(amounts, bit_lengths, openings, &mut transcript) + .map_err(|_| ProofError::Generation)? + .try_into()?; Ok(Self { context, proof }) } @@ -74,6 +77,12 @@ impl ZkProofData for BatchedRangeProofU128Data { #[cfg(not(target_os = "solana"))] fn verify_proof(&self) -> Result<(), ProofError> { let (commitments, bit_lengths) = self.context.try_into()?; + let num_commitments = commitments.len(); + + if num_commitments > MAX_COMMITMENTS || num_commitments != bit_lengths.len() { + return Err(ProofError::IllegalCommitmentLength); + } + let mut transcript = self.context_data().new_transcript(); let proof: RangeProof = self.proof.try_into()?; diff --git a/zk-token-sdk/src/instruction/batched_range_proof/batched_range_proof_u256.rs b/zk-token-sdk/src/instruction/batched_range_proof/batched_range_proof_u256.rs index da25267cded..7a135d70387 100644 --- a/zk-token-sdk/src/instruction/batched_range_proof/batched_range_proof_u256.rs +++ b/zk-token-sdk/src/instruction/batched_range_proof/batched_range_proof_u256.rs @@ -5,6 +5,7 @@ use { crate::{ encryption::pedersen::{PedersenCommitment, PedersenOpening}, errors::ProofError, + instruction::batched_range_proof::MAX_COMMITMENTS, range_proof::RangeProof, }, std::convert::TryInto, @@ -56,7 +57,9 @@ impl BatchedRangeProofU256Data { BatchedRangeProofContext::new(&commitments, &amounts, &bit_lengths, &openings)?; let mut transcript = context.new_transcript(); - let proof = RangeProof::new(amounts, bit_lengths, openings, &mut transcript).try_into()?; + let proof = RangeProof::new(amounts, bit_lengths, openings, &mut transcript) + .map_err(|_| ProofError::Generation)? + .try_into()?; Ok(Self { context, proof }) } @@ -72,6 +75,12 @@ impl ZkProofData for BatchedRangeProofU256Data { #[cfg(not(target_os = "solana"))] fn verify_proof(&self) -> Result<(), ProofError> { let (commitments, bit_lengths) = self.context.try_into()?; + let num_commitments = commitments.len(); + + if num_commitments > MAX_COMMITMENTS || num_commitments != bit_lengths.len() { + return Err(ProofError::IllegalCommitmentLength); + } + let mut transcript = self.context_data().new_transcript(); let proof: RangeProof = self.proof.try_into()?; diff --git a/zk-token-sdk/src/instruction/batched_range_proof/batched_range_proof_u64.rs b/zk-token-sdk/src/instruction/batched_range_proof/batched_range_proof_u64.rs index ae34771aa56..943b4f748b8 100644 --- a/zk-token-sdk/src/instruction/batched_range_proof/batched_range_proof_u64.rs +++ b/zk-token-sdk/src/instruction/batched_range_proof/batched_range_proof_u64.rs @@ -5,6 +5,7 @@ use { crate::{ encryption::pedersen::{PedersenCommitment, PedersenOpening}, errors::ProofError, + instruction::batched_range_proof::MAX_COMMITMENTS, range_proof::RangeProof, }, std::convert::TryInto, @@ -58,7 +59,9 @@ impl BatchedRangeProofU64Data { BatchedRangeProofContext::new(&commitments, &amounts, &bit_lengths, &openings)?; let mut transcript = context.new_transcript(); - let proof = RangeProof::new(amounts, bit_lengths, openings, &mut transcript).try_into()?; + let proof = RangeProof::new(amounts, bit_lengths, openings, &mut transcript) + .map_err(|_| ProofError::Generation)? + .try_into()?; Ok(Self { context, proof }) } @@ -74,6 +77,12 @@ impl ZkProofData for BatchedRangeProofU64Data { #[cfg(not(target_os = "solana"))] fn verify_proof(&self) -> Result<(), ProofError> { let (commitments, bit_lengths) = self.context.try_into()?; + let num_commitments = commitments.len(); + + if num_commitments > MAX_COMMITMENTS || num_commitments != bit_lengths.len() { + return Err(ProofError::IllegalCommitmentLength); + } + let mut transcript = self.context_data().new_transcript(); let proof: RangeProof = self.proof.try_into()?; diff --git a/zk-token-sdk/src/instruction/batched_range_proof/mod.rs b/zk-token-sdk/src/instruction/batched_range_proof/mod.rs index 07351d87854..5942f3852f7 100644 --- a/zk-token-sdk/src/instruction/batched_range_proof/mod.rs +++ b/zk-token-sdk/src/instruction/batched_range_proof/mod.rs @@ -54,7 +54,7 @@ impl BatchedRangeProofContext { fn new_transcript(&self) -> Transcript { let mut transcript = Transcript::new(b"BatchedRangeProof"); transcript.append_message(b"commitments", bytes_of(&self.commitments)); - transcript.append_message(b"bit-legnths", bytes_of(&self.bit_lengths)); + transcript.append_message(b"bit-lengths", bytes_of(&self.bit_lengths)); transcript } diff --git a/zk-token-sdk/src/instruction/range_proof.rs b/zk-token-sdk/src/instruction/range_proof.rs index 3342f5c34fb..abe42eeed8d 100644 --- a/zk-token-sdk/src/instruction/range_proof.rs +++ b/zk-token-sdk/src/instruction/range_proof.rs @@ -65,6 +65,7 @@ impl RangeProofU64Data { let bit_size = usize::try_from(u64::BITS).unwrap(); let proof = RangeProof::new(vec![amount], vec![bit_size], vec![opening], &mut transcript) + .map_err(|_| ProofError::Generation)? .try_into()?; Ok(Self { context, proof }) diff --git a/zk-token-sdk/src/instruction/transfer/with_fee.rs b/zk-token-sdk/src/instruction/transfer/with_fee.rs index 709b0b63797..8d7367647a3 100644 --- a/zk-token-sdk/src/instruction/transfer/with_fee.rs +++ b/zk-token-sdk/src/instruction/transfer/with_fee.rs @@ -559,7 +559,8 @@ impl TransferWithFeeProof { opening_fee_hi, ], transcript, - ); + ) + .expect("range proof: generator"); Self { new_source_commitment: pod_new_source_commitment, diff --git a/zk-token-sdk/src/instruction/transfer/without_fee.rs b/zk-token-sdk/src/instruction/transfer/without_fee.rs index ad4def104d3..02b8fa366f2 100644 --- a/zk-token-sdk/src/instruction/transfer/without_fee.rs +++ b/zk-token-sdk/src/instruction/transfer/without_fee.rs @@ -333,6 +333,7 @@ impl TransferProof { vec![&source_opening, opening_lo, opening_hi], transcript, ) + .expect("range proof: generator") } else { let transfer_amount_lo_negated = (1 << TRANSFER_AMOUNT_LO_NEGATED_BITS) - 1 - transfer_amount_lo; @@ -354,6 +355,7 @@ impl TransferProof { vec![&source_opening, opening_lo, &opening_lo_negated, opening_hi], transcript, ) + .expect("range proof: generator") }; Self { diff --git a/zk-token-sdk/src/instruction/withdraw.rs b/zk-token-sdk/src/instruction/withdraw.rs index 6a493d541fb..0d9e4607bc5 100644 --- a/zk-token-sdk/src/instruction/withdraw.rs +++ b/zk-token-sdk/src/instruction/withdraw.rs @@ -157,7 +157,8 @@ impl WithdrawProof { ); let range_proof = - RangeProof::new(vec![final_balance], vec![64], vec![&opening], transcript); + RangeProof::new(vec![final_balance], vec![64], vec![&opening], transcript) + .expect("range proof: generator"); Self { commitment: pod_commitment, diff --git a/zk-token-sdk/src/range_proof/errors.rs b/zk-token-sdk/src/range_proof/errors.rs index f832365fffc..aff3cb8eed6 100644 --- a/zk-token-sdk/src/range_proof/errors.rs +++ b/zk-token-sdk/src/range_proof/errors.rs @@ -8,3 +8,15 @@ use { #[error("range proof verification failed: {0}")] pub struct RangeProofError(#[from] pub(crate) ProofVerificationError); impl_from_transcript_error!(RangeProofError); + +#[derive(Error, Clone, Debug, Eq, PartialEq)] +pub enum RangeProofGenerationError { + #[error("maximum generator length exceeded")] + MaximumGeneratorLengthExceeded, +} + +#[derive(Error, Clone, Debug, Eq, PartialEq)] +pub enum RangeProofGeneratorError { + #[error("maximum generator length exceeded")] + MaximumGeneratorLengthExceeded, +} diff --git a/zk-token-sdk/src/range_proof/generators.rs b/zk-token-sdk/src/range_proof/generators.rs index f41f6fcd049..46facd50dcc 100644 --- a/zk-token-sdk/src/range_proof/generators.rs +++ b/zk-token-sdk/src/range_proof/generators.rs @@ -1,4 +1,5 @@ use { + crate::range_proof::errors::RangeProofGeneratorError, curve25519_dalek::{ digest::{ExtendableOutput, Update, XofReader}, ristretto::RistrettoPoint, @@ -6,10 +7,12 @@ use { sha3::{Sha3XofReader, Shake256}, }; +#[cfg(not(target_os = "solana"))] +const MAX_GENERATOR_LENGTH: usize = u32::MAX as usize; + /// Generators for Pedersen vector commitments. /// /// The code is copied from https://github.com/dalek-cryptography/bulletproofs for now... - struct GeneratorsChain { reader: Sha3XofReader, } @@ -70,14 +73,14 @@ pub struct BulletproofGens { } impl BulletproofGens { - pub fn new(gens_capacity: usize) -> Self { + pub fn new(gens_capacity: usize) -> Result { let mut gens = BulletproofGens { gens_capacity: 0, G_vec: Vec::new(), H_vec: Vec::new(), }; - gens.increase_capacity(gens_capacity); - gens + gens.increase_capacity(gens_capacity)?; + Ok(gens) } // pub fn new_aggregate(gens_capacities: Vec) -> Vec { @@ -90,25 +93,32 @@ impl BulletproofGens { /// Increases the generators' capacity to the amount specified. /// If less than or equal to the current capacity, does nothing. - pub fn increase_capacity(&mut self, new_capacity: usize) { + pub fn increase_capacity( + &mut self, + new_capacity: usize, + ) -> Result<(), RangeProofGeneratorError> { if self.gens_capacity >= new_capacity { - return; + return Ok(()); + } + + if new_capacity > MAX_GENERATOR_LENGTH { + return Err(RangeProofGeneratorError::MaximumGeneratorLengthExceeded); } - let label = [b'G']; self.G_vec.extend( - &mut GeneratorsChain::new(&[label, [b'G']].concat()) + &mut GeneratorsChain::new(&[b'G']) .fast_forward(self.gens_capacity) .take(new_capacity - self.gens_capacity), ); self.H_vec.extend( - &mut GeneratorsChain::new(&[label, [b'H']].concat()) + &mut GeneratorsChain::new(&[b'H']) .fast_forward(self.gens_capacity) .take(new_capacity - self.gens_capacity), ); self.gens_capacity = new_capacity; + Ok(()) } #[allow(non_snake_case)] diff --git a/zk-token-sdk/src/range_proof/inner_product.rs b/zk-token-sdk/src/range_proof/inner_product.rs index 2693e455a8f..ca0f6911bd4 100644 --- a/zk-token-sdk/src/range_proof/inner_product.rs +++ b/zk-token-sdk/src/range_proof/inner_product.rs @@ -206,7 +206,7 @@ impl InnerProductProof { transcript: &mut Transcript, ) -> Result<(Vec, Vec, Vec), RangeProofError> { let lg_n = self.L_vec.len(); - if lg_n >= 32 { + if lg_n == 0 || lg_n >= 32 { // 4 billion multiplications should be enough for anyone // and this check prevents overflow in 1< = bp_gens.G(n).cloned().collect(); let H: Vec = bp_gens.H(n).cloned().collect(); diff --git a/zk-token-sdk/src/range_proof/mod.rs b/zk-token-sdk/src/range_proof/mod.rs index b2221ff3707..75059c920fe 100644 --- a/zk-token-sdk/src/range_proof/mod.rs +++ b/zk-token-sdk/src/range_proof/mod.rs @@ -10,7 +10,9 @@ use { encryption::pedersen::{G, H}, errors::ProofVerificationError, range_proof::{ - errors::RangeProofError, generators::BulletproofGens, inner_product::InnerProductProof, + errors::{RangeProofError, RangeProofGenerationError}, + generators::BulletproofGens, + inner_product::InnerProductProof, }, transcript::TranscriptProtocol, }, @@ -59,7 +61,7 @@ impl RangeProof { bit_lengths: Vec, openings: Vec<&PedersenOpening>, transcript: &mut Transcript, - ) -> Self { + ) -> Result { // amounts, bit-lengths, openings must be same length vectors let m = amounts.len(); assert_eq!(bit_lengths.len(), m); @@ -69,9 +71,8 @@ impl RangeProof { let nm: usize = bit_lengths.iter().sum(); assert!(nm.is_power_of_two()); - // TODO: precompute generators - // TODO: double check Pedersen generators and range proof generators does not interfere - let bp_gens = BulletproofGens::new(nm); + let bp_gens = BulletproofGens::new(nm) + .map_err(|_| RangeProofGenerationError::MaximumGeneratorLengthExceeded)?; // bit-decompose values and generate their Pedersen vector commitment let a_blinding = Scalar::random(&mut OsRng); @@ -206,7 +207,7 @@ impl RangeProof { transcript, ); - RangeProof { + Ok(RangeProof { A, S, T_1, @@ -215,7 +216,7 @@ impl RangeProof { t_x_blinding, e_blinding, ipp_proof, - } + }) } #[allow(clippy::many_single_char_names)] @@ -230,7 +231,8 @@ impl RangeProof { let m = bit_lengths.len(); let nm: usize = bit_lengths.iter().sum(); - let bp_gens = BulletproofGens::new(nm); + let bp_gens = BulletproofGens::new(nm) + .map_err(|_| ProofVerificationError::InvalidGeneratorsLength)?; if !nm.is_power_of_two() { return Err(ProofVerificationError::InvalidBitSize.into()); @@ -400,7 +402,8 @@ mod tests { let mut transcript_create = Transcript::new(b"Test"); let mut transcript_verify = Transcript::new(b"Test"); - let proof = RangeProof::new(vec![55], vec![32], vec![&open], &mut transcript_create); + let proof = + RangeProof::new(vec![55], vec![32], vec![&open], &mut transcript_create).unwrap(); assert!(proof .verify(vec![&comm], vec![32], &mut transcript_verify) @@ -421,7 +424,8 @@ mod tests { vec![64, 32, 32], vec![&open_1, &open_2, &open_3], &mut transcript_create, - ); + ) + .unwrap(); assert!(proof .verify( diff --git a/zk-token-sdk/src/zk_token_elgamal/convert.rs b/zk-token-sdk/src/zk_token_elgamal/convert.rs index 44ebdc27646..5a9331c3375 100644 --- a/zk-token-sdk/src/zk_token_elgamal/convert.rs +++ b/zk-token-sdk/src/zk_token_elgamal/convert.rs @@ -104,7 +104,7 @@ mod tests { let proof = RangeProof::new(vec![55], vec![64], vec![&open], &mut transcript_create); - let proof_serialized: pod::RangeProofU64 = proof.try_into().unwrap(); + let proof_serialized: pod::RangeProofU64 = proof.unwrap().try_into().unwrap(); let proof_deserialized: RangeProof = proof_serialized.try_into().unwrap(); assert!(proof_deserialized @@ -112,7 +112,8 @@ mod tests { .is_ok()); // should fail to serialize to pod::RangeProof128 - let proof = RangeProof::new(vec![55], vec![64], vec![&open], &mut transcript_create); + let proof = + RangeProof::new(vec![55], vec![64], vec![&open], &mut transcript_create).unwrap(); assert!(TryInto::::try_into(proof).is_err()); } @@ -131,7 +132,8 @@ mod tests { vec![64, 32, 32], vec![&open_1, &open_2, &open_3], &mut transcript_create, - ); + ) + .unwrap(); let proof_serialized: pod::RangeProofU128 = proof.try_into().unwrap(); let proof_deserialized: RangeProof = proof_serialized.try_into().unwrap(); @@ -150,7 +152,8 @@ mod tests { vec![64, 32, 32], vec![&open_1, &open_2, &open_3], &mut transcript_create, - ); + ) + .unwrap(); assert!(TryInto::::try_into(proof).is_err()); }